
Agent 项目刚跑通 demo 的时候一切都很美好。你是某个业务团队里最早接触大模型开发的人用几百行 Python 把“查询天气”“查订单”“写会议纪要”串成了一个自动流程。当时你信心十足地说以后再加功能也很快。三周后业务方说要加一个“自动回复差评”的新工具又过了两周Agent 开始频繁报错同一个问题上午能跑通下午超时再往后新来的同事盯着代码看了半天只留下一句这个地方我不敢动。这不是段子而是大量 Agent 项目进入生产后的真实状态。我把这类项目称为“Agent 屎山”代码、Prompt、工具调用、状态记忆和外部系统配置全部堆叠在一起没有人说得清一条用户请求到底经过了哪些环节。更麻烦的是Agent 项目不像普通后端服务它的行为由模型决策驱动同一个输入可能走出完全不同的执行链路。于是清理这种屎山正在成为一门真实存在的生意。这篇文章不聊“Agent 会不会取代程序员”这种大话题而是聚焦一个更务实的问题Agent 项目的技术债从哪里来怎么拆以及为什么“给 Agent 清理屎山”是有商业价值的服务。如果你正在维护一个 Agent 项目或者你正好有技术能力想切入 AI 工程服务市场这篇文章值得读完。1. 为什么 Agent 项目最容易堆出屎山普通软件项目同样会产生技术债但 Agent 项目堆出屎山的速度要快得多。原因是它的“可运行”门槛太低而“可维护”门槛远超绝大多数团队预期。最早的 Agent 开发路径通常是这样的调用模型聊天接口再挂上工具调用让模型在一个循环里做计划、调用函数、拿到结果再继续。这个模式叫 Agent Loop它本身并不复杂。真正复杂的是 Agent 要接入的外部系统以及围绕 Agent 生成的决策状态。当项目只有 1 个 Agent、3 个工具时一切都很可控。你可以把所有工具分支写在一个if/else里Prompt 直接写在字符串常量中靠全局变量记录状态。但项目一旦进入生产环境马上会面临四个叠加因素。第一个因素是工具数量激增。每个业务方都会有“只要加一个工具”的需求而每个工具都带来参数校验、超时处理、错误重试、权限校验和返回值设计的问题。当工具从 3 个涨到 30 个继续往主流程里堆if/else就是灾难。做过传统后端开发的人都知道接口数量增加后通常会用注册中心、路由表或网关来组织服务调用但很多 Agent 项目从第一天起就没有这个意识。第二个因素是 Prompt 升级为行为逻辑的一部分。Agent 的行为边界经常调整可大部分团队把 Prompt 直接写在代码里每次改 Prompt 都要走代码发布流程这还算能接受。真正的隐患在于Prompt 一旦变长不同版本在线上效果差异很大。很多团队最后甚至说不清当前线上跑的是哪一版 Prompot新增一句约束后某个工具突然不触发了也没有人知道原因。第三个因素是状态与记忆管理混乱。Agent 在执行过程中需要知道“当前走到哪一步”“之前获取过什么信息”“用户的历史偏好是什么”。理想情况下短期状态和长期记忆应该分离但很多项目把状态直接丢进全局变量或内存字典。一旦 Agent 重启或者多个用户请求并发进来状态就开始互相污染。问题现象通常表现为用户 A 的对话上下文中突然出现了用户 B 的订单信息。第四个因素是 Agent 框架与概念选型混乱。现在相关术语非常多比如 harness、skill、MCP、memory它们各自解决不同问题。不少团队在一个项目里同时引入多套机制最后概念边界模糊数据流更混乱。所以“Agent 屎山”不是某个人的编码习惯问题而是 Agent 开发模式的必然结果。它的本质是模型行为、工具逻辑、业务状态、交互策略和可观测性没有被分层。只要项目从 demo 走向生产规模一上来堆叠就会快速崩坏。2. 识别 Agent 屎山的几个硬指标屎山听起来像一种主观评价但实际上可以用几个硬指标来判断。如果一个 Agent 项目命中下面任意两条说明它已经进入需要治理的阶段。2.1 工具调用链路是否“一次到底”打开主流程代码如果新增一个工具需要在主循环里加一个elif那么这个 Agent 已经处在工具调用链路耦合状态。另一个信号是如果去掉某个工具另一个工具就会报错说明工具之间存在隐式依赖。这样的系统每多一个工具暴露出的组合风险就上升一个级别。判断方法也很简单让一个不熟悉项目的人去加一个“查询物流”的工具看他要改几个文件。如果他要动主循环、动 Prompt、动工具判断分支那么这个项目必然是屎山。2.2 Prompt 是否与代码强耦合搜索代码中是否存在 10 行以上的 system prompt 字符串并且直接写在 Python 或 Java 文件里。如果有说明 Prompt 还没有独立管理。更严重的情况是同一套 Agent 在开发环境、测试环境、生产环境使用了不同 Prompt却没有版本标签。这会导致非常典型的线上事故开发和测试都正常一上生产行为就变了原因是生产环境的 Prompt 被某个同事手工改过但代码分支里看不到。2.3 是否能看到完整执行轨迹举一个真实场景线上一个 Agent 请求出了问题你能不能在 5 分钟内回答“模型在哪一步做了哪个工具调用、参数是什么、返回值是什么、为什么停止”。如果答案是不能说明这个 Agent 没有链路追踪。Agent 的可观测性比普通接口更重要因为它输出的是多步决策不是一次简单响应。没有 trace排查就只能靠猜。2.4 记忆是否被当成“万能缓存”部分 Agent 项目把用户的短期状态、长期偏好、历史对话、业务数据全部塞进同一个记忆模块不做命名空间隔离。短期上这会带来上下文膨胀模型输入越来越大调用成本越来越高长期来看记忆污染一定会出现一个业务场景写入的数据会被另一个场景错误读取。这两个问题在真实项目里都很难追查因为记忆模块的读写位置往往分散在几十个文件中。2.5 脚本有没有错误处理和重试边界很多 Agent 开发初期的“快乐版本”没有超时时间、没有重试策略、没有错误上下文。一个工具一次偶发超时整个 Agent 任务就报错终止。这也是为什么“the agent execution provider did not respond in time”这类报错会在搜索中频繁出现的原因。问题不一定来自模型服务本身很多时候是 Agent 层没有做容错设计也没有设置整体执行超时。识别完成之后下一步不是马上动手改代码而是先在架构上画清楚边界。这是清理 Agent 屎山最容易被跳过、也是最关键的一步。3. 先分清楚 Agent 架构里的四层边界清理 Agent 屎山不是把代码重写一遍而是先把系统的边界重新划清楚。我认为至少要划出四层模型层、工具层、编排层、状态与记忆层。模型层负责接收 Prompt 和控制参数输出自然语言或工具调用。它不承载业务逻辑模型选型、上下文窗口、温度参数都可以归在这一层。工具层负责 Agent 能做什么。工具本质上就是函数包含参数校验、外部调用、返回值格式化和错误处理。工具层是清理屎山时投入产出比最高的地方。把工具从主循环中剥离出去之后新增能力只是“加一个函数并注册”而不是改一段主流程。编排层负责 Agent Loop决定“当前要不要调用工具、调用哪个、拿到结果之后如何处理”。这一层应该保持精简不写具体业务逻辑只做循环控制和动作调度。如果编排层出现大量业务分支说明业务逻辑放错了位置。状态与记忆层负责短期状态和长期记忆。短期状态指当前执行上下文比如“已经查过订单不需要再查第二次”长期记忆指需要持久化到数据库或向量库的用户偏好、历史行为。两者必须分开否则会出现并发串号和记忆污染问题。除了四层边界有几个概念很容易混淆这里一起说明。概念主要职责容易混淆的点Harness把 Agent 执行循环封装成可复用运行器的框架不是业务框架不应该包含具体工具逻辑Skill定义 Agent 能力单元包含工具定义和使用说明不是单独的工具函数是更高一层的能力封装MCP工具接入协议让工具可以被不同 Agent 复用解决工具层标准化问题不等于 Agent 框架Memory管理 Agent 的短期状态和长期记忆不是缓存需要按场景做隔离实际项目中不要试图把所有边界都塞到同一个类里边界越清晰后续清理越安全。我见过很多重构失败的项目原因就是重构时只换了框架却没有把工具、Prompt、状态重新分层。结果就是从“自己堆的屎山”换成了“框架风格的自定义屎山”。4. 最小重构示例把一个“泥球”Agent 改成可维护系统这一节用一个最小示例演示清理思路。我不会引入特别复杂的框架只用 Python 标准库加一个工具注册表就能解决“工具逻辑堆在主流程”这个最常见的锅。4.1 重构前所有逻辑堆在一个文件里先看一个非常典型的“第一个能跑”的版本。# 文件路径legacy_agent.py # 这是大量“第一个能跑”的 Agent 的典型结构 # 示例代码实际运行时请接入你自己的模型服务 import json def call_llm(messages): # 这里是你对接模型服务的代码文中的写法是伪代码请替换为实际 SDK raise NotImplementedError(请替换为你实际使用的模型 SDK) def weather_api(city): # 示例工具查询城市天气 return {city: city, weather: 晴, temperature: 26} def order_api(order_id): # 示例工具查询订单状态 return {order_id: order_id, status: 已发货} def run_agent(user_input): system_prompt ( 你是一个智能客服助手。你可以查询天气、查询订单。 如果用户想查天气就用 get_weather 工具 如果用户想查订单就用 get_order 工具 不要编造工具返回值。 ) messages [ {role: system, content: system_prompt}, {role: user, content: user_input}, ] for step in range(10): response call_llm(messages) msg response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for tool_call in msg.tool_calls: name tool_call.function.name args json.loads(tool_call.function.arguments or {}) # 核心问题每新增一个工具就要在这里加一个 elif if name get_weather: result weather_api(args.get(city)) elif name get_order: result order_api(args.get(order_id)) else: result {error: funknown tool: {name}} messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) return Agent 执行超过最大步数这个版本的问题非常明显新增工具要改主循环工具异常没有统一边界没有任何日志没有超时没有权限校验。它非常适合写演示文档但不适合上生产。4.2 重构后工具注册表 配置驱动 链路日志清理后的项目结构可以这样组织agent_demo/ ├── agent_core/ │ ├── __init__.py │ ├── tools_registry.py │ └── agent.py ├── tools/ │ ├── __init__.py │ ├── weather.py │ └── order.py ├── main.py └── agent_config.yaml第一步把工具调用集中到一个注册表。# 文件路径agent_demo/agent_core/tools_registry.py import json from typing import Any, Callable, Dict class ToolRegistry: def __init__(self): self._tools: Dict[str, Dict[str, Any]] {} def register( self, name: str, description: str, parameters: Dict[str, Any], handler: Callable[[Dict[str, Any]], Any] ): self._tools[name] { name: name, description: description, parameters: parameters, handler: handler, } return self def schemas(self) - list: return [ { type: function, function: { name: tool[name], description: tool[description], parameters: tool[parameters], } } for tool in self._tools.values() ] def execute(self, name: str, arguments: Dict[str, Any]) - str: if name not in self._tools: return json.dumps({error: funknown tool: {name}}, ensure_asciiFalse) tool self._tools[name] # 统一入口可以在这里加超时、权限校验、日志记录等公共逻辑 result tool[handler](arguments) return json.dumps(result, ensure_asciiFalse)第二步让 Agent 主循环只依赖注册表不再感知具体工具实现。# 文件路径agent_demo/agent_core/agent.py import json import yaml from typing import Any, Dict, List from .tools_registry import ToolRegistry class Agent: def __init__(self, config_path: str, registry: ToolRegistry): with open(config_path, r, encodingutf-8) as f: self.config yaml.safe_load(f) self.registry registry self.max_steps self.config.get(max_steps, 10) def _system_prompt(self) - str: return self.config[system_prompt] def run(self, user_input: str) - Dict[str, Any]: trace [] messages [ {role: system, content: self._system_prompt()}, {role: user, content: user_input}, ] for step in range(self.max_steps): # 这里同样需要替换为你的模型服务 SDK 调用方式 response self._call_llm(messages, self.registry.schemas()) msg response.choices[0].message messages.append(msg) if not msg.tool_calls: return {answer: msg.content, trace: trace} for tool_call in msg.tool_calls: name tool_call.function.name try: args json.loads(tool_call.function.arguments or {}) result self.registry.execute(name, args) except Exception as exc: result json.dumps({error: str(exc)}, ensure_asciiFalse) trace.append({ step: step, tool: name, arguments: args, result: result, }) messages.append({ role: tool, tool_call_id: tool_call.id, content: result, }) return {answer: Agent 执行超过最大步数, trace: trace} def _call_llm(self, messages, schemas): # 伪代码请替换为你项目里实际的模型 Client raise NotImplementedError(请替换为你实际使用的模型 SDK)第三步把 Prompt 和运行参数外置到配置文件。# 文件路径agent_demo/agent_config.yaml system_prompt: | 你是一个智能客服助手。 你可以通过工具查询天气和订单信息。 涉及查询时优先调用工具不要编造结果。 工具返回结果后用自然语言总结给用户。 max_steps: 8 temperature: 0.2第四步写一个入口完成注册和调用。# 文件路径agent_demo/main.py from agent_core.agent import Agent from agent_core.tools_registry import ToolRegistry from tools.weather import register_weather_tool from tools.order import register_order_tool def main(): registry ToolRegistry() register_weather_tool(registry) register_order_tool(registry) agent Agent(agent_config.yaml, registry) result agent.run(帮我查一下北京的天气再查一下订单 12345 的状态) print(answer:, result[answer]) print(trace:, result[trace]) if __name__ __main__: main()这个示例没有引入重框架但已经把工具调用从主循环中剥离、把 Prompt 独立成配置、把执行过程记录到 trace。新增一个工具时只需要在 tools 下新增模块并注册不再需要改动主循环。这是清理 Agent 屎山时性价比最高的第一个动作。4.3 运行验证使用以下命令做基本验证# 语法检查 python -m py_compile legacy_agent.py agent_core/agent.py main.py # 跑入口脚本需先安装 pyyaml 依赖 pip install pyyaml python main.py这里有一个容易忽略的执行细节当你替换成真实模型 SDK 后第一件事不是验证回答是否准确而是验证 trace 是否完整。因为重构的核心目标不是让 Agent “更聪明”而是让每一步决策可追踪。如果 trace 里缺失某一次工具调用或者工具参数记录得不够完整那么这个可观测性基础设施就没有达到目的。5. 清理 Agent 屎山为什么能成为一门生意技术在讨论“怎么清理”市场则在问“谁愿意付钱”。这是两个不同的问题但商业价值恰好建立在技术可行性之上。5.1 需求来源第一类是 Agent 外包和定制开发项目。大量企业以“AI 应用开发”的名义拿到了 Agent 项目但交付之后往往没有人维护。客户对功能不满意开发方对屎山也头疼。这时候“Agent 体检 重构 交接培训”就是天然的补救型服务。这类项目通常有明确的时间节点客户已经在线上踩过坑付费意愿比从零开始的咨询项目强得多。第二类是企业内部 AI 平台团队。很多企业已经在 CRM、客服、工单、数据分析场景试点 Agent。试点期跑通 demo 很容易但进入正式业务之后工具数量、权限边界、审计要求会同步增长。内部团队愿意付费引入外部专家来做治理本质上买的是“稳定性和排错能力”。这类客户需求更偏长期往往以季度或年度合作的形式出现。第三类是独立开发者和中小团队。这类客户预算有限但痛点非常明确Agent 经常超时、工具调用错乱、Prompt 改了没用。他们需要的不是大而全的战略咨询而是一份诊断报告加关键代码重构再加一个能跑起来的监控方案。这个群体虽然客单价低但数量大适合做成标准化服务。5.2 技术服务形式围绕“Agent 清理”可以扩展出几种服务形态体检评估先不动代码只做代码审查、执行轨迹分析、成本与风险报告。这是最容易建立信任的入口。架构治理重新拆分工具层、编排层、状态层建立工具注册表、配置中心和统一日志。迁移重构把已有 Agent 从“单一 if/else 泥球”迁移到分层架构保留原有业务能力。长期运维提供 Agent 可观测性仪表盘、告警规则、Prompt 版本回滚机制按月度固定费用收取。培训与流程落地帮助团队建立 Agent 开发规范减少未来屎山生成的速度。这个行业有一个稳定特征Agent 项目的第一次开发成本往往不高但后续维护成本成倍增长。清理服务的盈利逻辑就是帮客户把“每个版本都不敢动”的隐性成本转化为一次性的确定性支出。用项目制收费也好用订阅制收费也好本质上卖的都不是代码而是“确定性”。5.3 这门生意的护城河不在模型而在工程模型迭代得非常快框架也更新频繁但“把工具调用、Prompt、状态、权限和日志分层管理”的工程能力是稳定的。做 Agent 清理服务核心竞争力不是会调用某个模型 API而是能准确判断一条失败请求到底死在 Prompt、工具、上下文、还是外部系统。真正能收到钱的是这种判断能力加上改完代码之后“线上仍然能跑”的交付底气。这也是为什么这门生意适合有后端工程经验的人切入模型能力只是入口工程判断才是壁垒。另一个需要注意的风险是清理市场并不缺“理论专家”缺的是肯在客户生产环境里做灰度、做回滚、做故障复盘的人。如果你只做一份漂亮的架构图却不敢碰线上真实链路这个生意大概率做不长。6. Agent 清理的工程实践清单如果你决定动手清理自己项目里的 Agent 屎山可以参考下面的清单。排序建议按照风险从低到高推进不要一上来就重写主流程。第一步先记录现状。把现有 Agent 支持的所有工具、Prompt、外部依赖和已知故障列成一张表。这张表就是后续重构的验收基线没有基线就谈不上回滚和对比。第二步建立工具注册表。即使不换框架也可以先用代码里的注册表类把所有工具函数集中起来。这一步风险最低因为它不改变线上行为只改变代码的组织位置。第三步把 Prompt 外置。放到 YAML、JSON 文件或配置中心支持按环境、按版本管理。如果这一步不做后续每次模型行为变化你都分不清是模型版本变了还是 Prompt 变了这是 Agent 项目最常见的调试盲区。第四步增加最小链路日志。至少记录每一步的工具名、入参、返回值、耗时和模型 token 消耗。日志不需要一开始就做成完整平台先保证 trace 数据存在后面才能分析。第五步做一次任务回归。从历史用户请求中挑出 30 到 50 个典型用例重构后跑一遍对比回答质量和工具调用轨迹是否收敛。这一步很多人会偷懒但不做回归就上生产等于把修改风险直接交给用户。第六步补权限和容错。给工具执行入口加上超时、重试、是否允许调用外部系统的开关。敏感工具要加最小权限校验和审计记录这一点在处理用户数据、财务数据时尤其重要。第七步分阶段切流。不要把重构结果一次性替换线上先灰度一小部分请求观察工具调用成功率和用户反馈后再放量。如果 Agent 系统涉及外部系统写入操作还要提前准备好回滚脚本和数据补偿方案。整个清理过程中真正要记住的原则只有一句话先保住现有功能再谈优化先看到链路再谈抽象先建立回滚点再大规模迁移。任何一次 Agent 重构都不能以“线上不可用”为代价。7. Agent 屎山清理中的常见问题与排查思路以下表格汇总了清理 Agent 项目时最常见的一批问题。这些问题不是理论推演而是任何把 Agent 放进生产环境的团队迟早会遇到的现象。问题现象可能原因排查方式解决方案Agent 执行超时多次出现 provider did not respond in time模型服务响应慢或 Agent 循环中没有整体超时控制查看单次模型调用耗时和整个 Agent 循环总耗时定位慢在哪一步给单工具调用和 Agent 整体执行分别设置超时增加超时重试新增工具后原有工具调用变少或不再触发Prompt 中加入了过多工具描述模型选择困难对比工具调用日志看新工具是否覆盖了旧工具的描述空间精简工具描述给重叠工具合并入口或增加工具路由规则