首页
/ Resume-Matcher 简历增强(Enrichment)功能全解析:AI 定向提问与增量改写的工作流与源码实现

Resume-Matcher 简历增强(Enrichment)功能全解析:AI 定向提问与增量改写的工作流与源码实现

2026-09-10 15:12:08作者:余洋婵Anita

本篇技术指南围绕 Resume-Matcher 的 AI 简历增强功能展开,讲解其"分析弱点 → 定向提问 → 增量补充 bullet"的三段式工作流、三个核心设计约束(最多 6 个问题、只增不改、优先排序)、后端五个 API 端点与前端的向导式状态机实现,并结合源码与测试给出可直接落地的调用方式。读完本文,你将掌握该功能从 Prompt 设计、LLM 调用、数据落库到 React 向导交互的完整实现链路,并能在自己的简历数据处理管线中复用同样的设计模式。

功能概览:为什么需要"增强"而不是"重写"

主简历(Master Resume)是用户所有求职材料的源头,但它经常存在描述笼统、缺少量化指标、技术栈不明等问题。Resume-Matcher 的 enrichment 功能解决的不是"重写一份新简历",而是在保留原有内容的基础上,帮助用户把薄弱条目补充得更具体、更有说服力。它的核心思路来自 enrichment.md 中的三步闭环:

  1. 分析简历,找出 Experience(工作经历)与 Projects(项目)中描述薄弱或过于笼统的条目;
  2. 针对这些条目向用户提出定向澄清问题;
  3. 根据用户的回答生成额外的 bullet point,追加到原有描述之后。

整个过程由 LLM 驱动,但关键决策——问什么、改哪里、怎么合并——通过精心设计的 Prompt 与代码逻辑进行约束,避免 AI 自由发挥破坏简历事实。

工作流:从点击按钮到写入数据库的五个阶段

根据 enrichment.md 与前端 use-enrichment-wizard.ts 中的状态机定义,完整流程分为五个阶段:

  1. 用户在 简历详情页/resumes/[id]/page.tsx#L369) 点击 "Enhance Resume" 按钮,打开 EnrichmentModal
  2. AI 分析简历(analyzing),识别薄弱条目并生成澄清问题;
  3. 用户逐题作答(questions),最多 6 个问题;
  4. AI 根据回答生成新的 bullet(generating),进入预览页(preview)供用户确认;
  5. 确认后写入主简历(applyingcomplete),新 bullet 被追加到原有内容之后。

前端状态机完整覆盖了 idle / analyzing / questions / generating / preview / applying / complete / error / no-improvements 九种状态,其中 no-improvements 是重要的人性化分支:当 items_to_enrichquestions 为空时,向导直接提示"简历已足够好"并给出 analysis_summary,而不是强行让用户走完流程(见 use-enrichment-wizard.ts)。

三大核心设计决策

enrichment.md 明确列出了本功能的三个设计约束,这些约束在源码中都有硬性落实:

设计决策 说明 源码落实
最多 6 个问题 避免让用户感到疲惫,AI 在所有条目中合计最多生成 6 个问题(而非每个条目 6 个) Prompt 中写入 "MAXIMUM 6 QUESTIONS TOTAL - this is a hard limit",见 enrichment.py;同时约束问题 ID 最多到 q_5
增量式增强 原有 bullet 一律保留,新生成的 bullet 追加在其后 routers/enrichment.pyexisting_desc + additional_bullets 直接拼接
问题优先排序 AI 优先提出收益最高的问题 Prompt 指令 "Prioritize the most impactful questions that will yield the best improvements",见 enrichment.py

这里"只增不改"是刻意为之:它既保护了用户简历中已经写好的事实,也让 AI 生成的补充内容永远处于"可审查"状态——用户在预览步骤可以逐个确认新增内容,而不是被迫接受一次大范围改写。

弱点识别规则:AI 如何判断"这段写得不行"

分析阶段的 Prompt(ANALYZE_RESUME_PROMPT)内置了 6 条"描述薄弱指标",这是整个功能的判断基准:

  1. 泛化措辞:出现 "responsible for"、"worked on"、"helped with"、"assisted in"、"involved in" 等模板化短语;
  2. 缺少量化指标:没有数字、百分比、金额或可测量的结果;
  3. 范围不明:团队规模、项目体量、用户数、职责边界含糊;
  4. 没有技术栈:未提及具体技术、工具或方法论;
  5. 被动语态且无归属:看不出候选人个人的实际贡献;
  6. 过于简短:单条 bullet 无法说明工作内容。

Prompt 同时给出了正面范例供模型参照,例如:

- "Led migration of 15 microservices to Kubernetes, reducing deployment time by 60%"
- "Built real-time analytics dashboard using React and D3.js, serving 10K daily users"
- "Architected payment processing system handling $2M monthly transactions"

