添加AI日志记录器支持,增强对话日志记录功能,记录请求和响应信息,包括成功和错误情况,以提高调试和监控能力。
This commit is contained in:
parent
8bebc13efe
commit
315301fc0b
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual Environment
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Project specific
|
||||||
|
parsed_fields.json
|
||||||
|
*.docx.bak
|
||||||
|
|
||||||
181
services/ai_logger.py
Normal file
181
services/ai_logger.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
@ -18,6 +18,14 @@ except ImportError:
|
|||||||
repair_json = None
|
repair_json = None
|
||||||
print("[AI服务] 警告: json-repair库未安装,将使用基础JSON修复功能。建议运行: pip install json-repair")
|
print("[AI服务] 警告: json-repair库未安装,将使用基础JSON修复功能。建议运行: pip install json-repair")
|
||||||
|
|
||||||
|
# 导入AI日志记录器
|
||||||
|
try:
|
||||||
|
from services.ai_logger import get_ai_logger
|
||||||
|
AI_LOGGER_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
AI_LOGGER_AVAILABLE = False
|
||||||
|
print("[AI服务] 警告: AI日志记录器未找到,将不记录对话日志")
|
||||||
|
|
||||||
|
|
||||||
class AIService:
|
class AIService:
|
||||||
"""AI服务类"""
|
"""AI服务类"""
|
||||||
@ -41,6 +49,16 @@ class AIService:
|
|||||||
# 确定使用的AI服务
|
# 确定使用的AI服务
|
||||||
self.ai_provider = self._determine_ai_provider()
|
self.ai_provider = self._determine_ai_provider()
|
||||||
|
|
||||||
|
# 初始化AI日志记录器
|
||||||
|
if AI_LOGGER_AVAILABLE:
|
||||||
|
try:
|
||||||
|
self.ai_logger = get_ai_logger()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[AI服务] 初始化日志记录器失败: {e}")
|
||||||
|
self.ai_logger = None
|
||||||
|
else:
|
||||||
|
self.ai_logger = None
|
||||||
|
|
||||||
def _determine_ai_provider(self) -> str:
|
def _determine_ai_provider(self) -> str:
|
||||||
"""确定使用的AI服务提供商(仅支持华为大模型)"""
|
"""确定使用的AI服务提供商(仅支持华为大模型)"""
|
||||||
if self.huawei_api_endpoint and self.huawei_api_key:
|
if self.huawei_api_endpoint and self.huawei_api_key:
|
||||||
@ -208,6 +226,9 @@ class AIService:
|
|||||||
"""
|
"""
|
||||||
单次调用华为大模型API(不包含重试逻辑)
|
单次调用华为大模型API(不包含重试逻辑)
|
||||||
"""
|
"""
|
||||||
|
# 生成会话ID(用于关联同一次调用的请求和响应)
|
||||||
|
session_id = f"session_{int(time.time() * 1000)}"
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"model": self.huawei_model,
|
"model": self.huawei_model,
|
||||||
"messages": [
|
"messages": [
|
||||||
@ -238,6 +259,18 @@ class AIService:
|
|||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 记录请求信息(发送请求前)
|
||||||
|
api_request_info = {
|
||||||
|
"endpoint": self.huawei_api_endpoint,
|
||||||
|
"model": self.huawei_model,
|
||||||
|
"messages": payload["messages"],
|
||||||
|
"temperature": payload.get("temperature"),
|
||||||
|
"max_tokens": payload.get("max_tokens"),
|
||||||
|
"enable_thinking": payload.get("enable_thinking", False),
|
||||||
|
}
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_request_only(prompt, api_request_info, session_id)
|
||||||
|
|
||||||
# 根据是否开启思考模式动态调整超时时间
|
# 根据是否开启思考模式动态调整超时时间
|
||||||
# 开启思考模式时,模型需要更多时间进行推理,超时时间需要更长
|
# 开启思考模式时,模型需要更多时间进行推理,超时时间需要更长
|
||||||
enable_thinking = payload.get('enable_thinking', False)
|
enable_thinking = payload.get('enable_thinking', False)
|
||||||
@ -250,17 +283,32 @@ class AIService:
|
|||||||
timeout = min(self.api_timeout, 120) # 最多120秒
|
timeout = min(self.api_timeout, 120) # 最多120秒
|
||||||
print(f"[AI服务] 思考模式未开启,使用超时时间: {timeout}秒")
|
print(f"[AI服务] 思考模式未开启,使用超时时间: {timeout}秒")
|
||||||
|
|
||||||
response = requests.post(
|
extracted_data = None
|
||||||
self.huawei_api_endpoint,
|
error_message = None
|
||||||
json=payload,
|
|
||||||
headers=headers,
|
|
||||||
timeout=timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
try:
|
||||||
raise Exception(f"API调用失败: {response.status_code} - {response.text}")
|
response = requests.post(
|
||||||
|
self.huawei_api_endpoint,
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
result = response.json()
|
if response.status_code != 200:
|
||||||
|
error_message = f"API调用失败: {response.status_code} - {response.text}"
|
||||||
|
# 记录错误
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=None,
|
||||||
|
extracted_data=None,
|
||||||
|
error=error_message,
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
|
raise Exception(error_message)
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
# 提取AI返回的内容
|
# 提取AI返回的内容
|
||||||
if 'choices' in result and len(result['choices']) > 0:
|
if 'choices' in result and len(result['choices']) > 0:
|
||||||
@ -323,9 +371,29 @@ class AIService:
|
|||||||
# 即使提取的字段不完整,也返回结果(更宽容的处理)
|
# 即使提取的字段不完整,也返回结果(更宽容的处理)
|
||||||
if any(v for v in normalized_data.values() if v): # 至少有一个非空字段
|
if any(v for v in normalized_data.values() if v): # 至少有一个非空字段
|
||||||
print(f"[AI服务] 返回提取的数据(包含 {sum(1 for v in normalized_data.values() if v)} 个非空字段)")
|
print(f"[AI服务] 返回提取的数据(包含 {sum(1 for v in normalized_data.values() if v)} 个非空字段)")
|
||||||
|
# 记录成功的对话
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=result,
|
||||||
|
extracted_data=normalized_data,
|
||||||
|
error=None,
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
return normalized_data
|
return normalized_data
|
||||||
else:
|
else:
|
||||||
print(f"[AI服务] 警告:提取的数据全部为空,但继续返回(允许部分字段为空)")
|
print(f"[AI服务] 警告:提取的数据全部为空,但继续返回(允许部分字段为空)")
|
||||||
|
# 记录对话(即使数据为空)
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=result,
|
||||||
|
extracted_data=normalized_data,
|
||||||
|
error="提取的数据全部为空",
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
return normalized_data
|
return normalized_data
|
||||||
|
|
||||||
# 如果无法提取JSON,记录错误但尝试更宽容的处理
|
# 如果无法提取JSON,记录错误但尝试更宽容的处理
|
||||||
@ -336,6 +404,16 @@ class AIService:
|
|||||||
parsed_data = self._parse_text_response(content, output_fields)
|
parsed_data = self._parse_text_response(content, output_fields)
|
||||||
if parsed_data and any(v for v in parsed_data.values() if v): # 至少有一个非空字段
|
if parsed_data and any(v for v in parsed_data.values() if v): # 至少有一个非空字段
|
||||||
print(f"[AI服务] 使用备用方法解析成功,提取到 {len(parsed_data)} 个字段")
|
print(f"[AI服务] 使用备用方法解析成功,提取到 {len(parsed_data)} 个字段")
|
||||||
|
# 记录对话
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=result,
|
||||||
|
extracted_data=parsed_data,
|
||||||
|
error=None,
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
# 如果所有方法都失败,尝试最后一次修复尝试
|
# 如果所有方法都失败,尝试最后一次修复尝试
|
||||||
@ -352,6 +430,16 @@ class AIService:
|
|||||||
normalized_data = self._normalize_field_names(extracted_data, output_fields)
|
normalized_data = self._normalize_field_names(extracted_data, output_fields)
|
||||||
normalized_data = self._normalize_date_formats(normalized_data, output_fields)
|
normalized_data = self._normalize_date_formats(normalized_data, output_fields)
|
||||||
normalized_data = self._post_process_inferred_fields(normalized_data, output_fields)
|
normalized_data = self._post_process_inferred_fields(normalized_data, output_fields)
|
||||||
|
# 记录对话
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=result,
|
||||||
|
extracted_data=normalized_data,
|
||||||
|
error=None,
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
return normalized_data
|
return normalized_data
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
@ -360,14 +448,36 @@ class AIService:
|
|||||||
|
|
||||||
# 如果所有方法都失败,返回空字典而不是抛出异常(更宽容)
|
# 如果所有方法都失败,返回空字典而不是抛出异常(更宽容)
|
||||||
# 这样至少不会导致整个调用失败,前端可以显示部分结果
|
# 这样至少不会导致整个调用失败,前端可以显示部分结果
|
||||||
print(f"[AI服务] 警告:无法从API返回内容中提取JSON数据,返回空结果。原始内容长度: {len(raw_content)}, 清理后内容长度: {len(content)}")
|
error_msg = f"无法从API返回内容中提取JSON数据。原始内容长度: {len(raw_content)}, 清理后内容长度: {len(content)}"
|
||||||
|
print(f"[AI服务] 警告:{error_msg}")
|
||||||
print(f"[AI服务] 完整内容: {content}")
|
print(f"[AI服务] 完整内容: {content}")
|
||||||
# 返回一个包含所有输出字段的空字典,而不是抛出异常
|
# 返回一个包含所有输出字段的空字典,而不是抛出异常
|
||||||
empty_result = {field['field_code']: '' for field in output_fields}
|
empty_result = {field['field_code']: '' for field in output_fields}
|
||||||
print(f"[AI服务] 返回空结果(包含 {len(empty_result)} 个字段,全部为空)")
|
print(f"[AI服务] 返回空结果(包含 {len(empty_result)} 个字段,全部为空)")
|
||||||
|
# 记录失败的对话
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=result,
|
||||||
|
extracted_data=empty_result,
|
||||||
|
error=error_msg,
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
return empty_result
|
return empty_result
|
||||||
else:
|
else:
|
||||||
raise Exception("API返回格式异常:未找到choices字段或choices为空")
|
error_msg = "API返回格式异常:未找到choices字段或choices为空"
|
||||||
|
# 记录错误
|
||||||
|
if self.ai_logger:
|
||||||
|
self.ai_logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request_info,
|
||||||
|
api_response=None,
|
||||||
|
extracted_data=None,
|
||||||
|
error=error_msg,
|
||||||
|
session_id=session_id
|
||||||
|
)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
def _extract_json_from_text(self, text: str) -> Optional[Dict]:
|
def _extract_json_from_text(self, text: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
95
test_scripts/test_ai_logger.py
Normal file
95
test_scripts/test_ai_logger.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
"""
|
||||||
|
测试AI日志记录功能
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 添加项目根目录到路径
|
||||||
|
project_root = Path(__file__).parent.parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
from services.ai_logger import get_ai_logger
|
||||||
|
|
||||||
|
def test_logger():
|
||||||
|
"""测试日志记录器"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("测试AI日志记录功能")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 获取日志记录器
|
||||||
|
logger = get_ai_logger()
|
||||||
|
|
||||||
|
# 测试记录一次对话
|
||||||
|
print("\n1. 测试记录对话...")
|
||||||
|
prompt = "请从以下文本中提取信息:张三,男,1980年5月出生,某公司总经理"
|
||||||
|
api_request = {
|
||||||
|
"endpoint": "http://test.example.com/v1/chat/completions",
|
||||||
|
"model": "test-model",
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": "你是一个数据提取助手"},
|
||||||
|
{"role": "user", "content": prompt}
|
||||||
|
],
|
||||||
|
"temperature": 0.2,
|
||||||
|
"max_tokens": 1000,
|
||||||
|
"enable_thinking": False
|
||||||
|
}
|
||||||
|
api_response = {
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": '{"target_name": "张三", "target_gender": "男"}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
extracted_data = {
|
||||||
|
"target_name": "张三",
|
||||||
|
"target_gender": "男",
|
||||||
|
"target_date_of_birth": "1980年05月"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_file = logger.log_conversation(
|
||||||
|
prompt=prompt,
|
||||||
|
api_request=api_request,
|
||||||
|
api_response=api_response,
|
||||||
|
extracted_data=extracted_data,
|
||||||
|
error=None,
|
||||||
|
session_id="test_session_001"
|
||||||
|
)
|
||||||
|
|
||||||
|
if log_file:
|
||||||
|
print(f"✓ 日志已保存: {log_file}")
|
||||||
|
else:
|
||||||
|
print("✗ 日志保存失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 测试读取日志
|
||||||
|
print("\n2. 测试读取日志...")
|
||||||
|
log_data = logger.read_log(log_file)
|
||||||
|
if log_data:
|
||||||
|
print(f"✓ 日志读取成功")
|
||||||
|
print(f" 时间戳: {log_data['timestamp']}")
|
||||||
|
print(f" 会话ID: {log_data['session_id']}")
|
||||||
|
print(f" 成功: {log_data['success']}")
|
||||||
|
print(f" 提取的字段数: {len(log_data.get('extracted_data', {}))}")
|
||||||
|
else:
|
||||||
|
print("✗ 日志读取失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 测试获取最近的日志
|
||||||
|
print("\n3. 测试获取最近的日志...")
|
||||||
|
recent_logs = logger.get_recent_logs(limit=5)
|
||||||
|
print(f"✓ 找到 {len(recent_logs)} 条最近的日志")
|
||||||
|
for i, log_file in enumerate(recent_logs, 1):
|
||||||
|
print(f" {i}. {Path(log_file).name}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("测试完成!")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"\n日志目录: {logger.log_dir}")
|
||||||
|
print(f"日志状态: {'启用' if logger.enabled else '禁用'}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_logger()
|
||||||
|
|
||||||
213
技术文档/AI对话日志使用说明.md
Normal file
213
技术文档/AI对话日志使用说明.md
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# AI对话日志使用说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
系统已集成AI对话日志记录功能,可以自动记录每次大模型调用的详细信息,包括:
|
||||||
|
- 输入提示词(prompt)
|
||||||
|
- API请求参数
|
||||||
|
- API响应内容(完整响应)
|
||||||
|
- 提取后的结构化数据
|
||||||
|
- 错误信息(如果有)
|
||||||
|
|
||||||
|
## 日志文件位置
|
||||||
|
|
||||||
|
日志文件保存在项目根目录下的 `logs/ai_conversations/` 目录中。
|
||||||
|
|
||||||
|
日志文件命名格式:`conversation_YYYYMMDD_HHMMSS_mmm.json`
|
||||||
|
|
||||||
|
例如:`conversation_20241215_143025_123.json`
|
||||||
|
|
||||||
|
## 日志文件格式
|
||||||
|
|
||||||
|
每个日志文件是一个JSON文件,包含以下字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timestamp": "2024-12-15T14:30:25.123456",
|
||||||
|
"session_id": "session_1702627825123",
|
||||||
|
"prompt": "请从以下输入文本中提取结构化信息...",
|
||||||
|
"api_request": {
|
||||||
|
"endpoint": "http://10.100.31.26:3001/v1/chat/completions",
|
||||||
|
"model": "DeepSeek-R1-Distill-Llama-70B",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"temperature": 0.2,
|
||||||
|
"max_tokens": 12000,
|
||||||
|
"enable_thinking": true
|
||||||
|
},
|
||||||
|
"api_response": {
|
||||||
|
"choices": [...],
|
||||||
|
"usage": {...}
|
||||||
|
},
|
||||||
|
"extracted_data": {
|
||||||
|
"target_name": "张三",
|
||||||
|
"target_gender": "男",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 启用/禁用日志记录
|
||||||
|
|
||||||
|
日志记录功能默认启用。可以通过环境变量控制:
|
||||||
|
|
||||||
|
### 方法1:设置环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
set AI_LOG_ENABLED=false
|
||||||
|
|
||||||
|
# Linux/Mac
|
||||||
|
export AI_LOG_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法2:在代码中修改
|
||||||
|
|
||||||
|
编辑 `services/ai_logger.py`,修改 `__init__` 方法中的默认值:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.enabled = os.getenv('AI_LOG_ENABLED', 'false').lower() == 'true' # 改为默认禁用
|
||||||
|
```
|
||||||
|
|
||||||
|
## 查看日志文件
|
||||||
|
|
||||||
|
### 方法1:直接查看JSON文件
|
||||||
|
|
||||||
|
日志文件是标准的JSON格式,可以用任何文本编辑器或JSON查看器打开。
|
||||||
|
|
||||||
|
### 方法2:使用Python脚本查看
|
||||||
|
|
||||||
|
可以使用以下Python代码查看最近的日志:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from services.ai_logger import get_ai_logger
|
||||||
|
|
||||||
|
logger = get_ai_logger()
|
||||||
|
|
||||||
|
# 获取最近的10条日志
|
||||||
|
recent_logs = logger.get_recent_logs(limit=10)
|
||||||
|
for log_file in recent_logs:
|
||||||
|
print(f"日志文件: {log_file}")
|
||||||
|
log_data = logger.read_log(log_file)
|
||||||
|
if log_data:
|
||||||
|
print(f" 时间: {log_data['timestamp']}")
|
||||||
|
print(f" 成功: {log_data['success']}")
|
||||||
|
if log_data.get('error'):
|
||||||
|
print(f" 错误: {log_data['error']}")
|
||||||
|
print()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法3:使用命令行工具
|
||||||
|
|
||||||
|
在项目根目录下,可以使用以下命令查看日志:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows PowerShell
|
||||||
|
Get-ChildItem logs\ai_conversations\*.json | Sort-Object LastWriteTime -Descending | Select-Object -First 10
|
||||||
|
|
||||||
|
# Linux/Mac
|
||||||
|
ls -lt logs/ai_conversations/*.json | head -10
|
||||||
|
```
|
||||||
|
|
||||||
|
## 日志文件管理
|
||||||
|
|
||||||
|
### 自动清理
|
||||||
|
|
||||||
|
日志文件会按日期组织,建议定期清理旧日志文件以节省磁盘空间。
|
||||||
|
|
||||||
|
### 手动清理
|
||||||
|
|
||||||
|
可以删除指定日期之前的日志文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows PowerShell - 删除7天前的日志
|
||||||
|
Get-ChildItem logs\ai_conversations\*.json | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-7)} | Remove-Item
|
||||||
|
|
||||||
|
# Linux/Mac - 删除7天前的日志
|
||||||
|
find logs/ai_conversations -name "*.json" -mtime +7 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
## 排查问题
|
||||||
|
|
||||||
|
### 查看失败的对话
|
||||||
|
|
||||||
|
查找包含错误的日志:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from services.ai_logger import get_ai_logger
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = get_ai_logger()
|
||||||
|
recent_logs = logger.get_recent_logs(limit=50)
|
||||||
|
|
||||||
|
for log_file in recent_logs:
|
||||||
|
log_data = logger.read_log(log_file)
|
||||||
|
if log_data and not log_data.get('success'):
|
||||||
|
print(f"失败日志: {log_file}")
|
||||||
|
print(f"错误: {log_data.get('error')}")
|
||||||
|
print(f"提示词: {log_data.get('prompt')[:200]}...")
|
||||||
|
print()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看特定字段的提取情况
|
||||||
|
|
||||||
|
```python
|
||||||
|
from services.ai_logger import get_ai_logger
|
||||||
|
|
||||||
|
logger = get_ai_logger()
|
||||||
|
recent_logs = logger.get_recent_logs(limit=20)
|
||||||
|
|
||||||
|
for log_file in recent_logs:
|
||||||
|
log_data = logger.read_log(log_file)
|
||||||
|
if log_data and log_data.get('extracted_data'):
|
||||||
|
extracted = log_data['extracted_data']
|
||||||
|
if 'target_gender' in extracted:
|
||||||
|
print(f"日志: {log_file}")
|
||||||
|
print(f" 性别: {extracted.get('target_gender', '(空)')}")
|
||||||
|
print(f" 姓名: {extracted.get('target_name', '(空)')}")
|
||||||
|
print()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **隐私和安全**:日志文件可能包含敏感信息,请妥善保管,不要将日志文件提交到公共代码仓库。
|
||||||
|
|
||||||
|
2. **磁盘空间**:日志文件会持续增长,建议定期清理旧日志。
|
||||||
|
|
||||||
|
3. **性能影响**:日志记录是异步的,对性能影响很小,但如果大量调用,建议定期清理日志文件。
|
||||||
|
|
||||||
|
4. **日志文件大小**:每个日志文件通常几KB到几十KB,取决于响应内容的大小。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 日志文件在哪里?
|
||||||
|
A: 日志文件保存在 `logs/ai_conversations/` 目录中。
|
||||||
|
|
||||||
|
### Q: 如何禁用日志记录?
|
||||||
|
A: 设置环境变量 `AI_LOG_ENABLED=false`。
|
||||||
|
|
||||||
|
### Q: 日志文件会占用多少空间?
|
||||||
|
A: 每个日志文件通常几KB到几十KB,取决于响应内容。如果每天有100次调用,大约占用几MB空间。
|
||||||
|
|
||||||
|
### Q: 可以自定义日志目录吗?
|
||||||
|
A: 可以,在创建 `AILogger` 实例时传入 `log_dir` 参数。
|
||||||
|
|
||||||
|
### Q: 日志文件格式可以修改吗?
|
||||||
|
A: 可以,修改 `services/ai_logger.py` 中的 `log_conversation` 方法。
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- `services/ai_logger.py` - 日志记录器实现
|
||||||
|
- `services/ai_service.py` - AI服务(集成日志记录)
|
||||||
|
- `logs/ai_conversations/` - 日志文件目录
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user