ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent工程化落地:从七要素到七个决策点,让智能体稳定运行

AI Agent工程化落地:从七要素到七个决策点,让智能体稳定运行

如果你最近在折腾 AI Agent,大概率遇到过两类内容:一类用论文语言讲 Agent 的原理和心智模型,看完只觉得“很有道理”,但回到代码里还是不知道怎么改;另一类是炫酷的 demo,跑完 demo 一换业务场景就失灵。我自己带团队做了快一年的 Agent 工程化落地,最大的体感是:Agent 的工程实现就是一连串的“选择题”——从“七要素”看清 Agent 的本质,再用“七个决策点”定下实现方案,这是把 Agent 从能跑变成能稳定跑的唯一路径。

这篇文章我把两条线都摊开讲:七要素负责让你彻底搞懂 Agent 是什么(目标、感知、规划、记忆、工具、行动、反思),七个决策点负责告诉你工程实现时选什么、为什么选、踩过哪些坑。适合正在把 Agent 从 demo 推向生产的工程师,也适合想系统理解 Agent 主流架构的产品和技术负责人。文章不偏向具体框架,Python、TypeScript、Rust 的思路通用。

1. 先把 Agent 拆开:七要素决定了 Agent 的本质

1.1 Agent 不是 Chatbot:差了一个“行动-观察”的闭环

很多人把带上下文的 Chatbot 当成 Agent,这是第一个认知误区。Chatbot 的闭环是“输入-生成-输出”,用户问一句,模型答一句,链路就结束了。Agent 的闭环是“目标-规划-行动-观察-再规划”,它要自己决定调哪个工具、按什么顺序调、看到工具返回结果之后再决定下一步。这个“行动-观察”的循环,是 Agent 和 Chatbot 的分水岭。

我举个具体例子。同样是“帮我把这周的销售数据整理成日报”,Chatbot 只能给你一段文字建议:“你可以导出数据、用 Excel 透视表”。Agent 会自己调用数据查询工具,拉取本周销量,调用代码解释器做聚合,然后生成日报文本,最后调用消息发送工具推送给指定的人。差别不在于模型本身,而在于系统是否给了模型一个“动手”的通道。

所以拆解 Agent 的第一步,是承认它是“多个能力模块的组合”,而不是“某个模型”。工程上如果要稳定地实现它,必须先把七大要素一一对齐。

1.2 逐一拆解七要素

七要素是我和团队在复盘多个 Agent 项目时沉淀下来的拆解框架:目标、感知、规划、记忆、工具、行动、反思。每个要素都对应一类工程问题。

要素作用常见误区
目标把模糊需求拆成可执行任务,定义“成功长什么样”直接把用户一句话当系统目标,缺少任务分解
感知接收用户输入、环境状态、工具返回的原始信息忽略工具返回中的异常信号
规划决定先做什么、后做什么,选择执行路径让模型自由发挥,没有边界
记忆短期工作记忆 + 长期事实存储全部塞进上下文,把记忆和上下文混为一谈
工具模型与外部世界交互的接口工具定义过粗或过细,模型不会用
行动调用工具、生成输出、发起外部请求行动没有确认机制,风险不可控
反思对执行过程与结果进行自我评估和纠错跑完就结束,不评估不回溯

逐个说。目标是所有要素里最容易被忽视的。很多项目把“用户输入”直接当目标,Agent 就会在模糊指令下做出不可预测的行为。工程上要给 Agent 一个显式的任务拆解层:用户需求进来后,先做一次目标解析,把“帮我把小红书账号打理好”变成“生成内容 -> 人工审核 -> 定时发布 -> 监控反馈”这样的子任务。目标拆得越清楚,后面规划、工具、评估就越好做。我见过一个失败的案例,目标层没做任务拆解,Agent 在“整理会议纪要”这个任务里自己去查了天气,因为模型觉得“整理”这个词有太多解释空间。

感知要解决的是“输入信号怎么进、异常怎么标记”。用户的文本只是感知的一部分,工具返回的状态码、外部 API 的错误信息、超时信号,这些都是感知输入。工程上要做统一的输入封装——把不同来源的信息标准化成模型能看懂的结构化文本,尤其是异常信息,要让模型明确知道“这一步失败了,失败原因是 xxx”,否则模型会基于错误信息继续往下编。

规划是 Agent 的“策略层”。主流实现有两条路线:一条是 ReAct 式的“边想边做”,每一步由模型决定是继续推理还是调用工具;另一条是 Plan-and-Execute,先让模型生成一份完整计划,再逐条执行。这两条路线没有绝对优劣,我后面会在决策点里展开。规划层最容易出现的问题是无边界——模型自己决定循环次数,不做上限控制,最后陷入死循环。

记忆分两层:短期工作记忆和长期记忆。工作记忆指的是当前任务上下文,通常由对话历史和工具调用记录构成;长期记忆指的是跨会话的事实,比如用户偏好、业务规则、历史决策。工程上最常犯的错是把所有记忆不加区分地塞进模型上下文,导致上下文爆炸、关键信息被稀释。记忆要分层、要裁剪、要摘要,这个在决策点三里详细讲。

工具是 Agent 能力的边界。一个 Agent 能做什么,不取决于模型多强,取决于你给它暴露了哪些工具。工具定义不是简单的函数签名,而是“模型怎么理解这个工具、怎么填参数、怎么处理返回值”的一整套设计。工具设计得差,模型就会频繁填错参数、选错工具。

行动是工具调用的具体执行层。它负责把模型的决策变成真实的 API 请求、代码执行、消息发送。这里的关键是:并非所有行动都应自动执行。写文件、发消息、转账这一类有副作用的行为,必须经过确认机制——要么人工审批,要么二次校验。行动层和控制层之间的边界,直接决定了系统的安全上限。

反思是大多数人会省略的一环。没有反思的 Agent 是一次性的:任务跑完就结束,错了就错了。加入反思意味着在执行结束后或每轮行动后,让模型评估一次“这一步的结果符合预期吗?还需要调整吗?”。这是把“能完成任务的 Agent”变成“能稳定完成任务的 Agent”的关键一步。

2. 七要素背后的工程含义:为什么先拆解再谈实现

2.1 七要素是需求分析工具,不是理论框架

我见过太多团队拿到需求就直接开写代码,把 Agent 当成一个“大模型 API 调用”,结果写到一半发现不知道该把业务逻辑放在哪一层——是放在提示词里?放在工具里?还是放在 Agent 循环的外面?七要素的真正价值,是逼你在写代码之前先回答一组问题:

  • 这个任务的目标是什么?需要拆成几步?
  • 输入信号有哪些?哪些需要 Agent 感知,哪些是内部参数?
  • 需要规划吗?还是简单任务直接执行?
  • 需要记忆吗?短期还是长期?跨会话吗?
  • 需要哪些工具?工具之间的依赖关系是什么?
  • 哪些行为可以自动执行?哪些必须人工确认?
  • 跑完之后,用什么标准判断“做得好”?

这七个问题对应七要素,本质上是一次需求澄清。我在项目里会把这一过程叫“Agent 需求七问”,你可以直接拿去用。回答不清楚的地方,就是后续一定会出 bug 的地方。

举一个实际的例子。之前有个团队想做一个“自动整理周报并发送”的 Agent,需求描述只有一句话。我们用七要素拆了一遍之后发现:目标不明确(周报是给谁看、需要哪些数据)、感知不明确(数据从哪个系统来、有没有权限)、行动有风险(自动发送邮件,发错了不可撤回)。这些在需求阶段暴露出来,比在线上事故阶段暴露出来要好处理得多。最终方案改成了:先生成周报草稿,发到人工确认队列,确认之后再发送。这就是行动要素逼出来的设计。

2.2 主流架构与七要素的对应关系

现在社区里讨论的 AI Agent 主流架构,本质上都是对七要素的不同侧重和编排方式。我整理了一个对照表,方便你做选型时快速定位:

架构核心思想强化要素适用场景
ReAct推理与行动交替进行,每步选择思考或调工具规划、行动、感知需要动态决策的多步任务
Plan-and-Execute先规划完整步骤,再按计划执行规划、目标流程相对明确、可预测的任务
Reflexion加入反思机制,对失败进行自我修正反思、记忆需要高可靠性的任务
Multi-Agent多个 Agent 分工协作,各负责子任务目标、规划、工具复杂任务、需要多个专业角色的场景

ReAct 是目前最主流的实现方式,因为它的灵活性最高、对场景的适应性最强。但灵活性高也意味着不稳定:模型每一轮都在做自由决策,链路上的错误会被逐步放大。所以工程实现时一般会对 ReAct 加约束,比如:限定最大轮数、限定工具候选集、在关键节点强制走固定流程。Plan-and-Execute 适合流程相对固定的场景,比如“每天拉数据 -> 汇总 -> 发报告”,这种任务你根本不需要模型每步都重新规划,预定义流程更稳、更省 token。Reflexion 则是把“反思”这个要素变成显式步骤,任务失败后让模型分析原因、调整策略,再重跑一轮。

如果你的任务复杂度不高,我不建议一上来就上 Multi-Agent。多个 Agent 之间的通信、共识、状态同步问题,比单 Agent 的模型调用问题难一个数量级。这个选择我用了一个判断标准:如果单 Agent 配上好工具能在 80% 的场景下完成任务,就不要上多 Agent。多 Agent 的价值在于“角色分工”,而不是“看起来更高级”。

3. 七个决策点:工程实现就是把“能做”变成“能稳定做”

七要素解决的是“Agent 是什么”的认知问题。到工程实现阶段,真正的挑战是:在每一步都做一次明确的选择。我把这些选择归纳成七个决策点,基本覆盖了从架构到上线的大部分关键路径。

3.1 决策点一:单体 Agent 还是多 Agent?

这个决策是全局性的,直接影响后续所有设计。判断标准有三条:任务是否需要多个不相关的专业能力?是否需要并行处理多个子任务?团队是否能承受多 Agent 的调试成本?

  • 单 Agent:一个模型实例 + 一组工具,内部用 ReAct 或 Plan-and-Execute 完成多步任务。优点是调试简单、状态管理容易、token 消耗可控;缺点是所有能力都压在一条推理链路上,长任务容易出错。
  • 多 Agent:多个 Agent 各有分工,通过消息传递或共享内存协作。优点是职责清晰、可并行;缺点是通信协议、状态同步、死锁排查都是额外负担。

我们团队在一开始做售前客服 Agent 时,上来就设计了“主控 Agent + 产品专家 Agent + 订单查询 Agent + 话术优化 Agent”四体架构。结果第一个版本光是在 Agent 之间的消息格式上就迭代了一周,最后主控 Agent 经常同时收到两个子 Agent 的相互矛盾的回答,不知道采信哪个。后来我们退回到单体 Agent,把所有工具直接挂在同一个模型上,效果反而更好——因为大部分客服问题只需要一次工具调用就能解决,根本不需要多 Agent 协作。我给你的建议是:先跑通单体基线,再把真正频繁冲突的部分拆出去。

3.2 决策点二:用单一模型还是多模型编排?

这个决策点被很多人忽略,觉得“就一个模型,有什么区别”。实际上模型编排直接决定了质量和成本的平衡。

三条路线:

  1. 单一大模型全包:所有任务都用同一个强模型。优点是实现最简单、一致性最好;缺点是贵,复杂任务单次可能消耗上万 token。
  2. 模型路由:先让一个小模型做意图识别,把简单任务分给快模型,复杂任务才交给大模型。这种“大小模型路由”可以在保持质量的情况下,省下 30% 到 50% 的成本。
  3. 多模型分工:规划用强模型、反思用中等模型、工具参数抽取用专用小模型。这个方案工程质量最高,但工程复杂度也最高。

我目前实践下来最优性价比的选择是“路由 + 双模型”:一个轻量模型处理简单查询和分类,一个强模型处理复杂推理。关键是路由判断本身要准,否则简单任务被误分到强模型,成本没省下来,延迟还高了。路由规则我建议先用规则写死,比如“带工具调用的任务走强模型、纯知识问答走轻量模型”,等积累了足够的日志之后,再考虑用分类模型自动路由。语言选型上,Python 和 TypeScript 生态最全,Rust 做 Agent 调度层能压运行时开销,但第三方生态不成熟,开发效率明显打折,大部分项目不值得用 Rust 硬磕。

3.3 决策点三:记忆放哪里、怎么存取?

记忆是所有决策点里最容易做坏的一个。很多人一提记忆就上向量数据库,这是个典型误区。向量数据库解决的问题是“语义相似检索”,但 Agent 的很多记忆需求是“精确读取最近状态”和“按规则提取摘要”,向量检索并不适合所有场景。

我建议把记忆分成三类来处理:

  • 短期工作记忆:当前任务上下文,包括最近的几轮对话、工具调用的输入和输出。实现上通常直接用模型上下文窗口的一部分,但要做滑动窗口裁剪——只保留最近三轮完整记录,更早的浓缩成摘要。
  • 长期事实记忆:用户偏好、业务规则、历史结论。这类记忆需要结构化存储,KV 数据库、SQLite、关系型数据库都能胜任,不一定非要向量化。比如“这个用户上次反馈过发票问题”这种记忆,直接存一个 key-value 就好。
  • 语义检索记忆:真正需要“根据相似度召回相关知识”的场景,比如从历史工单里找到类似问题的处理方案,这时候才用向量检索。

记忆存取和模型上下文的关系,可以用一个比喻理解:上下文窗口是“桌面”,长期记忆是“文件柜”。你不能把所有文件都堆在桌面上(塞进上下文),那样桌子就乱了;但需要用到某份文件时,得能快速从文件柜抽出来(检索),用完之后放回去而不是一直摊着。

工程上我会做一个 MemoryManager,统一管理三种记忆的写入、裁剪和清理。每轮对话结束后,由它决定哪些信息值得存、哪些该丢。这个模块的决策逻辑可以是规则的,也可以是模型辅助的——“这段对话里有什么值得长期记住的事实”这类判断,让模型做一个很小的抽取任务,效果很好。

3.4 决策点四:工具边界怎么画?

工具是 Agent 的能力边界,也是安全边界。工具反过来也会影响模型的决策行为。这里有几个实际经验可以分享。

工具的粗细粒度。工具太粗,模型的灵活性受限;工具太细,模型不知道该选哪个。例如发消息这个能力,如果你拆成“获取目标用户列表”“生成消息文本”“查询发送状态”“实际发送”四个工具,模型大概率会在参数填写上反复出错。合理做法是把“生成并发送消息”作为一个复合工具,内部自己完成编排,只暴露出几个填参数的口子:“接收者、内容、发送时间”。

工具描述怎么写。工具描述要包含四部分:这个工具做什么、什么时候用、参数是什么、返回值长什么样。我踩过一个坑:工具描述里没有写“失败时会返回 error 字段”的说明,模型在工具返回 error 之后依然往下执行,以为消息已经发出去了。后来我在所有工具的返回说明里都加上了失败信号说明,错误处理要由模型感知到。

工具的副作用控制。具备“发送、删除、转账、发布”等副作用的行为,不能直接暴露给模型自由调用。正确做法是把这些动作封装到一个受控工具里,工具内部先检查审批状态,如果没有人工确认,就直接返回“操作被拒绝,请等待人工审批”。这样一来,模型知道它“做不到”,就不会在规划里反复尝试了。

3.5 决策点五:安全与控制:Agent 的终止条件

Agent 失控的经典场景是“无限循环”——模型反复调用同一个工具、反复生成相似的中间结果、始终没有走到终止状态。这多半是规划层没有明确的终止条件导致的。

我建议在工程实现里设置四重终止控制:

  1. 最大步数:根据任务复杂度设定,一般 8 到 20 步之间。超过最大步数强制终止,标记为“未完成”。
  2. 重复动作检测:如果模型连续两轮调用同样的工具、传入同样的参数,直接打断,进入反思流程或宣告失败。
  3. 危险动作白名单:只有授权范围内的工具允许自动执行,其他一律转入人工确认队列。
  4. 超时与熔断:单步操作用时超过阈值,视为该工具不可用,跳过或重试一次;整体执行超过时间预算,强制结束。

还有一个人机协同的插入时机问题。不是所有任务都需要人参与,但“不可逆操作”和“影响外部系统状态的操作”必须有人确认。我们用的方案是:把审批做成一个特殊工具,Agent 执行到“发消息”这一步时,返回“已提交,等待审批”,任务挂起,审批人在后台点击确认之后,系统继续执行后续步骤。这个设计可以让 Agent 在没有人工打扰的情况下完成调研、准备、生成等环节,只在最后一步让真人把关。

3.6 决策点六:评估体系怎么搭?

大部分 Agent 项目死于“感觉还行”,缺少量化的评估。Prompt 写得好不好、工具设计是否合理、模型选择是否合适,如果没有评估数据,全都靠感觉。我总结了一套最小可用的评估体系,包含离线和在线两个层面。

离线评估:准备一组黄金测试用例(golden set),每个用例包含任务描述、预期工具调用序列、预期结果。每次修改 prompt 或工具,就批量跑一遍,对比“任务的完成率、平均步数、工具调用成功率、单任务平均 token 消耗”四个指标。这一步能挡住大部分回归问题。注意,评估不能只看“最终结果是否正确”,还要看“路径是否正确”——有的 Agent 虽然完成了任务,但它绕了三个无关工具,这种路径要识别出来,否则成本会悄悄失控。

在线评估:上线后要接入日志和抽样复盘。我给每个 Agent 任务分配一个唯一 ID,记录完整的轨迹日志,包括每一步的模型输入输出、工具调用、耗时和成本。每天抽几条日志人工复盘“这一单是不是最优路径”,然后把问题归类回对应要素和决策点。

影子模式:当你准备换一个模型或者改一套 prompt 时,不要直接全量上线。让新旧两个版本并行跑同样的请求,把结果对比,确认新版本在指标上优于旧版本,再切量。本质上就是 A/B 测试在 Agent 场景的应用。

这里也提醒一句,大多数人第一次评估时会遇到“指标都在涨,实际体验变差”的情况。问题出在评估用例覆盖不全——你的 golden set 只覆盖正常场景,没覆盖边界输入。补充一些对抗用例,比如“用户提供了冲突信息”“工具返回异常”,能让评估体系更可靠。

3.7 决策点七:成本、延迟与质量的平衡

最后一个决策点关乎项目能不能长期跑下去。成本主要来自 token 消耗,而 token 消耗大头集中在几个地方:系统提示词、工具定义、中间推理、工具返回结果。很多人只算“模型输出 token”,实际上输入 token 占大头。

解释一下 AI Agent token 是什么意思:模型按 token 计费,一个 token 大约等于一个汉字或几个英文字符,输入和输出都算钱。一个包含八条工具描述、两页业务规则的 Agent,每次请求光系统提示词和工具定义就可能烧掉几千输入 token,如果每条消息都原样携带,跑上几百次任务,费用可想而知。

