From 1e83bc9f68bbbe1253146ee297dc7396524f62fb Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Mon, 25 May 2026 22:57:29 +0800 Subject: [PATCH] chore(logging): tune title diagnostics and file logs --- server/app_legacy.py | 34 +++++++++++++++- server/chat_flow_helpers.py | 79 ++++++++++++++++++++++++++++++++++++- utils/logger.py | 42 ++++++++++++-------- 3 files changed, 137 insertions(+), 18 deletions(-) diff --git a/server/app_legacy.py b/server/app_legacy.py index fd001db..7f849ff 100644 --- a/server/app_legacy.py +++ b/server/app_legacy.py @@ -443,12 +443,32 @@ def start_background_jobs(): _load_last_active_cache() socketio.start_background_task(idle_reaper_loop) +TITLE_DEBUG_DIR = Path(LOGS_DIR).expanduser().resolve() / "title_debug" +TITLE_DEBUG_FILE = TITLE_DEBUG_DIR / "title_generation.log" + + +def _title_debug_log(message: str, **extra: Any) -> None: + try: + TITLE_DEBUG_DIR.mkdir(parents=True, exist_ok=True) + payload = { + "ts": datetime.now().isoformat(timespec="milliseconds"), + "message": str(message), + } + if extra: + payload["extra"] = extra + with TITLE_DEBUG_FILE.open("a", encoding="utf-8") as f: + f.write(json.dumps(payload, ensure_ascii=False) + "\n") + except Exception: + pass + async def _generate_title_async(user_message: str) -> Optional[str]: """使用快速模型生成对话标题。""" if not user_message: + _title_debug_log("skip_empty_user_message") return None client = DeepSeekClient(thinking_mode=False, web_mode=True) + _title_debug_log("start_generate_title", user_message_preview=str(user_message)[:200], user_message_len=len(str(user_message))) try: prompt_text = TITLE_PROMPT_PATH.read_text(encoding="utf-8") except Exception: @@ -466,11 +486,17 @@ async def _generate_title_async(user_message: str) -> Optional[str]: try: content = resp.get("choices", [{}])[0].get("message", {}).get("content") if content: - return " ".join(str(content).strip().split()) + normalized = " ".join(str(content).strip().split()) + _title_debug_log("title_api_success", title_preview=normalized[:200], title_len=len(normalized)) + return normalized + _title_debug_log("title_api_empty_content", resp_preview=str(resp)[:500]) except Exception: + _title_debug_log("title_api_parse_error", resp_preview=str(resp)[:500]) continue except Exception as exc: debug_log(f"[TitleGen] 生成标题异常: {exc}") + _title_debug_log("title_api_exception", error=str(exc)) + _title_debug_log("title_api_no_result") return None @@ -482,6 +508,7 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati async def _runner(): title = await _generate_title_async(user_message) if not title: + _title_debug_log("title_not_generated", conversation_id=conversation_id, username=username) return # 限长,避免标题过长 safe_title = title[:80] @@ -490,8 +517,11 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati ok = web_terminal.context_manager.conversation_manager.update_conversation_title(conversation_id, safe_title) except Exception as exc: debug_log(f"[TitleGen] 保存标题失败: {exc}") + _title_debug_log("title_save_exception", error=str(exc), conversation_id=conversation_id) if not ok: + _title_debug_log("title_save_failed", conversation_id=conversation_id, safe_title=safe_title) return + _title_debug_log("title_save_success", conversation_id=conversation_id, safe_title=safe_title) try: socketio.emit('conversation_changed', { 'conversation_id': conversation_id, @@ -503,11 +533,13 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati }, room=f"user_{username}") except Exception as exc: debug_log(f"[TitleGen] 推送标题更新失败: {exc}") + _title_debug_log("title_emit_exception", error=str(exc), conversation_id=conversation_id, username=username) try: asyncio.run(_runner()) except Exception as exc: debug_log(f"[TitleGen] 任务执行失败: {exc}") + _title_debug_log("title_background_runner_exception", error=str(exc), conversation_id=conversation_id, username=username) def cache_monitor_snapshot(execution_id: Optional[str], stage: str, snapshot: Optional[Dict[str, Any]]): """缓存工具执行前/后的文件快照。""" diff --git a/server/chat_flow_helpers.py b/server/chat_flow_helpers.py index b68e1f4..0cdb391 100644 --- a/server/chat_flow_helpers.py +++ b/server/chat_flow_helpers.py @@ -1,13 +1,57 @@ from __future__ import annotations import asyncio +import json +import os import re +from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional from core.web_terminal import WebTerminal +from config import LOGS_DIR from utils.api_client import DeepSeekClient +TITLE_DEBUG_DIR = Path(LOGS_DIR).expanduser().resolve() / "title_debug" +TITLE_DEBUG_FILE = TITLE_DEBUG_DIR / "title_generation.log" + + +def _title_debug_log(message: str, **extra: Any) -> None: + try: + TITLE_DEBUG_DIR.mkdir(parents=True, exist_ok=True) + payload = { + "ts": datetime.now().isoformat(timespec="milliseconds"), + "message": str(message), + } + if extra: + payload["extra"] = extra + with TITLE_DEBUG_FILE.open("a", encoding="utf-8") as f: + f.write(json.dumps(payload, ensure_ascii=False) + "\n") + except Exception: + pass + + +def _env_optional(name: str) -> Optional[str]: + value = os.environ.get(name) + if value is None: + env_path = Path(__file__).resolve().parents[1] / ".env" + if env_path.exists(): + try: + for raw_line in env_path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, val = line.split("=", 1) + if key.strip() == name: + value = val.strip().strip('"').strip("'") + break + except Exception: + value = None + if value is None: + return None + value = value.strip() + return value or None + async def _generate_title_async( user_message: str, @@ -16,9 +60,29 @@ async def _generate_title_async( ) -> Optional[str]: """使用快速模型生成对话标题。""" if not user_message: + _title_debug_log("skip_empty_user_message") return None client = DeepSeekClient(thinking_mode=False, web_mode=True) + title_base = _env_optional("AGENT_TITLE_API_BASE_URL") + title_key = _env_optional("AGENT_TITLE_API_KEY") + title_model = _env_optional("AGENT_TITLE_MODEL_ID") + if title_base: + client.fast_api_config["base_url"] = title_base + client.api_base_url = title_base + if title_key: + client.fast_api_config["api_key"] = title_key + client.api_key = title_key + if title_model: + client.fast_api_config["model_id"] = title_model + client.model_id = title_model + _title_debug_log("start_generate_title", user_message_preview=str(user_message)[:200], user_message_len=len(str(user_message))) + _title_debug_log( + "title_api_config", + base_url=client.fast_api_config.get("base_url"), + model_id=client.fast_api_config.get("model_id"), + has_api_key=bool(client.fast_api_config.get("api_key")), + ) try: prompt_text = Path(title_prompt_path).read_text(encoding="utf-8") except Exception: @@ -38,11 +102,17 @@ async def _generate_title_async( try: content = resp.get("choices", [{}])[0].get("message", {}).get("content") if content: - return " ".join(str(content).strip().split()) + normalized = " ".join(str(content).strip().split()) + _title_debug_log("title_api_success", title_preview=normalized[:200], title_len=len(normalized)) + return normalized + _title_debug_log("title_api_empty_content", resp_preview=str(resp)[:500]) except Exception: + _title_debug_log("title_api_parse_error", resp_preview=str(resp)[:500]) continue except Exception as exc: debug_logger(f"[TitleGen] 生成标题异常: {exc}") + _title_debug_log("title_api_exception", error=str(exc)) + _title_debug_log("title_api_no_result") return None @@ -62,6 +132,7 @@ def generate_conversation_title_background( async def _runner(): title = await _generate_title_async(user_message, title_prompt_path, debug_logger) if not title: + _title_debug_log("title_not_generated", conversation_id=conversation_id, username=username) return safe_title = title[:80] @@ -70,8 +141,11 @@ def generate_conversation_title_background( ok = web_terminal.context_manager.conversation_manager.update_conversation_title(conversation_id, safe_title) except Exception as exc: debug_logger(f"[TitleGen] 保存标题失败: {exc}") + _title_debug_log("title_save_exception", error=str(exc), conversation_id=conversation_id) if not ok: + _title_debug_log("title_save_failed", conversation_id=conversation_id, safe_title=safe_title) return + _title_debug_log("title_save_success", conversation_id=conversation_id, safe_title=safe_title) # 添加标题更新事件到任务事件流(用于轮询机制) try: @@ -91,6 +165,7 @@ def generate_conversation_title_background( ) except Exception as exc: debug_logger(f"[TitleGen] 添加任务事件失败: {exc}") + _title_debug_log("title_task_event_exception", error=str(exc), conversation_id=conversation_id, username=username) try: socketio_instance.emit( @@ -105,11 +180,13 @@ def generate_conversation_title_background( ) except Exception as exc: debug_logger(f"[TitleGen] 推送标题更新失败: {exc}") + _title_debug_log("title_emit_exception", error=str(exc), conversation_id=conversation_id, username=username) try: asyncio.run(_runner()) except Exception as exc: debug_logger(f"[TitleGen] 任务执行失败: {exc}") + _title_debug_log("title_background_runner_exception", error=str(exc), conversation_id=conversation_id, username=username) def get_thinking_state(terminal: WebTerminal) -> Dict[str, Any]: diff --git a/utils/logger.py b/utils/logger.py index 996fe85..7113d64 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -14,6 +14,15 @@ except ImportError: sys.path.insert(0, str(project_root)) from config import LOGS_DIR, LOG_LEVEL, LOG_FORMAT +def _env_flag(name: str, default: bool = False) -> bool: + value = str(os.environ.get(name, "")).strip().lower() + if not value: + return default + return value in {"1", "true", "yes", "on"} + + +_FILE_LOG_ENABLED = _env_flag("AGENT_FILE_LOG_ENABLED", default=False) + def setup_logger(name: str, log_file: str = None) -> logging.Logger: """ 设置日志记录器 @@ -40,22 +49,23 @@ def setup_logger(name: str, log_file: str = None) -> logging.Logger: console_handler.setFormatter(formatter) logger.addHandler(console_handler) - # 文件处理器 - if log_file: - file_path = Path(LOGS_DIR) / log_file - else: - # 默认日志文件 - today = datetime.now().strftime("%Y%m%d") - file_path = Path(LOGS_DIR) / f"agent_{today}.log" - - # 确保日志目录存在 - file_path.parent.mkdir(parents=True, exist_ok=True) - - file_handler = logging.FileHandler(file_path, encoding='utf-8') - file_handler.setLevel(getattr(logging, LOG_LEVEL)) - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) - + # 文件处理器(默认关闭,避免在工作区生成 agent_debug/agent_*.log) + if _FILE_LOG_ENABLED: + if log_file: + file_path = Path(LOGS_DIR) / log_file + else: + # 默认日志文件 + today = datetime.now().strftime("%Y%m%d") + file_path = Path(LOGS_DIR) / f"agent_{today}.log" + + # 确保日志目录存在 + file_path.parent.mkdir(parents=True, exist_ok=True) + + file_handler = logging.FileHandler(file_path, encoding='utf-8') + file_handler.setLevel(getattr(logging, LOG_LEVEL)) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + return logger class TaskLogger: