"""简历诊断 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, }