控制成本的手段相互配合:

  1. Prompt 缓存:系统提示词和工具定义在多次请求之间不变,可以启用缓存,命中缓存的输入 token 价格大幅降低。
  2. 压缩工具描述:工具描述不是越长越好。把描述精简到“名称、触发场景、必填参数、返回结构”四部分,去掉冗余解释,能直接减少 token。
  3. 模型路由降级:复杂任务用强模型,简单任务走轻量模型或规则预判。很多查询其实不需要完整推理,先走规则匹配能省掉大量成本。
  4. 摘要裁剪:上下文里的历史记录用摘要替代原文,每轮对话结束后把早期对话压成一句话摘要。这一步能把长对话的成本从增长曲线拉回线性。

延迟和质量的平衡也要考虑。Agent 每多一次工具调用,就多一次模型往返,延迟就会变长。如果你的场景对实时性要求高,比如客服在线聊天,就要限制工具调用次数、优先选少步的工具链路;如果任务类型是深度调研,可以允许长链路,这时用户等待几秒是可以接受的。

4. 从决策到落地:一个最小可用的 Agent 工程骨架

决策点聊完,我用一个具体的最小实现把工程骨架串起来。下面这套结构是我在多个项目里打磨过的,不依赖特定框架,你拿过去可以直接改。

4.1 目录结构和核心模块

agent_playground/ ├── configs/ │ ├── agent.yaml # Agent 行为配置:最大步数、模型选择、超时 │ └── tools.yaml # 工具清单与白名单 ├── core/ │ ├── agent.py # ReAct 主循环 │ ├── tools.py # 工具注册、参数校验、执行与错误包装 │ ├── memory.py # 短期记忆窗口 + 滚动摘要 │ ├── llm.py # 模型调用、重试、缓存 │ └── schema.py # 统一的输入输出数据结构 ├── tools/ │ ├── search.py # 检索工具 │ ├── calculator.py # 计算工具 │ └── send_message.py # 消息发送(带人工确认标记) ├── safety.py # 终止判断、重复动作检测、审批接口 ├── evaluator.py # 轨迹日志与指标统计 └── main.py # 入口与任务分发

如果你用 Rust 实现类似结构,core 部分可以直接用 trait 抽象 Agent 循环和工具行为,并发用 tokio 也不难,但工具生态和 prompt 工程迭代速度会慢。再次强调,调度层用什么语言写不是关键,关键的是架构和决策机制。

4.2 ReAct 主循环的代码实现

Agent 主循环代码示例:

# core/agent.py import json from typing import Any class ReActAgent: def __init__(self, llm, tools: dict, config: dict): self.llm = llm self.tools = tools self.max_steps = config.get("max_steps", 10) self.safety = config.get("safety", {}) self.memory = [] self.step_count = 0 def run(self, task: str) -> dict: system = "你是一个多步骤任务执行体。" \ "你可以调用工具,也可以直接回答。" \ "每次输出必须为 JSON:{"thought": "本案推理", "action": "工具名或DONE", "action_input": {...}}" self.memory = [{"role": "user", "content": task}] result = {"completed": False, "output": None, "trace": []} while self.step_count < self.max_steps: self.step_count += 1 # 1. 拼接上下文并调用模型 response = self.llm.chat(system, self.memory) parsed = self._safe_parse(response) # 2. 终结分支:模型决定结束 if parsed.get("action") == "DONE" or parsed.get("action") is None: result["completed"] = True result["output"] = parsed.get("action_input", {}).get("answer") break # 3. 工具执行分支:检查工具存在性与安全约束 tool_name = parsed.get("action") action_input = parsed.get("action_input", {}) if tool_name not in self.tools: observation = f"错误:工具 {tool_name} 不存在,可用工具: {list(self.tools.keys())}" elif not self._check_safety(tool_name, action_input): observation = "拒绝:该操作需要人工审批,已挂起等待确认" else: observation = self.tools[tool_name].execute(**action_input) # 4. 把执行结果写回记忆,形成下一轮观察 self._append_observation(parsed, observation) result["trace"].append({ "step": self.step_count, "action": tool_name, "input": action_input, "observation": observation[:500], # 截断过长的返回 }) # 5. 重复动作检测 if self._is_repeating(): observation = "检测到重复动作,强制终止,请用 DONE 输出当前答案" self._append_rejection(observation) break if not result["completed"]: result["output"] = "任务未完成:超出最大步数或被强制终止" return result def _safe_parse(self, response: str) -> dict: # 解析模型的 JSON 输出,失败时返回 DONE 避免死循环 try: return json.loads(response) except json.JSONDecodeError: return {"action": "DONE", "action_input": {"answer": response}} def _check_safety(self, tool_name: str, action_input: dict) -> bool: # 真实项目中这里会查工具配置里的审批标记 return not self.tools[tool_name].requires_approval def _is_repeating(self) -> bool: if len(self.memory) < 4: return False last_tool = self.memory[-3] prev_tool = self.memory[-6] if len(self.memory) >= 6 else None return json.dumps(last_tool) == json.dumps(prev_tool) def _append_observation(self, parsed: dict, observation: str) -> None: self.memory.append({"role": "assistant", "content": json.dumps(parsed, ensure_ascii=False)}) self.memory.append({"role": "system", "content": f"Observe: {observation}"}) # 记忆裁剪:只保留近 6 条完整记录,更早的交给滚动摘要 if len(self.memory) > 12: summary = self._summarize_early_memory() self.memory = [{"role": "assistant", "content": summary}] + self.memory[-12:] def _summarize_early_memory(self) -> str: # 调用模型把旧记录压缩成摘要 early = self.memory[:-12] prompt = "把下面的对话轨迹压缩成不超过200字的摘要,保留所有关键结论和已执行的操作:" return self.llm.chat(prompt, early)

4.3 记忆截断和工具调用的工程细节

上面的代码有几个工程细节值得展开。第一,模型输出用 JSON 格式并要求以其为主要交互格式,但 JSON 解析必然会出现失败,代码里兜底为“直接 DONE”,避免解析失败导致死循环。实际项目中我更推荐用 function calling 而不是 JSON mode——function calling 让模型严格按照工具 schema 输出参数,参数幻觉率明显更低,省去手动解析的麻烦。如果你用的模型不支持 function calling,再退回到 JSON mode。

第二,工具返回结果要做截断。真实工具返回可能是一整份数据库记录,好几千 token,全塞进上下文既费钱又稀释关键信息。我的做法是:工具内部先对返回做字段裁剪,默认只保留前 500 个字符,同时做一个“详细内容在附件里”的标记。模型需要更多细节时,会主动调用一个 get_detail 工具去拿完整数据。这是让模型学会“按需获取信息”,而不是一次性把所有信息都吞进上下文。

第三,记忆裁剪用“滚动摘要 + 最近完整记录”的模式。早期对话在模型输出里被压缩成一段摘要,近期对话保留全量。这样既保证了短期工作记忆的完整性,又控制了长期成本。

5. 实战中的翻车现场与排查技巧

5.1 问题一:模型在工具调用里无限循环

表现:Agent 反复调用同一个工具,或者两个工具之间来回切换,就是不结束。原因多半是终止条件缺失或观察反馈不足以改变模型的决策。

排查方法:第一步查轨迹日志,看循环的“锚点”是什么——模型为什么认为还需要再查一次?我遇到过一个案例:模型调用完订单查询工具后,返回结果里没有明确的“查询完成”信号,模型以为步骤没执行,又查了一遍。解决方法是调整工具返回值,加入“查询完成,共 N 条结果,如需更多请说明”之类的状态描述,给模型一个明确的完成感知。第二步是设置重复动作检测,连续两轮同样的工具 + 同样参数,直接强制终止。第三步,如果模型仍停不下来,检查是不是 max_steps 设置得太大了,让模型有了“慢慢逛”的容错空间。

