过去一年我大部分时间都在做 Agent 相关的事情,从最早的调研、搭 Demo,到后来在一个真实业务场景里把 Agent 推到线上。这一年里我试过很多办法:换更强的模型、重写 Prompt、加记忆、做反思、调工具协议……但如果要挑一个对最终效果提升最大的改动,我会毫不犹豫地说:把架构删了。不是优化架构,不是重构架构,是删。把当初精心设计的好几千行编排代码删掉,换成一个看起来毫无技术含量、甚至有点简陋的循环。这个决定让端到端任务成功率直接涨了二十多个百分点,延迟降了一半还多,维护成本更是断崖式下降。今天就把这段经历和背后的思考完整写出来,给同样在做 Agent、并且正被复杂框架折磨的朋友一个参考。
1. 先还原一下:那个被我删掉的架构长什么样
当时我负责的模拟项目 X,核心场景是:用户用自然语言描述一个目标,Agent 负责把目标拆解成若干步骤,并调度一批工具去完成。比如"把这周销售数据整理成周报发到群里"这类任务,涉及查询数据、生成图表、写摘要、发送消息等多个动作。
因为任务链路确实长,我一开始就走了一条"把系统做重"的路线,设计了一套多层架构:
- 输入层:负责意图识别、槽位抽取,把用户的话转成结构化指令
- 规划层:负责任务分解,产出子任务清单和依赖关系
- 执行层:负责调用具体工具、校验参数、解析返回结果
- 记忆层:短期记忆管上下文,长期记忆管用户偏好和历史结论
- 评估层:每完成一个子任务就做一次反思,决定是否需要重做
- 编排层:一个状态机,负责调度上面所有层,处理各种状态流转和异常分支
代码结构上,我基于某个热门的 Agent 编排框架把整条链路串起来,还额外引入了工作流引擎和消息队列来管理子任务之间的依赖。核心代码量大概 5500 行,不含工具实现和配置。当时搭建这套东西花了我将近一个月,说实话写完的那一刻很有成就感,觉得自己做出来的是一个"正规军"级别的系统。
1.1 当时为什么这么设计
现在回头看,这个设计思路有三层原因。
第一,我有比较长的时间在做传统后端系统,习惯性地认为复杂的业务场景就应该用工程化的手段来管理。任务分解不够稳定,就加一个显式的规划器;模型行为不确定,就用状态机把流程钉死;担心上下文信息太多,就加记忆层做筛选。本质上是在用处理分布式系统的那套思路来处理 Agent。
第二,当时市面上各种框架都在强调"编排能力",看多了以后产生了一种错觉:Agent 的工程价值就在于把链路搭得越细越好,每个环节都做成可测试的独立模块,这样出了问题能快速定位。
第三,也是最大的一层原因:我默认模型不可靠。我觉得规划、记忆、反思这些能力如果让模型自己来,结果一定飘,所以要用系统代码去兜底。现在想想,这个假设恰恰是整个项目里最贵的错误。
1.2 这套架构实际带来的问题
问题在系统上线后集中爆发。最直观的是每次任务要调用非常多轮模型。一个中等复杂度的任务,拆出来大概是:意图识别一轮、任务规划一轮、每个子任务执行前还要规划一轮,子任务执行完再反思一轮,最后汇总答案又一轮。算下来平均一个任务模型要参与 5.8 次调用。
这还不是最要命的。真正的灾难是,数据每经过一层,信息保真度就下降一截。意图层输出的是抽取后的槽位,规划层拿到槽位后输出的是任务清单,执行层拿到清单后输出的是工具调用,中间任何一层做了"我以为"式的加工,后面的层就会在一个错误前提上继续往前走,而且每一层都觉得自己是对的。
举一个真实发生过的例子:用户要求"把文档里的表格转成 CSV 并发送到指定邮箱"。意图层把这句话识别成了"文件格式转换",于是规划层拆出来的子任务里压根没有"发送邮件"这一步。执行层勤勤恳恳把 CSV 生成好,然后就直接结束了。用户收到一个半成品,完全不知道为什么邮件没发出去。
这种问题最可怕的地方在于无法排查。你说意图层错了?它提取的槽位确实是对的。你说规划层错了?它是照着意图层给的结构化指令做的规划。每一层都在忠实执行上一层给的错误输入,链条越复杂,这种"系统性问题"就越隐蔽。我统计过,这类因为链路传递失真导致的问题,占线上总失败案例的相当大一部分,而且靠打补丁永远修不完。
2. 一次失败归因实验,让我动了删架构的念头
真正让我下定决心删架构的,是一次持续了三天的失败归因实验。我从线上日志里随机捞了 40 个失败任务,不修代码,不做任何假设,纯粹一个任务一个任务地人工追踪,看问题到底出在哪一层。追踪方式很简单:把每个任务全链路的所有模型调用输入输出、工具返回、中间变量全部打点导出,然后逐个环节检查,在哪个环节开始偏离用户目标,那个环节就是问题源头。
结果统计下来非常扎心:
| 失败原因归类 | 数量 | 占比 |
|---|---|---|
| 意图或规划层判断错误 | 14 | 35% |
| 上下文被加工污染,模型答非所问 | 10 | 25% |
| 执行层没能正确调用工具 | 8 | 20% |
| 模型能力确实不足,给足信息也做不对 | 5 | 12.5% |
| 其他(超时、格式错误、外部接口问题) | 3 | 7.5% |
这个表格给我的冲击很大。真正属于"模型不会做"的只有 5 个,占比 12.5%。剩下 87.5% 的失败,本质上是架构层层包夹,把一个原本模型能解决的任务给搅黄了。我花了一个月搭起来的系统,最大的作用竟然是把简单问题变复杂。
2.1 最扎心的对照组
为了确认这个判断,我又做了一个对照组实验。同一批任务,不走我搭的这套框架,而是直接把用户原始输入、若干个工具的描述、以及一条"如果需要调用工具,请以 JSON 格式输出"的指令拼成一个 prompt,让模型一口气输出最终答案。也就是所谓的"裸调用"。
结果裸调用的成功率反而比我那套架构高。更讽刺的是,当我允许裸调用模式下进行简单的多轮交互后,也就是上一轮工具结果返回后拼到对话里再继续让模型决策,它的表现已经接近我那套架构的理想状态。我带储层之下,它甚至能自己推导出"先查数据,再画图,再写周报,再发送"的完整计划,而且比我的规划器拆得更合理。
这个对照实验让我没法再骗自己了。问题不在模型,不在数据,不在工具,就在我精心设计的那几千行架构上。
2.2 想明白的一件事:LLM 本身就是规划器
做完实验后我反复在想一个问题:现代大模型最擅长的能力之一就是推理,你把它放在一个小而干净的上下文里,给它足够的工具描述,它完全可以自己完成"理解意图-拆解任务-调用工具-观察结果-调整计划"这一整套动作。你再去写一个显式的 Planner,等于让模型和你的代码互相猜对方在想什么,猜对了是运气,猜错了就是事故。
所以后来我把自己的设计原则改成了这样:Agent 框架要做的事情不是替模型思考,而是给模型创造一个可以犯错、可以观察、可以修正的环境。规划是模型在每一次决策时顺便完成的事,不需要单独成为一个阶段。记忆就是上下文本身,状态就是对话历史里已经执行过的工具调用和返回结果。评估也不是单独的一步,而是模型在看到异常结果后做出的自我修正。
一句话:模型的思考能力是基础设施,我的架构代码才是多余的。架构越厚,模型的眼神越差。
3. 删掉之后:新的方案只有一层循环
下决心删架构之后,我重新搭的方案简单到有点不好意思说出口:一个循环,一块黑板,一堆工具定义,三条护栏。没有状态机,没有消息队列,没有独立规划器,没有记忆层。核心代码用 Python 写出来大概是这个形态:
# 伪代码:删完之后的 Agent 核心 messages = [{"role": "user", "content": task}] for step in range(MAX_STEPS): # 护栏1:迭代上限 # 把工具描述给模型,让它决定下一步 response = llm.chat(messages, tools=TOOL_SCHEMAS) # 如果模型认为任务已完成,直接返回最终答案 if response.type == "final_answer": return response.content # 护栏2:工具白名单和参数校验 with tool_registry.validate(response.tool_call): observed = execute_tool(response.tool_call) # 工具结果作为观察,回写入对话历史 messages.append(response.message) messages.append({"role": "tool", "content": observed}) # 护栏3:步数耗尽,返回已完成部分或明确告知用户 return stop_with_message(messages)整套核心逻辑不到 200 行。删完那一刻我心里其实很虚,总觉得"这么简单的东西真的能撑住那个业务场景吗",但跑了几天数据之后,这种心虚就彻底消失了。
3.1 五步闭环为什么足够
这套方案的运行逻辑其实就是五个动作的循环:模型基于当前上下文做决策,如果决策是调用工具就执行工具,把工具的原始返回当作观察结果写回上下文,然后带着新信息继续让模型决策,直到模型给出最终答案或者步数耗尽。
这五个动作每步都是必要的,且没有任何一步需要额外的"架构"来支撑。
第一步,模型必须能看到完整的对话历史。所有已执行过的工具调用、返回结果、模型自己的计划,都在同一个上下文里,信息不经过任何中间层的转述。第二步,模型做决策时唯一需要的外部输入是工具定义,它不需要一个系统来告诉它"现在该进入规划阶段了"。第三步,工具结果的呈现方式要尽量原始、准确、简洁,不要额外包一层"摘要"或"解析结果"。第四步,写回上下文这个动作本身就是记忆,模型能看到之前的失败和修正过程。第五步,什么时候算结束,交给模型判断,代码层面只需要管上限和异常。
这样做最大的变化是:每一个失败都被保留在上下文中,成为下一次决策的依据。而旧的架构恰恰相反,每一次失败都被中间层拦截、包装、丢失,模型根本感知不到自己前面哪一步做错了,自然也就谈不上修正。
3.2 我把力气花在了工具定义上
架构删掉之后,省下来的精力和时间我全砸在了一个地方:工具定义。后来事实也证明,工具定义的质量就是删完架构后系统的生命线。模型大多数情况下不是不会决策,而是决策依据不够清楚。
一个劣质的工具定义是这样的:
{ "name": "search_stock", "description": "查询股票", "parameters": { "code": "股票代码" } }这个定义的问题在于:什么场景该调用、什么场景不该调用、参数格式是什么、边界条件是什么,全都没有写清楚。模型很容易在用户只是随口提了一句"最近行情咋样"的时候就误调股票查询工具,或者拿着一个上市公司的名称而不是交易代码去查询。
我后来把它改成了这个样子:
{ "name": "query_stock_price", "description": "当用户询问个股价格、涨跌幅、成交量、K线数据时调用。如果用户的问题是模糊的,比如'某某股票怎么样',必须先调用 ask_user 确认具体是哪只股票。本工具不支持查询未上市公司的估值,也不支持指数行情。", "parameters": { "symbol": "交易所代码,格式如 SHA.600519,不接受纯名称", "start_date": "可选,开始日期,格式 YYYY-MM-DD,默认最近30天", "end_date": "可选,结束日期,默认当天,不得早于 start_date" } }改动之后,光这一个工具的误用率就明显下降。原理也很简单:模型不是靠参数名理解工具的,是靠描述和你告诉它的边界条件。你给的信息越具体、越接近真实业务中的判断逻辑,模型做对决策的概率就越高。写工具描述这件事,本质上就是在写模型做决策的判断规则。
3.3 唯一保留的"架构":三条护栏
删架构不等于裸奔。完全把模型直接暴露给真实系统,风险其实很大。我保留了三个非常薄的防护层,它们不参与流程编排,只在接口边界上做控制。
第一个是迭代上限。我把最大步数设在 15,超过就停止并返回当前状态。这个上限防止模型陷入死循环,也能兜住那些意志力异常坚定的错误尝试。
第二个是工具白名单加参数校验。模型并不直接执行系统命令或者访问任意接口,所有工具调用都要经过注册表和校验器,非白名单工具直接拒绝,参数不合法直接报错并把报错信息返回给模型,让它自己调整。这一步保证了安全边界,又不会打断模型的决策闭环。
第三个是高风险操作的人工确认位。比如"发送邮件""删除数据""对外发布消息"这类动作,我在参数模型里加了一个布尔字段,默认 false,模型可以请求执行,但系统看到这个标记后会挂起并询问用户。这个设计把最终决定权留给人,同时也不限制模型提出执行请求的能力。
这三条护栏加起来不到 200 行代码,但它们是"安全"和"可用"之间的平衡点。真正的风险控制应该放在接口边界上,而不是放在流程编排里。
4. 实测情况:删完之后的数字对比
架构简化前后,我在同一个模拟项目 X 上、用同一批评估用例、同一套评价口径做了对比。先说结果:
| 指标 | 删除前 | 删除后 |
|---|---|---|
| 端到端任务成功率 | 61% | 88% |
| 平均任务完成时长 | 9.4 秒 | 3.2 秒 |
| 平均模型调用次数 | 5.8 次 | 2.1 次 |
| 核心代码量 | 约 5500 行 | 约 900 行 |
| 定位一个线上问题的平均耗时 | 约 2 小时 | 约 20 分钟 |
| 单任务 token 成本 | 基准值 | 下降约 45% |
需要说明的是,具体数字会因业务场景不同而浮动,但几轮对照实验下来趋势非常一致:删掉架构之后,成功率、延迟、成本、可维护性全都在变好,没有一项指标变差。这个一致性比我预期的还要强。
4.1 用户能感受到的变化
接入了这个 Agent 的同事和外部用户,在没有被告知任何改动的情况下,主动反馈了几个变化:回答明显更快了,以前经常出现的"卡住十几秒然后失败"的情况几乎消失,结果的一致性也高了很多,同样一句话问三次,三次的返回不再像三个不同的助手在回答。
还有一个很微妙的体验改善:旧架构下,任务一旦中途出错,系统往往直接抛出冷冰冰的失败提示,因为状态机走到了终止态,没有给模型留出"补救"的机会。新架构下,模型在步骤内出错后,能看到报错信息,然后自己换个方式继续尝试。用户感知到的就是"这个东西偶尔会绕个弯,但很少会直接撂挑子"。
这种体验差异本质上来自一个设计选择:旧的架构把失败当成系统异常来处理,新的架构把失败当成普通观察结果来处理。前者需要人或者代码去"修复"失败,后者只需要模型自己顺着失败继续往前。
4.2 团队维护成本的变化
对我来说最实际收益的是维护成本。以前加一个新工具,流程是:写工具实现、写参数校验、在编排层注册、在状态机里加状态或迁移逻辑、在若干中间环节加适配代码。通常要动四五个文件,一个不小心的遗漏就会导致工具在某个环节不可用,而且报错信息还很隐晦。
现在加一个新工具,只需要两件事情:写工具实现,然后写一份高质量的工具描述 JSON。剩下的全部靠模型自己理解。新同事接手这个项目,花一个下午就能讲清楚全部运行逻辑,因为总共就一个循环、一份工具列表、三条护栏。测试用例也从原来的 30 多个精简到 5 个,回归从让人头皮发麻变成了随手一条命令的事。
5. 什么情况下适合删,什么情况下得留一点
写完上面这些,我不想给读者造成"所有 Agent 项目都应该把架构删光"的错觉。这套做法能不能落地,要看几个很现实的条件。
适合删架构的情况,大概有这些特征:业务领域相对聚焦,单一 Agent 只需要面对一类任务,而不是一个包罗万象的通用助手;工具数量在 10 到 50 个之间,模型能在一个上下文里看到全部关键工具的摘要;你依赖的模型本身在规划和推理上已经足够强,不需要你用规则去补足它的能力短板;你的团队有能力维护一套固定评估集,用行为回归来验证系统质量,而不是靠代码结构来保证不出错。
不适合删架构的情况也存在,而且我在实践中踩到过。比如严格需要审计和权限分级的场景,金融、医疗、企业内部后台这类环境,每一步操作都要记录操作人、操作原因、审批链路,这种审计要求必须用代码层面的结构来保证,不能指望模型自己记住"我调用了某个敏感接口"。再比如有些决策必须由确定性规则来完成,比如按照费率表计算折扣、按照合规清单判断某个动作能不能执行,这类规则放在代码里比让模型从描述中推断可靠得多。
5.1 一个折中方案:薄编排加策略配置文件
如果场景确实需要一定的流程确定性,我推荐一个折中方案,也是我目前在新项目里用的做法:保留一个非常薄的编排层,只负责确定任务的执行顺序,但把每个环节内部的决策权全部交给模型。
举个例子,如果业务要求"先校验资质,再发起交易,最后生成回执"这个顺序绝对不能变,那就用一个配置文件写明这个顺序,编排层机械地按照顺序触发每一步。但每一步具体怎么做、用什么工具、如何判断成功,都由模型在运行时自己决定。这样既保住了必须由规则控制的顺序,又保留了模型在单点环节上的灵活性。
这个折中的关键在于:把规则放在数据上,而不是放在代码流程上。用配置文件表达顺序和权限,用模型表达判断和应变。规则变的时候改配置,不用改代码;模型能力升级的时候换模型,也不用改配置。两者耦合度很低,维护起来很舒服。
5.2 "删架构"背后的判断标准
如果要用一句话概括我删架构的决策逻辑,那就是:架构存在的意义是降低不确定性,可当它本身变成最大的不确定性来源时,就该被清理掉。
这有点像装修房子。最开始你花大价钱在墙壁里预埋了密密麻麻的水电管路,觉得一步到位很专业。但住进去之后发现想改个插座位置、加个出水口,全要砸墙。反而是那些后期走明管、用拖线板的房间,改起来随心所欲,想动哪里动哪里。Agent 架构也是这样,你在框架里埋的每一层逻辑,都是在为"未来可能需要"做预留,但这些预留大概率不会像你预想的那样被使用,只会变成你改动系统时的阻碍。
6. 实操总结:三种可以立刻执行的简化动作
如果你看完前面的分析,也想给自己的 Agent 减减肥,我建议先从下面三个动作开始。它们不需要一次性推翻所有东西,可以逐个执行,每个动作做完跑一遍评估集,看看效果变化。
6.1 动作一:摘掉"意图识别"这一层
很多 Agent 框架里,意图识别、槽位抽取是独立的一层,通常还单独用一个小模型或者一套规则来做。我建议你直接把它删掉,让主模型在同一轮对话里既理解意图,又输出工具调用。这听起来像少了一个模块,实际上是把两个串行步骤合并成了一个并行步骤:模型在读用户的话的时候同时理解意图和判断工具,信息不经过转述,误判率明显更低。我删掉意图层之后,平均调用次数立刻降了一轮。
6.2 动作二:删掉"重试链",改成"报错给模型看"
旧架构里常见的做法是:工具调用失败后,系统走一套独立的重试逻辑,比如换个参数重试三次,三次不行就放弃。这套逻辑的问题在于它是盲目的,它不知道工具为什么失败,也不从失败里学习。
我的建议是删掉所有重试链,把工具的异常信息当作一次普通的工具返回,原样写回上下文,让模型看到"你刚才用这个参数调用是失败了,报错是这样的",然后由模型自己决定是换参数、换工具还是告诉用户任务无法完成。实践中模型自我纠错的效果远超固定重试,而且你完全可以在模型连续三次用相同参数调同一个工具时强制中断,这个检测逻辑比任何重试策略都简单直接。
6.3 动作三:把所有中间环节的"加工"去掉,只留原始信息
检查一下你的链路里有没有这种环节:工具返回之后,被某个中间模块做了格式化、摘要、字段抽取,然后再送到模型面前。如果有,请把这些加工层全部删掉。
一开始你会担心"工具返回太长了,模型读不完",真实的解法是把工具本身的返回值写得简洁准确,而不是在外围包一层摘要器。摘要器本质上是又一个信息失真源,它可能丢掉模型需要的关键细节。比如某个工具返回 200 行 JSON,你摘要成三行,恰恰把最关键的告警信息给滤掉了,模型就会做出完全错误的判断。让工具侧直接返回精简的关键字段,比在外面加工要可靠得多。
6.4 踩坑实录
最后分享几个我在删架构过程中踩过的坑,这些坑让我明白"删"不是一个偷懒的动作,而是一个需要配套工程手段的技术决策。
第一个坑是删了状态机之后,模型开始在一个错误方向上反复打转。它可能连着五次尝试同一个明显不合适的工具,每一步都失败,每一步都重来。后来加的"相同工具加相同关键参数连续出现三次就中断"规则把这个坑填平了,模型拿到"系统提醒:你已经连续三次尝试同样方案,建议换思路或询问用户"之后,十有八九会立刻换个策略。
第二个坑是工具返回结果太长导致上下文爆炸。早期我以为上下文塞满了没关系,模型会自己抓重点,实际上它处理长上下文的效率和准确度都下降。后来我把所有工具的返回都设计成"Meta 信息 + 精简结果 + 完整结果可查"的结构,默认让模型只读精简区,必要时再请求完整结果。
第三个坑是自以为删了架构就万事大吉,连续一个月没有跑回归。结果某次调整了一个 Prompt 措辞之后,一个子场景的成功率直接掉了 15 个点,因为没回归集,完全没察觉。这件事之后我花了一整天搭了一个 30 条用例的固定评估集,从此所有改动必须跑完这条集才允许合入。有 eval 才敢删、才敢改,这个原则现在是我做一切 Agent 工程的地基。
第四个坑是格式解析过于严格。模型输出偶尔会带一些多余的前导文字,或者把 JSON 字段顺序打乱,旧架构里我用严格的 parser,一失败就整个重来。后来我换成宽松解析器,先尝试 json.loads,失败就提取代码块或者手动补全缺失的括号,成功率和稳定性都上来了。模型本身不是 JSON 机器,你要么把它训练得更服帖,要么在解析侧多包容一点,后者明显省事得多。
我个人在实际做完这次"删架构"改动后的体会是:Agent 工程真正的价值不在于搭了一个多复杂的框架,而在于你有没有能力给模型创造一个足够干净、信息透明、可以自我修正的环境。模型已经在做最难的思考和决策工作,我们这些做工程的,最应该做的是把挡在模型视线前面的东西搬开,把工具擦干净,把边界立好,然后信任它。遇到效果上不去的 Agent 项目,别再首先想"架构还能加什么",先问一句"这里面有什么是完全可以删掉的",答案往往更值钱。