多Agent架构实战:从0到1搭建生产级简历生成系统

发布时间:2026/10/12 9:37:54
多Agent架构实战:从0到1搭建生产级简历生成系统
两年前我第一次尝试用大模型做简历生成工具当时的做法非常粗暴把用户一长串杂乱信息拼进一个Prompt丢给大模型让它一次吐出一份完整简历。结果只能用偶尔能看经常翻车来形容——工作经历写得像流水账技术栈排序和岗位要求完全对不上格式上三天两头出现非法JSON更别说让用户在一份不可控的长文里逐段修改了。后来我把这套方案推翻重做换成了多Agent协作架构信息采集、岗位分析、简历撰写、质量评审各由一个独立Agent负责中间用编排层串成一条可控的工作流。这个改动看着简单实际落地之后才体会到差别——单Agent方案再怎么调Prompt都很难达到生产级要求多Agent协作才是把写简历这件事拆明白的正确方式。这套系统的代号我称之为可写简历完整实现过程中踩了很多坑也沉淀了不少可以复用的经验。这篇文章适合两类人一类是想把简历生成这类文档工具真正做成产品的人另一类是准备应对大厂技术面试、需要在系统设计环节展示多Agent架构能力的开发者。我不会只讲概念而是把从0到1的实现路径、每个Agent的边界划分、参数配置、以及实际跑过程中遇到的各种问题全部展开说明。1. 为什么多Agent协作才是简历生成的生产级方案1.1 单Agent写简历的三个致命问题先聊聊我为什么抛弃最初的单Agent方案。一份合格的简历本质上不是一个从信息到文本的简单翻译任务而是包含了信息清洗、岗位匹配、内容组织、质量校验四条逻辑链路的复杂工程。单Agent方案要求模型在一个上下文窗口内同时完成所有事情这必然带来三个问题。第一是上下文过载与指令稀释。用户原始输入可能是一大段口语化描述加上目标JD、希望突出的重点以及格式要求整体拼起来之后Prompt非常长。模型在处理长上下文后期会出现明显的中间遗忘现象——开头的指令记得后半段的指令开始走样。我第一版就遇到过模型在项目经历部分忘记按STAR法则写、在个人总结部分编造用户没提供过的技能细节。第二是责任无法追溯。单Agent方案是一个黑盒输出质量差了你只能反复调Prompt碰运气没法定位到底是哪个环节出了问题。是信息没提取干净还是岗位匹配逻辑不对还是生成阶段表达太差所有问题混在一个环节里复盘和迭代的效率极低。第三是没有反馈闭环。单Agent方案产生结果之后整个流程就结束了没有任何自检机制。写得不好的简历只会原样输出给用户用户需要自己去逐段审视这与生产级完全背道而驰。生产级意味着系统自己要能判断这版简历是否达到了可交付的标准而不是交付一个半成品让用户买单。1.2 什么才算真正的生产级方案我在设计可写简历之前先给自己的系统定了四条生产级标准这四条标准后来成了每次结构决策的评判依据。稳定输出是底线。系统在相同的输入下输出质量不能有大的波动。简历左下角不能时而结构化、时而散文式更不能出现JSON字段随机缺失这类低级问题。这条标准直接把一个Agent干所有事的方案否掉了因为你没法控制一个超长任务在一次调用里的稳定性。可观测性同样关键。系统必须能把处理过程拆成一个个中间状态让开发者知道当前走到了哪个环节、这个环节的输出长什么样。单Agent方案只有一个最终输出中间过程完全不可见出了问题只能靠猜。可干预是产品的核心需求。用户中途可能要修改某段工作经历、补充一个项目、调整目标岗位。如果是一个完整的长文本生成过程用户没法在中间环节介入但如果是多Agent拆解后的模块化结构用户可以针对单个模块重新生成其他模块不受影响。成本可控也不能忽视。每次完整生成都调用一次超长上下文的模型翻车了就全量重来COST和延迟都难以接受。拆分成多个专注的小任务之后就算其中一个环节需要重跑其余结果可以缓存复用。1.3 这种方案在面试中意味着什么如果你是在准备大厂技术面试多Agent架构本身就是一个极佳的系统设计题目。面试官通常不会满足于我用多个Agent分别做不同的事这种回答他们更想听到的是你如何拆解任务边界、如何设计Agent间的协作协议、如何处理失败回退、如何控制成本和延迟。这些问题的答案恰恰就是你在实现一个生产级简历系统时真正要解决的问题。我后面会完整展开这套实现从架构设计到核心代码再到实战踩坑全程用一个真实的简历生成系统作为载体。这套方法论不只在简历场景适用凡是输入信息杂乱、需要匹配特定目标、产出有明确质量要求的文档生成任务都可以套用同样的多Agent架构。2. 整体架构设计与Agent分工2.1 核心链路与四类Agent可写简历最终落地的架构是四类Agent加一个编排层形成一条有反馈回路的工作流。用户输入原始信息和目标JD后流程按以下链路推进信息采集Agent接收用户的原始输入将口语化、顺序混乱、字段缺失的文本清洗成结构化JSON。这等于给系统建立稳定的数据底座后续任何环节都复用这一份结构化数据不重复解析用户原文。岗位分析Agent接收目标JD文本抽取显式要求、隐性要求、技能关键词、经验年限这些信息。它的存在让简历生成有了靶子后续撰写环节不是自由发挥而是照着岗位需求定向输出。简历撰写Agent本身内部按模块分拆。它拿到结构化信息和JD分析结果分为个人总结、工作经历、项目经历、技能清单四个模块分别生成每个模块各生成一次最后通过模板渲染拼装成完整简历。这样设计既保证了每个模块的专注度也方便用户单独修改某一段。评审Agent是整个流程的质量门禁。它以挑剔招聘官的视角对组装完成的简历打分从信息完整度、JD匹配度、量化程度、格式规范、真实性五个维度给出评分和修改意见。评分不达标时调度层会带着修改建议回到撰写Agent执行修订最多循环两轮。编排层不承担具体的生成任务但它管理所有状态流转、检查每个环节的输出合法性、决定何时进入下一阶段、何时触发重试。职能核心输入核心输出关键参数信息采集Agent用户原始文本结构化JSONtemperature0.1岗位分析Agent目标JD文本技能关键词/权重temperature0.2简历撰写Agent结构化数据JD分析分模块Markdowntemperature0.7评审Agent完整简历JD分析评分修改建议temperature0.22.2 Agent边界划分的三个依据确定这四类Agent的边界我遵循三个原则。第一个原则是单一职责。每个Agent只做一件逻辑独立的事信息采集只负责清洗不负责判断用户适合什么岗位岗位分析只解析JD不参与简历内容生成。这样的划分让每个Agent的Prompt足够聚焦模型的发挥更加稳定。第二个原则是上下文最小化。每个Agent只拿到完成自己任务所必需的信息而不是把全部数据一股脑塞给它。信息采集Agent只看到用户原始输入职位分析Agent只看到JD文本撰写Agent只看到结构化数据和JD分析结果不需要接触原始噪音文本。上下文越小模型的注意力和输出质量越可控。第三个原则是状态可持久化。每个Agent的输出都是一份可以被序列化保存的中间产物。这样做有两个好处一是用户中途改信息时只需要对受影响的模块重新调用Agent其他模块的结果继续复用二是在排查问题时每一环节的结果都可以单独拿出来检查。2.3 编排层与人工介入点编排层的核心是一个状态对象贯穿整个流程。我设计的GraphState至少包含以下字段原始用户输入、结构化信息、JD分析结果、各模块生成内容、组装后的完整简历、评审分数、当前循环次数。所有Agent都是无状态的纯函数输入输出都通过GraphState传递这样任何一步出错都可以从状态快照恢复重跑。这个架构里还专门预留了人工介入点。在撰写Agent完成任务、评审Agent给出分数之后系统不会直接把结果抛给用户而是先展示分模块的中间结果。用户可以在确认阶段单独要求重新生成某个模块也可以针对评审建议选择某一条手动接受或忽略。人工介入的存在让这套系统不是全自动黑盒而是半自动协作这个体验差异对文档生成类产品非常关键。从工程角度看人工介入点本质上是把Agent的执行结果当作提案而不是最终结论。生产级系统很多地方都需要这种人机协同的交互设计。3. 核心实现从0到1搭建多Agent简历系统3.1 技术选型为什么用图状态机编排这里我做一个关键决策说明多Agent的编排方式我选择的是基于图状态机的模型而不是简单的线性流水线。如果你直接用Python顺序调用几个函数很快会遇到一个问题——流程一旦有分支和回退线性代码就会变成一堆纠缠不清的if-else判断。比如评审Agent返回不达标时要回到撰写Agent修订修订完还要重新评审这个过程如果用顺序代码写加的循环和状态判断会很快让代码变得不可维护。图状态机的思路是把流程中的每个关键节点定义为状态把Agent执行定义为状态之间的跳转由调度器统一管理跳转逻辑。核心的状态定义大概是下面这样# 状态定义 class GraphState: raw_user_input: str jd_text: str structured_info: dict jd_analysis: dict resume_modules: dict assembled_resume: str review_result: dict revise_count: int 0 MAX_REVISE_COUNT: int 2状态机的优势是显式的状态流转逻辑。每个Agent是无状态的它只负责读取当前状态的某几个字段经过处理后写入另外几个字段不会产生副作用。调度器则负责决定当前状态该走哪个Agent、Agent执行完后该判断什么条件、是否跳转到回退分支。这种结构在调试时特别好用任何一步出了问题只要看状态快照和执行日志立刻能定位到原因。3.2 信息采集Agent把混乱输入变成结构化数据信息采集Agent的任务描述起来很简单但实现上有不少细节。用户可能给你发来这样一段话我在某互联网公司干了三年主要做后端开发搞过订单系统重构那边流量很大双十一活动的时候订单量翻了几倍我负责的模块没崩。技术栈主要是Java和Spring Boot也了解Redis。这种输入缺失信息严重没有时间格式、没有公司名称、没有量化成果的完整描述甚至带有很多口语化表达。信息采集Agent的Prompt需要注意三个要点。第一明确告诉模型只提取用户明确提供的信息绝对不能推断或补全——否则模型会自作主张编造公司名和项目名这在简历场景是不可接受的。第二要求它按固定JSON Schema输出缺字段用空字符串或空数组表示数字字段无法识别就置空。第三给定一个示例展示一段口语化输入 对应的结构化输出让模型的输出风格有锚点可循。这里我会写清楚结构化字段的定义方便直接参考{ personal_info: { name: , phone: , email: , city: }, work_experience: [ { company: , position: , start_time: , end_time: , description: , achievements: [] } ], project_experience: [ { name: , role: , description: , tech_stack: [], highlights: [] } ], education: { school: , major: , degree: , graduation_year: }, skills: [], self_summary: }实现时我给采集Agent设置了0.1的temperature保证它在结构化输出时的稳定性不会同一个输入在不同请求中返回不同的字段内容。这里顺手补充一个细节如果用户输入的信息量过大导致一次调用超长我会对工作经历和项目经历做分段提取先提取基本盘再逐段补充列表信息最后合并去重。3.3 岗位分析AgentJD解析与技能关键词抽取岗位分析Agent的输出质量直接决定了简历的匹配度这是整个系统里专业含量最高的环节。一个粗糙的做法是把整段JD扔给模型然后问请给出关键词这样抽取结果会非常浅——它只会捞出一堆名词分不清哪些是硬性要求哪些是加分项更读不出JD背后隐含的侧重方向。我的改进方案是让岗位分析Agent分两步走。第一步做显式解析提取JD中明确出现的技能关键词、工具名称、经验年限、学历要求输出成结构化字段。第二步做隐性需求推断结合JD中对工作内容的描述推断出候选人需要具备什么样的能力特质和项目经验。比如JD中反复强调在高流量场景下保障系统稳定性隐性需求可能包括处理高并发问题的实战经验针对线上故障的排查能力。实际输出的结构化结果长这样{ job_title: 高级后端开发工程师, required_skills: [Java, Spring Boot, 分布式系统, MySQL], preferred_skills: [Redis, 消息队列, Kubernetes], years_experience: 3, degree_requirement: 本科及以上, hidden_requirements: { stability_under_high_load: 需要体现高并发场景下的系统保障经验, system_design: 需要体现系统架构设计与技术选型能力, collaboration: 需要体现跨团队协作经验 }, keyword_weights: { Java: 5, Spring Boot: 4, 分布式系统: 4, MySQL: 3, Redis: 2 } }这里的关键是keyword_weights后续撰写Agent在描述工作经历时会参考这些权重决定重点描述什么技术方向。岗位分析Agent的temperature同样保持低值解析类任务不需要创造性。3.4 简历撰写Agent分模块生成与模板组装简历撰写Agent是唯一一个需要体现创造性写作能力的环节所以它的temperature我设置到0.7要给模型足够的表达自由度。但这个自由度受限于模块化拆解——我绝不一次性生成整份简历而是拆成四个模块分别调用。模块划分和对应职责如下个人总结模块抓取候选人最核心的定位标签提炼3到4句高度概括的优势陈述工作经历模块按公司维度展开职责与成果强制要求使用动作动词开头、结果量化表达项目经历模块挑出2到3个与目标岗位最相关的项目按项目背景-我的职责-技术方案-量化结果的结构书写技能清单模块根据JD权重排序技术栈把与目标岗位相关的技能往前排。每个模块的Prompt拼接逻辑是系统Prompt设定角色和写作规范用户Prompt注入结构化数据和JD分析结果。例如工作经历模块的用户Prompt简化之后大致是这个逻辑def build_work_experience_prompt(structured_info, jd_analysis): works json.dumps(structured_info[work_experience], ensure_asciiFalse) jd_context { required_skills: jd_analysis[required_skills], hidden_requirements: jd_analysis[hidden_requirements], keyword_weights: jd_analysis[keyword_weights] } return f 【候选人工作经历结构化数据】 {works} 【目标岗位关键词与权重】 {json.dumps(jd_context, ensure_asciiFalse)} 写作要求 1. 每段工作经历必须展开为至少3个要点每个要点以动作动词开头。 2. 涉及的技能尽量对齐岗位要求里权重高的关键词。 3. 有数据支持的内容要量化表达没有数据的内容如实描述禁止编造数据。 4. 只使用候选人结构化数据中存在的项目与成果不得补充额外经历。 模块生成完毕后用模板渲染对模块进行组装。我使用的是Jinja2模板把Markdown结构固定下来各模块内容以变量形式注入。固定模板的好处是保证整体格式一致性不会出现有的模块把工作经历和项目经历混在一起的情况。from jinja2 import Template RESUME_TEMPLATE Template( # {{ personal_info.name }}的简历 ## 个人总结 {{ modules.summary }} ## 工作经历 {{ modules.work_experience }} ## 项目经历 {{ modules.projects }} ## 技能清单 {{ modules.skills }} )组装之后的完整Markdown简历才进入评审环节。3.5 评审Agent最后一公里的质量门禁评审Agent的设计目标是用招聘官的视角挑毛病。实现上我做了几层防御来保证评审质量。系统Prompt里将评审Agent的人设设定为在大型互联网公司有着多年经验的资深面试官以挑剔著称。同时我明确要求它列出至少三条可执行的修改建议而不是笼统地说内容可以更丰富。这是为了对抗大模型天然倾向给出温和反馈的行为模式。评分逻辑用JSON结构化输出五个维度的权重分别是JD匹配度30%、信息完整度20%、量化程度20%、格式规范15%、真实性15%。总分低于75分时判定为不达标驳回给撰写Agent修订达到75分以上时标为通过。def review_agent(resume_md, jd_analysis): review_prompt f 请以挑剔的招聘官视角评审以下简历 --- {resume_md} --- 目标JD关键词与权重 {json.dumps(jd_analysis[keyword_weights], ensure_asciiFalse)} 请严格打分输出JSON {{ scores: {{ jd_match: 0-100, info_completeness: 0-100, quantification: 0-100, format_standard: 0-100, authenticity: 0-100 }}, overall: 0-100, must_fix_issues: [至少三条具体修改建议], optional_suggestions: [可选优化建议] }} raw llm_call(REVIEW_SYSTEM_PROMPT, review_prompt, temperature0.2, json_modeTrue) return parse_review_result(raw)回退逻辑在编排层实现最大修订次数设为2轮。为什么不是无限制循环因为每次修订都会产生token消耗而且实测下来超过两轮之后修改幅度递减性价比很低。两轮的策略是第一轮修掉必须修的问题第二轮只处理仍被点名的问题。如果两轮之后评审依然在关键维度上不达标系统直接输出当前版本并附上评审报告交给用户决策而不是继续烧token空转。3.6 Prompt与模型参数的实战配置我在调试这套系统的过程中逐渐固定了一套参数配置各个Agent因为任务性质不同temperature差异很大。信息采集和岗位分析属于抽取理解类任务temperature设为0.1和0.2保证同样的输入能稳定产出同样的结构化结果。评审Agent虽然带点主观判断色彩但我同样设为0.2因为评分标准需要一致性同一个简历两次评分不能差出20分。撰写Agent是唯一的创作环节temperature设为0.7让表述有变化、有活力但不超过0.8——再高就容易飘出现随意编造概念、风格失控这类问题。系统提示词还有一个重要设计原则为每个Agent定义清晰的角色人设并配套给出该角色的典型行为准则。我踩过的坑是早期Prompt写得像工具使用说明模型表现得机械且僵硬。后来我改成人设驱动的方式例如采集Agent的System Prompt开头就是你是一位资深的简历顾问你善于从口语化的信息中梳理关键事实你办事严谨绝不编造任何候选人没有提供的信息。实测下来模型的遵从度明显提升输出内容也更符合场景需求。模型参数配置汇总Agenttemperaturemax_tokens系统提示词角色定位备注信息采集0.12000资深简历顾问JSON模式输出岗位分析0.21500招聘JD解析器输出含关键词权重简历撰写0.74000技术简历撰写专家分模块生成可重复调用评审0.21000挑剔的资深面试官必须输出至少3条修订建议4. 实操过程中踩过的坑与排查实录4.1 结构化输出翻车非法JSON第一个高频问题是大模型返回的JSON偶尔会不合法。明明Prompt里写了必须输出合法JSON但模型有时会在JSON前后加一些解释性文字或者字段名里漏掉引号甚至在长字符串里夹杂未转义的中文符号。一开始我用正则硬解析发现效果很差因为错误模式太多了根本枚举不完。后来我改为两层防御。第一层是重试机制检测到JSON解析失败时把模型刚才的错误输出附上错误信息让它重新严格输出JSON通常一次重试就能成功。第二层是兜底解析如果重试也失败就进入本地容错逻辑提取结果中所有能识别的键值对优先保留可用的字段放弃损坏严重的部分。这两层下来结构化输出的成功率从90%提升到了99.5%以上。4.2 评审Agent放水评分严重虚高最开始评审Agent给的分数普遍在90分上下几乎不拒稿这明显不对。我后来定位到原因是系统Prompt里请严格评审这句话的约束力不够模型的默认行为总是倾向于给正面反馈。修复方式是三管齐下。第一在系统Prompt里加入示例偷懒场景——明确告诉模型如果你被评为候选人提供了过高的分数那说明你完成得不够好第二要求你必须在输出中列出至少三条必须修改的问题如果找不出三个问题请重新审阅简历第三把岗位JD的关键词权重喂给评审Agent让它把内容与实际岗位要求逐条对比而不是凭感觉打分。改完之后评审分数整体回落到合理区间拦截效果明显提升。4.3 长简历截断与信息丢失当用户的工作经历和项目经历加起来超过五个条块的时候模块化Prompt也会触到上下文长度限制。这导致生成的后半段简历质量明显下降细节丢失严重甚至出现未完成的句子。解决方法是把内容进一步分块。工作经历模块内部再按公司分块调用每家公司的经历单独生成一次项目经历模块同理每个项目单独生成。全部生成完成后由模板组装。每个子块的Prompt上下文控制在2000token以内模型在短上下文下的注意力更集中输出质量反而比一次性生成全部经历更好。同时我在组装时加入字段完整性校验检查最终简历是否覆盖了结构化数据中的所有重要条目发现遗漏会触发对特定缺失模块的重跑。4.4 多轮修订之后风格漂移评审Agent驳回后撰写Agent进入修订模式这一环节出现过风格漂移的问题。第一版生成时表述风格偏严谨第二轮修改时模型可能把语气改得太口语化或者不断重复强调某个技能导致整份简历看起来像两份拼接的。我增加了一个风格锚点机制系统Prompt中保存首轮生成摘要里的关键风格片段例如使用技术量化前缀如QPS提升到xx避免熟练掌握这类平淡表述修订时把风格锚点一并注入Prompt让修订在保持风格连续性的前提下执行修改建议。4.5 成本失控与重复生成浪费多Agent架构虽然每个Agent上下文更短但调用次数比单Agent多了好几倍。早期我没有做任何缓存用户在前端不小心点了两次生成后端就重复跑了整个流程token成本直接翻倍。后续我在部署层面做了两件事一是给所有Agent调用增加了基于输入参数哈希的结果缓存相同输入直接返回上次结果二是中间结果持久化到存储层即使整体流程中断重启也可以从最近的已完成节点续跑而不是从零开始。4.6 用户意图理解偏差与信息冲突信息采集环节还存在一个更微妙的坑用户在不同地方提供的信息会互相冲突。比如用户在自我介绍里说熟悉Python但技能清单里并没有Python或者工作经历的时间线前后对不上。早期的采集Agent会直接忽略冲突选择相信其中一个字段这会在最终简历里埋下事实错误。后来我在信息采集Agent的输出中增加了conflict_notes字段凡是发现冲突或疑点就记录下来用户在确认阶段可以看到系统检测到以下信息存在疑问请确认。制度上不替用户做决定只暴露问题这是文档生成类产品在可信度上的底线要求。4.7 常见问题速查表问题现象根因解决方案JSON解析失败模型输出非法JSON重试机制容错提取兜底评审分数虚高test模型默认倾向正面反馈人设强化强制列问题关键词对照长简历信息丢失单次Prompt超出上下文按公司/项目再分块生成修订后风格突变缺少风格连续性约束注入首轮风格锚点片段重复生成成本翻倍缺少缓存输入哈希缓存断点续跑信息冲突未提示采集Agent自行取舍输出conflict_notes交由用户确认关键词覆盖不全单次抽取遗漏隐性要求分显式/隐性两步解析5. 从这套系统里沉淀下来的几点经验梳理完整个可写简历项目的实现最让我感慨的是多Agent架构带来的不只是输出质量的提升更重要的是系统结构上的清晰化。以前单Agent版本里所有问题的排查都是黑盒猜谜现在已经变成流水线上定位问题每个环节的输入输出都在掌控之中用户也可以在生产流程的任意关键节点介入修正。想对正在准备大厂技术面试的开发者多说几句。面试里聊多Agent架构最忌面面俱到讲一堆Agent的名字和概念却说不清拆分的依据和协作的机制。真正加分的是你能抛出评审Agent为什么需要独立的角色人设修订为什么最多两轮状态信息如何持久化这类具体设计决策背后的思考过程。这套简历系统的体量刚好合适——复杂度足够展示架构能力又不会大到难以在面试时间尺度内讲清楚。我最后分享一个实际体验。把多Agent方案做完之后我又把同一套模板迁移到了另外两个文档场景——季度绩效自评生成和岗位晋升材料生成。两者都是输入信息杂乱、需要对齐评价标准、对结果有明确格式要求的类型只需要换掉岗位分析Agent的解析目标和撰写Agent的模板整体架构基本不动。这足以说明多Agent协作的通用价值不在某一个场景里而是建立了一套任何文档生成类任务都能快速接入的基础设施。