5.2 问题二:JSON 解析失败与参数幻觉

表现:模型生成了一段不符合 JSON 格式的输出,导致解析抛异常;或者模型给工具传了一个根本不存在的参数值,比如把用户名字用在了订单号字段上。

排查方法:优先改用 function calling。在顶层模型支持 function calling 的情况下,不要自己去维护 JSON 格式提示词。参数幻觉是模型对工具“语义理解不足”的信号,常见原因是工具描述里没有写清楚“参数从哪里来”。比如订单查询工具的参数应该写“订单号,来自用户消息或上一轮工具返回,不是系统编号”,这样模型在决策时更有依据。最后一道防线是参数校验:工具执行前,先按 schema 做类型和范围检查,不合法就返回错误,而不是直接执行。

5.3 问题三:记忆污染——摘要越压越失真

表现:长对话跑到后面,模型开始乱答,因为滚动摘要把关键细节丢失了。摘要压缩过程天然有损,特别是早期的事实性信息,比如用户偏好、已确认的业务规则,很容易在摘要里被模糊化。

排查方法:把“事实抽取”和“对话摘要”分成两件事来做。对话摘要负责压缩叙事过程,事实抽取负责把关键信息抽成结构化条目,单独存到长期记忆里。比如“用户预算上限是一万”“这周数据已经核对过”这类内容,不该只在摘要里出现,而应该作为结构化事实存入 KV 存储。每轮结束时,MemoryManager 执行一次抽取任务,把新事实写进长期记忆,摘要只负责过渡性内容。

5.4 问题四:评估空转——指标和真实体验对不上

表现:离线评估指标都在涨,上线后用户反馈反而更差。排查方向通常是 golden set 覆盖不足。评估用例如果只有正常流程,Agent 在异常输入、歧义表达、工具失败场景下的能力完全测不出来。

排查方法:扩充对抗用例集,把“边界输入”“冲突信息”“工具超时”“审批拒绝”这些场景都加进去。同时,不只看最终结果对错,还要评估轨迹质量——把“是否绕路”“是否询问了不必要的信息”“是否在关键节点做了无风险动作”纳入评估维度。这需要你在 evaluator 里记录完整的 trace 数据,而不是只留一个最终答案。

影子模式是另一个好用的手段。新模型、新提示词不可信上线,先在影子模式跑上一段时间,把新旧两条轨迹摆在一起对比。带团队这么久,我最大的体会是 Agent 类项目和传统后端项目有一个明显差别:传统后端是“确定性优先”,Agent 是“概率性优先”。一个后端接口,输入相同输出必然相同;一个 Agent,即使完全相同的输入,两次运行也可能走出完全不同的路径。这决定了你不能用传统软件工程那套“一次写对,永远可复现”的思路来要求它,必须用“评估 + 约束 + 兜底”的组合拳来收敛不确定性。

如果你正准备在项目里落地 Agent,我最后再给一个具体建议:从第一个版本开始,就建立一个“决策记录”文档,把每次架构、模型、工具、评估相关的选择都记下来,包括备选方案、选择理由、事后效果。这比记录代码变更要有用得多——Agent 项目里,代码只是决策的最终形态,真正值钱的是决策背后的上下文。三个月后你回头看,会感谢自己留了这份记录。

另外一个小技巧:第一次做某个业务场景的 Agent 时,先别想着把七要素全部做齐,做齐了意味着你要同时解决模型调用、记忆管理、工具设计、控制安全、评估体系五条线的问题,任何一条线都会拖垮你。先做一个最小闭环——就是一个模型、两个工具、一个循环、一个终止条件,跑通之后再按七要素逐个加码。记住,Agent 的本质不是一次调用,而是一条会自己走路的链路,永远是先让它走起来,再让它走稳。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进