驗(yàn)證閉環(huán)實(shí)現(xiàn)指南)
Resume Matcher 簡歷定制化改造Skill Verifier Loop 技能目標(biāo)驗(yàn)證閉環(huán)實(shí)現(xiàn)指南【免費(fèi)下載鏈接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher本文基于倉庫內(nèi)實(shí)現(xiàn)計(jì)劃文檔 docs/superpowers/plans/2026-05-06-resume-tailor-verifier-loop.md 展開圍繞技能目標(biāo)驗(yàn)證閉環(huán)Skill Verifier Loop這一主題通過把簡歷定制Tailoring拆成先產(chǎn)出技能目標(biāo)計(jì)劃、再由本地驗(yàn)證器過濾分類、最后交給差異生成的小步結(jié)構(gòu)化 LLM 通道讓簡歷能新增 JD 對齊的技能、并在有本地校驗(yàn)保護(hù)的前提下改寫工作/項(xiàng)目經(jīng)歷措辭。讀完本文你將掌握add_skill差異動作的設(shè)計(jì)、SKILL_TARGET_PLAN_PROMPT與verify_skill_target_plan()的完整分類邏輯、以及它們?nèi)绾谓尤爰扔械?Diff-Based 安全模型。背景單 Prompt 全量輸出為什么需要一次技能規(guī)劃通道在 Diff-Based Resume Improvement 設(shè)計(jì)文檔 中項(xiàng)目已經(jīng)完成了從讓 LLM 輸出整份簡歷 JSON到讓 LLM 只輸出定向變更diff的架構(gòu)遷移。其核心動機(jī)是整份輸出意味著 LLM 必須逐字復(fù)述它不想改動的字段——公司名、日期、每一條 bullet、技能列表——而每一次復(fù)述都是一次幻覺機(jī)會。Diff 模型用generate_resume_diffs() → apply_diffs() → verify_diff_result()的本地管線在結(jié)構(gòu)上消滅了這一類問題原簡歷是程序化保留的基底LLM 只能通過白名單路徑做定點(diǎn)修改。但這個模型中仍有一個結(jié)構(gòu)性缺口技能新增。原 Diff 模型對additional.technicalSkills只允許reorder重排不允許新增——因?yàn)闆]有任何本地證據(jù)約束 LLM 新造出來的技能。如果放開新增就會退回LLM 編造技能的老問題原設(shè)計(jì)文檔 1.1 節(jié)幻覺向量 #2Fabricated skills。Skill Verifier Loop 正是為了補(bǔ)上這個缺口而設(shè)計(jì)。它不在現(xiàn)有 Diff 模型之外另起爐灶而是在 Diff 模型內(nèi)部增加一次前置的、受本地驗(yàn)證保護(hù)的技能規(guī)劃通道第一次 LLM 調(diào)用產(chǎn)出技能目標(biāo)skill targets→ 本地驗(yàn)證器過濾并分類 → 既有的差異生成調(diào)用拿到這份已驗(yàn)證的目標(biāo)計(jì)劃可以追加被允許的 JD 技能同時(shí)圍繞這些目標(biāo)改寫 summary、work、project 的 bullet。從源碼結(jié)構(gòu)看這一設(shè)計(jì)已完整落地于 apps/backend/app/services/improver.pygenerate_skill_target_plan()負(fù)責(zé)產(chǎn)出計(jì)劃verify_skill_target_plan()負(fù)責(zé)本地過濾分類apply_diffs()新增add_skill動作負(fù)責(zé)落地追加generate_resume_diffs()新增skill_targets參數(shù)負(fù)責(zé)把驗(yàn)證結(jié)果注入差異 Prompt。整體架構(gòu)一次先規(guī)劃、后驗(yàn)證、再編輯的閉環(huán)實(shí)現(xiàn)計(jì)劃文檔給出的架構(gòu)一句話概括保持既有 Diff 安全模型但把規(guī)劃與編輯拆開。extract_keywords ──→ generate_skill_target_plan ──→ verify_skill_target_plan LLM #1 LLM #2規(guī)劃 本地零 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()編排該路由函數(shù)串聯(lián)了本輪全部新組件完整調(diào)用鏈為extract_job_keywords()提取 JD 關(guān)鍵詞保持不變generate_skill_target_plan()讓 LLM 產(chǎn)出緊湊的技能目標(biāo)計(jì)劃target_skillsstrategy_notesverify_skill_target_plan()用純本地規(guī)則過濾、分類這些目標(biāo)——這一步是Verifier Loop的核心確保進(jìn)入編輯階段的技能都有據(jù)可依generate_resume_diffs(..., skill_targetsverified)把驗(yàn)證過的目標(biāo)寫進(jìn)DIFF_IMPROVE_PROMPTLLM 據(jù)此追加技能、改寫 bulletapply_diffs()本地應(yīng)用變更含add_skill動作verify_diff_result()做質(zhì)量檢查warnings 經(jīng)response_warnings流向前端。技術(shù)棧與計(jì)劃文檔聲明一致Python 3.13 FastAPI Pydantic v2LLM 調(diào)用統(tǒng)一走 LiteLLM 封裝的complete_json()測試使用 pytest。Task 1add_skill—— 讓技能新增成為受驗(yàn)證的差異動作Schema 變更ResumeChange.action擴(kuò)為四值在 apps/backend/app/schemas/models.py 中ResumeChange.action的合法值從三值擴(kuò)展為四值action: Literal[replace, append, reorder, add_skill]同時(shí)該模型帶有一個關(guān)鍵的model_validator_list_original_only_for_reorderoriginal字段只有在reorder動作下才允許是列表LLM 回傳當(dāng)前條目用于比對對文本動作必須是字符串或None——否則會靜默繞過 replace 的原文校驗(yàn)關(guān)卡并導(dǎo)致后續(xù)編造指標(biāo)檢查崩潰。這一防御在解析期Pydantic 校驗(yàn)期就拒絕畸形輸入。apply_diffs()中的add_skill實(shí)現(xiàn)apps/backend/app/services/improver.py 中apply_diffs()對add_skill的約束非常嚴(yán)格逐條核對路徑鎖死path ! additional.technicalSkills直接拒絕——add_skill只能作用于技術(shù)技能列表不能作用于 languages / certifications / awards更不能作用于 summary 等文本字段目標(biāo)必須是列表actual_value必須是 list否則拒絕值必須是非空字符串空串、非字符串一律拒絕去重大小寫不敏感用item.casefold()構(gòu)建已存在集合new_skill.casefold() in existing即拒絕。測試test_add_skill_rejects_duplicate_case_insensitive驗(yàn)證了resume 已有Python嘗試追加python會被拒、原列表仍只有 1 個Python必須命中已驗(yàn)證目標(biāo)_normalize_skill_key(new_skill)歸一化空白 casefold必須出現(xiàn)在allowed_skill_targets構(gòu)建出的allowed_skill_keys集合中——這是Verifier Loop落地的最后一道閘門未經(jīng)驗(yàn)證器批準(zhǔn)的技能無論 LLM 怎么輸出都會被拒。只有全部通過才會actual_value.append(new_skill)并計(jì)入applied。allowed_skill_targets是apply_diffs()新增的可選參數(shù)正是由verify_skill_target_plan()的輸出喂入。對應(yīng)測試apps/backend/tests/unit/test_apply_diffs.py 中的TestApplyDiffsAddSkill覆蓋四條路徑test_add_skill_to_technical_skills允許列表含Kubernetes時(shí)追加成功test_add_skill_rejects_unverified_skillBananaDB不在允許列表拒絕且不進(jìn)入結(jié)果test_add_skill_rejects_duplicate_case_insensitive大小寫不敏感去重test_add_skill_rejects_non_skill_path對summary路徑執(zhí)行add_skill被拒。Task 2構(gòu)建并驗(yàn)證技能目標(biāo)計(jì)劃SKILL_TARGET_PLAN_PROMPT一次只做規(guī)劃的輕量調(diào)用新的規(guī)劃 Prompt 定義在 apps/backend/app/prompts/templates.py核心指令是Return ONLY a JSON object. Do not rewrite the resume——它與編輯通道徹底解耦token 預(yù)算小generate_skill_target_plan()中max_tokens2048。其規(guī)則要點(diǎn)優(yōu)先 JD 的 required / preferred 技能包含簡歷中與 JD 高度相關(guān)的既有技能允許包含簡歷技能列表里缺失的 JD 技能這正是新增的合法性來源不得包含與 JD 無關(guān)的技能不得包含證書certificationsreason 用輸出語言生成。Prompt 注入的上下文包括existing_skills現(xiàn)有技術(shù)技能列表、job_keywords格式化后的 JD 關(guān)鍵詞、job_description、original_resume結(jié)構(gòu)化 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 側(cè)封裝apps/backend/app/services/improver.py 中g(shù)enerate_skill_target_plan()的實(shí)現(xiàn)要點(diǎn)用complete_json(prompt..., system_promptYou are a resume skill planning agent..., max_tokens2048, schema_typediff)調(diào)用與 diff 通道共用同一 JSON 契約層入?yún)⒆詭Х烙鵢sanitize_user_input()對 JD 做提示注入凈化對應(yīng) LLM-011 規(guī)則_INJECTION_PATTERNS覆蓋ignore previous instructions、system標(biāo)簽等常見注入模式解析時(shí)同時(shí)接受字符串與 dict 兩種target_skills條目形態(tài)空目標(biāo)直接丟棄非列表輸出記錄 warning 并返回空計(jì)劃。verify_skill_target_plan()本地分類器閉環(huán)的驗(yàn)證器這是整個 Skill Verifier Loop 的規(guī)則核心。函數(shù)簽名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 提出的每個技能目標(biāo)按優(yōu)先級分類為四類分類判定依據(jù)處理existing已存在于additional.technicalSkills歸一化后精確命中接受作為低風(fēng)險(xiǎn)目標(biāo)jd_added存在于 JD 的required_skills/preferred_skills且若傳入job_description整詞出現(xiàn)在 JD 正文中接受——JD 要求的技能正是定制化產(chǎn)品的目的用戶會在差異預(yù)覽中審閱supported_by_resume未在技能列表但在簡歷 JSON 文本中整詞出現(xiàn)接受——技能背后有簡歷內(nèi)容支撐unsupported既不在簡歷也不在 JD拒絕底層輔助函數(shù)保證了判定的嚴(yán)謹(jǐn)性_extract_skill_index()對技能列表做歸一化索引_normalize_skill_key空白折疊 casefold()_extract_jd_skill_index()只收錄同時(shí)滿足是 required/preferred 技能且整詞出現(xiàn)在 JD 正文的技能_skill_mentioned_in_text()用(?!\w)...(?!\w)詞邊界正則做整詞匹配避免PyTorch誤命中PyTorchLightning這類子串。歸一化也保證了original與jd_added的判定對大小寫、多余空白不敏感。輸出為{accepted: [...], rejected: [...], strategy_notes: ...}每個條目帶skill、source上述四類之一、reason。拒絕并非硬失敗——被拒條目不會進(jìn)入編輯通道但結(jié)果整體仍可繼續(xù)。Task 3把已驗(yàn)證目標(biāo)接入差異生成generate_resume_diffs()新增skill_targets參數(shù)apps/backend/app/services/improver.py 中g(shù)enerate_resume_diffs()增加了可選參數(shù)skill_targets: list[dict[str, Any]] | None并在組裝 Prompt 時(shí)調(diào)用_prepare_skill_targets_for_prompt()把驗(yàn)證結(jié)果格式化為- Kubernetes (jd_added): Required by JD - Python (existing): Already present in resume skills無目標(biāo)時(shí)輸出No verified skill targets.。對應(yīng)測試 apps/backend/tests/service/test_improver.py 的test_includes_verified_skill_targets_in_prompt斷言傳入了skill_targets[{skill: Kubernetes, source: jd_added, ...}]后實(shí)際發(fā)給 LLM 的 prompt 中同時(shí)出現(xiàn)Verified skill targets、Kubernetes與add_skill字樣。DIFF_IMPROVE_PROMPT的擴(kuò)展編輯 Prompt 在 apps/backend/app/prompts/templates.py 中被擴(kuò)展新增的關(guān)鍵規(guī)則規(guī)則 10Exception to rule 2: you may add a skill only if it appears in the verified skill targets below—— 這是技能新增的唯一合法出口規(guī)則 11要求 LLM 默認(rèn)掃描 summary 與每一條 work / project / education 描述把已經(jīng)體現(xiàn) JD 關(guān)鍵詞或技能的內(nèi)容用 JD 術(shù)語改寫restate而非新增職責(zé)/指標(biāo)并保留候選人的真實(shí)成就規(guī)則 12保留原文大小寫REST、API、AWS 等專有名詞與縮寫不被改 casing允許路徑列表相應(yīng)擴(kuò)增additional.technicalSkills現(xiàn)在支持reorder或add_skilladditional.languages/certificationsTraining/awards新增reorder支持education[i].description新增 replace 支持它是單字符串而非列表故無[j]下標(biāo)形態(tài)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.——增加已驗(yàn)證 JD 技能被明確為 full 定制策略的能力之一。_improve_preview_flow()中的編排順序按實(shí)現(xiàn)計(jì)劃_improve_preview_flow()的執(zhí)行順序必須是先生成并驗(yàn)證技能目標(biāo)計(jì)劃再把它喂給差異生成。對應(yīng)測試test_generate_skill_target_plan_parses_llm_output與test_verify_skill_target_plan_allows_existing_and_jd_skills分別驗(yàn)證了解析與分類兩個環(huán)節(jié)。值得注意的實(shí)現(xiàn)細(xì)節(jié)_improve_preview_flow()還繼承了 Diff 模型的既有取舍——結(jié)構(gòu)化簡歷數(shù)據(jù)僅在含月級日期時(shí)用于 Prompt_has_month_in_dates()檢測Jan、Feb等月份模式否則回退到原始 Markdown以避免丟失月份精度。Task 4預(yù)覽路徑驗(yàn)證與測試策略實(shí)現(xiàn)計(jì)劃把最后一環(huán)定位為驗(yàn)證閉環(huán)的整體正確性落地到倉庫中體現(xiàn)為兩層測試單元層diff applierapps/backend/tests/unit/test_apply_diffs.py 覆蓋add_skill的全部通過/拒絕分支以及reorder的邊界——尤其 issue #736 的救回salvage邏輯當(dāng) LLM 把新增/刪除的條目混進(jìn)reorder時(shí)不整體丟棄該變更而是按請求順序保留原有條目、僅對技能列表插入通過驗(yàn)證的新條目走與add_skill相同的allowed_skill_keys關(guān)卡未驗(yàn)證的新條目一律丟棄原條目永不被靜默丟失。服務(wù)層prompt harnessapps/backend/tests/service/test_improver.py 覆蓋規(guī)劃輸出解析、驗(yàn)證器分類existing / jd_added / supported_by_resume / unsupported、驗(yàn)證目標(biāo)注入 prompt、空 changes 與缺失changeskey 的降級處理、非法 action 被 Pydantic 拒絕、JSON月級日期優(yōu)先于 Markdown 的輸入選擇、策略指令nudge / keywords / full選擇。計(jì)劃文檔要求最后運(yùn)行pytest聚焦 improver、diff applier、refiner 相關(guān)用例若改動涉及前端才需要 lint否則跳過并說明原因。防御縱深與失敗模式Skill Verifier Loop 與既有安全網(wǎng)的關(guān)系是主閘 兜底主閘add_skill只能落在additional.technicalSkills且技能必須命中驗(yàn)證器批準(zhǔn)的allowed_skill_keysreorder的救回邏輯對非技能列表的新增條目直接丟棄兜底refine_resume()的_preserve_original_skills()等安全網(wǎng)繼續(xù)運(yùn)行——即便 applier 出現(xiàn) bug 放行了未授權(quán)技能下游仍會修正。失敗模式與既有 Diff 模型保持一致最壞情況是沒有任何變更被應(yīng)用而非錯誤的變更被應(yīng)用失敗情形處理LLM 未按 diff 格式輸出無changeskey記 warning返回 0 變更generate_resume_diffs()中strategy_notes注明原因全部 diff 被拒返回原簡歷不變verify_diff_result()提示 No changes were applied部分 diff 被拒部分改進(jìn)生效rejected 列表生成 warning 流向前端add_skill目標(biāo)不在驗(yàn)證列表單項(xiàng)拒絕不影響其他變更文件變更總覽文件變更內(nèi)容apps/backend/app/schemas/models.pyResumeChange.action增加add_skilloriginal列表形態(tài)僅限 reorder 的校驗(yàn)器apps/backend/app/prompts/templates.py新增SKILL_TARGET_PLAN_PROMPT擴(kuò)展DIFF_IMPROVE_PROMPT驗(yàn)證目標(biāo)、add_skill、大小寫保留、JD 術(shù)語改寫規(guī)則更新DIFF_STRATEGY_INSTRUCTIONSapps/backend/app/prompts/init.py導(dǎo)出SKILL_TARGET_PLAN_PROMPTapps/backend/app/services/improver.py新增generate_skill_target_plan()、verify_skill_target_plan()、_prepare_skill_targets_for_prompt()apply_diffs()實(shí)現(xiàn)add_skill與 reorder 救回generate_resume_diffs()新增skill_targets參數(shù)apps/backend/app/routers/resumes.py_improve_preview_flow()編排先規(guī)劃、驗(yàn)證再生成差異apps/backend/tests/unit/test_apply_diffs.pyadd_skill通過/拒絕/去重/路徑約束測試reorder 救回測試apps/backend/tests/service/test_improver.py規(guī)劃解析、驗(yàn)證器分類、prompt 注入測試小結(jié)Verifier Loop 的工程價(jià)值從實(shí)現(xiàn)計(jì)劃文檔到倉庫源碼Skill Verifier Loop 展示了一個值得借鑒的 LLM 應(yīng)用模式不要讓 LLM 同時(shí)做決定改什么和決定怎么改而是用一次輕量的規(guī)劃調(diào)用 一次純本地的規(guī)則驗(yàn)證把可追加的技能集合顯式收斂再交給編輯通道在這個集合內(nèi)發(fā)揮。它沒有引入新的依賴、沒有推翻 Diff 安全模型、也沒有改變前端響應(yīng)契約resume_preview、diff_summary、detailed_changes原樣輸出而是以最小侵入把技能新增從不可控的幻覺源變成了可審計(jì)、可拒絕、可預(yù)覽的受控變更。對需要兼顧定制效果與事實(shí)保真的簡歷類產(chǎn)品這套規(guī)劃-驗(yàn)證-編輯-復(fù)核的閉環(huán)設(shè)計(jì)本身即是可直接復(fù)用的參考實(shí)現(xiàn)?!久赓M(fèi)下載鏈接】Resume-MatcherThe #1 AI Harness for Building Resumes, PDFs, Cover Letters more, locally with 100 LLMs support.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/Resume-Matcher創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考