可以看出,这三条范例分别示范了量化影响、技术栈 + 用户规模、财务量级 + 架构职责,正好与提问阶段想要抽取的信息维度(metrics、technologies、scope、ownership)一一对应。Prompt 还要求输出 JSON 中携带 weakness_reason,让用户在看问题时能理解"为什么问我这些"。

API 端点:三个核心接口与两个衍生接口

enrichment.md 列出了三个核心端点。需要补充的是:实际路由在 main.py 中以 /api/v1 前缀注册(router 自身前缀为 /enrichment),因此完整路径为 /api/v1/enrichment/...。此外,同一模块还扩展实现了 AI Regenerate 功能的两个端点,本文一并说明。

POST /api/v1/enrichment/analyze/{resume_id}

分析简历并生成问题。实现位于 routers/enrichment.py

  • 从数据库取出 processed_data,若为空返回 400(提示重新上传简历);
  • 将整个 processed_data 序列化为 JSON 填入 Prompt,并按 get_language_name 注入 output_language,保证生成的问题、占位符、摘要全部使用用户当前内容语言(项目支持中、英、日、西、法等多语言);
  • 调用 complete_json(prompt, max_tokens=8192, schema_type="enrichment"),并设置 180 秒硬超时
  • 超时返回 504,JSON 解析失败返回 422,其余异常返回 500。

响应结构由 schemas/enrichment.py 定义:

{
  "items_to_enrich": [
    {
      "item_id": "exp_0",
      "item_type": "experience",
      "title": "Software Engineer",
      "subtitle": "Company Name",
      "current_description": ["bullet 1", "bullet 2"],
      "weakness_reason": "Missing quantifiable impact and specific technologies used"
    }
  ],
  "questions": [
    {
      "question_id": "q_0",
      "item_id": "exp_0",
      "question": "What specific metrics improved as a result of your work?",
      "placeholder": "e.g., Reduced API response time by 40%, saved $50K annually"
    }
  ],
  "analysis_summary": "Brief summary of overall resume strength and areas for improvement"
}

注意 item_id 的命名规范:工作经历用 exp_0exp_1(按数组下标),项目用 proj_0proj_1,问题 ID 为 q_0 ~ q_5。这一约定是整个模块后续定位条目、按索引写回数据库的关键。

POST /api/v1/enrichment/enhance

根据用户回答生成增强描述。这是模块中实现最讲究性能的端点(routers/enrichment.py),包含双路径逻辑:

  • 快速路径(Fast path):如果所有 AnswerInput 都携带了 item_id,则通过 _extract_item_from_resume 直接从 processed_dataexp_0 / proj_0 模式解析出条目详情,完全跳过第二次 LLM 分析调用——这是避免"问完问题还要再花 180 秒重新分析"的关键优化;
  • 兼容路径(Legacy path):若答案缺少 item_id(如旧版本前端或直接调 API 的客户端),则重新调用 ANALYZE_RESUME_PROMPT,把 question_id 映射回 item_id,再按条目聚合答案。

无论哪条路径,最终都会对每个条目调用 ENHANCE_DESCRIPTION_PROMPTenrichment.py),要求模型生成 2-4 条新的 bullet。该 Prompt 的核心约束是:

  • 只 ADD,不 REPLACE,不重复已有 bullet;
  • 只使用用户回答中提供的信息,禁止虚构指标
  • 动作导向(Led、Built、Architected、Optimized 等强动词开头);
  • 量化优先,包含技术栈,明确个人贡献;
  • 过去经历用过去时,当前经历用现在时。

响应的 enhanced_description 字段就是将要追加的新 bullet 列表(代码还保留了旧 key additional_bullets 的向后兼容读取)。

POST /api/v1/enrichment/apply/{resume_id}

把增强结果写入主简历(routers/enrichment.py):

  1. 深拷贝 processed_data,避免直接修改数据库中的原始对象;
  2. item_type 分发到 workExperiencepersonalProjects 数组,用 item_id.split("_")[1] 解析下标;
  3. 将新增 bullet 与原有 description 列表拼接(existing_desc + additional_bullets),并处理了 description 可能是字符串的边缘情况;
  4. 同步更新 contentprocessed_data 两个字段后写回数据库;
  5. 返回 {"message": "Enhancements applied successfully", "updated_items": N}

衍生接口:AI Regenerate(按反馈重写)

