114 lines
4.0 KiB
Python
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,
|
|
}
|