你上手Agent的时候,多半先碰到的第一个问题是:我直接调一次大模型API,跟搭一个Agent,到底差在哪?这个困惑很真实,因为从单轮调用到多Agent协作,本质上是一条完整的能力演进主线,而不是几个并列的技术名词。这篇理论篇看标题就知道,不聊具体某家产品的配置,而是把Agent的各种实现方式按复杂度从低到高拆开:先是单轮调用,再是带上下文的会话,然后是工具调用和单Agent循环,最后到多Agent协作的编排模式。适合正在做技术选型的开发者、准备Agent岗位面试的同学,也适合初入行但想建立完整认知框架的从业者。
1. 先搞清楚一件事:Agent和“调用一次模型”不是一回事
很多人觉得,Agent不就是把用户的问题发给大模型,然后把返回结果展示出来吗?外表看起来确实像,但如果你真的在生产环境里待过,会知道这两者的工程形态完全是两码事。
普通调用的代码是线性的:你构造一段Prompt,扔给模型API,拿到输出,结束。整个过程只有一次模型交互,控制流程在代码手里。Agent则相反,模型被放进一个循环里,每一步模型都可以输出“我接下来要做什么”,而代码负责执行这个决策,再把执行结果喂回给模型,让模型继续决策。换句话说,Agent把“下一步怎么做”的路权交还给了模型本身。
我习惯用一个类比来解释:普通调用像用手机导航,你按导航提示一步一步走,每一步都是确定的;Agent像雇了一位司机,你只告诉他目的地,他看路况、决定路线、处理突发情况,最终把你送到。司机就是模型,车辆和道路系统就是承载它的运行时。
用伪代码看更直观。普通调用大概是这样:
response = llm.chat(messages=[system_prompt, user_input]) print(response)而一个最简Agent循环是这样:
while not done: response = llm.chat(messages=messages) if response.has_tool_call: tool_result = execute_tool(response.tool_call) messages.append(tool_result) else: done = True差异就在这个while循环上。别小看这个循环,它会导致一整串连锁反应:你的程序从“跑一次就结束”变成“跑很多次才结束”,Token成本不再可控,耗时不再固定,失败模式也从“请求报错”变成“循环不退出”“工具参数解析失败”“模型越权调用”等新型问题。
这也是为什么我把“实现方式”当成学习主线,而不是直接讲多Agent。你连单Agent的循环都控制不稳,多Agent只会把问题放大好几倍。在进入下面的具体形态之前,先记住这个判断标准:判断一个系统是不是Agent,不看它用没用大模型,而看模型是否在控制流里、是否有自主决策和工具执行的回环。
2. 从单轮调用到多轮会话,状态管理是第一条坎
先看实现方式里最基础的形态:单轮调用。它的特点是无状态,每次请求独立,模型只根据当前输入回答。好处是简单、可靠、并发容易做,坏处是没有任何上下文。你问它“今天天气怎么样”,它答完就忘,你再问“那明天呢”,它不知道“那”指的是天气。
单轮适合的场景很明确:一次性内容生成、单个问题回答、批量打标签、翻译润色。这类任务不依赖历史信息,做无状态服务非常合适,也是很多系统的第一步。
再往前走一步是多轮会话,它的核心其实就一句话:把历史消息拼进每次请求里。模型本身是无状态的,你对“多轮”的所有感知,都来自你主动把上一轮的对话记录再次发送给它。
messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": "今天天气怎么样"}, {"role": "assistant", "content": "今天晴,20度"}, {"role": "user", "content": "那明天呢"}, ] response = llm.chat(messages=messages)看起来很简单,但生产环境的第一个坑就埋在这里:Token会随着对话轮次线性增长。每轮都把所有历史带进去,多轮之后,请求体积越来越大,成本越来越高,响应越来越慢。更麻烦的是上下文窗口有上限,到一定程度就得做取舍。
落到工程上,记忆管理一般有三层策略。最简单的叫滑动窗口,只保留最近N轮消息,写起来省事,但用户早期的需求细节会被丢掉。再多一步是摘要记忆,每当历史超出阈值,就把旧对话压缩成一段摘要,用摘要替代原文。再成熟一点是检索式记忆,把历史消息切块存入向量库,每次请求时按相关性把最相关的几段拉回来。
这三个策略不是互斥的,实际项目里经常混用。比如最近5轮用原始消息、更早的内容用摘要、用户资料和业务偏好走向量检索。层数越多,效果越好,但实现复杂度也越高。
这里有个实操经验:不要为了省事把历史消息拼成一大段纯文本塞进Prompt。消息数组的role字段不仅是为了展示,模型依赖它区分系统指令、用户意图、助手输出和工具结果。混成一团会直接降低模型对上下文的判断能力,尤其是后面引入工具调用时,消息结构错乱会让整条链路崩掉。
还有一点容易被忽视:会话状态必须持久化。开发环境里大家习惯把消息数组放在内存里,一重启全丢。真正上线时,多轮会话一定要把状态存到Redis或数据库,并且每次请求带上会话ID,这既是为了恢复会话,也是后面做并发的必要前提。
多轮会话虽然有“记忆”,但它仍然算不上Agent,因为它只有对话,没有行动。模型再聪明,也只能说不能做。下一节讲的工具调用,才是Agent真正长出“手”的时刻。
3. 工具调用与ReAct循环:Agent“手”和“脑”的连接
Agent和普通聊天最大的区别,就是它能调用工具。实现这个能力的标准方案是Function Calling,中文常叫函数调用或工具调用。
它的工作机制需要耐心理解:模型本身不会执行代码,它只负责在回答中表达“我想调用哪个工具、参数是什么”,并以结构化JSON的形式输出。你的程序收到这个意图,解析参数,去执行真实的函数——查数据库、发HTTP请求、操作文件——然后把执行结果作为一条新的消息回填给模型。模型根据结果决定下一步。
# 伪代码:一次典型的工具调用循环 while steps < max_steps: response = llm.chat(messages) if response.tool_calls: for call in response.tool_calls: result = execute(call.function, call.arguments) messages.append({"role": "tool", "content": result}) steps += 1 else: break这个“推理→行动→观察→再推理”的循环,就是业内常说的ReAct模式。很多人第一次上手时,会发现效果不那么好,主要问题常常不在模型,而在工具定义和循环控制上。
工具定义的第一个关键点是描述的写法。工具的描述是模型判断“何时调用、传什么参”的最主要依据。我发现一个很普遍的坑:开发者把描述写成给同事看的开发注释,比如“查询订单状态”,却没有写清楚什么场景下用、参数是什么格式、有什么边界条件。模型是靠描述来理解工具的,写得太省略,它要么不敢调用,要么在错误场景调用。正确的做法是把它写成“使用说明”:什么情况下应该选择这个工具,参数含义是什么,有哪些限制。
第二个关键点是参数校验。模型输出的JSON不保证完全合法,漏参、类型错、日期格式错都很常见。你不能盲目信任模型的输出,拿到参数后要在本地做一次严格校验,不合法就返回错误信息给模型,让它重新生成。这一步是为了防止脏数据进入你的真实业务系统。
第三个关键点是终止条件。Agent循环不能没有刹车,否则模型会陷入“反复调用同一个工具就是不给最终答案”的无限循环。常规做法是设置最大步数,比如10步,超过之后强制模型基于已有信息总结,或者直接放弃并告诉用户需要人工介入。
第四个关键点是把工具错误回填给模型。很多人的循环里,只要工具调用报错就直接把异常抛给用户,这很浪费。工具执行失败时,把错误信息作为tool消息的内容回传给模型,模型可能会换一种方案重试,或者主动向用户解释失败原因。这是Agent自我修正的最基础形态,多做一步,体验会明显不一样。
这里讲一个我真实踩过的例子。一个天气查询Agent,用户问“下周三北京的天气怎么样”,模型每次调用查天气工具,返回的都是“今天,晴”。因为工具schema里date字段是可选参数,模型没传,程序兜底返回了当前日期。结果Agent拿着当天的天气,硬着头皮回答用户,“下周三北京晴”。排查了很久才发现问题不在模型,而在工具允许了空参。后来我把date改为必填,缺失直接报错,模型就会重新思考并补上日期。这个案例的教训很值钱:工具边界越严格,Agent的行为越可控。
从实现谱系看,工具调用是分水岭。到了这一步,代码已经不是简单的“调API”,而是进入了一个循环控制的领域。下一个问题是,这个循环你是自己手写,还是交给现成的框架?这就引出了最容易被问懵的一个概念:Harness和Agent到底有什么区别。
4. 从裸循环到运行时:框架、Harness与并发问题
如果你只用官方SDK写Agent,迟早会发现重复代码越来越多:循环控制、重试、超时、日志、上下文裁剪、工具执行,统统要自己维护。这些代码和具体业务无关,却决定Agent能不能稳定地跑起来。于是业内形成了Harness这个概念。
简单说,Harness是承载Agent运行的运行时环境,负责“让Agent跑起来”的所有通用逻辑;Agent本身是业务逻辑,负责“决定怎么做”。两者的关系可以这么理解:Agent是发动机,Harness是整辆车——底盘、传动、仪表盘都是Harness,发动机才是Agent。你问一个Agent“下一步调用哪个工具”,那是Agent的思考;但这个思考结果如何被调度、如何被重试、如何在异常时降级,都是Harness的职责。
意识到这个分层非常重要。很多人把“Agent”和“Harness”混为一谈,选型时争论“这个框架有没有Agent”,其实框架提供的都是Harness层能力,Agent层是你用模型和工具组装出来的。
主流的Harness选项里,LangGraph是图编排思路,把Agent流程拆成节点和边,适合有复杂状态流转的场景;Spring AI面向Java生态,把模型调用、工具调用、结构化输出都封装成Spring风格,Java团队上手很自然;Claude Agent SDK走原生Agent + Skill路线,适合围绕Claude模型构建应用;低代码平台如Coze、Dify这类则把编排可视化,业务人员也能搭原型,但对复杂逻辑的控制力弱一些。
选框架要看团队基础和维护半径,而不是追新。我见过团队因为LangGraph火就选它,结果流程其实只有三步,完全用不上图的复杂度。反过来,也见过团队硬用低代码搭复杂Agent,最后控制不了状态流转。合适的标准只有一个:团队能解释清楚每一步的状态变化,能快速定位问题。
不管用哪个框架,工程化上有三件事绕不开。
一是状态外置。Agent的会话状态、中间步数、工具执行记录,要放到外部存储而不是进程内存里。生产环境经常是多副本部署,内存态会直接导致用户串线、任务丢失。会话级状态可以放Redis,需要长期沉淀的放数据库或向量库。
二是并发与限流。模型API有并发限制,Agent单次任务可能调用多次模型,意味着一个用户请求会放大成好几个上游请求。扛不住模型端限流时,只能在上游控制并发:用信号量限制同时在途的Agent数量,用队列做缓冲,对模型API做指数退避重试。不要指望给模型端无限提速,你能控制的只是自己这边的并发调度。
三是可观测性。每个Agent请求要能追踪完整链路:哪一步调了哪个工具、工具返回了什么、模型停了多久、Token消耗多少。没有这些trace数据,Agent生产环境就是个黑盒。我自己的习惯是每次工具调用都记一条结构化日志,带request_id和step编号,排查问题时效率会高很多。
如果你看到这里,单Agent的整个形态已经清楚了。但业务一旦复杂,你会发现单Agent会撞上几堵墙。这就是多Agent协作要回答的问题。
5. 多Agent协作的四种组织模式与选型判断
为什么需要多Agent?因为单Agent有明确的天花板。
首先是工具数量问题。工具加到二三十个之后,模型选择工具的准确率会明显下降。就像一个人同时掌握三十种技能,临场时经常反应不过来该用哪个。其次是上下文隔离问题。一个Agent既要理解用户闲聊,又要查数据库写代码,还要做质量审核,这些任务混在同一个上下文里,提示词互相打架。最后是角色分工问题。真实业务里本来就存在天然角色,比如一个人懂业务、一个人做执行、一个人负责质检,硬塞给一个Agent并不自然。
多Agent的核心思想是各司其职,通过协作完成整体目标。几种常见的组织模式各有优劣,我放在一起对比:
| 模式 | 核心思路 | 适合场景 | 主要代价 |
|---|---|---|---|
| 编排者-执行者 | 中央Agent拆解任务,派发给子Agent,汇总结果 | 复杂多步骤业务 | 编排者单点、上下文聚合成本高 |
| 管道流水线 | 任务按固定环节依次流过多个Agent | 流程明确、可拆分的业务 | 扩展新环节要改结构 |
| 辩论/评审 | 一个生成,一个批判,多轮交叉验证 | 内容质量要求高的场景 | 延迟和Token成本高 |
| 自由协作 | Agent互为工具,动态调用对方能力 | 高度动态的临时任务 | 失控风险高、调试难 |
编排者-执行者模式是目前工业界用得最多的。中央Agent负责理解用户意图,把任务分解成子任务,分发给各个worker,再汇总所有结果。它的控制力最强,出现问题容易追溯,适合“帮我分析数据并生成报告”这类多步骤任务。缺点是编排者会成为信息汇聚点,所有子Agent的返回都要经过它组织,容易变成长上下文。
管道流水线模式适合那些流程天然固定的场景,比如内容生产管线:理解Agent先提取用户需求,生成Agent产出初稿,审核Agent检查合规,输出Agent调整风格。每个环节只处理自己那一块,逻辑单纯。缺点是环节之间耦合在结构上,想新增一个环节得调整整条流水线。
辩论/评审模式在内容生成领域价值很高。生成Agent写一版文案,评审Agent扮演苛刻的甲方挑毛病,生成Agent再改。几轮下来,质量通常会有明显提升。但代价也实打实:每一轮都是完整的模型调用,延迟和费用都会翻倍,不适合对实时性敏感的场景。
自由协作模式是Agent作为另一个Agent的工具被调用,拓扑是动态的。它最灵活,但也最危险,容易出现两个Agent互相调用停不下来,或者消息风暴把Token成本打爆的情况。要用这种模式,必须有非常强的护栏。我自己在大多数项目里会避开自由协作,除非是内部小范围实验。
这里必须强调一个容易被忽略的判断:多Agent不是银弹,很多场景压根不该用。判断要不要拆分,先问三个问题:任务是不是真的需要多个专业角色?多Agent带来的质量提升能否量化?延迟和成本的增加是否能接受?如果模型单Agent配合工具就能完成,就老老实实用单Agent。如果任务对延迟敏感,多加一层协作用户就要多等好几秒。如果还没有建立评测体系,你也无法知道改成多Agent后到底变好了没有。
6. 多Agent落地的硬骨头:上下文治理、安全与评测
多Agent协作在理论层面看着清晰,真正落地时,最折磨人的往往是几个偏工程的细节。
第一个是上下文治理。很多初学者会把完整对话历史传给每一个Agent,觉得这样信息全。但子Agent根本不需要知道用户三天前的闲聊,给它太多无关上下文,反而会稀释它对当前任务的理解。正确的做法是上下文裁剪:每个Agent只收到完成任务所需的最小信息。比如“查上周销售数据”这个子任务,worker只需要拿到任务描述和数据查询权限,不需要知道用户此前的抱怨。上下文隔离做得好,Agent的行为会更稳定,Token消耗也更低。
第二个是Agent之间的通信协议。Agent之间传递任务不能只靠自然语言“帮我做一下XX”,解析和验证都很脆弱。我建议定义一套结构化任务描述,比如包含任务类型、输入参数、期望输出格式、验收标准。子Agent返回结果也要有统一的JSON结构,方便编排者聚合。子任务要带唯一ID,这样编排者才能把不同来源的结果正确对应回去。
第三个是安全与权限。多Agent系统把模型的能力分散到了多个角色,攻击面和误操作面都变大了。最核心的原则是最小权限:每个Agent只能访问完成自己工作所需的数据和工具,不能给它全系统权限。比如财务统计Agent可以查账但绝不能执行转账;内容审核Agent可以标记但不能删除。敏感操作必须有人类介入,删除、发消息、支付这类动作要设置二次确认,或者要求模型达到足够置信度才允许自动执行。
还有个经常被忽略的问题是预算控制。多Agent的Token消耗是多个角色多次调用的叠加,跑一次复杂任务可能比单Agent贵好几倍。我给生产环境里的每个Agent都配置调用次数和Token预算,超过阈值自动降级为人工处理,防止出现“一个bug在夜里悄悄烧掉大量额度”的事故。
并发层面,多Agent比单Agent更考验状态管理。编排者通常会成为热点,为了支持多实例部署,编排者本身要设计成无状态,子任务队列要放到外部消息系统里,每个子任务记录归属和状态。这样即使某个实例挂了,另一个实例也能继续拉起未完成的任务。
评测也是多Agent绕不过去的坎。没有评测体系,你没法判断“多Agent比单Agent更好”这个结论是否成立。我的做法是构建一个任务评测集:从真实业务里收集一批典型用户请求,为每条请求标注期望的执行路径和最终结果。然后定义几个核心指标,每次改版后跑一遍回归:
| 指标 | 计算方式 | 说明 |
|---|---|---|
| 任务成功率 | 通过校验的任务数 / 总任务数 | 最核心的底线指标 |
| 工具调用有效率 | 被调用的工具中真正产生价值的比例 | 反映工具定义质量 |
| 平均轮次 | 模型总调用次数 / 任务数 | 反映Agent效率,轮次越高成本越高 |
| Token消耗 | 总Token / 任务数 | 直接关联成本控制 |
| 人工介入率 | 需要人工干预的样本 / 总样本 | 越高说明自动化水平越低 |
这套指标拼起来,才能回答“这个Agent改版是变好还是变坏”。没有评测就调Prompt,本质上是凭感觉改代码。
安全、并发、评测都做完,多Agent系统才算从“能跑”进入“好用”的阶段。这些事一件都不能少,我见过太多项目在Demo阶段很惊艳,一上生产就天天救火,原因就是只搭了协作骨架,忘了配套这些支撑层。
7. 常见问题与排查技巧实录
理论讲了这么多,最后落地时大家还是会遇到一堆具体问题。我整理了一张高频问题速查表,方便你对照排查。
| 现象 | 可能原因 | 排查步骤 | 解法 |
|---|---|---|---|
| 模型反复调用同一工具不终止 | 缺少终止条件,或工具返回结果不符合预期 | 看日志中每次tool_result,确认是否触发了模型继续调用的理由 | 设最大步数;在系统提示里明确“信息足够时直接回答” |
| 多轮后回答质量明显下降 | 历史过长、噪声过多 | 检查单次请求Token占用和消息结构 | 加滑动窗口历史裁剪,或把早期历史改成摘要 |
| 工具调用参数错乱 | 工具描述不清晰或校验缺失 | 看模型实际输出的JSON,对照schema找偏差 | 重写工具description,加必填参数校验 |
| 多Agent结果无法汇总 | 子Agent输出结构不统一 | 查看各子任务的原始返回消息 | 所有子Agent统一要求结构化JSON输出 |
| 用户串线、状态错乱 | 会话状态放进程内存 | 检查服务日志是否多用户消息交叉 | 状态外置到Redis/数据库,每个请求带sessionKey |
| Token成本异常超标 | Agent循环过多或多Agent消息风暴 | 看每一轮的trace,统计调用次数和Token | 设置预算阈值、限制最大轮次、减少无关上下文 |
排查Agent问题,我的习惯是严格按顺序来:先打开trace日志,完整回放消息流;再区分问题出在模型决策层还是运行时代码层;确认代码没问题后,才去改提示词。不要一上来就调Prompt,那样很容易把真问题掩盖掉。
最后讲一个真实案例。有个Agent上线后总在“查询天气”工具上打转,用户问“下周三适合出游吗”,它查一次天气,然后继续查,直到步数耗尽。开trace发现,每次工具返回的都是“今天,晴”,模型觉得“这个说法和用户问的时间对不上”,于是又去查一次,反复拿同一个结果。根因是工具schema里日期参数被误设为可选,模型不传,程序兜底返回了当天数据。模型自己也发现了不对劲,但它能做的只有再查一次。修复方法是把日期参数改成必填,缺失直接报错,模型收到错误后就会主动补上日期。整个排查过程不到半小时,但如果没有trace,光靠猜可能折腾一下午。
这个案例提醒我一件事:很多Agent的“不聪明”,根源往往在系统的边界约束太宽松。Agent理论和工程之间隔着大量这样的细节,这也是为什么我说实现方式的进阶路线,值得每个人都亲自走一遍。
最后分享一点我的真实体会。我最早做的Agent项目,一上来就规划了五个Agent协作的大架构,结果连单Agent的循环稳定都很难保证,每天花大量时间翻日志。后来砍掉多余角色,先跑通一个Agent加工具集合加评测的小闭环,再逐步加协作,项目才真正进入正轨。如果你也在从理论走向落地,我的建议很简单:从单轮调用开始,一个环节一个环节加复杂度,每一步都确认它带来明确收益,再往前走。先跑通,再分工,再编排,这句话我一直贴在工位旁边。