同一模块(同一 router、同样的 enrichment 前缀)还实现了两个衍生端点:

  • POST /api/v1/enrichment/regenerate:接收用户反馈指令(instruction 上限 2000 字符,由 RegenerateRequest 校验),对选中的 experience / project / skills 条目整体重写。与 enhance 的"追加"不同,这里是"替换",且同样禁止编造事实("Do NOT add any new facts, metrics...")。所有条目通过 asyncio.gather 并行处理,单个失败不会拖垮整体,失败项以 errors 列表返回;
  • POST /api/v1/enrichment/apply-regenerated/{resume_id}:落库前执行严格的一致性校验——通过标题、副标题、原文内容三层匹配定位条目,若简历在重写期间被改动导致无法唯一匹配,则整体拒绝(409 Conflict),防止覆盖用户数据。这一逻辑可以从 apply_regenerated_items 中的 _find_unique_index_by_metadata_lines_equal 辅助函数推断,属于"安全落库"的防御性设计。

前端对应的 API 客户端封装在 lib/api/enrichment.ts,五个函数(analyzeResumegenerateEnhancementsapplyEnhancementsregenerateItemsapplyRegeneratedItems)与后端端点一一对应,错误信息统一取 data.detail 展示。

LLM 调用层:JSON 模式、截断检测与重试

所有端点都经由 complete_json 调用 LLM,这是保证"结构化输出可解析"的公共底座:

  • 强制 JSON-only 系统提示("You must respond with valid JSON only");
  • 模型支持时启用 response_format={"type": "json_object"}(JSON mode),失败后自动降级为纯提示约束;
  • 截断感知重试:按 schema_type="enrichment" 检查返回结果是否缺少 items_to_enrich / questions / analysis_summary 等关键键,若截断则附带提示语重试(最多 2 次重试),提示语见 llm.py
  • 通过 _extract_json 从响应中稳健抽取 JSON 对象(支持前后缀文本包裹)。

分析阶段使用 max_tokens=8192(为多语言输出留足空间),生成阶段使用默认 4096,技能重写阶段为 2048,超时由 calculate_timeout 依据模型与 provider 动态计算。这意味着该功能天然兼容项目支持的 100+ 种 LLM(本地模型、OpenAI 兼容聚合器等),只要模型能输出 JSON 即可接入。

前端实现:向导式交互的工程细节

前端通过 useReducer 实现严格的单向状态流(use-enrichment-wizard.ts),三个核心工程细节值得借鉴:

  1. 问题导航NEXT_QUESTION / PREV_QUESTION / GO_TO_QUESTIONcurrentQuestionIndex 做边界钳制;question-step.tsx 提供进度条、所属条目徽标(工作经历用 Briefcase 图标、项目用 FolderKanban 图标)、以及 Ctrl/⌘ + Enter 快捷提交
  2. 空答案过滤:生成阶段将 answers 中空白作答过滤掉(answer.trim() !== ''),避免把空字符串塞给 LLM;
  3. 错误重试分级retry 根据当前状态智能回退——已有预览则重试 apply,已有答案则回到问题页,否则从头分析,见 use-enrichment-wizard.ts

弹窗组件 enrichment-modal.tsx 使用原生 <dialog> 实现,并在 analyzing / generating / applying 三个加载态禁止关闭(阻止 ESC 与背景点击),避免用户在 LLM 调用中途误操作丢失进度。

质量保障:测试覆盖

后端对该模块的约束在测试中有直接体现:

  • test_regenerate_endpoints.py 验证 RegenerateRequest.instruction 的 2000 字符上限(2001 字符触发 ValidationError),并验证多条目并行处理路径;
  • schema_type="enrichment" 的截断检测逻辑在 test_llm.py 中覆盖。

从测试结构可以推断(见 conftest.py 对 enrichment 相关 mock 的引用),团队将"LLM 不可靠"视为默认前提,测试重点是校验模式、超时、错误码与落库安全性,而非 LLM 的输出内容。

关键文件索引

文件 作用
prompts/enrichment.py 4 组 Prompt:分析、增强、条目重写、技能重写
routers/enrichment.py 5 个端点:analyze / enhance / apply / regenerate / apply-regenerated
schemas/enrichment.py 全部请求/响应 Pydantic 模型
llm.py complete_json:JSON 模式、截断检测、重试与超时
use-enrichment-wizard.ts 向导状态机(useReducer)
lib/api/enrichment.ts 前端 API 客户端
components/enrichment/ 弹窗、提问页、预览页、加载步骤组件
test_regenerate_endpoints.py 端点与校验约束测试

设计模式总结:可复用的"人机协作改写"范式

纵观整个功能,它实际上示范了一个通用的人机协作内容改写范式:AI 负责发现薄弱点并提出好问题(分析阶段),人负责提供事实增量(提问阶段),AI 再负责把事实组织成高质量表述(生成阶段),最后由代码以"只追加、可预览、先校验再落库"的方式保证数据安全。三个核心约束——问题数量上限、增量不改写、事实不虚构——分别从体验、数据完整性和可信度三个维度保护了用户,这套设计同样适用于求职信、自我介绍等任何由用户资料驱动的 AI 生成场景。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
931
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
605
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23