From 556302816b77dc3694845fb2c6c708f4f844725a Mon Sep 17 00:00:00 2001 From: zk Date: Wed, 24 Jun 2026 10:32:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8A=80=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kiro/specs/skill-gap-analysis.md | 45 +++++++++++++++++---------- app/ai/skill_gap_analyzer/analyzer.py | 18 ++++++++--- app/ai/skill_gap_analyzer/prompts.py | 15 ++++++--- app/services/skill_gap_service.py | 15 ++++----- 4 files changed, 60 insertions(+), 33 deletions(-) diff --git a/.kiro/specs/skill-gap-analysis.md b/.kiro/specs/skill-gap-analysis.md index 972e5fd..e8d5b66 100644 --- a/.kiro/specs/skill-gap-analysis.md +++ b/.kiro/specs/skill-gap-analysis.md @@ -44,7 +44,7 @@ 3. 查岗位: - 查 `bg_job` 拿 id、title、skill_tags - 岗位不存在 → 报错 - - skill_tags 为空 → 直接返回满分 10,missingSkills 为空数组 + - skill_tags 为空 → 直接返回满分 10,gaps 为空数组 4. 查简历子表(拼 AI 输入): - `bg_user_resume_education` - `bg_user_resume_work` @@ -53,8 +53,8 @@ - `bg_user_resume_competition` 5. 调 AI(一次): - 输入:岗位 skill_tags 列表 + 简历 skills 字段 + 各子表经历描述 - - 输出:缺失技能的 JSON 数组,必须是 skill_tags 的子集 -6. 计算匹配分:`score = (skill_tags总数 - missingSkills数) / skill_tags总数 × 10`,保留一位小数 + - 输出:差距条目 JSON 数组,每条含 keyword(缺失技能,skill_tags 子集)、title(≤12字)、description(≤30字) +6. 计算匹配分:`score = (skill_tags总数 - gaps条目数) / skill_tags总数 × 10`,保留一位小数 ### 返回 @@ -71,7 +71,10 @@ "resumeName": "李华_产品经理", "targetPosition": "电商产品经理" }, - "missingSkills": ["Python", "SQL", "数据分析", "跨部门沟通"] + "gaps": [ + { "keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。" }, + { "keyword": "数据分析", "title": "数据分析证据不足", "description": "建议补充指标拆解与结论产出的经历。" } + ] } ``` @@ -81,13 +84,13 @@ |------|------| | 用户无简历 | 报错"请先创建简历" | | 岗位不存在 | 报错 | -| skill_tags 为空 | 满分 10,missingSkills 为空数组 | +| skill_tags 为空 | 满分 10,gaps 为空数组 | | AI 调用失败 | 降级:全部标记为缺失,分数 0 | ### AI Prompt ``` -你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,判断用户简历中未覆盖的技能。 +你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。 【岗位技能标签】 {skill_tags} @@ -97,10 +100,15 @@ 规则: 1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能) -2. 只输出用户简历未覆盖的技能,必须是岗位技能标签的子集,原文输出不要修改 -3. 返回 JSON 数组格式,如:["Python", "SQL"] -4. 如果全部覆盖,返回空数组 [] -5. 只返回 JSON 数组,不要其他内容 +2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出 +3. 每个差距条目包含三个字段: + - keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改 + - title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词") + - description:结合岗位要求的一句说明或建议,不超过 30 个字 +4. 返回 JSON 数组格式,如: + [{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}] +5. 如果全部覆盖,返回空数组 [] +6. 只返回 JSON 数组,不要其他内容 ``` --- @@ -128,7 +136,7 @@ - `resumeId`:以哪份简历为模板(来自差距分析返回的 resumeId,用户可能切换过简历) - `optimizeModules`:用户勾选要优化的模块,可选值:summary(个人概述)、skills(技能)、experience(过往经历) -- `addSkills`:用户勾选要新增的技能关键词(来自差距分析的 missingSkills) +- `addSkills`:用户勾选要新增的技能关键词(来自差距分析 gaps 的 keyword) ### 处理流程 @@ -383,7 +391,7 @@ ### 1. 差距分析 Prompt ``` -你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,判断用户简历中未覆盖的技能。 +你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。 【岗位技能标签】 {skill_tags} @@ -393,10 +401,15 @@ 规则: 1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能) -2. 只输出用户简历未覆盖的技能,必须是岗位技能标签的子集,原文输出不要修改 -3. 返回 JSON 数组格式,如:["Python", "SQL"] -4. 如果全部覆盖,返回空数组 [] -5. 只返回 JSON 数组,不要其他内容 +2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出 +3. 每个差距条目包含三个字段: + - keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改 + - title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词") + - description:结合岗位要求的一句说明或建议,不超过 30 个字 +4. 返回 JSON 数组格式,如: + [{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}] +5. 如果全部覆盖,返回空数组 [] +6. 只返回 JSON 数组,不要其他内容 ``` ### 2. 定制简历 - summary 优化 Prompt diff --git a/app/ai/skill_gap_analyzer/analyzer.py b/app/ai/skill_gap_analyzer/analyzer.py index a620574..e6c8ea0 100644 --- a/app/ai/skill_gap_analyzer/analyzer.py +++ b/app/ai/skill_gap_analyzer/analyzer.py @@ -28,17 +28,25 @@ _skill_gap_chain = ( ) -async def analyze_skill_gap(skill_tags: list[str], resume_json: str) -> list[str]: - """分析技能差距,返回缺失技能列表""" +async def analyze_skill_gap(skill_tags: list[str], resume_json: str) -> list[dict]: + """分析技能差距,返回差距条目列表 [{keyword, title, description}],keyword 为缺失技能""" try: raw = await _skill_gap_chain.ainvoke({"skill_tags": str(skill_tags), "resume_json": resume_json}) result = parse_llm_json(raw) if isinstance(result, list): - return [s for s in result if isinstance(s, str) and s in skill_tags] - return skill_tags # 解析异常降级:全部标记缺失 + return result + return _fallback_gaps(skill_tags) # 解析异常降级:全部标记缺失 except Exception as e: log.warning(f"AI技能差距分析失败: {e}") - return skill_tags # 降级:全部标记缺失 + return _fallback_gaps(skill_tags) # 降级:全部标记缺失 + + +def _fallback_gaps(skill_tags: list[str]) -> list[dict]: + """降级:把全部岗位技能标记为缺失""" + return [ + {"keyword": kw, "title": f"缺少{kw}", "description": "岗位要求该技能,简历暂未体现。"} + for kw in skill_tags + ] # ===== 定制简历 - summary 优化 ===== diff --git a/app/ai/skill_gap_analyzer/prompts.py b/app/ai/skill_gap_analyzer/prompts.py index 20e5aad..f7f05b1 100644 --- a/app/ai/skill_gap_analyzer/prompts.py +++ b/app/ai/skill_gap_analyzer/prompts.py @@ -3,7 +3,7 @@ 注意:prompt 中的 JSON 示例花括号必须用 {{ }} 转义,避免被 ChatPromptTemplate 当作变量。 """ -SKILL_GAP_PROMPT = """你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,判断用户简历中未覆盖的技能。 +SKILL_GAP_PROMPT = """你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。 【岗位技能标签】 {skill_tags} @@ -13,10 +13,15 @@ SKILL_GAP_PROMPT = """你是一个技能匹配助手。给定岗位要求的技 规则: 1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能) -2. 只输出用户简历未覆盖的技能,必须是岗位技能标签的子集,原文输出不要修改 -3. 返回 JSON 数组格式,如:["Python", "SQL"] -4. 如果全部覆盖,返回空数组 [] -5. 只返回 JSON 数组,不要其他内容""" +2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出 +3. 每个差距条目包含三个字段: + - keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改 + - title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词") + - description:结合岗位要求的一句说明或建议,不超过 30 个字 +4. 返回 JSON 数组格式,如: + [{{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}}] +5. 如果全部覆盖,返回空数组 [] +6. 只返回 JSON 数组,不要其他内容""" SUMMARY_OPTIMIZE_PROMPT = """你是一个简历优化助手。根据目标岗位信息,微调用户的个人概述。 diff --git a/app/services/skill_gap_service.py b/app/services/skill_gap_service.py index bf557e4..6124e88 100644 --- a/app/services/skill_gap_service.py +++ b/app/services/skill_gap_service.py @@ -79,19 +79,20 @@ class SkillGapService: return self._gap_result(10.0, job, detail.resume, []) # 4. 拼 AI 输入 resume_json = _build_resume_json(detail) - # 5. AI 分析 - missing = await analyze_skill_gap(skill_tags, resume_json) - # 6. 计算匹配分 - score = round((len(skill_tags) - len(missing)) / len(skill_tags) * 10, 1) - return self._gap_result(score, job, detail.resume, missing) + # 5. AI 分析(返回差距条目 [{keyword, title, description}]) + gaps = await analyze_skill_gap(skill_tags, resume_json) + # 6. 计算匹配分(缺失技能 = 差距条目的 keyword) + missing_count = len(gaps) + score = round((len(skill_tags) - missing_count) / len(skill_tags) * 10, 1) + return self._gap_result(score, job, detail.resume, gaps) @staticmethod - def _gap_result(score: float, job: Job, resume: UserResume, missing: list[str]) -> dict: + def _gap_result(score: float, job: Job, resume: UserResume, gaps: list[dict]) -> dict: return { "score": score, "job": {"jobId": str(job.id), "title": job.title, "skillTags": job.skill_tags or []}, "resume": {"resumeId": str(resume.id), "resumeName": resume.resume_name or "", "targetPosition": resume.target_position or ""}, - "missingSkills": missing, + "gaps": gaps, } # ===== 生成定制简历 =====