diff --git a/app/ai/resume_diagnoser/diagnoser.py b/app/ai/resume_diagnoser/diagnoser.py index e4d5880..94067dd 100644 --- a/app/ai/resume_diagnoser/diagnoser.py +++ b/app/ai/resume_diagnoser/diagnoser.py @@ -9,7 +9,7 @@ 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.ai.resume_diagnoser.prompts import DIAGNOSE_MODULE_PROMPT, SUMMARY_PROMPT, POLISH_PROMPT from app.core.logger import log @@ -62,6 +62,45 @@ async def generate_summary(grade: str, urgent_total: int, important_total: int, return "简历诊断已完成,请查看各模块的详细诊断结果。" +_polish_chain = ( + ChatPromptTemplate.from_messages([("system", POLISH_PROMPT), ("human", "请开始优化。")]) + | LLM.CLAUDE_SONNET_4.create(temperature=0.3) + | 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_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 = "" diff --git a/app/ai/resume_diagnoser/prompts.py b/app/ai/resume_diagnoser/prompts.py index 0767bbf..2ba6c08 100644 --- a/app/ai/resume_diagnoser/prompts.py +++ b/app/ai/resume_diagnoser/prompts.py @@ -77,3 +77,26 @@ SUMMARY_PROMPT = """你是一位资深简历顾问。请根据以下简历诊断 4. 一句鼓励或行动建议 直接输出评价文本,不要输出JSON或其他格式标记。控制在300字以内。""" + +POLISH_PROMPT = """你是一位资深简历顾问。请对用户提供的简历描述文本进行润色优化,让语言更精练、更专业。 + +## 模块类型 +{module_type} + +## AI 之前的优化版本(仅供参考) +{reference_content} + +## 用户提交的文本(以此为主进行优化) +{user_content} + +## 优化要求 +- 以用户提交的文本为主体进行润色,AI之前的版本仅作参考 +- 让语言更精练、更专业,去除冗余表达 +- 尽量使用数据量化成果 +- 保持原意不变,不凭空捏造内容 +- 输出为 JSON 数组格式,每个元素是一个段落的纯文本 +{summary_constraint} + +## 输出格式 +严格输出 JSON 数组,不要输出其他内容: +["优化后的段落1", "优化后的段落2"]""" diff --git a/app/api/resume_diagnose.py b/app/api/resume_diagnose.py index 90af040..0a8b7ab 100644 --- a/app/api/resume_diagnose.py +++ b/app/api/resume_diagnose.py @@ -3,7 +3,7 @@ from fastapi import APIRouter from pydantic import BaseModel, Field -from app.ai.resume_diagnoser.diagnoser import diagnose_all, generate_summary +from app.ai.resume_diagnoser.diagnoser import diagnose_all, generate_summary, polish_content from app.core.context import RequestContext from app.core.database import get_db from app.services.resume_diagnose_service import ResumeDiagnoseService, aggregate_results @@ -83,3 +83,25 @@ async def feedback_issue(issue_id: int, param: FeedbackParam): async for session in get_db(): service = ResumeDiagnoseService(session) await service.update_feedback(issue_id, user_id, param.user_feedback) + + +class PolishParam(BaseModel): + content: list[str] = Field(..., description="用户编辑后的文本段落数组") + + +@router.post("/issue/{issue_id}/polish", summary="AI润色用户编辑的文本") +async def polish_issue_content(issue_id: int, param: PolishParam): + """基于诊断问题上下文,AI润色用户编辑后的文本""" + user_id = RequestContext.user_id.get() + + async for session in get_db(): + service = ResumeDiagnoseService(session) + ctx = await service.get_issue_for_polish(issue_id, user_id) + + result = await polish_content( + module_type=ctx["module_label"], + reference_content=ctx["optimized_content"], + user_content=param.content, + is_summary=ctx["is_summary"], + ) + return {"content": result} diff --git a/app/services/resume_diagnose_service.py b/app/services/resume_diagnose_service.py index 70aaf28..22fc32e 100644 --- a/app/services/resume_diagnose_service.py +++ b/app/services/resume_diagnose_service.py @@ -160,6 +160,21 @@ class ResumeDiagnoseService: issue.user_feedback = user_feedback await self.session.flush() + async def get_issue_for_polish(self, issue_id: int, user_id: int) -> dict: + """获取 issue 润色所需的上下文信息""" + result = await self.session.execute( + select(ResumeDiagnosisIssue).where( + ResumeDiagnosisIssue.id == issue_id, ResumeDiagnosisIssue.user_id == user_id)) + issue = result.scalar_one_or_none() + if issue is None: + raise ValueError("诊断问题不存在") + return { + "module_type": issue.module_type, + "module_label": _MODULE_LABELS.get(issue.module_type, issue.module_type), + "optimized_content": issue.optimized_content, + "is_summary": issue.module_type == "summary", + } + # ===== 工具函数 =====