多智能体职位查找器的设计:流程、HyDE以及可观测性机制。
一个包含九个步骤的LangGraph流程,用于将简历与职位匹配,其中在HyDE检索、模型路由、速率限制及可观测性方面有八种设计选择。
来源: Agentic AI in Practice: A Case Study in Multi-Agent System Design
智能体、代理型人工智能、多智能体系统:这类术语随处可见,每周都有新的模型、框架或编排模式出现。提示工程、RAG、工具调用、MCP、记忆层……这个生态系统的发展速度让大多数从业者难以跟上。在种种喧嚣之下,那些核心问题依然没有改变:
- 该如何将一个问题拆解为多个推理步骤?
- 哪些操作应该由大语言模型来完成,哪些绝不能交由其处理?
- 如何确认系统确实能做到人们所期望的功能?
新框架的发布并未让这些事情变得更简单。为便于说明,本案例研究选取了数百万人都面临的问题:在海量招聘信息中找到合适的工作。它为此设计了一个端到端的AI系统,并利用该系统回答三个实际问题:在架构层面,“智能体”意味着什么?何时多个智能体会创造价值,而何时又会成为负担?又是什么让一个AI系统具备可观测性、可靠性并适合投入实际应用?
找工作并非真正的搜索
大多数人将找工作视为一种搜索任务,但实际上它更接近于匹配任务。没人真的在寻找包含特定关键词的招聘信息。更深层次的问题是:基于现有的经验、技能和职业方向,哪些职位才是真正适合的?
招聘网站将这一过程视为搜索操作。你输入几个关键词,再指定地点,希望找到相关结果。然而简历中包含多年的工作经验、领域知识、技术深度、可迁移技能、职业发展历程及背景信息,而搜索框却要求用寥寥数词概括这一切,还假定匹配的词汇就意味着意图一致。
大家都明白这样的过程会是怎样:
- 在多个平台上浏览数百条招聘信息
- 逐条判断信息的相关性
- 遇到重复内容、过期的招聘广告以及失效链接
- 怀疑最初选择的职位是否真的合适
枯燥乏味并非核心问题。真正的差距在于候选人的经历与招聘岗位的实际需求之间的差异,而缩小这一差距需要解读、背景了解和判断力。简而言之,这需要推理能力,而这正是大型语言模型的强项。假设有一个系统能够阅读简历、推断合理的职业发展路径、查找职位,并根据实际匹配度而非关键词重合度对它们进行排序,那就是构建该系统所要解决的问题。
逐步流程
整个过程从一项操作开始:上传简历。之后就无需任何人再使用搜索框、滚动浏览职位列表或决定哪些岗位值得申请。系统会接管并执行九个步骤,每个步骤都有明确的功能。
- 简历解析代理。它如同经验丰富的招聘人员,将简历转化为结构化的个人资料而非关键词列表。该资料涵盖曾任职职位、技能、专业领域、职业发展历程,以及一段简短的ATS风格描述,后续所有代理都会将此作为候选人的标准描述。模型:Claude Sonnet。
- 资料推荐代理。它会根据候选人的水平、领域和技能,提出两到三个合适的目标职位名称,并为每个职位提供不同的搜索查询语句。模型:Claude Haiku。
- 人工确认环节。这是唯一由人做出决策的步骤。用户会查看建议的个人资料,挑选最合适的那个,可选择添加地点和职级偏好,然后进行确认。整个流程会在此之前暂停,不涉及任何模型,仅依赖人工判断。
整个流程共包含九个步骤,需要调用四次大型语言模型,由人工做出一次决策,总运行时间不到三分钟。
技术架构图
上图将当前人工智能技术栈的各个主要组件置于该流程中。每个系统都是一系列选择的组合,因此接下来的章节将介绍哪些方案被考虑、哪些被舍弃以及相应的成本。
八项设计决策
1. 选择LangGraph而非CrewAI、AutoGen或ReAct循环
常见的候选方案有LangGraph、CrewAI和AutoGen,而在LangGraph内部又可进一步分为有向图与ReAct循环两种形式。
CrewAI侧重于自主协作,通过角色扮演的智能体之间进行协商与任务分配。AutoGen则聚焦于对话式的多智能体交互。这两种方案都适用于开放式的 workflows,但同时也带来了协调开销和非确定性,而固定的流程结构则难以处理这类问题。
当步骤数量无法提前确定时,ReAct循环就能大显身手,比如那些需要浏览网页却无法预估搜索次数的大型语言模型。但找工作并非如此——问题的结构已经固定了各个阶段:先解析再推荐,先搜索再过滤,先去重再嵌入。在运行时不会发现任何新情况。
有向图非常适合描述这些流程。每个转换都是明确的,人工干预是一种一级原语,而状态则是每个节点都能读写的一个共享Pydantic模型。一旦出现故障,出问题的节点一目了然。
2. 专用智能体优于单一超级提示词
编写一个能够解析简历、推荐职位名称及评分列表的单一提示语固然很有吸引力,且开发速度更快。但它会以复杂的方式出现故障,一处出错就会波及全局。很难判断是哪条指令导致了不良输出,调整某一部分可能会引发其他部分的退化问题,而且上下文中还会充斥着与当前任务无关的指令。
分离式的智能体可以避免这些问题。评分功能表现不佳时,只需单独检查该评分提示语即可进行调试。每个智能体都有独立的测试脚本,因此无需整个流程就能对解析器进行回归测试。此外,如设计选择6所示,不同的智能体还可以对应不同的模型。
3. 人工干预机制
大多数关于智能体的教程都将人工审核视为可选的优化步骤,只是为了让用户有参与感而添加的内容。但在这里,人工干预机制却是核心的架构决策。
推荐系统会提供两三个选项,用户选定哪个选项后,后续的所有步骤——搜索、筛选、评分——都将以此为依据。如果让系统自动选择,一次错误的判断就会影响整个流程,最终为候选人根本不感兴趣的类型生成一份排序整齐的列表。在整套流程中,没有比这更具风险的决定,也没有什么需要由人工来处理的更好了,因为候选人对自己感兴趣的方向有着解析器无法获取的信息。
4. 排序器之前的预筛选器
现实世界中的数据从来都不干净,来自五个API的列表总会存在一些常见的问题:
- 由于帖子过期却未被删除而产生的死链接和404错误
- 重复内容,因为同一份工作会同时出现在多个API中
- 聚合器导致的重定向,即URL跳转至招聘网站的主页而非具体职位页面
这些并非边缘情况,而是外部 API 数据的常态。验证与去重步骤的存在是为了在发生任何高成本操作之前清理数据池,因此进入语义预过滤器的数据都是有效、唯一且真实的。
此时成本就变得重要了。在实际运行中,排序器占据了整个 LLM 使用成本的 70% 到 80%。解析器的成本固定为 2.7 至 3.0 卢比,无论有多少任务经过;推荐器的成本也固定在约 0.30 卢比。排序器的成本则根据接收到的任务数量在 3 卢比到 11 卢比之间波动,因此每次运行的总成本在 6 卢比到 14 卢比之间。预过滤器只会保留与候选项最匹配的条目,这样排序器处理的就只是信号较强的子集,而非来自五个任务 API 的杂乱原始输出。
5. 使用 HyDE 而非直接比较
显而易见的方法是嵌入候选人的ATS摘要,然后通过余弦相似度将其与每份职位描述进行比较。这种方法简单、快速,且无需额外调用大语言模型。但在测试中,其结果令人失望。
原因在于嵌入空间存在不匹配。候选人的摘要是以第一人称叙述的,类似“负责部署机器学习流程、主导模型上线工作,并与多个团队协作”。而职位描述则是以第三人称列出要求,比如“需要3年以上机器学习工作经验、掌握PyTorch技能且具备良好的沟通能力”。即便两者描述的都是相同的能力,由于表达风格不同,最终会落在不同的空间区域,因此直接比较会导致对真正匹配度的低估。
HyDE通过提出不同的问题来规避这一问题:针对该候选人,理想的职位描述应该是什么样的?大语言模型会根据简历内容撰写两份假设性的职位描述。
- JD1以特定领域为基准,表述方式如同该领域内的企业正在招聘具有相应背景的人选。
- JD2则侧重于可迁移技能,表述方式似乎将领域专业知识视为次要因素,而将通用能力作为重点。
这些虚构的职位描述与真实职位描述位于同一区域,因为它们虽为假设性的职位描述,但仍属于职位描述范畴。将它们与真实招聘信息进行比较时,可实现类似的语义匹配。每条真实招聘信息都会被归类到与其最为相似的虚构描述对应的类别中:若与JD1更相似,则被归入第1部分,即候选人所在领域的职位;若与JD2更相似,则被归入第2部分,即更广泛的就业机会。
始终遵循的一个限制是:只能使用通用嵌入模型,不得使用领域专用模型。领域的特殊性源自HyDE内容,由大语言模型将其融入合成描述中,而非嵌入模型的选择。这保证了该系统能在不同职业领域间通用。
6. 不同智能体使用不同模型
并非所有任务都需要相同的推理能力。解析器与HyDE生成器的每次运行成本分别约为2.7至3.0卢比和0.50卢比,且不会随处理量变化。由于低质量的解析会破坏人物档案,而低质的假设描述则会影响嵌入匹配的结果,因此Sonnet是最佳选择。
在排名系统中,评分是针对每项任务单独进行的,因此这一环节的成本会随着处理量增加而上升,目前已占据了预算的70%到80%。如果没有更合理的评分结构,Sonnet的花费大概会是这个数字的三倍。而Haiku则能解决这个问题。具体路由方式如下:
7. 每个可调节参数对应一个配置文件
代理系统最终会面临参数调整的问题。更换模型、调整阈值或改变并发限制时,不应需要在五个代理文件中逐一查找合适的常量。
该系统通过一个单一的YAML文件来管理流程中的所有参数,这样既避免了在代理代码中硬编码常量,也防止了提示词中出现隐含的特殊数值。它负责管理:
- 每个代理所使用的模型。要将Claude Haiku替换为更新版本的模型,只需进行一行编辑即可。
semaphore_size: 4、batch_size: 4以及batch_delay_secs: 2,具体说明见后文。8. 受速率限制影响的并行排序
最初的排序方式是顺序处理的:调用一次大型语言模型,等待后处理下一个任务。当有40个任务且每次调用耗时约2.5秒时,总处理时间将达到100秒。管道数据也印证了这一点,在典型的166秒运行时间内,仅排序环节就需约95秒。
一次性发送全部40次请求看似是解决方案,但这会违反Anthropic的速率限制。一级访问权限对每分钟的请求数设有上限,因此40到60次同时发送的请求会立即超出限制,导致429错误。在最早没有采用限流机制的生产环境中,只能返回部分结果:在API停止响应之前,仅完成了1到70号任务的评分。
解决方案是一种三部分的并发设计,完全通过配置文件进行控制:
- 信号量(
semaphore_size: 4)限制了任何时刻正在处理的请求数量。每次最多4个请求,远低于一级访问权限的限制。 - 批量处理(
batch_size: 4)将任务按每组4个进行评分,且每组完成后再开始下一组。 - 批量延迟(
batch_delay_secs: 2)在每组之间设置两秒的间隔,以应对突发流量带来的限制。
其优势在于:15批共60个任务的处理时间大约在90到95秒之间,相比相同任务量下的顺序处理方式快约四倍,且不会达到性能上限。
可观测性
一个无人能够观察到的系统只是一种期望,算不上真正的系统。实际发生的故障便能体现这一点。在首次完整生产运行中,EC2实例上缺少VOYAGE_API_KEY。HyDE预过滤模块悄悄跳过了嵌入处理,将全部89个任务直接转发给排序器,导致在第70个任务时触发速率限制,最终只返回了部分结果。在没有可观测性的情况下,这会表现为随机且令人费解的行为;而有了可观测性后,追踪故障原因仅需不到一分钟。
此处的可观测性在两个层面发挥作用:
- LangSmith能够追踪每一次大语言模型调用:包括具体的提示词、响应内容、每次调用的延迟时间以及Token数量。它提供了对推理过程的微观级洞察,在开发过程中可用于在问题影响整个流程之前及时发现提示词相关的缺陷。
- MLflow会记录每次运行情况,包括每个智能体的延迟时间、总成本以及Token数量。其
job_audit.json文件是最有用的诊断工具,其中包含了进入处理流程的每个任务的完整记录:该任务落在HyDE的哪个阶段、相似度得分、是哪个过滤器将其过滤掉以及原因、拟合分数的各项构成,还有最终的排名。
成熟度的体现
人工智能系统的成熟度并非以它的自主程度来衡量,而是以人们对其功能、行为原因以及错误所在的理解深度来评判。目前关于“智能体型人工智能”的讨论都是以错误的指标为优化目标。自主性虽然能带来令人印象深刻的演示效果,但往往并非良好的工程特性,因为要观察和调试自主行为成本极高。
对于如今正在构建人工智能系统的人而言,重要的问题不在于它的自主程度或拥有多少智能体,而应是以下几点:
- 能否观察其在每一步的具体操作?
- 能否在不查看源代码的情况下解释特定输出结果?
- 当其行为出现偏差时,能否重新调整参数?
- 能否区分良好表现与隐性故障?
能够通过这四项测试的系统才是真正可用的机器学习系统,其余的都只是营销效果出色的原型而已。