ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

AI Agent可靠性实战:从状态机到幂等工具的生产级指南

AI Agent可靠性实战:从状态机到幂等工具的生产级指南

说实话,现在聊AI Agent的人很多,但能把一个Agent从"Demo能跑"推到"生产环境不出事"的人,少之又少。我这两年带过好几个类似的项目——有工单自动分派的,有客户反馈分析的,也有内部数据查询助手——踩过的坑比写出来的代码还多。如果有人问我"AI Agent怎么做",我第一反应不是讲Prompt技巧,而是反问一句:你要的是一个会聊天的机器人,还是一个能干活、而且干活干不砸的自动化系统?这两者的差别,就是"看起来聪明的Agent"和"真正可靠的Agent"之间的分水岭。

我先说一个可能不太好听的观点:LLM本身是概率系统,同一句话问十遍,十遍的措辞和推理路径都可能有细微差别。让一个概率系统直接去操作真实业务、调用真实工具,不出错才是反常的。所以构建可靠Agent的关键,不是把模型调教得永不犯错——那不可能——而是通过系统架构、工具设计和评测闭环,把不确定性的影响范围压到最小,让错误发生之后可发现、可止损、可恢复。这篇文章就把我自己验证过的思路和踩过的坑完整梳理一遍。

1. 先搞清楚什么叫"可靠"的Agent

1.1 可靠性不是"回答正确",而是"行为可控"

很多人对Agent可靠性的理解是"它得答对问题"。但当你让Agent去做一个多步骤任务——比如"查一下上个月的退款订单,逐个核对金额,金额不对的标记出来,并把汇总表发到群里"——你会发现"答对"只是底线。真正的问题是:

  • 它有没有按正确的顺序调用工具?
  • 它调工具时给的参数是真的还是编的?
  • 中间某一步失败了,它是自己恢复还是直接摆烂?
  • 同一个任务跑两次,结果会不会不一样?
  • 它会不会陷入死循环,把API配额烧光?

我自己总结过一个公式:Agent可靠性 = 结果可复现 + 过程可解释 + 失败可恢复 + 影响可控制。"回答正确"只是结果可复现的一部分,甚至不是最重要的部分。你见过一个Agent在一个测试用例上答得很好,但中间把测试库的数据删了几条吗?我见过。从那以后我就把"行为可控"放在"答案准确"前面。

所以我给团队立的第一条规矩是:凡是Agent能触达的工具,必须能承受它犯错。这句话听起来抽象,翻译成人话就是——工具得幂等、有权限边界、有操作留痕。如果你做完这一步再回头看,会发现自己对Agent的整个设计思路都不一样了。

1.2 为什么Agent天然不可靠:概率系统的本质

要理解Agent为什么容易翻车,得先理解它内部的机制。现在的对话模型本质上是在做"下一个token的预测",它生成的内容只是概率分布的一次采样。你看到它输出一段像模像样的JSON,也许是模型真的理解了,也许是它沿着"最可能的续写路径"在走。两者在多数时候分不出来,但一旦走到概率分布的尾部,就会出现经典的幻觉。

幻觉在普通聊天里危害不大,但在Agent里就致命了。比如一个客户查询Agent需要调用"查订单"工具,模型发现上下文里没有订单号,按理说应该反问用户,但概率采样让它"猜测"了一个订单号填进去。这个猜出来的号可能根本不存在,也可能——更糟——碰巧是另一个真实订单的号。后者就是实打实的数据越权。

还有一层不稳定性来自"规划"。给模型一个复杂任务,第一遍它可能规划成三步,第二遍规划成五步,第三遍干脆跳过关键步骤。前者可以叫"灵活",后者就是"不可控"。所以你看,Agent不可靠根本不是bug,而是概率系统的固有属性。我们要做的不是消灭不确定性——那等于消灭LLM——而是用一套机制把它约束住。

1.3 从聊天机器人到软件系统:思维转变

