尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Agent技术债清理指南:从屎山代码到可维护架构

Agent技术债清理指南:从屎山代码到可维护架构 给 Agent 清理“屎山”正在成为一门赚钱的生意。这句话听起来像段子但如果你最近维护过哪怕一个落地超过三个月的 Agent 项目应该能立刻感受到它背后的真实痛点Agent 跑起来很容易稳定运行很难Demo 只要一个下午线上版本要按周迭代功能越加越多代码越改越乱Prompt 散落在聊天记录里记忆逻辑塞满了全局变量工具调用的分支判断长得像一棵没有修剪过的树。这次我们就把“Agent 屎山”当成一个正经的工程问题来拆一遍。重点不是吐槽而是讲清楚Agent 项目的技术债到底是怎么堆起来的怎么判断你的项目已经进入屎山状态以及如果要系统性地做一次清理和重构应该从哪里下手。如果你正在做 Agent 开发、想把现有 Agent 项目交付给客户、或者打算接 Agent 清理与重构类的技术服务这篇文章可以直接收藏。1. Agent“屎山”现象速览先把 Agent 项目里最常见的屎山现象整理成一张表方便对照你的项目。维度典型现象影响Prompt 管理Prompt 散落在代码、配置文件、数据库、聊天记录里无版本管理改一个角色设定要翻半天代码回滚困难状态与记忆记忆逻辑直接用全局字典、临时变量会话一长就错乱多轮对话结果不可复现用户感知“越聊越傻”工具调用每个工具写一套调用分支没有统一协议错误处理随缘工具一多代码爆炸失败后无法自动恢复框架混用LangChain 写一段、自研代码写一段、MCP 接入又套一层依赖混乱升级框架引发连锁故障日志与排错只 print 文本没有结构化日志无法追踪一次 Agent 执行的完整链路线上出问题只能靠猜复现成本极高测试覆盖只有“能跑通”的 Demo没有基于真实场景的回归测试每改一个 Prompt 都可能弄坏另一个场景多 Agent 协作子 Agent 之间通过硬编码调用没有统一通信协议协作链路难以理解一个子任务失败拖垮整个流程这些问题单独看都不致命但叠加在一起就会让一个 Agent 系统变得不可维护、不可扩展、不可交付。更麻烦的是Agent 项目里相当一部分“代码”其实是自然语言写的 Prompt自然语言天然就是不稳定的改一句话就可能改变整个行为风格这不是传统代码 review 能完全兜住的。2. 为什么 Agent 项目比传统业务系统更容易堆技术债2.1 生成速度远大于治理速度Agent 项目的开发速度通常很快尤其是用 AI 辅助编程之后一个新功能从想法到代码可能只需要几个小时。但问题是Agent 的复杂度不体现在“代码行数”上而体现在“上下文链路”上一个 Agent 要做什么、它需要哪些工具、它如何理解用户的意图、它在多轮对话中如何维护状态这些逻辑分散在 Prompt、代码、工具定义、记忆存储等多个位置。传统业务系统有明确的模块边界Controller、Service、DAO 分得清清楚楚但 Agent 项目天然是“跨层”的一条用户请求进来先过 Prompt 理解层再走工具调度层然后可能落到外部 API最后还要把结果写回记忆。每层都有各自的规则和失败模式任何一个环节的“临时补丁”都会演变成长期的复杂度。当生成速度远超治理速度技术债就会被快速叠加。你今天加了一个搜索工具明天发现结果不稳定于是套了一层重试后天发现重试导致回复变慢又加了一层缓存再后来缓存和记忆冲突了你又写了一段逻辑去绕开它——这些“临时补丁”就是屎山的第一批砖。2.2 Agent 天然有状态状态却没人管传统 Web 服务尽量无状态Session 要么放数据库要么放 Redis请求和请求之间互不干扰。Agent 不一样一个 Agent 的核心价值恰恰在于它的记忆能力它能记住用户偏好、能延续上下文、能在多轮任务中保持目标一致。问题在于很多 Agent 项目的记忆实现非常原始全局变量、文件读写、临时 Redis Key甚至直接塞进 Prompt 里。这种实现方式有几个致命问题没有会话隔离用户 A 的上下文可能串到用户 B 的对话里没有记忆淘汰机制上下文无限膨胀最终超过模型上下文窗口表现就是“越聊越笨”没有存储抽象所有记忆逻辑和业务代码耦合在一起想从内存换数据库就得改动所有调用点。这类状态管理问题在传统后端开发里早就有了标准答案但到了 Agent 项目里因为开发速度太快、大家都想先跑通 Demo状态管理往往被放到最后才考虑。结果就是Demo 能跑生产环境一上量就崩。2.3 工具调用链路的“分支地狱”Agent 的核心能力之一就是调用外部工具无论你是写代码、查数据库还是调第三方 API都需要通过工具调用层来执行。很多 Agent 项目的工具调用代码长这样# 示意代码工具调用分支逐渐失控 if tool_name search: try: result search_client.search(query) return format_result(result) except SearchException as e: # 项目里这种异常处理到处都是 return {error: str(e)} elif tool_name calculator: if expression is None: return {error: expression is required} try: return {result: eval(expression)} except Exception: return {error: invalid expression} elif tool_name db_query: # 又一套完全不同的参数格式和错误处理 ...这看起来每个分支都挺简单但真实项目里工具数量会迅速膨胀。一旦工具超过 10 个这种 if-elif 结构就会变成灾难参数格式不统一、错误处理不统一、返回结构不统一Agent 即使有很强的推理能力也很难根据混乱的返回结果做下一步决策。更麻烦的是现在很多 Agent 项目会通过 MCP 或类似协议接入第三方工具。MCP 本身是好事它给工具调用定义了通用协议但如果你上一个项目用的是 LangChain 的 Tool下一个项目用的是 MCP再下一个项目用自研框架那么“框架与编排层”就会成为新的复杂度来源。很多团队在还没有吃透一个框架的情况下就已经把三个框架的代码混在同一个项目里了。2.4 记忆、Skill 与 MCP 的标准不统一围绕 Agent 开发现在有一堆概念Agent、Harness、Skill、MCP、Tool、Memory、RAG。这些概念不是完全独立的它们在功能上有重叠很多开发者边学边用最后把每一层都写成了“大杂烩”。举个例子Agent Skill 和 MCP 之间的边界就很模糊。Skill 通常指 Agent 可以动态加载的一组能力描述MCP 是一种模型上下文协议用于让 Agent 和外部数据源、工具通过标准化接口交互。但实际项目里有人把 Skill 实现成一个 Python 类有人把它写进系统 Prompt还有人直接把 MCP Server 当成 Skill 来调用。底层机制不同上层接口自然难以统一。Harness 和 Agent 的关系也容易混淆。Harness 可以理解为承载 Agent 运行的运行环境负责执行循环、错误处理、上下文管理Agent 则更偏重策略和决策逻辑。把两者混在一个类里好处是写起来快坏处是后面想换推理模型或者换执行策略几乎所有代码都要动。这类问题在技术讨论中会被反复提起但在真实项目里往往是“能用就行”的状态。等到要清理的时候就变成了一项需要通盘理解的工程工作而这类工作正好是“给 Agent 清理屎山”这门生意的机会点。3. 诊断怎么判断你的 Agent 已经变成“屎山”3.1 症状清单如果你的项目满足下面任意三条基本可以判定已经进入屎山状态可以考虑安排一次系统性重构症状具体表现修改一个功能要动三个文件改 Prompt 要去代码里找改工具协议要动调用层改记忆要全局替换多轮对话结果不可复现同一个用户问题连续问两次得到完全不同且不可解释的答案工具数量超过 10 个工具调用函数已经超过 500 行新工具添加开始复制粘贴上下文窗口频繁超限Prompt 越来越长记忆没有裁剪策略只能靠“忘了前面的内容”硬撑日志无法追踪单次请求一次 Agent 执行跨越多个模块但日志里只有零散 print无法拼接调用链没有回归测试改一个 Prompt第二天发现另一个场景表现变了没有自动化手段兜底多 Agent 协作靠硬编码子 Agent 之间通过固定 ID 互相调用增加新成员需要改主流程代码依赖版本不敢升级框架升级会牵连太多改动只能锁定旧版本安全补丁也拖着不打3.2 一个可执行的诊断流程诊断不能靠感觉要让证据说话。建议在重构开始前先做一轮“技术债盘点”统计代码规模统计核心 Agent 代码的文件数、函数数、平均函数长度单独统计工具调用相关代码行数。梳理 Prompt 分布在代码库中搜索所有包含大段自然语言的字符串找出哪些 Prompt 是写在代码里的、哪些在外部配置文件里、哪些在数据库里。检查状态管理搜索全局变量、全局字典、临时文件读写判断状态存储是否集中、是否有生命周期管理。分析依赖树用依赖分析工具查看第三方包引用关系特别注意是否同时引入了多个功能重叠的 Agent 框架。做一次失败复盘拉出最近两周的线上报错按“Prompt 问题”“工具调用问题”“记忆问题”“框架问题”分类统计占比。这五步走完基本就能看出屎山的主要裂缝在哪里。很多时候你会发现真正的问题不是某一处代码写得烂而是整个项目的“架构契约”缺失没有统一的工具接口、没有统一的日志格式、没有统一的错误处理规范。4. 清理动作从代码到运行链路的工程化改造清屎山不是“重写所有代码”而是先建立约束再在约束范围内做减法。下面是一套相对通用的施工顺序适合大多数已经跑了一段时间的 Agent 项目。4.1 先冻结功能建立基线测试重构之前必须先把“当前系统的正确行为”固定下来。否则你一边重构一边改需求永远无法判断重构是否引入了行为变化。建立基线测试的常见做法准备一组覆盖核心场景的测试用例包括单轮问答、多轮对话、工具调用成功、工具调用失败、长文本记忆回复、特殊边界输入。在重构前跑一遍记录每一类场景的输出结果和时间消耗。重构过程中持续跑同一组用例对比输出差异差异超过容忍度的立即排查。这里要注意Agent 的输出天然有随机性不能用“字符串完全一致”来对比更合理的做法是“关键信息核对 人工抽查”。比如工具调用类场景可以断言工具是否被正确调用、返回是否被正确解析对话类场景可以人工检查是否符合预期意图。4.2 给 Prompt 和 Skill 建立版本仓库Prompt 是 Agent 项目中最大的隐性技术债来源。把散落在代码和数据库里的 Prompt 收集起来统一放到配置目录或专业 Prompt 管理平台是清理工作的第一优先级。一个参考目录结构agent-project/ prompts/ system/ assistant_v1.yaml assistant_v2.yaml workflows/ research_agent.yaml writer_agent.yaml skills/ search_skill.md summary_skill.md src/ agent/ tools/ memory/ tests/ scenarios/ config/ tools.yaml每个 Prompt 文件至少应该包含版本号、变更说明、适用模型、评测结果四个字段# prompts/system/assistant_v1.yaml version: 1.2 model: gpt-4o-mini description: 默认助手角色的系统提示词 history: - version: 1.0 change: 初始版本 - version: 1.1 change: 增加输出格式约束避免 Markdown 乱码 - version: 1.2 change: 缩短角色背景描述减少上下文占用 evaluation: - dataset: scenario_basic_v1 pass_rate: 0.92把 Prompt 版本化和代码版本化放在同等位置后续改动 Prompt 就能走 review 流程出了问题也能快速回滚到稳定版本。4.3 工具调用层统一接口协议工具调用层是 Agent 架构里最容易扩张也最容易腐化的一层。清理的目标是所有工具对外暴露统一的调用接口、统一的参数格式、统一的返回结构、统一的错误处理。参考实现思路# 统一工具协议示意代码 from typing import Any, Dict from dataclasses import dataclass, field dataclass class ToolInput: arguments: Dict[str, Any] dataclass class ToolResult: success: bool data: Any None error: str error_code: str class BaseTool: name: str base_tool description: str input_schema: dict {} def run(self, tool_input: ToolInput) - ToolResult: raise NotImplementedError # 公共的日志与监控方法 def _log(self, level: str, message: str) - None: # 接入统一日志框架 ...所有新增工具都继承BaseTool只需要实现run方法不需要关心上层调用方式。上层 Agent 在执行工具时也只认ToolInput和ToolResult两个类型不需要为每个工具写特判逻辑。这种做法能显著降低工具增长带来的复杂度。新工具添加变成一个纯增量过程定义 name、description、input_schema实现 run 方法注册到工具列表完事。4.4 记忆子系统独立化记忆是 Agent 项目里最容易被塞进“临时方案”的部分但它在整个系统中重要性极高。建议把记忆子系统拆成独立模块对外提供统一接口。核心接口至少包含class MemoryStore: def add(self, session_id: str, message: dict) - None: ... def get_recent(self, session_id: str, limit: int 10) - list: ... def search(self, query: str, top_k: int 5) - list: ... def clear(self, session_id: str) - None: ...这个设计有几个好处存储实现可以随时切换开发阶段用内存列表测试阶段用 SQLite生产阶段换成 Redis 或向量数据库上层代码不用改记忆逻辑可以单独测试不用每次都要启动完整 Agent可以方便地加上记忆裁剪策略比如超过 N 条就丢到长期记忆避免上下文无限膨胀。这里要特别注意“会话隔离”多条用户会话的 memory 必须按 session_id 区分否则线上环境一定会出现串对话的问题。4.5 引入可观测性Agent 项目的排错难度远高于传统 Web 服务因为每次回答背后可能经历了多轮推理、多次工具调用、多段记忆检索。没有可观测性排错全靠猜。一个基础的 Agent 可观测性设计应该覆盖三层层级记录内容实现方式请求层用户输入、最终输出、耗时、消耗 token在 Agent 入口和出口打结构化日志推理层每次 LLM 调用的输入输出、模型名、温度、耗时在 LLM 调用封装层记录工具层工具名、输入参数、返回结果、错误码、重试次数在 BaseTool 公共方法中记录日志格式建议使用 JSON便于后续接入日志平台{ timestamp: 2025-01-15T10:30:00.123Z, event: tool_call, agent_id: research_agent_v1, session_id: session_12345, tool_name: web_search, input: {query: AI Agent 技术债}, output_success: true, latency_ms: 2345, model: gpt-4o-mini }有了这类结构化日志就能把一次 Agent 执行的完整链路拼出来哪个环节慢、哪次工具调用失败、Prompt 在哪一步发生了变化都会清楚很多。5. 运行治理与性能观察清理完代码结构之后还要关注运行期的性能与资源消耗。Agent 服务的资源占用比传统 API 服务更难预测因为 LLM 推理的耗时和 token 消耗波动很大。运行治理阶段需要重点关注四个指标指标观察方式治理手段Token 消耗每次请求前后记录 token 使用量对长 Prompt 做裁剪、设置缓存、限制无意义循环LLM 调用延迟记录每次 LLM 调用的首 token 延迟和总延迟调整模型档位、设置更合理的超时时间上下文窗口占用统计每轮对话发送的 token 数建立记忆压缩策略定期摘要历史对话工具调用失败率按工具维度统计失败次数和错误码对不稳定工具加重试、降级策略必要时人工介入一个非常值得做的优化是“Prompt 瘦身”很多 Agent 项目的系统提示词越写越长把各种功能说明都塞进去导致每次请求都消耗大量 token并且挤占上下文空间。清理时应该把系统提示词压缩到只保留角色、约束和核心流程把具体操作细节放到 Agent 运行时按需加载的 Skill 或工具描述中。6. 这门“清理生意”在卖什么技术能力回到标题给 Agent 清理“屎山”正在成为一门赚钱的生意。这件事能成立是因为很多团队缺的不是写代码的人而是系统化治理 Agent 项目的经验。6.1 交付物清单一个标准的 Agent 清理项目交付物通常包括技术债诊断报告详细列出当前系统的风险点、优先级、影响范围。重构后的代码库工具调用层统一、记忆模块独立、Prompt 版本化、日志结构化。回归测试集覆盖核心场景的自动化测试用例作为后续变更的安全网。运行监控大盘接入日志平台和指标监控让 Agent 运行状态可观测。维护文档清晰的架构说明、新工具接入指南、Prompt 修改流程。6.2 验收标准验收不能只看“功能正常”还要看“可维护性是否真的提升”新工具接入时间从原来的半天缩短到 30 分钟内新增一个对话场景不需要修改核心代码只需新增配置线上问题定位时间从小时级缩短到分钟级回滚能力Prompt 或代码变更可以快速回滚到历史版本。这五个交付物和验收标准合在一起其实就是一套 Agent 项目从“能跑”到“能维护、能交付、能扩展”的完整升级路径这也是这类服务能产生商业价值的核心逻辑。7. 常见问题与排查方法问题现象可能原因排查方式解决方案多轮对话后回复质量明显下降上下文过长超出窗口后被截断查看请求日志中的 token 数和 max_tokens 设置建立记忆压缩策略定期摘要或裁剪历史工具调用返回结果 Agent 无法理解返回格式不统一Agent 解析失败查看工具层日志检查实际返回结构统一 ToolResult 结构在工具内做格式整理更新 Prompt 后某些场景突然变差Prompt 版本未管理变更影响面不可控对比新旧 Prompt 差异检查评测集结果建立 Prompt 版本仓库和评测集回归测试通过再上线Agent 执行链路中断无报错错误处理缺失异常被静默吞掉检查结构化日志中是否有 error_code统一错误处理逻辑所有工具调用必须返回明确错误多 Agent 协作时子任务超时子 Agent 依赖外部 API响应不稳定查看子 Agent 调用链路耗时增加超时控制和重试机制必要时降级为手动处理本地启动很慢依赖加载或模型加载耗时过长观察启动日志中的耗时分布分离模型加载和业务服务用预加载或懒加载优化显存或内存持续增长记忆或会话状态未释放监控内存曲线判断是否存在泄漏给 session 增加生命周期管理定期清理过期状态8. 最佳实践如何让 Agent 不再快速“屎山化”8.1 小步快跑但要有“架构护栏”不要一开始就投入大量时间做“完美架构”但要在项目初期就定下几个不可妥协的约束统一工具接口、统一日志格式、Prompt 必须建版本、记忆必须隔离会话。这四条守住了Agent 项目后面再烂也烂不到哪里去。8.2 每加一个功能同步更新测试集很多人维护 Agent 项目时加了功能忘了补测试。正确做法是每新增一个 Agent 场景、每接入一个新工具、每修改一次系统 Prompt都同步补充对应的回归测试用例。测试集是防止屎山蔓延最有效的工具。8.3 区分“配置”和“代码”凡是变化频繁的部分尽量做成配置例如工具注册表、Prompt 文件、Skill 列表。凡是稳定性要求高的部分放在代码里例如调用框架、错误处理、记忆管理。这个区分能极大减少后期维护成本。8.4 建立合规与安全边界接入外部工具和 API 时先确认数据使用权限尤其是用户隐私数据和第三方版权内容Agent 涉及人脸、声音、版权素材处理时必须确认是否有合法授权并在交付文档中明确使用边界接口服务默认绑定内网地址只在明确需要时开放公网访问对于可能产生误导的高风险输出场景要加人工复核环节批量任务要限制单用户可提交量避免资源被占用。9. 总结Agent 项目正在从“能跑 Demo”进入“要稳定交付、要持续维护”的阶段这个过程一定会催生大量技术债清理需求。给 Agent 清理“屎山”之所以能成为生意本质上是技术基建跟不上增长速度之后出现的必然分工专注于业务逻辑的团队会把 Agent 架构治理、工具接入规范、Prompt 工程化、可观测体系这类复杂度较高的工程问题交给专业的人去做。如果你正在维护自己的 Agent 项目最值得先做的三件事是把散落的 Prompt 收进版本仓库、给工具调用层加统一协议、给系统打上结构化日志。这三件事做完你的项目体验会立刻上一个台阶。最容易踩的坑则是在功能还没稳定的时候就想“一步到位重写架构”。正确顺序永远是先固定基线、再逐步重构用小步快跑的方式把屎山一点点铲平。后续可以继续扩展的方向包括Agent 自动化评测体系、多 Agent 协作链路的可视化排错、基于向量数据库的长期记忆系统、以及 Agent 运行成本治理。这些方向每一个都值得单独开一篇来写。
返回列表