添加ai日志

This commit is contained in:
zk
2026-07-07 21:43:49 +08:00
parent cb94fb7bfe
commit ca0b13a45d
4 changed files with 66 additions and 9 deletions
+22
View File
@@ -0,0 +1,22 @@
"""PostgreSQL: ai_call_log 表模型"""
from datetime import datetime
from typing import Optional
from sqlalchemy import BigInteger, DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import PgBase
class AiCallLog(PgBase):
"""AI 调用日志表"""
__tablename__ = "ai_call_log"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, comment="主键ID(雪花)")
scene: Mapped[str] = mapped_column(String(32), nullable=False, comment="场景标识: structure/major_match/skill_extract/company_enrich")
system_prompt: Mapped[str] = mapped_column(Text, nullable=False, comment="系统提示词")
user_message: Mapped[str] = mapped_column(Text, nullable=False, comment="发给AI的用户消息")
response: Mapped[Optional[str]] = mapped_column(Text, comment="AI返回原文")
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, comment="创建时间")
+40 -5
View File
@@ -7,14 +7,19 @@
import asyncio import asyncio
import re import re
from typing import Any from datetime import datetime
from typing import Any, Optional
from json_repair import repair_json from json_repair import repair_json
from langchain_core.language_models import BaseChatModel from langchain_core.language_models import BaseChatModel
from langchain_core.messages import SystemMessage, HumanMessage from langchain_core.messages import SystemMessage, HumanMessage
from snowflake import SnowflakeGenerator
from app.core.logger import log from app.core.logger import log
# AI 日志专用雪花ID
_log_id_gen = SnowflakeGenerator(instance=2)
# 匹配 <think>任意内容</think>,用于剥离推理模型的思考过程 # 匹配 <think>任意内容</think>,用于剥离推理模型的思考过程
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE) _THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
@@ -39,7 +44,7 @@ def parse_llm_json(text: str) -> Any:
return repair_json(cleaned, return_objects=True) return repair_json(cleaned, return_objects=True)
async def ai_chat(llm: BaseChatModel, system_prompt: str, user_message: str) -> str: async def ai_chat(llm: BaseChatModel, system_prompt: str, user_message: str, scene: Optional[str] = None) -> str:
"""异步调用 LLM,返回原始文本。接口异常时触发门闸阻塞重试直到成功。""" """异步调用 LLM,返回原始文本。接口异常时触发门闸阻塞重试直到成功。"""
global _gate_probing global _gate_probing
@@ -53,7 +58,11 @@ async def ai_chat(llm: BaseChatModel, system_prompt: str, user_message: str) ->
HumanMessage(content=user_message), HumanMessage(content=user_message),
] ]
response = await llm.ainvoke(messages) response = await llm.ainvoke(messages)
return response.content content = response.content
# 记录 AI 调用日志(失败不影响主流程)
if scene:
await _save_call_log(scene, system_prompt, user_message, content)
return content
except Exception as e: except Exception as e:
# 接口失败 → 关闭门闸 # 接口失败 → 关闭门闸
if _gate_event.is_set(): if _gate_event.is_set():
@@ -64,6 +73,9 @@ async def ai_chat(llm: BaseChatModel, system_prompt: str, user_message: str) ->
# 当前协程成为探针,负责退避重试直到成功 # 当前协程成为探针,负责退避重试直到成功
_gate_probing = True _gate_probing = True
result = await _probe_until_recover(llm, system_prompt, user_message) result = await _probe_until_recover(llm, system_prompt, user_message)
# 记录 AI 调用日志
if scene:
await _save_call_log(scene, system_prompt, user_message, result)
# 探针成功,直接返回结果(不用再调一次) # 探针成功,直接返回结果(不用再调一次)
return result return result
# 已有探针在工作,回到 while 顶部 await _gate_event.wait() 等待唤醒 # 已有探针在工作,回到 while 顶部 await _gate_event.wait() 等待唤醒
@@ -96,9 +108,9 @@ async def _probe_until_recover(llm: BaseChatModel, system_prompt: str, user_mess
delay = min(delay * 2, max_delay) delay = min(delay * 2, max_delay)
async def ai_chat_json(llm: BaseChatModel, system_prompt: str, user_message: str) -> Any: async def ai_chat_json(llm: BaseChatModel, system_prompt: str, user_message: str, scene: Optional[str] = None) -> Any:
"""异步调用 LLM,返回解析后的 JSON 对象""" """异步调用 LLM,返回解析后的 JSON 对象"""
raw = await ai_chat(llm, system_prompt, user_message) raw = await ai_chat(llm, system_prompt, user_message, scene=scene)
if not raw or not raw.strip(): if not raw or not raw.strip():
log.warning("AI 返回为空") log.warning("AI 返回为空")
return None return None
@@ -107,3 +119,26 @@ async def ai_chat_json(llm: BaseChatModel, system_prompt: str, user_message: str
except Exception as e: except Exception as e:
log.warning("AI JSON 解析失败: {}, raw={}", e, raw[:200]) log.warning("AI JSON 解析失败: {}, raw={}", e, raw[:200])
return None return None
async def _save_call_log(scene: str, system_prompt: str, user_message: str, response: Optional[str]) -> None:
"""异步写入 AI 调用日志到 PG,失败只 warning 不影响主流程"""
try:
from app.core.database import PgSession
from sqlalchemy import insert
from app.models.pg.ai_call_log import AiCallLog
async with PgSession() as pg:
await pg.execute(
insert(AiCallLog).values(
id=next(_log_id_gen),
scene=scene,
system_prompt=system_prompt,
user_message=user_message,
response=response,
created_at=datetime.now(),
)
)
await pg.commit()
except Exception as e:
log.warning("AI 调用日志写入失败: {}", e)
+1 -1
View File
@@ -62,7 +62,7 @@ async def _do_clean(company: dict) -> None:
short_name = company.get("short_name", "") short_name = company.get("short_name", "")
user_msg = f"【公司简称】\n{short_name}\n\n【行业列表】\n{dict_cache.industry_text}" user_msg = f"【公司简称】\n{short_name}\n\n【行业列表】\n{dict_cache.industry_text}"
result = await ai_chat_json(CompanyCleanModel.ENRICH, COMPANY_ENRICH_SYSTEM, user_msg) result = await ai_chat_json(CompanyCleanModel.ENRICH, COMPANY_ENRICH_SYSTEM, user_msg, scene="company_enrich")
if result is None or not result.get("valid", False): if result is None or not result.get("valid", False):
await _update_status(company_id, 4) await _update_status(company_id, 4)
+3 -3
View File
@@ -127,7 +127,7 @@ async def _do_clean(data: dict) -> None:
# 第一次AI:结构化提取 # 第一次AI:结构化提取
user_message = _build_user_message(data) user_message = _build_user_message(data)
result = await ai_chat_json(JobCleanModel.STRUCTURE, JOB_STRUCTURE_SYSTEM, user_message) result = await ai_chat_json(JobCleanModel.STRUCTURE, JOB_STRUCTURE_SYSTEM, user_message, scene="structure")
if result is None or not result.get("valid", False): if result is None or not result.get("valid", False):
log.info("[id={}] 丢弃:AI判定无效", data_id) log.info("[id={}] 丢弃:AI判定无效", data_id)
await _update_pg_status(data_id, "discarded") await _update_pg_status(data_id, "discarded")
@@ -217,7 +217,7 @@ async def _match_major(job_id: int, result: dict) -> None:
req = result.get("requirement", "") req = result.get("requirement", "")
user_msg = f"【岗位信息】\n标题: {title}\n职责: {desc}\n要求: {req}\n\n【专业分类列表】\n{dict_cache.major_category_text}" user_msg = f"【岗位信息】\n标题: {title}\n职责: {desc}\n要求: {req}\n\n【专业分类列表】\n{dict_cache.major_category_text}"
data = await ai_chat_json(JobCleanModel.MAJOR_MATCH, MAJOR_MATCH_SYSTEM, user_msg) data = await ai_chat_json(JobCleanModel.MAJOR_MATCH, MAJOR_MATCH_SYSTEM, user_msg, scene="major_match")
if data is None: if data is None:
return return
@@ -242,7 +242,7 @@ async def _extract_skill_tags(job_id: int, result: dict) -> None:
req = result.get("requirement", "") req = result.get("requirement", "")
user_msg = f"【岗位信息】\n标题: {title}\n职责: {desc}\n要求: {req}" user_msg = f"【岗位信息】\n标题: {title}\n职责: {desc}\n要求: {req}"
skills = await ai_chat_json(JobCleanModel.SKILL_EXTRACT, SKILL_EXTRACT_SYSTEM, user_msg) skills = await ai_chat_json(JobCleanModel.SKILL_EXTRACT, SKILL_EXTRACT_SYSTEM, user_msg, scene="skill_extract")
if not skills or not isinstance(skills, list): if not skills or not isinstance(skills, list):
return return