我观察到一个普遍现象:很多人把Agent当成"更聪明的聊天机器人"来开发,写个system prompt,塞几个工具定义,然后就开始测试。这个思路在原型阶段没问题,但进入生产就是灾难。真正可靠的做法,是把Agent当成一个分布式软件系统来设计。聊天机器人是你问它答,Agent是它在真实环境里做一系列有副作用的操作。两者的工程复杂度不在一个量级。

我比较喜欢的一个类比是:把Agent想象成一个刚入职的实习生。这个实习生聪明、学习能力强,但你不会第一天就给他生产库的写权限,不会让他随便给客户打电话,不会让他连续工作十个小时不汇报。你会给他一套SOP、一个有限的操作范围、一个上级审批机制和一套日报体系。可靠的Agent也需要这些:SOP就是状态机和编排逻辑,操作范围就是工具白名单和权限边界,审批就是关键动作的人工确认,日报就是事件日志和可观测性。

从这个思维转变出发,后面的每一块设计就都有了主线。下面我按架构、工具、参数、评测四个层面展开讲,这四块是我认为缺一不可的。

2. 架构层面:把不确定性关进笼子

2.1 用状态机约束规划,而不是让模型自由发挥

第一次做Agent项目的时候,我也走过"让模型全权规划"的路线——给它一个目标,让它自己决定调什么工具、按什么顺序。跑起来确实惊艳,但稳定性一塌糊涂。后来我改成"有限状态机 + 模型在指定节点做决策"的模式,可靠性一下就上去了。

核心思路是:把整个任务流程拆成固定的几个状态,模型只能在当前状态允许的动作集合里选择。举个实际例子——某工单自动处理Agent,我只让它在这几个状态之间流转:

  • idle(空闲)→ 接收任务,进入planning
  • planning(规划)→ 模型产出执行计划,进入executing
  • executing(执行)→ 按计划调用工具,每步完成后校验结果,全部完成进入reviewing
  • reviewing(复核)→ 模型复核执行结果是否符合初始目标,符合则done,不符合则回到executing或转人工wait_human
  • failed(失败)→ 连续重试无果,或达到步数上限,挂起转人工

关键在于:每个状态只暴露该状态下允许的工具。比如planning状态里,模型只能看到"查询订单""获取用户信息"这类只读工具,绝不能看到"删除订单""批量修改"这类高危操作。这样就算模型在规划阶段产生幻觉,也因为工具不在视野里而无法执行。

这样做的好处非常明显。第一,出错路径从"无限多"变成"有限几种",每一段都容易检测和兜底。第二,模型需要做的决策变简单了——它不需要从零开始规划整个任务,只需要在给定状态下选下一步动作,决策空间小,出错率自然下降。第三,事件日志有天然的结构,每一条记录都带状态字段,排查问题非常方便。

注意:不要一上来就设计十几个状态的复杂状态机。我建议从4到6个状态起步,跑通后再根据实际报错逐步细化。状态太多会让大模型也开始混乱,得不偿失。

2.2 任务分解的标准姿势:子Agent模式

状态机约束了"怎么走",但还没解决"走多远的任务怎么拆"。当一个任务本身太复杂,比如"分析上季度销售数据并生成汇报PPT",让一个Agent从头到尾做,中间任何一步跑偏都会殃及全局。我的做法是改成子Agent模式:主Agent只负责拆解任务和汇总结果,具体每一步交给专门的子Agent处理。

举个例子。某客户反馈分析Agent,主Agent收到一个任务"把本周差评归类并给出处理建议",它会拆成三个子任务:

  1. 调用"拉取反馈"子Agent,从数据库取原始数据;
  2. 调用"文本分类"子Agent,对差评做意图归类;
  3. 调用"建议生成"子Agent,基于归类结果生成处理建议。

每个子Agent有各自独立的prompt、工具白名单和输出格式。主Agent只负责拿到子Agent的结果进行组装。这么做还有一个额外好处——子Agent可以被单独复用和测试。我经常在项目里把常用的子Agent固化成内部组件,新需求来了就像搭积木一样组合,省了不少功夫。

