diff --git a/modules/web_api/llm/agent.py b/modules/web_api/llm/agent.py index 1149e49..fc5b762 100644 --- a/modules/web_api/llm/agent.py +++ b/modules/web_api/llm/agent.py @@ -20,7 +20,8 @@ import logging import re from typing import List, Dict, Any, Tuple, Optional -# Импорт фасада базы данных +# Импорт фасада базы данных и прямого подключения +from .db.connection import get_db_connection from .db_tools import ( db_get_active_system_prompt, db_add_system_prompt, @@ -81,7 +82,7 @@ def process_chat_message( 2. Перехват завершения работы ('нет, закончить настройку') с автоочисткой эфемерных сообщений. 3. Формирование системного контекста с учетом активного действия (черновика). 4. Выполнение вызванного Tool и опрос Data-Driven реестра действий. - 5. Проверка счетчика отвлечений (idle_turns) и Context Guard на 3-м шаге. + 5. Проверка счетчика отвлечений (idle_turns), Context Guard на 3-м шаге и автоочистка при N > 3. 6. Возврат кортежа: (reply_text, chat_history, action_metadata). """ logger.info(f"Получено сообщение от user_id={user_id}, session_id={session_id}: {user_message}") @@ -114,7 +115,9 @@ def process_chat_message( if user_msg_clean in ["подтверждаю", "подтвердить", "да", "сохранить", "применить", "ок", "хорошо"]: db_add_system_prompt("main_agent", draft_text) - db_clear_session_state(session_id) + + # Переводим сессию в режим follow-up диалога + db_set_session_state(session_id, "PROMPT_FOLLOWUP", {"idle_turns": 0}) action_cfg = db_get_tool_action("db_confirm_prompt_preview") reply_text = action_cfg["success_template"] if action_cfg else "Системный промпт успешно сохранен и применен в базе данных." @@ -123,15 +126,16 @@ def process_chat_message( reply_text = reply_text.replace("✅", "").replace("❌", "").replace("**", "").replace("*", "").strip() + is_eph = action_cfg.get("is_ephemeral", 1) if action_cfg else 1 db_save_chat_message(session_id, "user", full_user_content, is_ephemeral=1) - db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1) + db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=is_eph) return reply_text, db_get_chat_history(session_id), { "type": action_cfg.get("action_type") if action_cfg else "FOLLOW_UP_ACTION", "buttons": action_cfg.get("buttons", []) if action_cfg else [] } elif user_msg_clean in ["отмена", "отменить", "отклонить", "назад", "стоп"]: - db_clear_session_state(session_id) + db_set_session_state(session_id, "PROMPT_FOLLOWUP", {"idle_turns": 0}) action_cfg = db_get_tool_action("db_cancel_prompt_preview") reply_text = action_cfg["success_template"] if action_cfg else "Изменения системного промпта отменены." @@ -140,8 +144,9 @@ def process_chat_message( reply_text = reply_text.replace("✅", "").replace("❌", "").replace("**", "").replace("*", "").strip() + is_eph = action_cfg.get("is_ephemeral", 1) if action_cfg else 1 db_save_chat_message(session_id, "user", full_user_content, is_ephemeral=1) - db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=1) + db_save_chat_message(session_id, "assistant", reply_text, is_ephemeral=is_eph) return reply_text, db_get_chat_history(session_id), { "type": action_cfg.get("action_type") if action_cfg else "FOLLOW_UP_ACTION", "buttons": action_cfg.get("buttons", []) if action_cfg else [] @@ -160,11 +165,11 @@ def process_chat_message( # Информируем модель о наличии незавершенного действия в сессии active_state_context = "" - if session_state and session_state.get("state_type") == "PROMPT_PREVIEW": + if session_state and session_state.get("state_type") in ["PROMPT_PREVIEW", "PROMPT_FOLLOWUP"]: active_state_context = ( "\n[АКТИВНОЕ ДЕЙСТВИЕ В СЕССИИ]\n" - "В данный момент оператор рассматривает подготовленный черновик системного промпта.\n" - "- Если оператор просит продолжить правки или уточняет детали по черновику — продолжай работу с ним.\n" + "В данный момент оператор находится в процессе настройки системного промпта.\n" + "- Если оператор просит продолжить правки или уточняет детали — продолжай работу с ним.\n" "- Если оператор переключился на другую тему или вызвал другой инструмент — выполни его команду штатно.\n" ) @@ -250,7 +255,7 @@ def process_chat_message( draft_text = session_state.get("pending_data", "") db_add_system_prompt("main_agent", draft_text) - db_clear_session_state(session_id) + db_set_session_state(session_id, "PROMPT_FOLLOWUP", {"idle_turns": 0}) if action_cfg and action_cfg.get("bypass_llm"): reply_text = action_cfg["success_template"] @@ -271,7 +276,7 @@ def process_chat_message( return err_reply, db_get_chat_history(session_id), None elif fn_name == "db_cancel_prompt_preview": - db_clear_session_state(session_id) + db_set_session_state(session_id, "PROMPT_FOLLOWUP", {"idle_turns": 0}) if action_cfg and action_cfg.get("bypass_llm"): reply_text = action_cfg["success_template"] if action_cfg.get("follow_up_question"): @@ -374,6 +379,16 @@ def process_chat_message( "idle_turns": 0 } db_set_session_state(session_id, "PROMPT_PREVIEW", state_payload) + + # Помечаем последнее сообщение пользователя как эфемерное + with get_db_connection() as conn_fix: + cursor_fix = conn_fix.cursor() + cursor_fix.execute(""" + UPDATE chat_messages + SET is_ephemeral = 1 + WHERE id = (SELECT MAX(id) FROM chat_messages WHERE session_id = ? AND role = 'user') + """, (session_id,)) + conn_fix.commit() preview_reply = ( f"Ваше изменение успешно предпросмотрено. Полный обновленный системный промпт теперь выглядит так:\n\n" @@ -425,24 +440,32 @@ def process_chat_message( raw_content = sec_res_data.get("message", {}).get("content", "").strip().replace("**", "").replace("*", "") final_content = clean_raw_tool_tags(clean_output(raw_content)) - # Если была активна фоновая транзакция, инкрементируем счетчик Topic Shift - if session_state and session_state.get("state_type") == "PROMPT_PREVIEW": + # Определяем, был ли вызов служебного инструмента просмотра промпта + tool_names_called = [t["function"]["name"] for t in tool_calls] + is_output_ephemeral = 1 if any(name in ["db_get_system_prompt", "db_get_system_prompts", "db_preview_prompt_merge"] for name in tool_names_called) else 0 + + # Отслеживание отвлечений при активном процессе настройки + if session_state and session_state.get("state_type") in ["PROMPT_PREVIEW", "PROMPT_FOLLOWUP"]: idle_count = db_increment_session_idle(session_id) - logger.info(f"Выполнен инструмент вне черновика. Текущий idle_turns: {idle_count}") + logger.info(f"Смена темы ({session_state.get('state_type')}). Текущий idle_turns: {idle_count}") - if idle_count >= 3: - guard_note = "\n\nНапоминание: У вас остался непримененный черновик системного промпта. Сохранить его или сбросить?" + if idle_count == 3: + guard_note = "\n\nНапоминание: Завершить настройку системного промпта и очистить сессию?" final_content += guard_note - db_save_chat_message(session_id, "assistant", final_content, is_ephemeral=1) + db_save_chat_message(session_id, "assistant", final_content, is_ephemeral=is_output_ephemeral) return final_content, db_get_chat_history(session_id), { - "type": "PROMPT_PREVIEW_GUARD", + "type": "FOLLOW_UP_ACTION", "buttons": [ - {"label": "Применить черновик", "value": "подтверждаю", "style": "primary"}, - {"label": "Сбросить черновик", "value": "отмена", "style": "danger"} + {"label": "Да, закончить", "value": "нет, закончить настройку", "style": "primary"}, + {"label": "Показать промпт", "value": "покажи системный промпт", "style": "secondary"} ] } + elif idle_count > 3: + logger.info(f"Автоочистка сессии: оператор переключился на другую тему (idle_turns={idle_count}).") + db_clear_session_state(session_id) + db_purge_ephemeral_messages(session_id) - db_save_chat_message(session_id, "assistant", final_content, is_ephemeral=0) + db_save_chat_message(session_id, "assistant", final_content, is_ephemeral=is_output_ephemeral) return final_content, db_get_chat_history(session_id), None # Если вызовов инструментов не было (обычный текстовый диалог) @@ -450,20 +473,25 @@ def process_chat_message( content_str = clean_raw_tool_tags(clean_output(raw_str)) final_reply = content_str or "Запрос обработан." - if session_state and session_state.get("state_type") == "PROMPT_PREVIEW": + if session_state and session_state.get("state_type") in ["PROMPT_PREVIEW", "PROMPT_FOLLOWUP"]: idle_count = db_increment_session_idle(session_id) - logger.info(f"Текстовый диалог вне черновика. Текущий idle_turns: {idle_count}") - if idle_count >= 3: - guard_note = "\n\nНапоминание: У вас остался непримененный черновик системного промпта. Сохранить его или сбросить?" + logger.info(f"Текстовый диалог вне настройки ({session_state.get('state_type')}). Текущий idle_turns: {idle_count}") + + if idle_count == 3: + guard_note = "\n\nНапоминание: Завершить настройку системного промпта и очистить сессию?" final_reply += guard_note - db_save_chat_message(session_id, "assistant", final_reply, is_ephemeral=1) + db_save_chat_message(session_id, "assistant", final_reply, is_ephemeral=0) return final_reply, db_get_chat_history(session_id), { - "type": "PROMPT_PREVIEW_GUARD", + "type": "FOLLOW_UP_ACTION", "buttons": [ - {"label": "Применить черновик", "value": "подтверждаю", "style": "primary"}, - {"label": "Сбросить черновик", "value": "отмена", "style": "danger"} + {"label": "Да, закончить", "value": "нет, закончить настройку", "style": "primary"}, + {"label": "Показать промпт", "value": "покажи системный промпт", "style": "secondary"} ] } + elif idle_count > 3: + logger.info(f"Автоочистка сессии: оператор переключился на другую тему (idle_turns={idle_count}).") + db_clear_session_state(session_id) + db_purge_ephemeral_messages(session_id) db_save_chat_message(session_id, "assistant", final_reply, is_ephemeral=0) return final_reply, db_get_chat_history(session_id), None