1. 为什么"写简历"这种看起来简单的任务,必须上多Agent
1.1 单轮对话生成简历的三个致命问题
先讲个真实场景。我第一次尝试用大模型做简历生成的时候,路径跟大多数人一样:把职位描述(JD)粘贴进去,把自己的旧简历粘贴进去,再加一句"帮我生成一份匹配这个岗位的简历"。结果出来的东西,怎么说呢,能用,但也就停留在"能用"的水平。语句通顺、结构完整,但充满了一种似曾相识的模板感:所有经历都被写成了"负责XX系统的开发与维护",没有任何可衡量的成果,也没有针对JD关键词做针对性改写。
这个问题的根源不在模型能力,而在任务组织方式。你让一个模型在一次推理里同时完成五件事:理解JD、提取经历、做技能映射、组织语言、排版格式化。这些任务的性质完全不同,有的需要严格的结构化输出,有的需要创造性的语言组织,有的需要判断力。混在一个上下文里,模型只能"平均用力",最后往往用最保险的套路化表达来应付。
第二个问题是上下文窗口的焦虑。一份JD几百字,一份原始简历几千字,加上角色设定、输出格式要求、示例,很容易逼近上下文上限。更麻烦的是,当你需要迭代修改某个模块时,整段长Prompt要重新发一遍,改一处动全身,Token成本也跟着涨。
第三个问题,也是单轮方案里最无解的:没有质量闭环。模型写完了就是写完了,没有第二双眼睛去检查。JD里的核心关键词是不是覆盖了?量化成果够不够?STAR逻辑通不通?有没有事实性错误?这些单靠一次生成不可能保证。
我后来用一个很简单的实验验证了这件事:把同样一份素材用"单Prompt直接生成"和"分步骤串行生成"各跑五遍,前者的输出方差非常大,有时能到70分,有时只有40分;后者基本稳定在80分以上。这就是我想说的核心观点——多Agent不是一种花哨的架构选择,而是把简历生成这个复杂任务,从"一次不可控的大推理"拆成"一串可验证的小推理"。
1.2 多Agent拆的是不确定性,不是工作量
理解多Agent的切入点,不是"用了多个模型所以高级",而是"简历生成这个任务的每个阶段,需要完全不同的认知能力"。
拆开来看,简历生成其实包含五个性质迥异的子任务:
- 需求理解:把JD里的显性要求和隐性偏好抽出来,这需要信息提取能力;
- 经历挖掘:从用户提供的散乱原始素材里找出真的有价值的内容,这需要判断力和提问能力;
- 能力匹配:把经历映射到JD要求上,找出缺口和亮点,这需要对照分析能力;
- 内容写作:把素材组织成有说服力的表达,这需要语言生成能力;
- 质量审查:检查内容是否准确、格式是否规范、表达是否有力,这需要批判性评估能力。
让同一个模型在一轮对话里切换这五种认知模式,它的表现必然打折。但让五个独立的Agent各司其职,每个Agent的Prompt里只强调一种能力、只负责一段输入输出,模型的注意力就集中了,输出质量会明显提升。
打个比方。你让一个全栈工程师同时干产品经理、UI设计师、后端开发和测试的活,他也能干完,但你不会指望每个环节都是生产级水平。真正的生产流程是每个环节有专人负责、有明确交接物、有质量检查点。多Agent架构就是在软件里复刻这套流水线。
另外很重要的一点是:拆成子任务之后,每个子任务的输出都变成结构化数据,这意味着系统可以在任意节点插入校验规则。比如需求解析Agent输出的是一个JSON结构,我可以在代码层面检查它是否真的提取出了JD里的前五个核心关键词;技能映射Agent输出的是矩阵,我可以检查是否存在"JD要求但经历中缺失"的项。这些校验在单Prompt方案里完全做不到,而在多Agent架构里,每一层都可以加护栏。项目上线之后我最大的感受是:调试成本降低了一个量级。单Prompt方案出了问题,你不知道是哪一步推理错了;多Agent方案的日志里可以看到每一步的输入输出,问题定位是线性的。
2. 完整架构设计:五个Agent和一个编排器
2.1 角色定义、输入输出与模型选型
我的生产级方案里一共定义了五个Agent,外加一个编排器。先列一张表,把每个角色的边界交代清楚:
| Agent角色 | 输入 | 输出 | 推荐模型档位 |
|---|---|---|---|
| 需求解析Agent | 原始JD文本 | 结构化需求:关键词列表、职责分类、硬性要求、软性偏好 | 小模型(如7B-13B开源/入门级API) |
| 经历挖掘Agent | 用户原始素材、历史对话 | 按STAR拆解的经历条目列表,每条带事实标记 | 大模型(需要交互问答) |
| 技能映射Agent | JD需求JSON + 经历条目 | 能力-经历矩阵:每项要求的匹配度、证据、缺口 | 小模型 |
| 简历撰写Agent | 映射矩阵 + 素材库 | 分模块的简历初稿 | 大模型(语言质量要求最高) |
| 质量评审Agent | 初稿 + JD需求 + 校验规则 | 评审意见、评分、修改建议 | 大模型 |
这里有个选型心得:不是所有Agent都要用最贵的模型。需求解析和技能映射本质上是信息抽取和表格对照,对语言生成能力要求不高,但要求输出格式绝对稳定。用参数量较小的模型反而有个好处——小模型的输出模式更机械、更可控,不容易"发挥过头"生成一堆附属信息。而经历挖掘和撰写两个环节需要深度理解和高质量语言组织,这部分的预算要舍得花。
每个Agent的职责边界必须通过Prompt严格圈定。我的做法是在每个Agent的System Prompt里写清楚三条规则:第一,只允许操作输入字段指定的内容;第二,只输出约定的JSON格式;第三,如果信息不足,必须明确输出unknown而不是编造。这三条规则看起来简单,但能挡住后续70%的脏数据问题。
2.2 编排模式选型:为什么是Orchestrator而不是Pipeline
多Agent系统的架构模式无非三种:Pipeline(流水线)、Orchestrator(中央编排)、Blackboard(黑板模式)。
Pipeline最简单,把Agent串成一条链,前一个的输出直接喂给后一个。成本最低,但问题也明显——中间环节一旦出错,错误会一路传导下去,没有回退机制。比如经历挖掘Agent漏了一条关键经历,后面的技能映射和撰写根本不知道,最后产出的简历就少了一块。而且流程一旦定死,想加一个"发现信息不足时追问用户"的分支,就要把整条链路改一遍。
Blackboard模式是把所有Agent写到一个共享的"黑板"上,Agent之间不直接通信,都只读写黑板。灵活性高,但调试困难,Agent之间可能互相覆盖数据,而且对编排者(通常是主循环)的智能程度要求很高,不太适合新手团队。
我最终选了Orchestrator模式,理由很实际:控制流是显式的,每个Agent的执行顺序、重试逻辑、分支跳转都能在代码里看到,出了问题可以直接在编排器这一层定位。Orchestrator本质是一个状态机,它负责任务调度而不是自己写内容——它知道每个Agent该在什么时候被调用、传入什么、校验什么输出,但绝不直接参与内容生成。这保证了"调度逻辑"和"生成逻辑"彻底分离,架构上是干净的。
编排器承担的职责包括:维护全局上下文对象、记录每个Agent的执行状态、处理超时和重试、在质量评审不通过时触发重写循环、在用户补充信息时回退到对应阶段。
2.3 上下文与消息协议:让所有Agent说同一种语言
多Agent系统最常见的架构失误,是每个Agent的输入输出格式都是自定义的,最后编排器代码里全是格式转换的if-else。我踩过这个坑之后立了一条规矩:所有Agent之间只通过一个统一的上下文对象交互,消息格式固定。
@dataclass class AgentOutput: agent_id: str task_id: str status: str # success / partial / failed data: dict # 结构化输出,永远是dict warnings: list # 校验警告 cost_tokens: int latency_ms: int@dataclass class ResumeContext: jd_raw: str jd_analysis: dict raw_notes: list experience_items: list skill_matrix: dict draft: dict review: dict state: str字段类型用dict而不是强类型对象,主要是为了兼容不同模型的JSON输出,反正进入系统之后还有一层校验。所有Agent的输入都从ResumeContext的某个字段取,输出也都写回ResumeContext的对应字段。这样就保证了一个关键性质:任何Agent都可以被单独测试——你只需要构造一个ResumeContext,跑某一个Agent,检查它对Context的改动是否符合预期。多Agent系统最怕的就是Agent之间产生隐式依赖,用统一Context能让所有依赖显式化。
3. 从零实现生产级多Agent系统的关键环节
3.1 质量门禁:规则校验在前,模型评审在后
整个系统的核心成败,其实不在Agent的Prompt写得有多花哨,而在于有没有一套能拦住错误输出的质量门禁。我设计了双层校验体系。
第一层是硬性规则校验,纯代码实现,不消耗模型调用。规则包括:JD里的核心关键词在简历草稿里是否出现;每个项目经历是否有对应的时间段和量化数据;字数是否在预设区间;是否有空字段;格式是否符合模板(比如工作经历必须包含公司、岗位、时间段、成就列表)。这一层的价值是拦截70%的明显错误,而且每一条规则的错误信息都可以直接反馈给用户。
第二层才是模型评审。质量评审Agent拿到草稿和JD需求之后,会做一次全面的评估,输出一个带评分的结构化JSON:
{ "score": 87, "dimensions": { "keyword_coverage": 0.92, "achievement_evidence": 0.85, "streture_logic": 0.9, "format_compliance": 0.95 }, "issues": [ {"severity": "high", "location": "experience_2", "desc": "缺少量化数据"}, {"severity": "medium", "location": "summary", "desc": "栈名与JD不完全匹配"} ], "suggestions": "建议补充..." }如果评分低于阈值(比如85分),编排器触发重写循环:把评审建议回传给撰写Agent,让它定向修改问题项。这个循环最多跑三遍,每跑一遍成本都在增加,所以必须设定最大迭代次数,避免死循环烧钱。
3.2 编排器核心逻辑:状态机、重试与分支回退
编排器的代码看起来不复杂,但细节非常多。核心是一个状态机循环:
class ResumeOrchestrator: def __init__(self, agents: dict, config: dict): self.agents = agents self.config = config self.max_retries = 3 def run(self, ctx: ResumeContext) -> ResumeContext: ctx.state = "PARSE_JD" ctx = self.safe_call("jd_parser", ctx) ctx.state = "EXTRACT_EXPERIENCE" ctx = self.safe_call("experience_miner", ctx) ctx.state = "MAP_SKILLS" ctx = self.safe_call("skill_mapper", ctx) ctx.state = "GENERATE_DRAFT" ctx = self.safe_call("writer", ctx) for round_no in range(self.config["max_review_rounds"]): ctx.state = f"REVIEW_ROUND_{round_no}" ctx = self.safe_call("reviewer", ctx) if self.is_pass(ctx.review): break ctx.state = f"REVISE_ROUND_{round_no}" ctx = self.safe_call("writer", ctx, revision_feedback=ctx.review) return ctx def safe_call(self, agent_name, ctx, **kwargs): for attempt in range(self.max_retries): try: result = self.agents[agent_name].run(ctx, **kwargs) if self.validate_output(agent_name, result): return self.merge_result(ctx, agent_name, result) except TimeoutError: # 降级策略:用上一版本的上下文中的可用数据继续 return ctx raise AgentExecutionError(agent_name)这段代码里有几个容易漏掉的点。超时处理不能只是简单重试,因为LLM调用超时往往意味着服务端已经产生了部分消耗,重试意味着双倍成本。我的做法是:超时后先检查上游缓存里有没有该阶段的历史结果,如果有就直接复用;没有才重试,并且退避时间指数增长。另外,每个Agent的输出必须做schema校验,用一个轻量的JSON Schema定义每个阶段的结构,不满足就扔回给Agent重新生成,这比事后清洗数据靠谱得多。
3.3 从0到1的跑通路径:先本地模拟,再接真实模型
我在实现过程中摸索出的稳妥路线是:先不接任何真实模型,用Mock Agent把整条链路跑通。
具体做法是给每个Agent写一个假的实现,它不调用大模型,而是从预设的固定数据里返回结果,但保留完全相同的接口和数据结构。这样带来的好处太明显了:编排器、状态机、重试逻辑、质量门禁这些核心代码,在没有模型依赖的前提下全部可以单测覆盖。等整套流程稳定了,再一个个把Mock替换成真实模型调用。这一步的次序颠倒会让调试地狱加倍——如果一开始就接真实模型,你根本分不清报错是来自编排逻辑还是模型输出。
Mock替换的顺序也有讲究。我建议先替换技能映射Agent,因为它的输入输出最简单,替换成本最低;最后替换撰写Agent,因为它输出的文本质量主观性最强,需要最多的迭代调优。
4. 多Agent系统实战踩坑记录
4.1 上下文漂移:Agent各自为政,整份简历跑偏
多Agent架构最大的隐性坑是上下文漂移。每个Agent只看到Context里分配给自己的字段,但生成结果会隐式依赖很多不在字段里的"背景知识"。比如撰写Agent负责写项目经历,但它不知道需求解析Agent提炼出来的JD核心关键词是"高并发"和"分布式事务",于是写出来的项目描述是另一个方向,整体跑偏。
我的解决方案是在ResumeContext里增加一个global_facts字段,由编排器在每一步都动态汇总当前已知的关键信息,并强制作为每条Agent调用的系统级输入。也就是说,让每个Agent都能获得一份持续更新的"全局摘要",而不是只看自己的局部数据。这个摘要长度控制在200字以内,不破坏Token预算,但足以保持上下文的一致性。
4.2 幻觉信息:Agent编造不存在的项目经历
这是简历生成场景里最致命的错误,没有之一。模型在生成过程中倾向于把内容写得更完整、更有说服力,于是会出现"为某头部公司开发了支撑千万级用户的高可用系统"这种用户根本没做过的经历。这既涉及伦理问题,也直接毁掉产品的可信度。
我用了三重防线。第一,在写经历挖掘Agent和撰写Agent的Prompt里,明确加入"不允许使用给定材料之外的事实信息,所有事实描述必须能溯源到原始素材中的某一条记录"。第二,在经历条目和简历草稿中加入source_tags字段,每条撰写出来的内容都要标注引用了哪条原始素材,没有source_tag的句子直接判定为不合格。第三,在质量评审Agent的规则清单里,专门加一条"事实核验"维度,输出结果里标记每条陈述的置信度。这套组合下来,幻觉率从早期的21%降到了可接受的2%以内。
4.3 成本失控:Token预算与模型分级
多Agent的Token消耗是单Prompt方案的5到8倍,这是很多人真正上手后才震惊的。我第一版跑完一次全流程花了将近3万Token,如果全用顶级模型,单次生成的API成本高得吓人,还不算迭代重试的消耗。
成本控制的思路有三个。第一个已经在前面提过,模型分级:抽取类任务用低档模型,创作和评审用高档模型,单次生成成本能省40%。第二个是预算上限:在Context里记录每个Agent的历史成本,当单次运行总成本超过阈值(比如2元),编排器自动通知用户并暂时切换到只改关键模块的模式,不再做全量重写。第三个是缓存:同一个用户的同一份JD,需求解析结果直接缓存24小时,因为JD解析结果是稳定不变的。这个优化看似不起眼,但能砍掉每次运行10%的Token消耗。
4.4 超时、重试与幂等:LLM不是稳定组件
做生产级系统必须切换一个心理模型:大模型API不是数据库,它是脆弱的远程服务。我在一个月的运营周期里统计过,真实模型的调用失败率(含超时和返回格式错误)在3%到7%之间波动,高峰期更高。
针对这个问题,核心手段是幂等性设计。每个Agent调用生成一个唯一的request_id,编排器把这个ID连同输入内容的hash一起记录。如果调用超时后重试,先用这个ID查询是否已经有结果——很多主流API支持按请求ID查询历史结果,这样就能避免重复计费和重复生成。另外,所有Agent的Prompt里都要求"输出必须是一个可解析的JSON对象,不要输出任何解释性文字"。这个约定非常重要,因为返回文本里哪怕多一个Markdown代码块标记,解析就会失败,而这个错误率有时候能到20%。格式预校验和自动修复(剥离代码块标记、补全缺失的括号)是必须写的代码。
5. 大厂技术面试:如何把多Agent架构讲出深度
5.1 面试官真正想听的决策链路
很多人在面试里介绍多Agent项目时,喜欢上来就讲Agent的分工设计,这其实是舍本逐末。面试官在这个问题上想验证的,不是你有没有用过框架流,而是你有没有架构决策能力,也就是从需求到方案之间那条完整的推理链。
我在模拟面试中把自己这套项目讲成了一条决策链路:需求分析(简历生成任务是高不确定性的,单次推理不可控)→ 约束识别(成本敏感、延迟可容忍、对事实准确性极度敏感)→ 方案比较(Pipeline、Orchestrator、Blackboard三者的权衡)→ 选型决策(因为需要回退和可观测性而选择Orchestrator)→ 质量保障设计(规则门禁+模型评审的双层校验)→ 成本控制(模型分级+缓存+预算上限)。这套链路讲下来,面试官往往会在"质量保障"和"成本控制"两个点继续追问,而这两处恰好是有真实数据支撑的深水区。
一个实用技巧是,提前准备好三个指标的精确数字:系统吞吐量(一次完整生成的耗时和成本)、质量数据(评审通过率和用户满意率)、故障率(Agent调用失败率和不合格输出率)。面试官问任何深度问题,你都能用数据而不是形容词来回答,这就跟普通背项目的人拉开了差距。
5.2 从Demo到生产级,还差哪些硬指标
很多人在简历里写"精通多Agent开发",但项目还停留在本地跑通的Demo阶段。我建议用下面这张清单做一次自查,它能帮你判断自己的项目到底离生产级还差多远。
第一是可观测性。Demo阶段靠print调试,生产级必须给每个Agent加上链路追踪:一次完整的简历生成从进入到结束,每个Agent的耗时、Token消耗、重试次数、输出校验结果都要落日志,并且用trace_id串联起来。这个设计不是给运维看的,是给你自己看的——没有它,你无法定位任何线上问题。
第二是评测集。你需要至少准备50组(JD + 原始素材 + 期望产出)的测试数据,每次改动Prompt或编排逻辑,都要全量跑一遍评测集,看质量指标是否回退。没有评测集的迭代就是裸奔,你今天觉得改好了,实际可能是另一个维度的输出变差了。
第三是安全与合规。简历场景涉及大量个人信息,所有传输必须加密,日志中的原始素材要脱敏,用户对生成内容的删除权要落实。这部分的代码量不大,但缺了它,系统永远只能是内部Demo。
第四是限流与熔断。真实用户不会像你测试时那样一个个来,高峰期并发请求大概率打爆模型API额度。编排器前面必须挂一个队列系统,配合按用户维度的限流策略,以及对模型API异常状态的熔断开关——一旦连续5次调用失败,直接停止新请求排队,避免雪崩。
6. 最后分享一点实际经验
整个项目做完之后,我最大的体会是多Agent的价值不在"多个Agent"这三个字,而在"可插拔的步骤化流程"这个内核。因为步骤化,每一步都可以验证、可以回退、可以单独优化;因为统一Context,每一步都看得见摸得着,不产生隐式魔法。这也是我为什么反复强调,多Agent是生产级方案的真正原因——它不是性能的堆叠,而是确定性的回归。
如果你也想从零搭一套类似系统,我的建议是先把范围缩小:不要一上来就做五个Agent,先用三个最核心的(需求解析、撰写、评审)跑通流程,感受一下状态机和质量门禁带来的确定性提升,再逐步扩展经历挖掘和技能映射。这个过程的节奏感很重要,宁可一个阶段磨到稳定,也不要急着铺开所有角色。
还有一个细节想分享:尽量把艺术留到最后。先做"能用的系统",再做"聪明的系统"。我见过太多人花两周调撰写Agent的文风,结果发现评审Agent一接入,80%的草稿要被打回重写。先让流水线稳定运转起来,再让每个Agent在自己的岗位上打磨手艺,这样的推进顺序会省掉大量返工。