当然,子Agent模式不是银弹。子任务之间的数据传递如果设计不好,就会在边界上出错——比如子Agent A的输出格式变了,子Agent B解析不了。所以我给每个子Agent都定了严格的输入输出Schema,并且要求输出必须是严格JSON,这一步后续在工具设计部分会详细讲。

2.3 记忆分层:短期任务上下文、长期知识库、运行时工作区

Agent不可靠的另一个大来源是"记忆"混乱。LLM的上下文窗口是有限的,把所有历史都塞进去,很快就把窗口撑爆,然后模型就开始遗忘早期指令。我的方案是把记忆分成三层,各管各的,不混用。

  • 短期任务上下文:就是当前任务的对话历史,放在会话窗口里。这一层用滚动窗口管理,保留最近的N轮关键对话和工具结果,更早的内容要么丢弃、要么压缩成摘要。不要无脑把所有历史都留着。
  • 长期知识库:用户偏好、历史事实、领域规则这类跨任务的信息,放向量库或KV存储里。需要时用检索的方式显式取回,而不是靠上下文里飘着。做知识库型Agent时,这层基本是必选项。
  • 运行时工作区:任务执行过程中产生的中间结果、临时文件、上次查询的结果集,放到一个专门的workspace里。后续步骤要引用时,通过"读取工作区"工具显式获取,而不是让模型从对话历史里猜。

这里有一个我踩过很深的坑:早期项目里,我让Agent在对话历史里查找上一步生成的订单摘要,结果它经常找不到,因为它"回头看"的能力远比我们想象的弱。改成工作区模式之后,上一步的结果显式写入一个结构化文件,下一步直接读取,准确率一下子从八成多拉到了百分之九十九。这件事给我的启发是:别指望模型在长上下文里做精确检索,把关键信息显式放在它面前,比训练它"翻聊天记录"靠谱得多。

3. 工具层才是可靠性的主战场

3.1 函数调用的参数幻觉怎么治:Schema校验 + 重试

Agent和普通聊天最本质的区别是能调用工具。而工具调用的核心风险,就是我前面提到的参数幻觉——模型凭空捏造参数值。比如你让它调用"查运费"工具,参数要求传一个order_id,它找不到这个值的时候,有概率会自己编一个。这个问题的解法分为三层。

第一层是工具定义约束。每个工具的入参必须有完整JSON Schema,并且把必填项标清楚。模型在生成函数调用时会参照Schema,Schema越清楚,瞎填的概率越低。这里有个小提示:参数描述也要写详细,比如"order_id:订单编号,来自订单查询结果字段order_id,格式为ORD-后跟8位数字",这种说明比只写"订单编号"好用得多。

第二层是返回校验。模型返回的函数调用参数,不能直接进工具,必须先过一层Schema校验。我用的是JSON Schema的validator,在Python和Node生态里都有现成库。校验不通过,就向模型返回一个明确的错误消息,让它重新生成。这相当于给模型加了一道"语法检查",很多低级错误在这一步就被拦住了。

第三层是语义校验。Schema只能查格式,查不了"订单号是不是真的存在"。这一层只能靠工具本身执行结果来反馈。工具查无此单时,要返回一个结构化错误,比如{"error": "ORDER_NOT_FOUND", "detail": "订单号ORD-12345678不存在"},而不是返回一个空结果。模型看到明确的错误码,才能知道"这个参数是错的",进而追问用户或者换一个参数。

三层加起来的组合效果很直观:格式层拦住乱填的,执行层拦住编造的,剩下的错误至少是可解释、可追踪的。这就把参数幻觉从"静默发生、看不见"变成了"显式报错、可纠正"。

3.2 所有工具都必须幂等:一次调用和十次调用结果一样

可靠性里有一条铁律:任何会被Agent反复调用的工具,必须幂等。幂等的意思是,无论你调用一次还是十次,对系统造成的影响都是一样的。这条规则对于读操作天然成立,麻烦的是写操作。

