From bff74741a6c445b7904fabbd8995058bc1ff392f Mon Sep 17 00:00:00 2001 From: mirivlad Date: Wed, 25 Feb 2026 12:22:31 +0800 Subject: [PATCH] feat: add dialogue compaction with summary integration - Add /compact command for manual compaction - Integrate summary loading from ChromaDB - Add summary to AI prompt context - Automatic compaction at 70% threshold - Keep last 20 messages uncompressed Co-authored-by: Qwen-Coder --- bot.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/bot.py b/bot.py index c5916e5..8d06a7e 100644 --- a/bot.py +++ b/bot.py @@ -213,8 +213,17 @@ async def handle_ai_task(update: Update, text: str): def on_oauth_url(url: str): pass # OAuth обрабатывается автоматически - # Формируем контекст с историей + памятью - history_context = "\n".join(state.ai_chat_history) + # Формируем контекст с историей + памятью + summary + # Получаем summary и последние сообщения из ChromaDB + summary = None + try: + summary, recent_messages = compactor.get_context_with_summary(limit=20) + # Формируем историю из последних сообщений + history_context = "\n".join([f"{msg['role']}: {msg['content']}" for msg in recent_messages]) + except Exception as e: + logger.error(f"Ошибка загрузки summary: {e}") + # Fallback на старую логику + history_context = "\n".join(state.ai_chat_history) # Получаем контекст из системы памяти (профиль + релевантные факты) memory_context = get_context(user_id, query=text) @@ -230,16 +239,30 @@ async def handle_ai_task(update: Update, text: str): # Собираем полный промпт с системным промптом system_prompt = qwen_manager.load_system_prompt() - - full_task = ( - f"{system_prompt}\n\n" - f"=== КОНТЕКСТ ПАМЯТИ ===\n" - f"{memory_context}\n\n" - f"=== ИСТОРИЯ ДИАЛОГА ===\n" - f"{history_context}\n\n" - f"=== ЗАПРОС ПОЛЬЗОВАТЕЛЯ ===\n" - f"{text}" - ) + + # Формируем полный промпт с summary (если есть) + if summary: + full_task = ( + f"{system_prompt}\n\n" + f"=== SUMMARY ДИАЛОГА (контекст) ===\n" + f"{summary}\n\n" + f"=== КОНТЕКСТ ПАМЯТИ ===\n" + f"{memory_context}\n\n" + f"=== ИСТОРИЯ ДИАЛОГА (последние 20 сообщений) ===\n" + f"{history_context}\n\n" + f"=== ЗАПРОС ПОЛЬЗОВАТЕЛЯ ===\n" + f"{text}" + ) + else: + full_task = ( + f"{system_prompt}\n\n" + f"=== КОНТЕКСТ ПАМЯТИ ===\n" + f"{memory_context}\n\n" + f"=== ИСТОРИЯ ДИАЛОГА ===\n" + f"{history_context}\n\n" + f"=== ЗАПРОС ПОЛЬЗОВАТЕЛЯ ===\n" + f"{text}" + ) # Выполняем задачу (системный промпт уже добавлен в full_task) result = await qwen_manager.run_task(user_id, full_task, on_output, on_oauth_url, use_system_prompt=False) @@ -1371,17 +1394,59 @@ async def ai_command(update: Update, context: ContextTypes.DEFAULT_TYPE): ) +@check_access +async def compact_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """Обработка команды /compact — ручная компактификация истории диалога.""" + user_id = update.effective_user.id + + logger.info(f"Пользователь {user_id} запросил ручную компактификацию") + + status_msg = await update.message.reply_text( + "🔄 **Запуск компактификации истории...**\n\n" + "_Сжатие старой истории в структурированный summary._\n" + "_Это может занять несколько секунд._", + parse_mode="Markdown" + ) + + result = await compactor.compact() + + await status_msg.delete() + + if result.success: + if result.messages_compressed > 0: + await update.message.reply_text( + f"✅ **Компактификация завершена!**\n\n" + f"📊 Сжато сообщений: `{result.messages_compressed}`\n" + f"📝 Длина summary: `{result.summary_length}` символов\n" + f"💾 Экономия токенов: ~`{result.tokens_saved}`\n\n" + f"_Summary автоматически используется в контексте диалога._", + parse_mode="Markdown" + ) + else: + await update.message.reply_text( + "ℹ️ **Компактификация не требуется**\n\n" + "_Недостаточно сообщений для сжатия или summary уже актуален._", + parse_mode="Markdown" + ) + else: + logger.error(f"Компактификация не удалась: {result.error}") + await update.message.reply_text( + f"⚠️ **Ошибка компактификации:**\n`{result.error}`", + parse_mode="Markdown" + ) + + @check_access async def memory_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Обработка команды /memory — статистика памяти ИИ.""" user_id = update.effective_user.id - + stats = get_memory_stats(user_id) - + if not stats: await update.message.reply_text("ℹ️ Память не инициализирована") return - + # Форматируем статистику total_messages = stats.get("total_messages", 0) total_facts = stats.get("total_facts", 0) @@ -1389,21 +1454,21 @@ async def memory_command(update: Update, context: ContextTypes.DEFAULT_TYPE): vector_docs = stats.get("vector_documents", "N/A") vector_model = stats.get("vector_model", "N/A") hybrid_mode = stats.get("hybrid_mode", False) - + text = ( "🧠 *Статистика памяти:*\n\n" f"📊 Сообщений: `{total_messages}`\n" f"📌 Фактов: `{total_facts}`\n" f"📁 Сессий: `{total_sessions}`\n" ) - + if hybrid_mode: text += ( f"\n🔮 *Векторная память:*\n" f" Документы: `{vector_docs}`\n" f" Модель: `{vector_model}`\n" ) - + text += "\n_Память использует SQLite + ChromaDB с семантическим поиском._" await update.message.reply_text(text, parse_mode="Markdown") @@ -1567,6 +1632,7 @@ def main(): application.add_handler(CommandHandler("menu", menu_command)) application.add_handler(CommandHandler("stop", stop_command)) application.add_handler(CommandHandler("memory", memory_command)) + application.add_handler(CommandHandler("compact", compact_command)) application.add_handler(CommandHandler("facts", facts_command)) application.add_handler(CommandHandler("forget", forget_command)) application.add_handler(CallbackQueryHandler(menu_callback))