Files
offerpai_python_ai/app/ai/resume_diagnoser/diagnoser.py
T

114 lines
4.0 KiB
Python

"""简历诊断 AI 引擎:并行诊断 + 汇总评价"""
import asyncio
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from app.ai.model_config import DiagnoserModel
from app.ai.resume_diagnoser.prompts import DIAGNOSE_MODULE_PROMPT, SUMMARY_PROMPT, POLISH_PROMPT
from app.core.logger import log
from app.tool.json_helper import parse_llm_json
# 诊断链(StrOutputParser 拿原始文本,再手动解析 JSON,避免 markdown 代码块导致解析失败)
_diagnose_chain = (
ChatPromptTemplate.from_messages([("system", DIAGNOSE_MODULE_PROMPT), ("human", "请开始诊断。")])
| DiagnoserModel.MODULE
| StrOutputParser()
)
# 汇总评价链(纯文本输出)
_summary_chain = (
ChatPromptTemplate.from_messages([("system", SUMMARY_PROMPT), ("human", "请生成整体评价。")])
| DiagnoserModel.SUMMARY
| 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 "简历诊断已完成,请查看各模块的详细诊断结果。"
_polish_chain = (
ChatPromptTemplate.from_messages([("system", POLISH_PROMPT), ("human", "请开始优化。")])
| DiagnoserModel.POLISH
| StrOutputParser()
)
async def polish_content(module_type: str, reference_content: list[dict] | str | None,
user_content: list[str], is_summary: bool) -> list[str]:
"""润色用户编辑后的文本"""
ref_text = ""
if reference_content:
if isinstance(reference_content, list):
ref_text = "\n".join(
item.get("text", "") if isinstance(item, dict) else str(item)
for item in reference_content
)
else:
ref_text = str(reference_content)
if not ref_text:
ref_text = ""
inp = {
"module_type": module_type,
"reference_content": ref_text,
"user_content": "\n".join(user_content),
"summary_constraint": "- 注意:此模块只能输出一个段落,数组只能有一个元素" if is_summary else "",
}
try:
raw = await _polish_chain.ainvoke(inp)
result = parse_llm_json(raw)
if isinstance(result, list):
return [str(item) for item in result]
return [str(result)]
except Exception as e:
log.warning(f"AI润色失败: {e}")
return user_content
async def _safe_invoke(task: dict) -> dict:
"""单条记录诊断,失败返回空结果"""
raw = ""
try:
raw = await _diagnose_chain.ainvoke(task)
return parse_llm_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,
}