举一个真实踩过的坑。某项目里有一个"创建退款申请"工具,Agent在执行业务时网络超时了,框架自动重试了两次。结果两次重试都进了工具,于是生产系统里出现了两笔一模一样的退款申请。钱多退了一倍,客服同事加班两天才处理完。这个问题的根子就在于"创建申请"这个操作不幂等——每次调用都会新建一条记录。

修复方案是引入幂等键(Idempotency Key)。调用方(Agent框架)在调用任何写操作前,生成一个全局唯一的request_id,把它作为必填参数传入。工具服务端在收到request_id时,先查一下这个ID是否处理过,处理过就直接返回第一次的结果,不再重复创建。就这么个小小的机制,把写操作的重复风险基本清零了。

我在给团队做工具开发规范时,会把工具的副作用写进文档里。凡是有副作用的工具,必须满足两个条件之一:要么实现幂等键,要么增加"二次确认"参数——即高危操作必须额外传入一个confirm: true,没有这个参数直接拒绝执行。这个confirm机制在后来的很多业务场景里都救过场。

3.3 防失控设计:超时、次数上限、沙箱执行

Agent跑着跑着失控,是生产环境最常见的噩梦。失控的形式很多:陷入工具调用死循环、单步工具耗时过长、模型在一个错误上反复重试、甚至模型被诱导去执行危险命令。我在Agent框架里强制加了四道保险:

  • 单步超时:每个工具调用设置超时时间,默认15秒,超时即返回TOOL_TIMEOUT错误。这个值要根据业务调整,但一定要有,因为外部API的P99延迟永远比你预期的高。
  • 步数上限:整个任务设置最大步数,我一般默认20步。达到上限还没完成,Agent自动进入wait_human状态,绝不允许无限跑下去。这是防死循环的最后一堵墙。
  • Token预算:每个任务设置Token上限,达到上限就熔断。这个防止的是"上下文爆掉之后模型开始乱来"的场景,也顺便控制成本。
  • 沙箱执行:对于需要跑代码的Agent,所有代码在隔离容器里执行,容器没有生产网络权限,只有最小的文件系统。别让Agent直接在你本机或生产环境上执行任意代码,我不是在开玩笑。

这四道保险不是限制Agent的灵活性,而是给失控行为设置止损点。一个可靠的Agent不是永远不会出问题,而是出了问题能立刻被拦住,并且现场信息足够让你定位根因。

4. 关键参数与提示词设计:细节决定成败

4.1 温度、Top-P、Max Tokens怎么设置才不翻车

不少人对采样参数这件事比较随意,但在我这里,参数设置是可靠性的第一道闸门。模型默认行为不是为Agent场景设计的,不改参数就上生产,等于裸奔。

  • temperature:可靠性优先的场景设0,最多不超过0.3。Temperature控制随机性,0就是每次都取概率最高的输出,最大程度保证"同一个任务同一结果"。有些人担心设0会让Agent变笨,我的实际经验是:在工具调用和结构化输出场景,设0的准确率明显更高,生成质量并没有下降。如果确实需要一点多样性,比如创意文案生成,那另说。但Agent干活,老实点最好。
  • top_p:建议0.9到1.0之间,一般不用动。它和temperature共同作用,如果你调了temperature,top_p保持默认就好。不要两个一起激进地调,会让输出变得非常不稳定。
  • max_tokens:这个值要按输出类型来算。如果要求模型输出一个JSON结构,你得估算这个JSON最长大概多少token,然后留出30%以上的余量。给太小,JSON会被截断——我见过太多因为JSON被截断导致解析失败、整个任务卡死的例子。给太大,模型可能在没有明确任务时"凑字",生成一堆无用内容。所以这个值要基于实际输出格式算,而不是随手填一个2048。
  • 停止符(stop):如果你要求模型输出严格JSON,可以把}作为停止符之一,或者用模型的JSON模式。很多模型的JSON模式会把输出强制约束为合法JSON,强烈建议开启,能直接砍掉一大半格式错误。

