这个Agent现在还能加新工具吗? 能,但谁都不敢动。 为什么? 因为没人知道改完其中某一段,哪条链路会突然挂掉。上次我只是给一个工具的返回结果加了三个字段,另一个流程的动作就全乱了。
这不是我第一次听到类似的描述。最近这段时间,从技术社区到朋友公司内部,都能感受到同一个信号:Agent项目过了最初的新鲜期之后,普遍进入了同一个阶段——功能还在跑,但代码已经变成一片没人敢踩的沼泽。调用链越拉越长,prompt越叠越厚,工具越挂越多,真正决定行为逻辑的东西散落在各个模块里,没人能说清。
在我看,Agent真正的问题不是“不够聪明”,而是“不可维护”。而“不可维护”这件事,正在催生一门很多人低估的生意——帮别人清理Agent堆积起来的技术债。这个话题我不想只停留在猎奇层面。我想聊清楚三件事:Agent项目到底是怎么一步步变成“屎山”的;为什么清理这堆东西在这个时间点特别有价值;以及如果你手头就有一个跑得费劲的Agent系统,可以从哪下手。
1. Agent项目跑着跑着,就成了没人敢碰的“屎山”
1.1 症状不是“跑不动”,而是“改不动”
一个Agent系统如果已经运行过一段时间,你不需要先去看代码。问三个问题,就能判断它现在处于什么状态。
第一个问题:给Agent新增一个普通工具,通常要改几个文件、改多少条prompt、影响几条已有链路?如果答案是需要小心地翻半天代码,再顺手把好几段prompt都改一遍,那说明它已经没有清晰的边界了。
第二个问题:某次模型升级之后,有没有哪个流程的输出格式悄悄变了,但没有被任何人及时发现?这个问题问到点上,大多数人都会愣一下。因为Agent的行为不完全由代码决定,模型一换,很多原本被prompt压住的“自由发挥”就会重新冒出来。
第三个问题:当一次Agent执行中途失败时,团队能不能精确回答“它已经执行到哪一步、调用过哪几个工具、用了多少上下文、失败在哪一层”?如果报错之后只能靠猜,那说明观测能力基本为零。
最近看到有人在搜那个“agent execution provider did not respond in time”和“agent execution terminated due to error”的报错。这些报错本身并不算罕见,真正麻烦的是团队面对它们的时候没有后续手段:不知道失败是偶发还是必然,是上下文太长还是工具无响应,是模型服务不稳定还是Agent自己的设计缺陷。没有这些信息,大家就只能一遍遍重试,赌运气。
1.2 为什么Agent比传统老项目更容易积累垃圾
传统Web项目和Agent项目最大的区别,不是语言或框架,而是“动作主体”变了。传统项目里,最终行为基本由代码决定:输入进函数,函数按逻辑返回结果,你可以在任意位置打断点,也可以断言输出的每一个字段。Agent项目里,很多行为由模型决定,而模型对同样的输入,可能给出不同的解释和执行路径。模型版本升级、温度参数调高、上下文内容变化,都会让行为产生偏移。
这会带来一个很实际的结果:传统项目里的“重构”手段,放到Agent项目里不一定成立。
传统项目里,你可以把一段重复代码抽成公共函数,只要输入输出不变,外部行为基本不变。但在Agent项目里,把一段prompt从A模块挪到B模块,模型行为可能就完全变了。因为prompt的位置、上下文的前后顺序、甚至分隔符,都可能改变模型对任务的优先级理解。
另一个原因更加隐蔽:Agent开发的“快捷方式”特别容易变成债务。功能表现不好,开发者的第一反应往往是——给prompt加一句约束、给Agent多挂一个工具、在返回结果里再做一层修正。这些方式短期见效快,几乎没有什么接入门槛,于是它们不断在项目里累积。三个月之后,你会得到一份超长prompt、十几个注册了但很少被调用的工具、四五层互相叠加的输出修正逻辑。每一层单看都有道理,叠在一起就没人敢动了。
所以,给Agent清理“屎山”这件事,本质不是把代码写得好看,而是给一团没有边界的行为重新画清楚边界。
2. 先弄清楚Agent“屎山”到底是哪几层脏
想把Agent清理好,第一步不是动手重构,而是先承认“脏”不是一种模糊感觉。它通常体现在三个具体技术层上。
2.1 第一层:Prompt 越长,行为越玄学
很多Agent项目的问题,写在看起来最无害的地方——prompt。最典型的状态是:某个Agent的system prompt已经积累到几千字,里面包含角色设定、业务规则、输出格式、若干条“注意”“非常重要”“禁止”之类的强调,甚至还有上一位同事加的“当不确定时,请按以下方式处理”的兜底说明。
这种超长prompt最大的麻烦,不是token成本,而是不可观测。
你删掉其中一句提醒,模型行为不一定变差,可能反而变好了;你增加一句新规则,原本稳定的输出格式反而乱了。因为模型对指令的理解不是线性叠加的,它会把整个上下文组合出语义优先级。你根本不知道哪条规则真正生效,哪条规则已经被后面的内容覆盖,哪条规则在多个Agent之间共享着——改A的时候,B和C也被动变了。
很多团队在不知不觉中把prompt写成了“项目文档”:所有历史规则都保留,所有尝试过的约束都堆上去。这个习惯放在代码里勉强能接受,放在prompt里,就是在制造不可维护性。真正需要的是把prompt做分层:角色定义、场景说明、功能约束、输出格式,每一层独立成块,每一块只负责一件事,任何改动都配得上一次回归验证。
2.2 第二层:工具、记忆、编排逻辑全搅在一起
Agent系统通常由几个不同能力构成:会调用外部接口的工具、会读写历史信息的记忆、决定调用顺序和结束条件的编排逻辑。理论上它们是三个独立模块。但实际项目里,我经常看到它们被写成了一团:
- 工具函数里塞了业务规则,同一个工具在不同链路里的返回格式还不统一;
- 记忆被无差别写入,大量临时信息、中间思考、失败尝试都留在了记忆库里,后续Agent再检索时,很容易被脏数据带偏;
- 编排逻辑没有写在代码里,而是用“请一步一步来”“重复调用工具直到成功”这种话写在prompt里,让模型自己决定什么时候继续、什么时候该停。
这个结构最大的问题,是没法单独调试。链路出问题时,你分不清是模型理解错了,还是工具实现错了,还是记忆被污染了。
这也能解释为什么现在社区里越来越多人在讨论“harness和agent区别”“agent框架与编排”“agent memory”——因为大家慢慢意识到,Agent不能只靠一个模型加一堆工具就完事,它需要清晰的边界来承载可维护性。把复杂逻辑全交给模型自由发挥,短期很爽,长期就是债。
2.3 第三层:没有观测,就没有定位能力
传统系统出了问题,你可以看日志、看链路追踪、看指标。而很多Agent项目,在设计时完全没有留观测点。
没有观测的Agent系统,一次执行就是一次黑盒:你只看得到最终结果,看不见中间过程。它到底调用过哪些工具、每一步用的什么输入、走了哪条分支、在哪一步触发了终止,全都无从得知。
更麻烦的是,Agent有随机性。同一段输入,第一次跑可能调用了工具A,第二次跑可能选择了工具B,第三次跑干脆没走工具直接输出。如果你没有把每次执行的关键字段记录下来,出了问题想复现,往往复现不出来。这也不是模型在“抽风”,而是Agent本来就是一个概率性系统。概率性系统必须靠日志、指标、样本回放来管理,不能靠人肉记忆。
所以我会把“观测”放在所有清理动作之前。没有观测手段,任何重构都是盲人摸象;有了观测手段,你才能知道“屎山”具体堵在哪一层。
3. 清一条Agent链路,本质上是在清边界
很多团队聊Agent治理时,第一反应是换模型、重写prompt、换框架。这个思路很危险。一个已经在线上跑的Agent系统,真正的清理目标不是“重写得更好看”,而是“清出边界,让每个部分可以被单独解释、单独测试、单独替换”。
3.1 别急着重构,先盘点真实调用链
如果让我去处理一个难维护的Agent项目,第一步不是改代码,而是拿着代码、日志、配置,把系统里所有Agent链路画一遍。画的时候重点写清楚:从用户输入到最终输出,会经过哪些模块;哪些工具可能在什么时候被调用;记忆库在什么时候被读取、在什么时候被写入;哪些分支是代码写死的,哪些是让模型自由发挥的。
画完之后,通常会浮现出几个特别扎眼的事实:
- 很多工具注册了,但从来没有被调用过;
- 有几条prompt规则之间,根本是互相矛盾的;
- 某个链路里有一段已经被废弃的重试逻辑,但没有人删掉,还在继续消耗token;
- 多个Agent共享同一个工具,但每个Agent对这个工具返回格式的解析方式都不一样。
这些信息不盘点,是不会浮出水面的。清理“屎山”的第一步永远是“搞清楚现状”,而不是“我觉得这里应该那样改”。
3.2 工具层、记忆层、编排层要分开
在盘点完现状之后,真正值得动刀的地方,不是某个prompt写得好不好,而是模块边界。一个可维护的Agent系统,在结构上应该尽量把这几件事拆开:
- 工具层:只负责执行单个动作,返回值必须是结构化、可预期的,不感知业务流程;
- 记忆层:只负责数据的存取和检索,不决定任务走向,定期清理过期和低质量记忆;
- 编排层:决定调用顺序、终止条件、异常处理,这部分是工程代码,最好放在代码和配置里,不要丢给模型;
- Prompt层:定义Agent的角色、表达方式和约束,复杂业务逻辑尽可能不塞进prompt。
这样改完之后,最大的变化是:当工具返回异常时,你只需要看工具层;当上下文被污染时,你只需要检查记忆写入逻辑;当Agent绕过了某个必要步骤,你去查编排层和prompt边界。问题可定位,比能力优化要优先得多。
这也是为什么我认为,如果只是为了让Agent“更会聊天”“更像真人”,可能不需要把工程结构搞这么复杂;但一旦要把Agent放到真实业务里用——对接客户、处理订单、查询内部系统——边界和分层就是必修课。
3.3 Skill、Tool、MCP:不是越丰富越好
最近关于Agent能力的讨论很多,很多人开始研究Skill、Tool、MCP这些概念。我的理解是这样的:
Tool是Agent可以直接调用并获取结构化结果的最小执行单元;Skill可以理解为带有流程的复用能力,把几段常见操作组合成特定技能;MCP更多是面向工具接入的标准化方式,让Agent按统一协议发现并调用外部能力。
注意,不要因为市场上的概念和框架很多,就把所有东西都往Agent里堆。一个上下文窗口有限、记忆检索能力一般的Agent,如果挂了50个工具、注册了20个Skill,实际表现大概率不会更好,只会更糟:每次决策时搜索空间变大,误选工具的概率上升,运行成本成倍增加。
清理这类问题的常规思路,是先做减法。把Agent只暴露给当前任务真正需要的那几个工具,把偶尔才用的能力挪到“按需加载”。这不是技术倒退,而是给Agent降噪。真正让Agent变强的,不是工具数量,而是在有限上下文里,把最合适的工具和信息送到模型面前。
4. 为什么“清理Agent屎山”正在变成一门生意
有人会对“生意”两个字感到好奇:清理技术债不是每个团队该自己做的事吗,为什么会有人愿意花钱请别人来做?站在这个时间点,我认为有几个因素正好凑到了一起。
4.1 Agent项目集体进入维护期,这是时间窗口
从2023年起,不少公司已经做过一到两轮Agent尝试。早期那些跑通demo、做出效果、完成内部汇报的Agent,现在大多已经变成了“半成品系统”:功能上了、效果还行、但没人愿意长期接手。当初负责调研和Demo的人可能已经换了项目,新来的团队一看代码,复杂度超出预期,文档基本没有,测试基本缺失,会陷入“看得懂局部、看不懂整体”的处境。
与此同时,企业主很少接受“推倒重来”的方案。因为Agent确实在跑,业务也确实在用,只是它变得越来越难加新东西。老板的诉求通常很朴素:别崩、能继续加功能、出了问题能快速定位。这个诉求天然指向的不是“重写”,而是“清理”“整理”“加固”。
4.2 企业需要的是“不破坏现状的可维护性”
如果去问一家正在被Agent“屎山”困扰的公司,它需要的到底是什么,答案往往不是“一个更先进的Agent架构”,而是“现状还能继续用,但希望它不要变成定时炸弹”。
这意味着,清理业务的交付物和“开发一个新Agent”完全不是一回事。
开发新Agent的时候,你可以自由选择框架、模型、prompt结构,怎么顺手怎么来。清理“屎山”的时候,大多数情况下不允许大拆大建。你要在一个已经跑起来的系统上做切片手术:补日志、抽公共逻辑、统一返回格式、去掉互相矛盾的prompt规则、接入测试样例。每一步都必须小心,不能今天改完,明天流程就废了。
这种“不破坏现状的可维护性”,就是典型的存量服务。而存量服务通常是被普通开发者嫌弃的:改别人的乱代码、处理历史包袱、不能大刀阔斧。但换个角度想,麻烦和稀缺,正是服务定价的空间。
4.3 这门生意的交付物到底是什么
结合我看到的情况,我给这类业务梳理出几种典型交付物:
- 诊断报告:不是泛泛而谈的“你代码写得很乱”,而是把真实调用链画出来、标出高风险节点、指出哪些链条已经没有人维护、哪些prompt规则互相冲突;
- 最小改造:先处理最影响线上稳定性的几个问题,不做全面重写;
- 维护规范:形成一整套prompt变更流程、工具接入标准、日志字段要求和回归用例集,让客户自己的工程师以后按标准执行;
- 基础培训:教客户团队如何看Agent日志、如何排查一次失败链路、如何在不破坏其他链路的情况下新增工具。
收费方式也很多样:有一次性的诊断费,有按几个月到稳定期的固定项目费,也有每周来看一次系统状态的订阅服务。但核心都一样:公司买到的不只是一段代码,而是一个“以后不用再提心吊胆改Agent”的能力。
5. 一套可复用的Agent清理路径
前面几章更像是在解释“是什么”和“为什么”,这一章写怎么做。如果你手头也有一个已经不太能动的Agent系统,或者你未来可能接到类似的清理需求,可以按下面这条路径走。
5.1 第一步:建立基线,先有日志再谈清账
清理的前提是掌握信息。所以无论系统现在多乱,都先补最基础的一层:结构化日志。
日志字段需要能够回答这些基础问题:这次请求是谁发起的、用了哪个Agent、调了哪个模型、用了哪个prompt模板、上下文有多长、调用了哪些工具、每个工具的输入输出是什么、最终结果是什么、有没有失败、失败在哪一步。
一个常见的记录结构可以长这样:
{ "request_id": "req_20250101_abcd", "agent_id": "order_intake", "session_id": "sess_123", "model": "gpt-4o-mini", "prompt_template": "order_intake_v3", "context": { "system_prompt_chars": 1800, "history_count": 12, "retrieved_chunks": 4 }, "tool_calls": [ { "tool_name": "query_order", "input": {"order_id": "A1001"}, "output_summary": "found 1 order, status=paid", "duration_ms": 230, "ok": true } ], "final_output": "...", "error": null, "total_tokens": 1820, "cost_estimate_usd": 0.009, "created_at": "2025-01-01T12:00:00Z" }这只是一个示意,实际字段取决于你的业务。核心原则是:每条Agent执行链路都要留下足够的时间点信息。有了这份基线,后面所有排查、优化、归因才有下手点。
5.2 第二步:从链路中切出最小可维护片段
日志补好之后,先不要碰整个系统,只挑一个范围最小的核心链路来做改造。判断标准是:这类执行在业务里出现频率最高,或者它对线上稳定性影响最大。
然后做切片:
- 列出该链路的每一个步骤;
- 把“可以确定的判断”从prompt里挪到代码里,例如格式判断、条件分支、权限校验;
- 只把“真正依赖语义理解的判断”留给模型;
- 对模型返回的结构化字段先做一次校验,不合格就自动要求重新输出,而不是直接透传到下一步。
这里有一个值得强调的边界:不是所有判断都要挪出prompt。如果某个判断本质上依赖上下文和语义,比如判断用户投诉文本是不是在表达不满,那交给模型是合理的。如果是判断“订单状态是否为paid”这种明确条件,那应该用代码,而不是让模型去猜。这个边界划清楚,你的Agent会一下子稳定很多。
注意:不是所有判断都要挪出prompt。能用代码判断的用代码,需要语义理解的留给模型,这个边界才是清理完之后系统能否保持稳定的关键。
5.3 第三步:补齐工程化能力
很多Agent项目看起来乱,不是因为Agent没写好,而是缺少传统工程里那些“基本功”。清理时可以直接把这几个能力补上:
- 幂等:一个用户请求被意外重试两次,不应该产生两笔订单或两条通知;
- 重试:区分哪些错误值得重试,比如临时超时、限流;哪些错误不值得重试,比如参数错误、业务校验不通过;
- 上下文压缩:当历史消息太长时,先做摘要或裁剪,而不是硬塞给模型;
- 输出校验:对Agent返回结果做schema校验,出错就走修复或人工兜底;
- 预算控制:在代码层面限制单次任务的token上限、工具调用次数上限,避免Agent无限空转。
关于前面热词里反复出现的那些报错,比如“agent execution terminated due to error”或者“did not respond in time”。从排查链路来看,通常可以按下面这个顺序查:
- 先看日志:这次失败发生在哪个阶段,是模型调用失败、工具调用失败,还是输出校验失败;
- 再看上下文:是不是上下文超长、超过超时限制、token超限;
- 再看外部依赖:工具服务是否可用、API是不是超时、模型服务有没有限流;
- 最后看参数:温度、最大token、重试次数是否设置得符合场景。
这四步走完,大部分“Agent执行失败”都能定位到具体层,而不是只能靠重试赌运气。
5.4 第四步:沉淀成检查和维护规范
清理“屎山”的最后一步,是把这次踩的坑变成下一次的检查清单。否则,三个月后它又会重新长回来。
建议沉淀的清单可以包括:
- 新增工具时要提供哪些字段,返回格式是否符合统一规范;
- 修改prompt前要跑哪几条核心样例做回归;
- 上下文超长时的默认处理策略是什么;
- 记忆写入前是否需要先做摘要或过滤;
- 灰度发布Agent时,需要对比哪些指标。
如果团队规模不大,这个清单可以是文档。如果团队规模大一点,可以把部分检查点做成CI脚本或CLI。比如prompt模板变更后自动运行回归集,工具接入时校验返回字段。规范的意义,不是限制谁,而是让“不确定”变成“可查”。
6. 与其等“屎山”堆高,不如一开始设护栏
最后一部分想做一个提醒:清理Agent“屎山”这门生意之所以能成立,背后暴露的问题其实是很多人从第一天起就没有替Agent系统设置任何护栏。
6.1 新Agent项目应该有的几道护栏
如果你正在起一个新Agent项目,与其将来花一笔钱请人来清理,不如提前做几件投入不大、回报明确的事:
- prompt模板化:把prompt拆成角色、业务、约束、输出格式几个部分,每个部分独立管理,不把零散规则直接粘在代码里;
- 工具接入标准:所有工具必须是结构化输入输出,不搞“每个工具返回格式都不一样”的玩法;
- 日志从第一天就开:哪怕日志字段还不