""" AI对话日志记录模块 用于记录大模型对话的输入和输出信息,方便排查问题 """ import os import json import time from datetime import datetime from pathlib import Path from typing import Dict, Optional, Any from threading import Lock class AILogger: """AI对话日志记录器""" def __init__(self, log_dir: Optional[str] = None): """ 初始化日志记录器 Args: log_dir: 日志文件保存目录,默认为项目根目录下的 logs/ai_conversations 目录 """ if log_dir is None: # 默认日志目录:项目根目录下的 logs/ai_conversations project_root = Path(__file__).parent.parent log_dir = project_root / "logs" / "ai_conversations" self.log_dir = Path(log_dir) self.log_dir.mkdir(parents=True, exist_ok=True) # 线程锁,确保日志写入的线程安全 self._lock = Lock() # 是否启用日志记录(可通过环境变量控制) self.enabled = os.getenv('AI_LOG_ENABLED', 'true').lower() == 'true' print(f"[AI日志] 日志记录器初始化完成,日志目录: {self.log_dir}") print(f"[AI日志] 日志记录状态: {'启用' if self.enabled else '禁用'}") def log_conversation( self, prompt: str, api_request: Dict[str, Any], api_response: Optional[Dict[str, Any]] = None, extracted_data: Optional[Dict[str, Any]] = None, error: Optional[str] = None, session_id: Optional[str] = None ) -> str: """ 记录一次完整的AI对话 Args: prompt: 输入提示词 api_request: API请求参数 api_response: API响应内容(完整响应) extracted_data: 提取后的结构化数据 error: 错误信息(如果有) session_id: 会话ID(可选,用于关联多次对话) Returns: 日志文件路径 """ if not self.enabled: return "" try: with self._lock: # 生成时间戳和会话ID timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # 精确到毫秒 if session_id is None: session_id = f"session_{int(time.time() * 1000)}" # 创建日志记录 log_entry = { "timestamp": datetime.now().isoformat(), "session_id": session_id, "prompt": prompt, "api_request": { "endpoint": api_request.get("endpoint", "unknown"), "model": api_request.get("model", "unknown"), "messages": api_request.get("messages", []), "temperature": api_request.get("temperature"), "max_tokens": api_request.get("max_tokens"), "enable_thinking": api_request.get("enable_thinking", False), }, "api_response": api_response, "extracted_data": extracted_data, "error": error, "success": error is None } # 保存到文件(按日期组织) date_str = datetime.now().strftime("%Y%m%d") log_file = self.log_dir / f"conversation_{date_str}_{timestamp}.json" with open(log_file, 'w', encoding='utf-8') as f: json.dump(log_entry, f, ensure_ascii=False, indent=2) print(f"[AI日志] 对话日志已保存: {log_file.name}") return str(log_file) except Exception as e: print(f"[AI日志] 保存日志失败: {e}") return "" def log_request_only( self, prompt: str, api_request: Dict[str, Any], session_id: Optional[str] = None ) -> str: """ 仅记录请求信息(在发送请求前调用) Args: prompt: 输入提示词 api_request: API请求参数 session_id: 会话ID Returns: 日志文件路径 """ return self.log_conversation( prompt=prompt, api_request=api_request, session_id=session_id ) def get_recent_logs(self, limit: int = 10) -> list: """ 获取最近的日志文件列表 Args: limit: 返回的日志文件数量 Returns: 日志文件路径列表(按时间倒序) """ try: log_files = sorted( self.log_dir.glob("conversation_*.json"), key=lambda x: x.stat().st_mtime, reverse=True ) return [str(f) for f in log_files[:limit]] except Exception as e: print(f"[AI日志] 获取日志列表失败: {e}") return [] def read_log(self, log_file: str) -> Optional[Dict]: """ 读取指定的日志文件 Args: log_file: 日志文件路径 Returns: 日志内容字典,如果读取失败返回None """ try: log_path = Path(log_file) if not log_path.is_absolute(): log_path = self.log_dir / log_file with open(log_path, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: print(f"[AI日志] 读取日志文件失败: {e}") return None # 全局日志记录器实例 _ai_logger: Optional[AILogger] = None def get_ai_logger() -> AILogger: """获取全局AI日志记录器实例""" global _ai_logger if _ai_logger is None: _ai_logger = AILogger() return _ai_logger