顺带说一句,模型选型也很影响可靠性。以我当时测过的经验,在同样的工具调用任务上,一个中等规模但专门针对工具调用微调的模型,正确率可能比一个大而全的通用模型还高。选模型的时候别只看跑分,拿你自己的工具定义去测"函数调用正确率",这个指标比什么都实在。

4.2 工具描述怎么写,模型才不会瞎调用

工具描述是Prompt工程里和技术文档一样的存在——文档写不好,用户就瞎操作;工具描述写不好,模型就瞎调用。我见过太多工具定义只有一句话:"获取订单信息。"然后模型就在不确定参数的时候硬调用,或者干脆把其他工具的参数塞进来。

我的工具描述模板长这样:

工具名:get_order 用途:根据订单ID查询订单详情。 适用场景:用户询问订单状态、金额、物流信息,或后续操作需要订单详情时。 不适用场景:查询用户信息、查询商品信息,请分别调用get_user、get_product。 参数说明: - order_id(必填):订单编号,格式ORD-8位数字,只能来自历史消息或上游工具输出的order_id字段。 - include_items(可选):是否返回订单商品列表,默认false。 返回说明:返回订单基本信息;若订单不存在,返回错误码ORDER_NOT_FOUND。

你发现没有,这里面写清了"什么时候用""什么时候不用"和"参数从哪来"。模型看到这种描述,误调用的概率大幅下降。尤其是"参数从哪来"这一条,直接告诉模型参数值的来源,是从上下文里找还是从其他工具的输出里取,这能挡住很大一部分参数幻觉。

另外,工具数量也要克制。每增加一个工具,模型的选择空间就变大一次,误用概率不是线性上升而是指数上升。我通常建议一个Agent暴露的工具控制在8个以内,超过这个数就该考虑拆成多个子Agent了。工具多不是好事,模型也是会被选择困难症击垮的。

4.3 几个实测有效的Prompt设计技巧

关于Agent的System Prompt,我不想给一个万能模板,因为不同场景差异太大。但有几个原则是我在所有项目里反复验证过有效的:

  • 明确输出格式:如果你需要模型输出JSON,在prompt里给出具体的JSON示例,并注明"只输出JSON,不要任何解释文字"。给示例比给一万字描述都管用。
  • 拒绝猜测参数:这是最核心的一条。prompt里写死:"当工具参数缺少必要信息时,必须调用ask_user工具向用户确认,严禁猜测或编造参数值。猜参数是不可接受的行为。"这句话配合参数校验,是防幻觉的双保险。
  • 分步执行的要求:在planning状态要求模型"一次只规划一步,每步都必须基于上一步的实际输出,不得跳步"。这会降低模型一次性规划过多然后中途崩盘的概率。
  • 失败恢复指引:prompt里写清楚"工具调用失败时,先看错误码。ORDER_NOT_FOUND说明订单号有误,应重新确认订单号;NETWORK_TIMEOUT说明网络问题,可以重试一次。"这样模型在出错时知道怎么恢复,而不是瞎试。

再有就是少样本示例(few-shot)。我在Agent的prompt里会放2到3个完整的"用户请求→Agent规划→工具调用→结果整合"示例,每个示例都展示正确的行为模式。模型看到这种完整示例如同一本操作手册,要比你反复强调规则管用得多。示例不用多,质量要高,而且必须贴近真实业务。

5. 从"能跑"到"可靠":评测与监控是最后一公里

5.1 先有一套评测集,再谈优化

很多团队做Agent是"写prompt、跑两个case、看着行、上线"。这么做在娱乐场景没问题,但在业务场景就是赌博。我自己现在的流程是:任何Agent上线前,必须先建评测集。

评测集从哪里来?从真实数据里来。把历史日志里的真实用户请求抽出来,按业务场景归类,整理出50到100条。每条样本标注好预期结果:应该调用哪些工具、按什么顺序调、最终输出要包含什么字段。这个标注工作不轻松,但它是可靠性的地基。

