ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Resume Matcher 简历定制化改造:Skill Verifier Loop 技能目标验证闭环实现指南

Resume Matcher 简历定制化改造:Skill Verifier Loop 技能目标验证闭环实现指南 Resume Matcher 简历定制化改造Skill Verifier Loop 技能目标验证闭环实现指南【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher本文基于仓库内实现计划文档 docs/superpowers/plans/2026-05-06-resume-tailor-verifier-loop.md 展开围绕技能目标验证闭环Skill Verifier Loop这一主题通过把简历定制Tailoring拆成先产出技能目标计划、再由本地验证器过滤分类、最后交给差异生成的小步结构化 LLM 通道让简历能新增 JD 对齐的技能、并在有本地校验保护的前提下改写工作/项目经历措辞。读完本文你将掌握add_skill差异动作的设计、SKILL_TARGET_PLAN_PROMPT与verify_skill_target_plan()的完整分类逻辑、以及它们如何接入既有的 Diff-Based 安全模型。背景单 Prompt 全量输出为什么需要一次技能规划通道在 Diff-Based Resume Improvement 设计文档 中项目已经完成了从让 LLM 输出整份简历 JSON到让 LLM 只输出定向变更diff的架构迁移。其核心动机是整份输出意味着 LLM 必须逐字复述它不想改动的字段——公司名、日期、每一条 bullet、技能列表——而每一次复述都是一次幻觉机会。Diff 模型用generate_resume_diffs() → apply_diffs() → verify_diff_result()的本地管线在结构上消灭了这一类问题原简历是程序化保留的基底LLM 只能通过白名单路径做定点修改。但这个模型中仍有一个结构性缺口技能新增。原 Diff 模型对additional.technicalSkills只允许reorder重排不允许新增——因为没有任何本地证据约束 LLM 新造出来的技能。如果放开新增就会退回LLM 编造技能的老问题原设计文档 1.1 节幻觉向量 #2Fabricated skills。Skill Verifier Loop 正是为了补上这个缺口而设计。它不在现有 Diff 模型之外另起炉灶而是在 Diff 模型内部增加一次前置的、受本地验证保护的技能规划通道第一次 LLM 调用产出技能目标skill targets→ 本地验证器过滤并分类 → 既有的差异生成调用拿到这份已验证的目标计划可以追加被允许的 JD 技能同时围绕这些目标改写 summary、work、project 的 bullet。从源码结构看这一设计已完整落地于 apps/backend/app/services/improver.pygenerate_skill_target_plan()负责产出计划verify_skill_target_plan()负责本地过滤分类apply_diffs()新增add_skill动作负责落地追加generate_resume_diffs()新增skill_targets参数负责把验证结果注入差异 Prompt。整体架构一次先规划、后验证、再编辑的闭环实现计划文档给出的架构一句话概括保持既有 Diff 安全模型但把规划与编辑拆开。extract_keywords ──→ generate_skill_target_plan ──→ verify_skill_target_plan LLM #1 LLM #2规划 本地零 LLM 成本 │ 分类existing / jd_added / supported_by_resume / unsupported ▼ generate_resume_diffs ──→ apply_diffs ──→ verify_diff_result ──→ refiner LLM #3编辑 本地 本地 LLM #4对照仓库中 apps/backend/app/routers/resumes.py 的_improve_preview_flow()编排该路由函数串联了本轮全部新组件完整调用链为extract_job_keywords()提取 JD 关键词保持不变generate_skill_target_plan()让 LLM 产出紧凑的技能目标计划target_skillsstrategy_notesverify_skill_target_plan()用纯本地规则过滤、分类这些目标——这一步是Verifier Loop的核心确保进入编辑阶段的技能都有据可依generate_resume_diffs(..., skill_targetsverified)把验证过的目标写进DIFF_IMPROVE_PROMPTLLM 据此追加技能、改写 bulletapply_diffs()本地应用变更含add_skill动作verify_diff_result()做质量检查warnings 经response_warnings流向前端。技术栈与计划文档声明一致Python 3.13 FastAPI Pydantic v2LLM 调用统一走 LiteLLM 封装的complete_json()测试使用 pytest。Task 1add_skill—— 让技能新增成为受验证的差异动作Schema 变更ResumeChange.action扩为四值在 apps/backend/app/schemas/models.py 中ResumeChange.action的合法值从三值扩展为四值action: Literal[replace, append, reorder, add_skill]同时该模型带有一个关键的model_validator_list_original_only_for_reorderoriginal字段只有在reorder动作下才允许是列表LLM 回传当前条目用于比对对文本动作必须是字符串或None——否则会静默绕过 replace 的原文校验关卡并导致后续编造指标检查崩溃。这一防御在解析期Pydantic 校验期就拒绝畸形输入。apply_diffs()中的add_skill实现apps/backend/app/services/improver.py 中apply_diffs()对add_skill的约束非常严格逐条核对路径锁死path ! additional.technicalSkills直接拒绝——add_skill只能作用于技术技能列表不能作用于 languages / certifications / awards更不能作用于 summary 等文本字段目标必须是列表actual_value必须是 list否则拒绝值必须是非空字符串空串、非字符串一律拒绝去重大小写不敏感用item.casefold()构建已存在集合new_skill.casefold() in existing即拒绝。测试test_add_skill_rejects_duplicate_case_insensitive验证了resume 已有Python尝试追加python会被拒、原列表仍只有 1 个Python必须命中已验证目标_normalize_skill_key(new_skill)归一化空白 casefold必须出现在allowed_skill_targets构建出的allowed_skill_keys集合中——这是Verifier Loop落地的最后一道闸门未经验证器批准的技能无论 LLM 怎么输出都会被拒。只有全部通过才会actual_value.append(new_skill)并计入applied。allowed_skill_targets是apply_diffs()新增的可选参数正是由verify_skill_target_plan()的输出喂入。对应测试apps/backend/tests/unit/test_apply_diffs.py 中的TestApplyDiffsAddSkill覆盖四条路径test_add_skill_to_technical_skills允许列表含Kubernetes时追加成功test_add_skill_rejects_unverified_skillBananaDB不在允许列表拒绝且不进入结果test_add_skill_rejects_duplicate_case_insensitive大小写不敏感去重test_add_skill_rejects_non_skill_path对summary路径执行add_skill被拒。Task 2构建并验证技能目标计划SKILL_TARGET_PLAN_PROMPT一次只做规划的轻量调用新的规划 Prompt 定义在 apps/backend/app/prompts/templates.py核心指令是Return ONLY a JSON object. Do not rewrite the resume——它与编辑通道彻底解耦token 预算小generate_skill_target_plan()中max_tokens2048。其规则要点优先 JD 的 required / preferred 技能包含简历中与 JD 高度相关的既有技能允许包含简历技能列表里缺失的 JD 技能这正是新增的合法性来源不得包含与 JD 无关的技能不得包含证书certificationsreason 用输出语言生成。Prompt 注入的上下文包括existing_skills现有技术技能列表、job_keywords格式化后的 JD 关键词、job_description、original_resume结构化 JSON。输出格式为{ target_skills: [ { skill: skill name, reason: why this skill should be emphasized } ], strategy_notes: brief notes for the next editing pass }generate_skill_target_plan()LLM 侧封装apps/backend/app/services/improver.py 中generate_skill_target_plan()的实现要点用complete_json(prompt..., system_promptYou are a resume skill planning agent..., max_tokens2048, schema_typediff)调用与 diff 通道共用同一 JSON 契约层入参自带防御_sanitize_user_input()对 JD 做提示注入净化对应 LLM-011 规则_INJECTION_PATTERNS覆盖ignore previous instructions、system标签等常见注入模式解析时同时接受字符串与 dict 两种target_skills条目形态空目标直接丢弃非列表输出记录 warning 并返回空计划。verify_skill_target_plan()本地分类器闭环的验证器这是整个 Skill Verifier Loop 的规则核心。函数签名def verify_skill_target_plan( raw_plan: dict[str, Any], original_resume_data: dict[str, Any], job_keywords: dict[str, Any], job_description: str | None None, ) - dict[str, list[dict[str, str]] | str]:它对 LLM 提出的每个技能目标按优先级分类为四类分类判定依据处理existing已存在于additional.technicalSkills归一化后精确命中接受作为低风险目标jd_added存在于 JD 的required_skills/preferred_skills且若传入job_description整词出现在 JD 正文中接受——JD 要求的技能正是定制化产品的目的用户会在差异预览中审阅supported_by_resume未在技能列表但在简历 JSON 文本中整词出现接受——技能背后有简历内容支撑unsupported既不在简历也不在 JD拒绝底层辅助函数保证了判定的严谨性_extract_skill_index()对技能列表做归一化索引_normalize_skill_key空白折叠 casefold()_extract_jd_skill_index()只收录同时满足是 required/preferred 技能且整词出现在 JD 正文的技能_skill_mentioned_in_text()用(?!\w)...(?!\w)词边界正则做整词匹配避免PyTorch误命中PyTorchLightning这类子串。归一化也保证了original与jd_added的判定对大小写、多余空白不敏感。输出为{accepted: [...], rejected: [...], strategy_notes: ...}每个条目带skill、source上述四类之一、reason。拒绝并非硬失败——被拒条目不会进入编辑通道但结果整体仍可继续。Task 3把已验证目标接入差异生成generate_resume_diffs()新增skill_targets参数apps/backend/app/services/improver.py 中generate_resume_diffs()增加了可选参数skill_targets: list[dict[str, Any]] | None并在组装 Prompt 时调用_prepare_skill_targets_for_prompt()把验证结果格式化为- Kubernetes (jd_added): Required by JD - Python (existing): Already present in resume skills无目标时输出No verified skill targets.。对应测试 apps/backend/tests/service/test_improver.py 的test_includes_verified_skill_targets_in_prompt断言传入了skill_targets[{skill: Kubernetes, source: jd_added, ...}]后实际发给 LLM 的 prompt 中同时出现Verified skill targets、Kubernetes与add_skill字样。DIFF_IMPROVE_PROMPT的扩展编辑 Prompt 在 apps/backend/app/prompts/templates.py 中被扩展新增的关键规则规则 10Exception to rule 2: you may add a skill only if it appears in the verified skill targets below—— 这是技能新增的唯一合法出口规则 11要求 LLM 默认扫描 summary 与每一条 work / project / education 描述把已经体现 JD 关键词或技能的内容用 JD 术语改写restate而非新增职责/指标并保留候选人的真实成就规则 12保留原文大小写REST、API、AWS 等专有名词与缩写不被改 casing允许路径列表相应扩增additional.technicalSkills现在支持reorder或add_skilladditional.languages/certificationsTraining/awards新增reorder支持education[i].description新增 replace 支持它是单字符串而非列表故无[j]下标形态JSON 输出示例中加入了add_skill动作的完整样例original: null, value: verified skill target missing from the skills list。策略指令DIFF_STRATEGY_INSTRUCTIONS中full档也被更新为You may rephrase bullets,add verified JD skills, and add new bullets that elaborate on existing work, but do not invent new responsibilities.——增加已验证 JD 技能被明确为 full 定制策略的能力之一。_improve_preview_flow()中的编排顺序按实现计划_improve_preview_flow()的执行顺序必须是先生成并验证技能目标计划再把它喂给差异生成。对应测试test_generate_skill_target_plan_parses_llm_output与test_verify_skill_target_plan_allows_existing_and_jd_skills分别验证了解析与分类两个环节。值得注意的实现细节_improve_preview_flow()还继承了 Diff 模型的既有取舍——结构化简历数据仅在含月级日期时用于 Prompt_has_month_in_dates()检测Jan、Feb等月份模式否则回退到原始 Markdown以避免丢失月份精度。Task 4预览路径验证与测试策略实现计划把最后一环定位为验证闭环的整体正确性落地到仓库中体现为两层测试单元层diff applierapps/backend/tests/unit/test_apply_diffs.py 覆盖add_skill的全部通过/拒绝分支以及reorder的边界——尤其 issue #736 的救回salvage逻辑当 LLM 把新增/删除的条目混进reorder时不整体丢弃该变更而是按请求顺序保留原有条目、仅对技能列表插入通过验证的新条目走与add_skill相同的allowed_skill_keys关卡未验证的新条目一律丢弃原条目永不被静默丢失。服务层prompt harnessapps/backend/tests/service/test_improver.py 覆盖规划输出解析、验证器分类existing / jd_added / supported_by_resume / unsupported、验证目标注入 prompt、空 changes 与缺失changeskey 的降级处理、非法 action 被 Pydantic 拒绝、JSON月级日期优先于 Markdown 的输入选择、策略指令nudge / keywords / full选择。计划文档要求最后运行pytest聚焦 improver、diff applier、refiner 相关用例若改动涉及前端才需要 lint否则跳过并说明原因。防御纵深与失败模式Skill Verifier Loop 与既有安全网的关系是主闸 兜底主闸add_skill只能落在additional.technicalSkills且技能必须命中验证器批准的allowed_skill_keysreorder的救回逻辑对非技能列表的新增条目直接丢弃兜底refine_resume()的_preserve_original_skills()等安全网继续运行——即便 applier 出现 bug 放行了未授权技能下游仍会修正。失败模式与既有 Diff 模型保持一致最坏情况是没有任何变更被应用而非错误的变更被应用失败情形处理LLM 未按 diff 格式输出无changeskey记 warning返回 0 变更generate_resume_diffs()中strategy_notes注明原因全部 diff 被拒返回原简历不变verify_diff_result()提示 No changes were applied部分 diff 被拒部分改进生效rejected 列表生成 warning 流向前端add_skill目标不在验证列表单项拒绝不影响其他变更文件变更总览文件变更内容apps/backend/app/schemas/models.pyResumeChange.action增加add_skilloriginal列表形态仅限 reorder 的校验器apps/backend/app/prompts/templates.py新增SKILL_TARGET_PLAN_PROMPT扩展DIFF_IMPROVE_PROMPT验证目标、add_skill、大小写保留、JD 术语改写规则更新DIFF_STRATEGY_INSTRUCTIONSapps/backend/app/prompts/init.py导出SKILL_TARGET_PLAN_PROMPTapps/backend/app/services/improver.py新增generate_skill_target_plan()、verify_skill_target_plan()、_prepare_skill_targets_for_prompt()apply_diffs()实现add_skill与 reorder 救回generate_resume_diffs()新增skill_targets参数apps/backend/app/routers/resumes.py_improve_preview_flow()编排先规划、验证再生成差异apps/backend/tests/unit/test_apply_diffs.pyadd_skill通过/拒绝/去重/路径约束测试reorder 救回测试apps/backend/tests/service/test_improver.py规划解析、验证器分类、prompt 注入测试小结Verifier Loop 的工程价值从实现计划文档到仓库源码Skill Verifier Loop 展示了一个值得借鉴的 LLM 应用模式不要让 LLM 同时做决定改什么和决定怎么改而是用一次轻量的规划调用 一次纯本地的规则验证把可追加的技能集合显式收敛再交给编辑通道在这个集合内发挥。它没有引入新的依赖、没有推翻 Diff 安全模型、也没有改变前端响应契约resume_preview、diff_summary、detailed_changes原样输出而是以最小侵入把技能新增从不可控的幻觉源变成了可审计、可拒绝、可预览的受控变更。对需要兼顾定制效果与事实保真的简历类产品这套规划-验证-编辑-复核的闭环设计本身即是可直接复用的参考实现。【免费下载链接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.项目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表