182 lines
6.0 KiB
Python
182 lines
6.0 KiB
Python
"""
|
||
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
|
||
|