Files
offerpai_python_ai/app/ai/resume_diagnoser/diagnoser.py
T
2026-04-07 20:15:43 +08:00

90 lines
3.2 KiB
Python

"""简历诊断 AI 引擎:并行诊断 + 汇总评价"""
import asyncio
import json
import re
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from app.ai.models import LLM
from app.ai.resume_diagnoser.prompts import DIAGNOSE_MODULE_PROMPT, SUMMARY_PROMPT
from app.core.logger import log
def _parse_json(text: str) -> dict:
"""解析 AI 输出的 JSON,自动去除 markdown 代码块包裹,容错处理"""
cleaned = re.sub(r"^```(?:json)?\s*\n?", "", text.strip())
cleaned = re.sub(r"\n?```\s*$", "", cleaned)
try:
return json.loads(cleaned)
except json.JSONDecodeError:
# AI 可能在 JSON 字符串值中嵌入了未转义的引号,尝试提取最外层 { }
match = re.search(r"\{[\s\S]*\}", cleaned)
if match:
return json.loads(match.group())
raise
# 诊断链(StrOutputParser 拿原始文本,再手动解析 JSON,避免 markdown 代码块导致解析失败)
_diagnose_chain = (
ChatPromptTemplate.from_messages([("system", DIAGNOSE_MODULE_PROMPT), ("human", "请开始诊断。")])
| LLM.CLAUDE_SONNET_4.create(temperature=0)
| StrOutputParser()
)
# 汇总评价链(纯文本输出)
_summary_chain = (
ChatPromptTemplate.from_messages([("system", SUMMARY_PROMPT), ("human", "请生成整体评价。")])
| LLM.CLAUDE_SONNET_4.create(temperature=0.3)
| StrOutputParser()
)
async def diagnose_all(tasks: list[dict]) -> list[dict]:
"""并行诊断所有模块记录
tasks: [{"module_type": ..., "target_position": ..., "context": ..., "description_text": ...}, ...]
返回: 与 tasks 一一对应的诊断结果列表
"""
log.info(f"开始{len(tasks)}路并行AI诊断")
results = await asyncio.gather(*[_safe_invoke(task) for task in tasks])
log.info("并行AI诊断完成")
return results
async def generate_summary(grade: str, urgent_total: int, important_total: int,
expression_total: int, target_position: str, all_findings: str) -> str:
"""AI 生成整体评价文本"""
inp = {
"grade": grade, "urgent_total": str(urgent_total),
"important_total": str(important_total), "expression_total": str(expression_total),
"target_position": target_position or "未指定", "all_findings": all_findings,
}
try:
return await _summary_chain.ainvoke(inp)
except Exception as e:
log.warning(f"AI生成整体评价失败: {e}")
return "简历诊断已完成,请查看各模块的详细诊断结果。"
async def _safe_invoke(task: dict) -> dict:
"""单条记录诊断,失败返回空结果"""
raw = ""
try:
raw = await _diagnose_chain.ainvoke(task)
return _parse_json(raw)
except Exception as e:
log.warning(f"AI诊断[{task.get('module_type', '')}]失败: {e}\n原始输出: {raw[:500]}")
return _empty_result()
def _empty_result() -> dict:
return {
"finding": "", "importance": "", "suggestion": "",
"urgent_issues": {"typo": 0},
"important_issues": {"no_result": 0, "no_quantify": 0, "weak_relevance": 0},
"expression_issues": {"not_concise": 0, "format_inconsistent": 0},
"optimized_content": None,
}