1. 从单轮到多 Agent:为什么这个话题值得认真聊
1.1 一个绕不开的现实问题
如果你最近半年在折腾大模型应用,大概率会有一种感觉:单轮调用已经不够用了。最开始大家写个 Prompt,调一次接口拿结果,能跑通就觉得挺美。但真到了业务场景里,问题一个接一个冒出来——模型记不住上下文、复杂任务一步做不完、工具调用经常出错、多步骤流程没法自动衔接。这时候你自然会想:能不能让模型自己决定下一步做什么?能不能让它调用工具、观察结果、再决定下一步?这就是 Agent 的起点。
我是一名大模型开发工程师,过去一年多的时间里,从最简单的单轮问答到多 Agent 协作系统,几乎每种实现方式都踩过坑。这篇文章不打算给你一个“标准答案”,因为 Agent 这个领域本身就没有标准答案。我想做的是把从单轮调用到多 Agent 协作这条演进路径上的核心思路、关键决策点和实操经验,尽可能完整地梳理出来。无论你是刚接触 Agent 开发的新手,还是已经在做企业级 Agent 平台的同行,应该都能从中找到一些有用的东西。
1.2 这篇文章适合谁看
先说清楚受众。如果你完全没写过大模型调用,连 Prompt 都没调过,那这篇文章可能会有点吃力,建议先补一下基础。但如果你已经能写 Prompt、调 API,想搞清楚 Agent 到底怎么从零搭起来,那这篇文章就是为你准备的。如果你已经在做 Agent 项目,但对多 Agent 协作的架构设计还有困惑,那第三、四部分应该能给你一些参考。
文章会覆盖几个核心概念:Prompt Engineering、Context Engineering、Loop Engineering、Graph Engineering。这四个词听起来像 buzzword,但它们其实对应了 Agent 实现方式演进的四个关键阶段。我会用实际代码和架构图(文字描述)来说明每个阶段解决了什么问题、引入了什么新问题、以及什么时候该用哪种方式。
提示:这篇文章偏理论架构,但所有理论都会配上可落地的实现思路。不会只讲概念不给代码。
1.3 先给一个全局地图
在深入细节之前,先给一个全局视角。从单轮调用到多 Agent 协作,大致可以分成四个阶段:
| 阶段 | 核心特征 | 关键技术 | 典型场景 |
|---|---|---|---|
| 单轮调用 | 一次输入一次输出 | Prompt Engineering | 分类、摘要、简单问答 |
| 循环调用 | 模型自主决定下一步 | Loop Engineering | 工具调用、多步推理 |
| 上下文管理 | 动态管理记忆和状态 | Context Engineering | 长对话、复杂任务 |
| 多 Agent 协作 | 多个 Agent 分工配合 | Graph Engineering | 复杂工作流、企业级应用 |
这四个阶段不是严格线性的,很多系统会同时用到多种技术。但理解这个演进路径,能帮你在做架构决策时想清楚:我现在到底需要哪一层的能力?是不是过度设计了?还是能力不够?
2. 单轮调用与 Prompt Engineering:一切的基础
2.1 单轮调用的本质与局限
单轮调用是最简单的形式:你给模型一个输入,模型给你一个输出。没有记忆,没有工具,没有循环。听起来很原始,但说实话,很多业务场景用单轮调用就够了。比如文本分类、情感分析、简单摘要、翻译,这些任务不需要多步推理,一次调用就能搞定。
但单轮调用的局限也很明显。最核心的问题是:模型只能基于你给它的输入做决策,它没法主动获取额外信息,也没法在输出之后根据结果调整策略。举个例子,你让模型“查一下今天北京的天气然后告诉我穿什么”,单轮调用做不到,因为它没法查天气。你只能先把天气信息查好,拼到 Prompt 里,再让模型给建议。这就是所谓的“人工编排”——你替模型做了决策。
我见过不少团队在这个阶段就卡住了,觉得 Agent 不过如此。其实问题不在 Agent,在于任务本身就不需要 Agent。如果一个任务用单轮调用加好的 Prompt 就能解决,那就别上 Agent,简单可靠比什么都强。
2.2 Prompt Engineering 的核心原则
Prompt Engineering 是单轮调用阶段的核心技能。虽然现在大家都在聊 Agent,但 Prompt 写得好不好,直接决定了 Agent 的上限。我总结了几条在实际项目中反复验证过的原则:
第一条:明确角色和边界。不要只说“你是一个助手”,要说“你是一个专门处理电商退货申请的客服助手,你只能处理退货相关问题,其他问题一律转人工”。角色越具体,模型的表现越稳定。
第二条:给例子比给规则更有效。你写十条规则,不如给三个高质量的例子。Few-shot 的效果在大多数场景下都优于 zero-shot,尤其是格式要求严格的场景。
第三条:输出格式要显式约束。如果你需要 JSON,就在 Prompt 里写清楚 JSON 的 schema,最好给一个示例。不要指望模型“猜”你想要什么格式。
第四条:把复杂任务拆成步骤。Chain-of-Thought 不是玄学,它确实有效。让模型“一步一步思考”,在推理类任务上能显著提升准确率。
# 一个典型的单轮调用 Prompt 结构 prompt = """ 你是一个电商退货申请处理助手。 ## 你的职责 - 判断用户的退货申请是否符合退货政策 - 如果符合,提取退货原因和订单号 - 如果不符合,说明原因并建议联系人工客服 ## 退货政策 1. 签收后 7 天内可无理由退货 2. 商品必须未使用、包装完整 3. 定制商品不支持退货 ## 输出格式 请以 JSON 格式输出: { "eligible": true/false, "reason": "退货原因", "order_id": "订单号", "message": "给用户的回复" } ## 用户输入 {user_input} """这段 Prompt 看起来简单,但每一条都有讲究。角色定义让模型知道自己的边界,政策说明给了判断依据,输出格式约束让后续程序能直接解析。在实际项目中,这种结构化的 Prompt 比随意写的 Prompt 准确率能高出 20% 以上。
2.3 什么时候该从单轮升级到循环
判断标准其实很简单:如果任务需要模型根据中间结果决定下一步做什么,那就需要循环。比如:
- 需要调用外部工具获取信息
- 需要多步推理,且每一步依赖上一步的结果
- 需要根据执行结果决定是否重试或调整策略
如果任务不需要这些,那就老老实实用单轮调用。我见过太多项目为了“显得智能”硬上 Agent,结果复杂度上去了,稳定性下来了,效果还不如单轮。
注意:Prompt Engineering 不是“写个提示词”那么简单。它是一套系统化的输入设计方法,包括角色定义、任务拆解、格式约束、示例选择等多个维度。在 Agent 时代,Prompt 的重要性不是降低了,而是更高了——因为 Agent 的每一步决策都依赖 Prompt。
3. Loop Engineering:让模型自己决定下一步
3.1 ReAct 模式:循环调用的经典实现
Loop Engineering 的核心思想是:让模型在一个循环里运行,每次循环它可以决定是调用工具、还是给出最终答案。最经典的实现就是 ReAct(Reasoning + Acting)模式。
ReAct 的流程大致是这样的:
- 模型接收任务和当前上下文
- 模型输出一个“思考”(Thought)
- 模型决定一个“行动”(Action),比如调用某个工具
- 系统执行行动,得到“观察结果”(Observation)
- 把观察结果拼回上下文,回到第 1 步
- 直到模型输出“最终答案”(Final Answer)
这个循环看起来简单,但实现起来有很多细节要注意。比如:怎么判断循环该结束了?工具调用失败了怎么办?模型陷入死循环怎么处理?
# ReAct 循环的简化实现 def react_loop(task, tools, max_iterations=10): context = f"任务:{task}\n\n" for i in range(max_iterations): # 模型决策 response = llm_call(context + "请思考下一步行动。") # 解析输出 if "Final Answer:" in response: return extract_final_answer(response) action = extract_action(response) tool_name = action["tool"] tool_input = action["input"] # 执行工具 if tool_name in tools: try: observation = tools[tool_name](tool_input) except Exception as e: observation = f"工具执行失败:{str(e)}" else: observation = f"未知工具:{tool_name}" # 更新上下文 context += f"思考:{response}\n" context += f"观察:{observation}\n\n" return "达到最大迭代次数,任务未完成"这段代码是简化版,实际项目中还需要处理更多边界情况。但核心逻辑就是这样:模型决策、执行、观察、再决策。
3.2 循环终止条件的设计
循环终止条件是 Loop Engineering 里最容易被忽视、但最容易出问题的地方。我踩过的坑包括:模型一直调用同一个工具、模型在思考阶段就卡住不输出行动、工具返回结果太长导致上下文爆炸。
常见的终止条件设计:
- 最大迭代次数:最简单粗暴,但有效。一般设 5-15 次,根据任务复杂度调整。
- 重复检测:如果模型连续两次调用同一个工具且输入相同,强制终止。
- 超时控制:整个循环设置一个总超时时间,比如 60 秒。
- 显式终止信号:模型输出特定标记时终止,比如 “FINAL_ANSWER”。
实操心得:最大迭代次数不要设太大。我见过设 50 次的,结果模型在第 30 次还在瞎转,浪费 token 不说,用户体验极差。一般 10 次以内能解决的任务,才是适合用 Agent 的任务。超过 10 次还搞不定的,要么是任务太复杂需要拆解,要么是 Prompt 有问题。
3.3 工具调用的工程化处理
工具调用是 Loop Engineering 的核心能力,但也是最容易出工程问题的地方。几个关键点:
工具描述要清晰。模型是根据工具描述来决定调不调的。描述写不清楚,模型就会乱调。每个工具的描述应该包括:功能说明、输入参数格式、输出格式、使用场景。
参数校验要做在工具内部。不要指望模型每次都传对参数。工具函数内部要做类型检查和边界检查,返回明确的错误信息,让模型知道哪里错了。
工具返回结果要精简。如果工具返回一大段文本,直接拼到上下文里会迅速消耗 token。最好在工具层面做摘要或截断,只返回关键信息。
工具调用要有超时和重试。外部 API 可能超时,数据库可能连接失败。工具层面要有超时控制和有限重试,避免整个循环卡死。
# 工具定义的推荐格式 tools = { "search_weather": { "description": "查询指定城市的天气信息", "parameters": { "city": "城市名称,如'北京'、'上海'", "date": "日期,格式 YYYY-MM-DD,默认为今天" }, "returns": "天气状况、温度、湿度、风力", "function": search_weather_impl } }这种结构化的工具定义,比单纯写个函数名效果好得多。模型能清楚知道每个工具能做什么、需要什么参数、返回什么。
3.4 Loop Engineering 的适用边界
Loop Engineering 不是万能的。它适合的任务类型有:需要调用外部工具获取信息的任务、需要多步推理且步骤间有依赖的任务、需要根据中间结果调整策略的任务。
不适合的任务类型:纯文本生成任务(单轮就够了)、需要严格确定性输出的任务(循环引入不确定性)、对延迟极度敏感的任务(循环意味着多次模型调用)。
我个人的经验是:如果一个任务用单轮调用加好的 Prompt 能解决 80% 的情况,那就别上循环。循环带来的复杂度提升是显著的,包括调试难度、成本、延迟、稳定性风险。只有当单轮确实搞不定的时候,才考虑 Loop Engineering。
4. Context Engineering:Agent 的记忆与状态管理
4.1 为什么上下文管理是 Agent 的核心难题
Loop Engineering 解决了“让模型自己决定下一步”的问题,但引入了一个新问题:上下文越来越长。每次循环都会往上下文里追加思考、行动、观察结果,几轮下来上下文就爆了。而且,模型在长上下文里的表现会下降——这是目前所有大模型的通病。
Context Engineering 就是解决这个问题的。它的核心目标是:在有限的上下文窗口里,放入最相关的信息,让模型做出最好的决策。这包括几个子问题:什么信息该保留?什么信息该丢弃?什么信息该压缩?什么信息该外部存储?
我见过很多 Agent 项目,Prompt 写得不错,循环逻辑也没问题,但就是效果不稳定。排查下来,十有八九是上下文管理出了问题。要么是历史信息太多淹没了关键信息,要么是重要信息被截断了,要么是工具返回结果格式混乱导致模型理解困难。
4.2 上下文窗口的分配策略
上下文窗口是有限资源,怎么分配直接决定了 Agent 的表现。我通常会把上下文分成几个区域:
| 区域 | 内容 | 占比建议 | 说明 |
|---|---|---|---|
| 系统提示 | 角色、规则、工具定义 | 15-20% | 固定不变,每次都要带 |
| 任务描述 | 当前任务目标 | 5-10% | 简洁明确 |
| 历史记录 | 之前的思考和行动 | 30-40% | 需要动态管理 |
| 工具结果 | 最近一次工具返回 | 20-30% | 可能很长,需要压缩 |
| 输出指令 | 格式要求 | 5-10% | 固定不变 |
这个分配不是绝对的,但思路是:系统提示和输出指令是固定的,任务描述要精简,历史记录和工具结果是动态的,需要重点管理。
注意:不同模型的上下文窗口大小不同,但“窗口大”不等于“可以随便塞”。实测下来,即使模型支持 128K 上下文,有效信息超过 30K 之后,模型对中间部分的注意力就会明显下降。所以上下文管理不是“能塞多少塞多少”,而是“该塞多少塞多少”。
4.3 历史记录的压缩与摘要
历史记录是上下文里增长最快的部分。每一轮循环都会追加思考、行动、观察结果。如果不管理,几轮下来就占满了。
常见的压缩策略:
滑动窗口。只保留最近 N 轮的历史,更早的直接丢弃。简单有效,但可能丢失重要信息。
摘要压缩。把较早的历史让模型总结成一段摘要,保留关键信息,丢弃细节。比滑动窗口更智能,但需要额外的模型调用。
关键信息提取。从历史中提取结构化信息(比如已完成的步骤、已获取的数据),只保留这些关键信息,丢弃原始对话。
外部存储。把完整历史存到外部数据库,上下文里只放一个引用 ID,需要时再检索。
我通常的组合策略是:最近 3 轮保留完整记录,3 轮之前的做摘要压缩,10 轮之前的只保留关键信息提取结果。这样既能保证近期信息的完整性,又能控制上下文长度。
# 历史记录管理的简化实现 def manage_history(history, max_recent=3, max_summary=10): if len(history) <= max_recent: return history recent = history[-max_recent:] older = history[:-max_recent] if len(older) <= max_summary: summary = summarize_history(older) return [{"type": "summary", "content": summary}] + recent else: # 更早的历史只保留关键信息 key_info = extract_key_info(older) return [{"type": "key_info", "content": key_info}] + recent4.4 工具返回结果的格式化处理
工具返回结果是上下文里的另一个大头。一个搜索工具可能返回几千字的网页内容,直接塞进上下文就是灾难。
处理原则:只保留与当前任务相关的信息,其余丢弃或摘要。
具体做法:
- 结构化提取:如果工具返回 JSON,只提取需要的字段。
- 长度截断:设置最大长度,超出部分截断并加省略标记。
- 摘要生成:让模型对长文本做摘要,只保留关键信息。
- 分页加载:如果数据量大,先返回摘要,模型需要时再加载详情。
我踩过的一个坑是:搜索工具返回了 10 条结果,每条 500 字,总共 5000 字全塞进上下文。结果模型在后续推理中完全忽略了这些信息,因为信息量太大,模型“抓不住重点”。后来改成只返回前 3 条结果的标题和摘要,总共 300 字,模型反而能有效利用这些信息。
4.5 Context Engineering 的实战检查清单
在实际项目中,我通常会对照这个清单来检查上下文管理是否到位:
- [ ] 系统提示是否精简且完整?
- [ ] 任务描述是否明确无歧义?
- [ ] 历史记录是否有压缩策略?
- [ ] 工具返回结果是否做了格式化?
- [ ] 上下文总长度是否在模型有效范围内?
- [ ] 关键信息是否在上下文中容易被模型注意到?
- [ ] 是否有机制检测上下文溢出并自动处理?
这个清单看起来简单,但每一条在实际项目中都可能出问题。尤其是最后一条,很多项目没有上下文溢出的检测机制,等到模型开始胡言乱语了才发现上下文爆了。
5. Graph Engineering:多 Agent 协作的架构设计
5.1 从单 Agent 到多 Agent 的演进逻辑
当一个 Agent 搞不定的时候,自然会想到用多个 Agent。但多 Agent 不是简单地把任务分给几个 Agent 就完事了。它引入了一堆新问题:Agent 之间怎么通信?任务怎么分配?冲突怎么解决?状态怎么同步?
Graph Engineering 就是解决这些问题的。它的核心思想是:把多 Agent 系统看作一个有向图,节点是 Agent 或工具,边是通信路径,整个图的执行就是任务的求解过程。
这个思路其实借鉴了工作流引擎和分布式系统的设计理念。但和传统工作流不同的是,Agent 图里的节点是“智能”的,它们可以根据输入动态决定输出,而不是执行固定的逻辑。
5.2 多 Agent 协作的常见拓扑结构
根据任务特点,多 Agent 系统可以采用不同的拓扑结构:
流水线结构。Agent 按顺序执行,前一个的输出是后一个的输入。适合步骤明确、线性执行的任务,比如“需求分析 -> 方案设计 -> 代码生成 -> 代码审查”。
并行结构。多个 Agent 同时执行不同子任务,最后汇总结果。适合子任务之间无依赖的场景,比如“同时搜索多个数据源”。
层级结构。有一个协调者 Agent 负责任务分配和结果汇总,多个执行者 Agent 负责具体子任务。适合复杂任务,协调者可以根据执行情况动态调整。
辩论结构。多个 Agent 对同一问题给出不同答案,通过辩论或投票达成一致。适合需要多角度分析的任务,比如风险评估。
| 拓扑结构 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 流水线 | 线性任务 | 简单可控 | 无法并行,容错差 |
| 并行 | 独立子任务 | 效率高 | 结果汇总复杂 |
| 层级 | 复杂任务 | 灵活可扩展 | 协调者成为瓶颈 |
| 辩论 | 决策类任务 | 多角度分析 | 成本高,收敛慢 |
我实际项目中用得最多的是层级结构。一个协调者 Agent 负责理解任务、拆解子任务、分配给执行者 Agent、收集结果、判断是否完成。执行者 Agent 各自负责一个具体能力,比如搜索、计算、代码执行。
5.3 Agent 间通信协议的设计
多 Agent 协作的核心是通信。Agent 之间怎么传递信息、怎么表达意图、怎么处理冲突,都需要一套协议。
我通常采用的消息格式:
{ "from": "coordinator", "to": "search_agent", "type": "task", "content": { "task_id": "task_001", "description": "搜索2024年AI Agent领域的重要进展", "constraints": { "max_results": 5, "time_range": "2024-01-01 to 2024-12-31" } }, "timestamp": "2024-12-15T10:30:00Z" }响应格式:
{ "from": "search_agent", "to": "coordinator", "type": "result", "content": { "task_id": "task_001", "status": "success", "data": [...], "summary": "找到5条相关进展..." }, "timestamp": "2024-12-15T10:30:05Z" }这种结构化消息格式的好处是:清晰、可追溯、易于调试。每个消息都有明确的发送者、接收者、类型和内容,出问题时可以快速定位。
实操心得:Agent 间通信最容易出的问题是“信息丢失”和“信息过载”。信息丢失是指发送方以为接收方知道某个信息,但实际没传过去。信息过载是指发送方把所有信息都塞给接收方,导致接收方处理不过来。解决方法是:明确每个 Agent 的输入输出契约,只传必要信息,必要时做摘要。
5.4 状态管理与一致性保障
多 Agent 系统里,状态管理是个大问题。每个 Agent 都有自己的状态,Agent 之间的状态需要同步。如果处理不好,就会出现“Agent A 以为任务完成了,Agent B 还在执行”这种情况。
常见的状态管理方案:
中心化状态。有一个全局状态存储,所有 Agent 都读写这个存储。优点是状态一致性好,缺点是中心存储可能成为瓶颈。
去中心化状态。每个 Agent 维护自己的状态,通过消息传递同步。优点是扩展性好,缺点是一致性难保证。
混合方案。关键状态中心化存储,非关键状态各 Agent 自己维护。这是我在实际项目中用得最多的方案。
状态一致性保障机制:
- 版本号:每次状态更新递增版本号,冲突时以高版本为准。
- 锁机制:关键状态更新时加锁,防止并发写冲突。
- 事务:多个状态更新打包成一个事务,要么全成功要么全失败。
- 补偿:状态不一致时,通过补偿操作回滚或修正。
5.5 多 Agent 系统的可观测性建设
多 Agent 系统比单 Agent 复杂得多,出问题时排查难度也大得多。可观测性建设不是可选项,是必选项。
我通常会记录以下信息:
- 每个 Agent 的输入输出:完整记录,用于复现问题。
- 消息传递日志:谁在什么时候给谁发了什么消息。
- 状态变更历史:状态什么时候被谁改成了什么。
- 性能指标:每个 Agent 的响应时间、token 消耗、工具调用次数。
- 错误日志:所有异常和错误,包括堆栈信息。
这些信息汇总到一个可视化面板上,可以实时看到整个系统的运行状态。出问题时,可以快速定位是哪个 Agent、哪个环节出了问题。
# 可观测性埋点的简化实现 class AgentMonitor: def __init__(self): self.logs = [] self.metrics = {} def log_message(self, from_agent, to_agent, message): self.logs.append({ "type": "message", "from": from_agent, "to": to_agent, "content": message, "timestamp": time.time() }) def log_state_change(self, agent, old_state, new_state): self.logs.append({ "type": "state_change", "agent": agent, "old": old_state, "new": new_state, "timestamp": time.time() }) def record_metric(self, agent, metric_name, value): key = f"{agent}.{metric_name}" if key not in self.metrics: self.metrics[key] = [] self.metrics[key].append({ "value": value, "timestamp": time.time() })这套埋点机制看起来简单,但在实际排查问题时非常有用。我遇到过好几次“Agent 莫名其妙不执行”的情况,最后都是通过消息日志发现是某个消息格式不对导致接收方解析失败。
6. 常见问题与排查技巧实录
6.1 Agent 陷入死循环怎么办
这是最常见的问题。表现是:Agent 反复调用同一个工具,或者反复输出同样的思考,就是不给最终答案。
排查思路:
- 检查工具返回结果:是不是工具一直返回错误,导致 Agent 反复重试?如果是,修复工具或增加错误处理。
- 检查 Prompt:是不是 Prompt 里没有明确的终止条件?加上“如果无法完成,请输出 FINAL_ANSWER: 无法完成”。
- 检查循环终止逻辑:是不是最大迭代次数设太大了?调小一点。
- 检查上下文:是不是上下文里有什么信息让 Agent 困惑了?清理上下文试试。
我遇到过一个案例:Agent 反复调用搜索工具,因为搜索工具返回的结果里包含“未找到相关信息”,Agent 以为需要换个关键词再搜。但实际上那个信息确实不存在。后来在 Prompt 里加了“如果搜索三次仍未找到,请直接告知用户未找到”,问题解决。
6.2 工具调用参数错误怎么处理
模型传错参数是常态。处理方式:
- 工具内部做参数校验:类型不对、格式不对、超出范围,都返回明确的错误信息。
- 错误信息要具体:不要只说“参数错误”,要说“city 参数必须是字符串,你传的是数字”。
- 给模型重试机会:工具返回错误后,模型可以根据错误信息修正参数再试。
- 设置重试上限:同一个工具连续失败 3 次,强制终止,避免无限重试。
6.3 上下文溢出怎么预防
上下文溢出是 Agent 运行一段时间后必然遇到的问题。预防措施:
- 实时监控上下文长度:每次循环前检查,接近上限时触发压缩。
- 设置硬上限:超过硬上限直接终止,返回错误。
- 压缩策略要提前设计:不要等到溢出了才想怎么办。
- 工具返回结果要控制长度:从源头控制上下文增长。
6.4 多 Agent 协作时任务分配不均
表现是:有的 Agent 忙死,有的 Agent 闲死。协调者把大部分任务都分给了一个 Agent。
排查思路:
- 检查协调者的分配逻辑:是不是分配策略有问题?
- 检查 Agent 的能力描述:是不是某个 Agent 的能力描述太宽泛,导致协调者什么都分给它?
- 检查任务拆解粒度:是不是任务拆得太粗,导致无法并行?
解决方法是:细化 Agent 的能力描述,让每个 Agent 的职责更明确;优化任务拆解逻辑,让子任务更均衡;必要时引入负载均衡机制。
6.5 常见问题速查表
| 问题 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Agent 死循环 | 工具一直报错/终止条件不明确 | 查看工具日志和 Prompt | 修复工具/加终止条件 |
| 参数传错 | 工具描述不清/模型理解偏差 | 查看工具调用日志 | 优化工具描述/加参数校验 |
| 上下文溢出 | 历史记录太长/工具返回太长 | 监控上下文长度 | 压缩历史/截断工具返回 |
| 任务分配不均 | 协调者逻辑问题/能力描述模糊 | 查看任务分配日志 | 优化分配逻辑/细化能力描述 |
| Agent 不执行 | 消息格式错误/状态不一致 | 查看消息日志和状态历史 | 修复消息格式/同步状态 |
| 结果质量差 | Prompt 问题/上下文问题 | 对比输入输出 | 优化 Prompt/清理上下文 |
提示:这张表建议打印出来贴在工位上。Agent 开发中 80% 的问题都能在这张表里找到对应项。
7. 从理论到落地:一些个人体会
7.1 不要为了 Agent 而 Agent
这是我最大的体会。Agent 是手段,不是目的。如果一个任务用单轮调用能解决,就别上循环。如果一个 Agent 能搞定,就别上多 Agent。复杂度是有代价的,包括开发成本、维护成本、运行成本、调试成本。
我见过太多项目,明明是个简单的分类任务,非要搞个多 Agent 协作,结果准确率还不如单轮调用。问为什么,答“显得先进”。这种思路要不得。
7.2 可观测性比功能更重要
Agent 系统出问题是常态,不出问题才是意外。所以可观测性建设要优先于功能开发。没有可观测性,出了问题就是两眼一抹黑,只能靠猜。有了可观测性,大部分问题都能在几分钟内定位。
我的做法是:在写第一行 Agent 代码之前,先把日志、监控、追踪的框架搭好。后面每加一个功能,都顺手加上埋点。这样系统越复杂,可观测性的价值越大。
7.3 上下文管理是永恒的主题
无论 Agent 架构怎么演进,上下文管理始终是核心难题。模型的能力在提升,上下文窗口在扩大,但“在正确的时间给模型正确的信息”这个挑战不会消失。反而随着任务复杂度提升,这个挑战会越来越大。
我的建议是:把上下文管理当作一个独立的模块来设计和实现,不要散落在各个 Agent 的代码里。统一的上下文管理模块,能让整个系统的行为更可预测、更可调试。
7.4 多 Agent 协作的边界
多 Agent 不是银弹。它适合的任务类型是:任务可以明确拆解成子任务、子任务之间可以并行或流水线执行、每个子任务需要不同的专业能力。如果任务本身是高度耦合的、需要全局视角的,那多 Agent 反而会降低效果。
我个人的经验是:先从单 Agent 开始,遇到瓶颈再考虑多 Agent。单 Agent 的瓶颈通常是:上下文不够用、能力太杂导致 Prompt 太长、需要并行执行。这时候再引入多 Agent,才有明确的收益。
7.5 最后分享一个小技巧
在开发 Agent 系统时,我习惯先写一个“最小可运行版本”——只有一个 Agent、一个工具、最简单的循环。跑通之后,再逐步加功能、加 Agent、加工具。每加一个东西,都确保系统还能跑通。
这样做的好处是:任何时候都有一个可工作的基线,出问题可以快速回滚到上一个可用版本。比一次性设计一个大而全的系统,然后花几周时间调试,效率高得多。
这个思路其实适用于所有复杂系统的开发。Agent 系统也不例外。从简单开始,逐步演进,每一步都确保可运行、可观测、可回滚。这样即使最终系统很复杂,你也能清楚地知道每个部分是怎么来的、为什么这么设计。