有了评测集,后面所有优化都有依据。我每次改prompt、换模型、调参数,都会跑一遍评测集,看整体通过率。通过率低于95%就不上线,这是硬指标。

除了通过率,我还会看四个辅助指标:

  • 工具调用准确率:模型调用的工具和参数是否正确。
  • 无效调用率:调了不该调的工具、或参数不满足Schema校验的比例。
  • 平均步数:完成任务平均要几步,步数越多,失控概率越大。
  • 人工介入率:有多少任务最终需要转人工。这个指标最重要,直接反映Agent的自主完成能力。

评测集不是一次性资产。后续每次线上出现bad case,都把它沉淀进评测集。这样评测集会越来越厚,Agent也会越来越稳。这是一个典型的"你不可能一次做对,但你可以靠反馈逼近正确"的过程。

5.2 日志与可观测性:没有痕迹就无法排查

Agent一出问题,最痛苦的事情是什么?是不知道它为什么出错。聊天机器人出错了,你拷一段对话就行;Agent出错,你面对的可能是一长串工具调用记录、状态变迁、中间结果。没有结构化的日志体系,排查起来就是灾难。

我用的是一套结构化事件流,每次Agent运行都会产生一串事件:

  • agent_started:记录任务目标、用户ID、trace_id
  • plan_created:记录模型生成的执行计划
  • tool_called:记录调用的工具名、入参、耗时、返回摘要
  • state_changed:记录状态机的状态跳转
  • context_trimmed:记录上下文裁剪发生时丢了哪些信息
  • agent_finished:记录最终结果、总步数、总Token数、是否转人工

每一个事件都带上trace_id,这样一次完整任务从头到尾都可以串联起来。出了bad case,我第一件事就是打开trace,像看监控回放一样把Agent每一步的行为看一遍。这个习惯帮我省下的排查时间,保守说也有好几个星期。

关于日志,还有一个经验:除了记录模型输出,一定要记录模型的prompt——尤其是系统prompt和用户消息拼装后的完整内容。很多问题其实是prompt模板的bug,不记录完整prompt根本看不出来。有些时候你以为模型"变笨了",查完日志才发现是prompt里一个变量渲染错了。

5.3 灰度发布与回滚:Agent上线也要走发布流程

Agent本质上是代码+prompt+模型的混合体,任何一个部分变了,行为都可能大变。所以Agent的版本管理不能像写死代码那样随意。我这边有一套上线流程,流程不复杂,但每条都很必要:

  • 先跑评测集:新版本在评测集上通过率不得低于线上版本,否则不许上。
  • 灰度放量:先让新版本承接5%到10%的真实流量,跑1到3天,看人工介入率和bad case数量。这两个指标不比线上差,再逐步放量。这个思路和做后端服务的灰度发布完全一样——只是很多人写Agent的时候忘了这茬。
  • 可回滚:prompt和模型版本必须纳入版本管理,一条命令能回滚到上一版。我见过有人直接在生产环境里手改prompt,结果改完效果不好,却怎么都想不起来原来那一版写的是什么。用Git管理prompt真的不丢人。

在灰度期间,我特别关注一个指标:同一批灰度流量里,新版本和旧版本在同样输入下的人工介入率差异。如果新版本明显更高,立刻回滚。这个指标比"回答质量"更能反映生产环境的真实表现。

6. 常见问题排查速查表(实战记录)

6.1 Agent死循环:症状、定位、解法

我在不止一个项目里撞上过死循环。最典型的一次,Agent调用了一个"解析发票"工具,结果发票图片本身模糊,工具返回PARSE_FAILED。正常情况应该转人工,但Agent收到错误码之后,居然换了个参数又重新调用,连调了十几次,把API配额度烧掉一大半才被步数上限拦住。

排查时打开trace,一眼就看到tool_called事件里连续十几条同样的调用记录,间隔时间都差不多。这种重复模式在事件流里非常显眼。根因是prompt里没有写清楚"解析失败后应该怎么办",模型只能自己脑补策略。

