chore(logging): tune title diagnostics and file logs
This commit is contained in:
parent
eaf270766f
commit
1e83bc9f68
@ -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]]):
|
||||
"""缓存工具执行前/后的文件快照。"""
|
||||
|
||||
@ -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]:
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user