更新技能
This commit is contained in:
@@ -44,7 +44,7 @@
|
|||||||
3. 查岗位:
|
3. 查岗位:
|
||||||
- 查 `bg_job` 拿 id、title、skill_tags
|
- 查 `bg_job` 拿 id、title、skill_tags
|
||||||
- 岗位不存在 → 报错
|
- 岗位不存在 → 报错
|
||||||
- skill_tags 为空 → 直接返回满分 10,missingSkills 为空数组
|
- skill_tags 为空 → 直接返回满分 10,gaps 为空数组
|
||||||
4. 查简历子表(拼 AI 输入):
|
4. 查简历子表(拼 AI 输入):
|
||||||
- `bg_user_resume_education`
|
- `bg_user_resume_education`
|
||||||
- `bg_user_resume_work`
|
- `bg_user_resume_work`
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
- `bg_user_resume_competition`
|
- `bg_user_resume_competition`
|
||||||
5. 调 AI(一次):
|
5. 调 AI(一次):
|
||||||
- 输入:岗位 skill_tags 列表 + 简历 skills 字段 + 各子表经历描述
|
- 输入:岗位 skill_tags 列表 + 简历 skills 字段 + 各子表经历描述
|
||||||
- 输出:缺失技能的 JSON 数组,必须是 skill_tags 的子集
|
- 输出:差距条目 JSON 数组,每条含 keyword(缺失技能,skill_tags 子集)、title(≤12字)、description(≤30字)
|
||||||
6. 计算匹配分:`score = (skill_tags总数 - missingSkills数) / skill_tags总数 × 10`,保留一位小数
|
6. 计算匹配分:`score = (skill_tags总数 - gaps条目数) / skill_tags总数 × 10`,保留一位小数
|
||||||
|
|
||||||
### 返回
|
### 返回
|
||||||
|
|
||||||
@@ -71,7 +71,10 @@
|
|||||||
"resumeName": "李华_产品经理",
|
"resumeName": "李华_产品经理",
|
||||||
"targetPosition": "电商产品经理"
|
"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 调用失败 | 降级:全部标记为缺失,分数 0 |
|
||||||
|
|
||||||
### AI Prompt
|
### AI Prompt
|
||||||
|
|
||||||
```
|
```
|
||||||
你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,判断用户简历中未覆盖的技能。
|
你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。
|
||||||
|
|
||||||
【岗位技能标签】
|
【岗位技能标签】
|
||||||
{skill_tags}
|
{skill_tags}
|
||||||
@@ -97,10 +100,15 @@
|
|||||||
|
|
||||||
规则:
|
规则:
|
||||||
1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能)
|
1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能)
|
||||||
2. 只输出用户简历未覆盖的技能,必须是岗位技能标签的子集,原文输出不要修改
|
2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出
|
||||||
3. 返回 JSON 数组格式,如:["Python", "SQL"]
|
3. 每个差距条目包含三个字段:
|
||||||
4. 如果全部覆盖,返回空数组 []
|
- keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改
|
||||||
5. 只返回 JSON 数组,不要其他内容
|
- title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词")
|
||||||
|
- description:结合岗位要求的一句说明或建议,不超过 30 个字
|
||||||
|
4. 返回 JSON 数组格式,如:
|
||||||
|
[{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}]
|
||||||
|
5. 如果全部覆盖,返回空数组 []
|
||||||
|
6. 只返回 JSON 数组,不要其他内容
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -128,7 +136,7 @@
|
|||||||
|
|
||||||
- `resumeId`:以哪份简历为模板(来自差距分析返回的 resumeId,用户可能切换过简历)
|
- `resumeId`:以哪份简历为模板(来自差距分析返回的 resumeId,用户可能切换过简历)
|
||||||
- `optimizeModules`:用户勾选要优化的模块,可选值:summary(个人概述)、skills(技能)、experience(过往经历)
|
- `optimizeModules`:用户勾选要优化的模块,可选值:summary(个人概述)、skills(技能)、experience(过往经历)
|
||||||
- `addSkills`:用户勾选要新增的技能关键词(来自差距分析的 missingSkills)
|
- `addSkills`:用户勾选要新增的技能关键词(来自差距分析 gaps 的 keyword)
|
||||||
|
|
||||||
### 处理流程
|
### 处理流程
|
||||||
|
|
||||||
@@ -383,7 +391,7 @@
|
|||||||
### 1. 差距分析 Prompt
|
### 1. 差距分析 Prompt
|
||||||
|
|
||||||
```
|
```
|
||||||
你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,判断用户简历中未覆盖的技能。
|
你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。
|
||||||
|
|
||||||
【岗位技能标签】
|
【岗位技能标签】
|
||||||
{skill_tags}
|
{skill_tags}
|
||||||
@@ -393,10 +401,15 @@
|
|||||||
|
|
||||||
规则:
|
规则:
|
||||||
1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能)
|
1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能)
|
||||||
2. 只输出用户简历未覆盖的技能,必须是岗位技能标签的子集,原文输出不要修改
|
2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出
|
||||||
3. 返回 JSON 数组格式,如:["Python", "SQL"]
|
3. 每个差距条目包含三个字段:
|
||||||
4. 如果全部覆盖,返回空数组 []
|
- keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改
|
||||||
5. 只返回 JSON 数组,不要其他内容
|
- title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词")
|
||||||
|
- description:结合岗位要求的一句说明或建议,不超过 30 个字
|
||||||
|
4. 返回 JSON 数组格式,如:
|
||||||
|
[{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}]
|
||||||
|
5. 如果全部覆盖,返回空数组 []
|
||||||
|
6. 只返回 JSON 数组,不要其他内容
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 定制简历 - summary 优化 Prompt
|
### 2. 定制简历 - summary 优化 Prompt
|
||||||
|
|||||||
@@ -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:
|
try:
|
||||||
raw = await _skill_gap_chain.ainvoke({"skill_tags": str(skill_tags), "resume_json": resume_json})
|
raw = await _skill_gap_chain.ainvoke({"skill_tags": str(skill_tags), "resume_json": resume_json})
|
||||||
result = parse_llm_json(raw)
|
result = parse_llm_json(raw)
|
||||||
if isinstance(result, list):
|
if isinstance(result, list):
|
||||||
return [s for s in result if isinstance(s, str) and s in skill_tags]
|
return result
|
||||||
return skill_tags # 解析异常降级:全部标记缺失
|
return _fallback_gaps(skill_tags) # 解析异常降级:全部标记缺失
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"AI技能差距分析失败: {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 优化 =====
|
# ===== 定制简历 - summary 优化 =====
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
注意:prompt 中的 JSON 示例花括号必须用 {{ }} 转义,避免被 ChatPromptTemplate 当作变量。
|
注意:prompt 中的 JSON 示例花括号必须用 {{ }} 转义,避免被 ChatPromptTemplate 当作变量。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SKILL_GAP_PROMPT = """你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,判断用户简历中未覆盖的技能。
|
SKILL_GAP_PROMPT = """你是一个技能匹配助手。给定岗位要求的技能标签列表和用户简历信息,找出用户简历中未覆盖的技能并生成差距说明。
|
||||||
|
|
||||||
【岗位技能标签】
|
【岗位技能标签】
|
||||||
{skill_tags}
|
{skill_tags}
|
||||||
@@ -13,10 +13,15 @@ SKILL_GAP_PROMPT = """你是一个技能匹配助手。给定岗位要求的技
|
|||||||
|
|
||||||
规则:
|
规则:
|
||||||
1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能)
|
1. 逐个判断岗位技能标签,用户简历中是否体现了该技能(包括直接提及、经历中隐含的技能)
|
||||||
2. 只输出用户简历未覆盖的技能,必须是岗位技能标签的子集,原文输出不要修改
|
2. 只针对未覆盖的技能输出差距条目,已覆盖的技能不输出
|
||||||
3. 返回 JSON 数组格式,如:["Python", "SQL"]
|
3. 每个差距条目包含三个字段:
|
||||||
4. 如果全部覆盖,返回空数组 []
|
- keyword:缺失的技能,必须是岗位技能标签中的原文,不要修改
|
||||||
5. 只返回 JSON 数组,不要其他内容"""
|
- title:差距点的简短标题,不超过 12 个字(例如"缺少 SQL 关键词")
|
||||||
|
- description:结合岗位要求的一句说明或建议,不超过 30 个字
|
||||||
|
4. 返回 JSON 数组格式,如:
|
||||||
|
[{{"keyword": "SQL", "title": "缺少 SQL 关键词", "description": "岗位要求数据查询分析能力,简历未体现。"}}]
|
||||||
|
5. 如果全部覆盖,返回空数组 []
|
||||||
|
6. 只返回 JSON 数组,不要其他内容"""
|
||||||
|
|
||||||
SUMMARY_OPTIMIZE_PROMPT = """你是一个简历优化助手。根据目标岗位信息,微调用户的个人概述。
|
SUMMARY_OPTIMIZE_PROMPT = """你是一个简历优化助手。根据目标岗位信息,微调用户的个人概述。
|
||||||
|
|
||||||
|
|||||||
@@ -79,19 +79,20 @@ class SkillGapService:
|
|||||||
return self._gap_result(10.0, job, detail.resume, [])
|
return self._gap_result(10.0, job, detail.resume, [])
|
||||||
# 4. 拼 AI 输入
|
# 4. 拼 AI 输入
|
||||||
resume_json = _build_resume_json(detail)
|
resume_json = _build_resume_json(detail)
|
||||||
# 5. AI 分析
|
# 5. AI 分析(返回差距条目 [{keyword, title, description}])
|
||||||
missing = await analyze_skill_gap(skill_tags, resume_json)
|
gaps = await analyze_skill_gap(skill_tags, resume_json)
|
||||||
# 6. 计算匹配分
|
# 6. 计算匹配分(缺失技能 = 差距条目的 keyword)
|
||||||
score = round((len(skill_tags) - len(missing)) / len(skill_tags) * 10, 1)
|
missing_count = len(gaps)
|
||||||
return self._gap_result(score, job, detail.resume, missing)
|
score = round((len(skill_tags) - missing_count) / len(skill_tags) * 10, 1)
|
||||||
|
return self._gap_result(score, job, detail.resume, gaps)
|
||||||
|
|
||||||
@staticmethod
|
@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 {
|
return {
|
||||||
"score": score,
|
"score": score,
|
||||||
"job": {"jobId": str(job.id), "title": job.title, "skillTags": job.skill_tags or []},
|
"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 ""},
|
"resume": {"resumeId": str(resume.id), "resumeName": resume.resume_name or "", "targetPosition": resume.target_position or ""},
|
||||||
"missingSkills": missing,
|
"gaps": gaps,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ===== 生成定制简历 =====
|
# ===== 生成定制简历 =====
|
||||||
|
|||||||
Reference in New Issue
Block a user