解法有三个层面的配合:一是全局步数上限必须设置,这是保命绳;二是给关键错误码配置重试策略,比如PARSE_FAILED直接转人工,不进入重试逻辑;三是在prompt里写明失败处理路径。三者配合下来,我后面再没遇到过这种烧配额的死循环。

6.2 工具调用参数幻觉:症状、定位、解法

参数幻觉是Agent所有问题里最隐蔽的,因为它不会让程序报错,只会让业务逻辑悄悄出错。某数据查询Agent曾经在用户问"最近一个月销售额"时,调用日期工具传了一个去年同期的日期。返回结果四舍五入也看不出大问题,直到月末对账才发现数据对不上。

定位方式是在tool_called事件里比对传入参数和用户原始请求。如果发现参数值在上下文里根本没有来源,基本可以断定是幻觉。我还总结过一个规律:模型在"找不到参数又不愿意承认"的时候,特别容易编造一个看起来合理的值。所以关键还是防患于未然——工具描述里写明参数来源,schema校验强制必填,再加上prompt里对猜测行为的明令禁止。

另外,给模型的prompt里加一条"如果找不到可靠数据源,直接告诉用户信息不足,而不是给一个确定的答案"。这条看着简单,实际效果非常明显,能让编造概率跌一个量级。

6.3 上下文爆掉:症状、定位、解法

上下文窗口一旦塞满,模型会出现典型的"老年痴呆"症状:忘记早期的用户要求,重复执行已完成的操作,甚至把不同子任务的信息混在一起。我遇到过最夸张的一次,Agent在长对话里把两个不同客户的订单编号搞混了,生成了完全错误的处理建议。

排查时看context_trimmed事件,如果发现系统频繁裁剪原文,那说明上下文压力早就超了。根本解法还是我在记忆分层里说的那套:短期上下文只留关键信息,中间结果和工作记录单独放工作区,历史数据上卷成摘要。有些场景还可以主动设置阈值——上下文使用率超过80%就强制触发一次摘要压缩,而不是等窗口满了再被动裁剪。

6.4 任务中途漂移:症状、定位、解法

任务漂移的表现是:用户想要A,Agent做到中间开始做B,最后交付C。我印象很深的一次,某用户想让Agent"查找上个月的退款订单并统计总金额",Agent查完订单之后,可能是模型觉得"光统计太简单了",自顾自地附加了一份"退款原因分析报告"。看着很贴心,但用户其实只需要那一个数字,而且这份多出来的分析还拖延了响应时间。

定位方法就是回放plan和每步tool调用的结果,对照最初的用户目标。我在解法上用了两个办法:一是planning阶段生成的计划必须写清楚和用户目标之间的对应关系,后续每完成一步都校验结果是否仍然对齐目标;二是如果模型要在计划外添加额外步骤,必须先调用ask_user征得同意。这个规则写进prompt之后,任务漂移频率明显下降。

最后再分享一个我个人的习惯,也不算技术,纯粹是经历过太多凌晨排查之后形成的工作方法:我会把Agent每次决策的关键依据记录下来,形成结构化的操作日志。这听起来像可观测性那件事,但比日志多一步——我会在每次上线前自己扮演一次"用户",把最刁钻、最边界的情况跑一遍,亲眼看看Agent的决策过程。这种事前体验比任何指标都直接,它能在第一时间告诉你,这个Agent到底能不能放心交给真实用户。

我一直觉得,可靠的Agent不是某一个天才prompt写出来的,也不是某一个"最强模型"撑出来的,而是架构约束、工具设计、参数调优、评测监控四件事一起兜出来的。如果你正在做一个Agent项目,我真心建议你从一个小而固定的业务场景入手,把有限状态机、幂等工具和评测集这三样先搭起来,再逐步放开模型的自由度。顺序对了,踩坑是可控的;顺序反了,你会发现自己每天都在救火。这个行业的乐趣就在于——把最不可控的东西,变成最可控的生产力。

RELATED READING

延伸阅读

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