1. 从"一锅炖"到"分层交付":AI Agent 工程化的分水岭
如果你最近在折腾 AI Agent,大概率经历过这样的场景:Demo 阶段一切丝滑,模型能聊天、能调工具、能给出看起来靠谱的答案,你信心满满准备上线。结果一进真实业务,问题全冒出来了——同一个问题问两遍答案不一样,工具调用时好时坏,模型偶尔"幻觉"出一个根本不存在的接口,日志里全是无法复现的报错。你回头一看代码,发现提示词、工具定义、业务逻辑、状态管理、模型调用全糊在一个文件里,改一处牵动全身。
这就是典型的"把智能糊进一锅"。AI Agent 工程化的核心命题,不是让模型更聪明,而是让这套系统在真实业务里可交付、可维护、可迭代。而"分层交付"正是从"能跑"到"能上线"之间那道最关键的分水岭。
我先把结论摆出来:一个能进生产的 Agent 系统,至少要拆成模型层、能力层(工具/技能)、编排层、状态与记忆层、交付接口层这五层。每一层有独立的职责边界、独立的测试方式、独立的迭代节奏。把它们混在一起,你得到的不是 Agent,是一个随时会炸的黑盒。
这篇内容适合三类人:一是正在从 0 到 1 搭建 AI Agent 的开发者,二是被"Demo 很惊艳、上线很拉胯"折磨过的工程同学,三是需要给团队定 Agent 开发规范的技术负责人。我会把分层交付的每一层拆开讲清楚——为什么这么分、每层具体放什么、层与层之间怎么通信、以及我在实际项目里踩过的坑。
先澄清一个高频困惑:Agent、LLM、AI 模型到底什么关系?很多人把这三个词混着用。AI 模型是最大的范畴,指所有经过训练的神经网络模型;LLM(大语言模型)是 AI 模型里专门处理语言的那一类,比如 DeepSeek、GPT 系列都属于 LLM;而 Agent 是在 LLM 之上加了一层"感知—决策—行动"的循环结构,它能调用工具、维护状态、根据结果调整下一步。打个比方:LLM 是一个博学但只会动嘴的顾问,Agent 是给这个顾问配了手、配了记事本、配了执行流程之后的"项目经理"。分层交付,本质就是把这个"项目经理"的各个器官拆开管理,而不是指望一个大脑包办所有事。
2. 为什么"糊成一锅"的 Agent 一定会在生产环境翻车
2.1 耦合带来的三个致命问题
我见过太多项目,一个agent.py文件里塞了三千行:系统提示词写在最上面,工具函数定义在中间,业务分支判断散落各处,模型调用参数硬编码在函数里。这种结构在 Demo 阶段跑得飞快,因为改起来"方便"——反正都在一个文件里。但一旦进入多人协作和持续迭代,三个问题会同时爆发。
第一是可测试性归零。你想单独验证"工具选择"这个环节对不对,但工具选择逻辑和提示词、和模型参数、和业务上下文全绑在一起,你没法隔离测试。只能端到端跑一遍,看最终输出对不对。问题是最终输出错了,你根本不知道是提示词的问题、工具描述的问题,还是模型本身的问题。
第二是可替换性归零。业务方说"我们想从 A 模型换成 B 模型试试效果",你打开代码发现模型调用散落在十几个地方,每个地方的参数格式还不一样。换模型这件事从"改一个配置"变成了"重构一遍"。
第三是可观测性归零。线上出了问题,你只能看到"用户输入 X,系统输出 Y",中间发生了什么完全是个黑盒。是模型没理解意图?是工具调用失败了?是状态丢失了?没有分层埋点,你连排查的入口都找不到。
2.2 一个真实的翻车案例
我之前参与过一个客服 Agent 项目,早期版本就是典型的"一锅炖"。上线第一周,用户投诉"同一个退款问题,Agent 给的答复前后矛盾"。排查过程极其痛苦:日志里只有输入输出,没有中间态。我们花了整整两天,最后定位到根因——记忆层和编排层耦合。Agent 在处理多轮对话时,把上一轮的临时工具返回结果错误地写进了长期记忆,导致下一轮对话时模型读到了过期的上下文。
如果当初做了分层,这个问题在架构上根本不会发生:记忆层有明确的"写入策略"和"读取策略",临时结果和长期记忆物理隔离,编排层只负责调度不负责存储。分层不是为了好看,是为了让某类 bug 在结构上不可能出现。
2.3 分层的本质:把"不确定性"关进笼子
LLM 最大的特点是不确定性——同样的输入可能得到不同的输出。这是它的能力来源,也是工程化的最大敌人。分层交付的核心思路,是把不确定性收敛到尽可能小的范围里。
模型层是唯一允许存在不确定性的地方。能力层、编排层、状态层、接口层,全部要做成确定性的、可测试的、可预测的。这样当系统出问题时,你能快速判断:是模型层的不确定性导致的(那就调提示词、换模型、加约束),还是其他层的确定性逻辑写错了(那就正常 debug)。
这个思路和传统软件工程里的"隔离变化"原则一脉相承。把易变的部分(模型行为)和稳定的部分(业务流程、数据结构)分开,让变化的影响范围可控。
3. 五层架构逐层拆解:每层到底放什么
3.1 模型层:唯一的不确定性来源
模型层是整个系统里唯一"不保证确定性"的部分,所以它的职责要尽可能纯粹:接收结构化的输入,返回结构化的输出。除此之外什么都不干。
具体来说,模型层要封装这几件事:模型选型与路由(不同任务用不同模型)、提示词模板管理、输出格式约束(强制 JSON Schema)、重试与降级策略、Token 计量与成本控制。注意,这里说的是"封装",不是"实现业务逻辑"。模型层不应该知道"退款流程"是什么,它只知道"给我一段文本和一个 schema,我还你一段符合 schema 的 JSON"。
我在实际项目里会给模型层定义一个统一接口,类似这样:
class ModelLayer: def invoke(self, prompt_template: str, variables: dict, output_schema: dict, model_hint: str = "default") -> dict: # 1. 渲染提示词 # 2. 根据 model_hint 路由到具体模型 # 3. 调用模型,强制输出符合 output_schema # 4. 校验输出,失败则重试(最多 N 次) # 5. 记录 token 消耗和延迟 # 6. 返回结构化结果 pass这个接口的关键在于输出 schema 强制校验。很多人调模型就是response = model.chat(prompt),然后拿字符串去解析,解析失败就崩。正确做法是让模型层负责"保证输出结构正确",上层拿到的永远是校验过的结构化数据。重试逻辑也放在这一层,因为"模型输出格式不对"是模型层的问题,不该让上层处理。
提示:模型层的重试要有上限,并且要区分"格式错误重试"和"内容错误重试"。格式错误可以自动重试,内容错误(比如模型说"我无法回答")重试通常没用,应该直接向上抛,由编排层决定降级策略。
3.2 能力层:工具与技能的标准化封装
能力层是 Agent 的"手脚",也就是工具(Tool)和技能(Skill)的集合。这一层的核心任务是:把每一个外部能力封装成模型能理解、系统能调用的标准单元。
一个标准的工具定义包含四部分:名称、自然语言描述(给模型看的)、参数 schema(结构化)、执行函数。很多人只写执行函数,描述随便糊一句,结果模型根本不知道该在什么时候调用这个工具。工具描述的质量,直接决定 Agent 的工具选择准确率,这一点怎么强调都不过分。
我总结的工具描述写法:说清楚"这个工具做什么"、"什么时候该用"、"什么时候不该用"、"参数的含义和格式"。举个例子,一个查询订单的工具,描述不该只写"查询订单",而应该写"根据订单号查询订单的详细状态,包括物流、支付、退款进度。当用户询问具体订单的状态时使用。不要用于查询用户的历史订单列表,那应该用 list_orders 工具"。
能力层还要处理工具执行的容错。外部 API 会超时、会返回错误、会限流。这些异常不该直接抛给模型,而应该在能力层被捕获并转换成模型能理解的错误信息,比如"订单查询服务暂时不可用,请稍后重试"。这样模型可以据此决定是重试、换工具还是告知用户。
| 工具封装要素 | 常见错误 | 正确做法 |
|---|---|---|
| 工具描述 | 一句话带过 | 说明用途、使用时机、禁用场景 |
| 参数 schema | 用自然语言描述参数 | 用 JSON Schema 严格定义类型和必填项 |
| 错误处理 | 异常直接抛出 | 捕获后转成模型可读的错误信息 |
| 幂等性 | 不区分读写 | 写操作要支持幂等键,防止重复执行 |
| 超时控制 | 用默认超时 | 按工具特性设置独立超时 |
3.3 编排层:Agent 的"大脑回路"
编排层是决定"下一步做什么"的地方,也是 Agent 区别于普通 LLM 调用的核心。它负责:意图识别、任务规划、工具选择、多步执行、结果整合。但注意,编排层本身应该是确定性的代码逻辑,而不是又一层模型调用。
这里有个常见的误区:很多人把编排也交给模型,让模型输出"下一步该调用哪个工具"。这在简单场景下可行,但在复杂业务里会失控——模型可能陷入循环、可能跳过必要步骤、可能做出业务上不允许的操作。我的做法是混合编排:用代码定义主流程骨架(确定性的状态机),在关键决策点让模型做选择(受约束的不确定性)。
比如一个退款 Agent 的主流程是确定的:验证身份 → 查询订单 → 判断是否符合退款条件 → 执行退款 → 通知用户。这个骨架用代码写死。但"判断是否符合退款条件"这一步,可以让模型结合订单信息和退款政策做判断。这样既保证了流程可控,又利用了模型的语义理解能力。
编排层还要负责循环控制。Agent 的多步执行必须有明确的终止条件:达到最大步数、任务完成、或者遇到无法处理的错误。没有终止条件的 Agent 循环是生产事故的温床。
3.4 状态与记忆层:别让上下文变成垃圾场
状态与记忆层是最容易被忽视、也最容易出问题的一层。它要回答三个问题:当前对话的状态是什么、历史信息怎么存、什么时候读什么时候写。
我把记忆分成三类,物理隔离存储:
- 会话状态:当前这一轮对话的临时数据,比如用户刚说的订单号、刚调用的工具结果。生命周期是单次会话,会话结束即销毁。
- 短期记忆:最近 N 轮对话的摘要,用于维持对话连贯性。有容量上限,超出后做摘要压缩。
- 长期记忆:用户的偏好、历史行为、关键事实。需要显式的写入策略,不能什么都往里塞。
前面那个翻车案例的根因,就是把"临时工具结果"错误地写进了长期记忆。正确做法是:工具返回结果默认只进会话状态,只有经过明确的"记忆提取"步骤(判断这条信息是否值得长期记住)才写入长期记忆。
注意:长期记忆的写入一定要有"准入判断"。我见过太多项目把用户说的每句话都存进向量库,结果检索时全是噪音。记忆不是越多越好,是越准越好。
3.5 交付接口层:对外的稳定契约
交付接口层是 Agent 对外的门面,负责协议适配、鉴权、限流、日志埋点、以及最重要的——对外契约的稳定性。
这一层的设计原则是:内部怎么改都行,对外接口不能变。业务方调用你的 Agent,不应该关心你用的是哪个模型、工具怎么封装、记忆怎么存。他们只关心输入什么、输出什么、错误码是什么。
接口层还要做全链路追踪。每个请求分配一个 trace_id,贯穿模型层、能力层、编排层、状态层。这样线上出问题时,你能通过一个 trace_id 还原整个执行链路,看到每一步的输入输出、耗时、token 消耗。没有这个,排查问题就是大海捞针。
4. 层与层之间怎么通信:契约先行的实操方法
4.1 用数据结构定义层间契约
分层之后,层与层之间的通信就成了关键。我的经验是:先定义数据结构,再写实现。每一层的输入输出都用明确的 schema 定义,任何跨层传递的数据都必须符合 schema。
比如编排层调用能力层的契约:
# 编排层 -> 能力层 { "tool_name": "query_order", "arguments": {"order_id": "12345"}, "trace_id": "abc-123", "timeout_ms": 3000 } # 能力层 -> 编排层 { "status": "success", # success | error | timeout "data": {...}, # 成功时的结构化数据 "error": None, # 失败时的错误信息 "latency_ms": 245 }这种契约先行的好处是:任何一层都可以被 mock 掉单独测试。测试编排层时,能力层返回预设的 mock 数据;测试能力层时,不需要启动模型。可测试性是分层交付最大的红利。
4.2 依赖方向:单向依赖,禁止反向调用
分层架构有一条铁律:依赖只能单向,上层依赖下层,下层绝不反向调用上层。模型层不知道能力层的存在,能力层不知道编排层的存在。
违反这条规则的典型症状是:工具函数里直接调用了编排逻辑,或者模型层里硬编码了业务判断。一旦出现反向依赖,分层就名存实亡了。
我判断一个 Agent 项目分层是否合格,有个简单方法:看能不能把某一层单独抽出来复用。如果模型层能被另一个完全不同的 Agent 项目直接拿去用,说明分层是干净的;如果抽出来发现到处是业务耦合,说明还是糊在一起的。
4.3 异常传播:每层只处理自己该处理的异常
异常处理是分层里最容易乱的地方。原则是:每层只处理自己能处理的异常,处理不了的向上抛,但抛之前要转换成上层能理解的格式。
模型层的异常(格式错误、超时)在模型层重试,重试失败后转成"模型调用失败"向上抛。能力层的异常(API 错误、限流)在能力层转成模型可读的错误信息。编排层收到异常后决定降级策略(换工具、告知用户、终止流程)。接口层负责把最终结果转成对外错误码。
这样每一层的异常处理逻辑都很清晰,不会出现"底层异常直接冒到顶层,用户看到一个看不懂的堆栈"的情况。
5. 落地时的真实坑:分层不是画架构图
5.1 过度分层的陷阱
分层虽好,但容易走极端。我见过一个项目,把 Agent 拆成了十二层,每层之间还要经过消息队列异步通信。结果一个简单的问答请求,链路走下来要几百毫秒的额外开销,调试时要在十几个服务之间跳来跳去。
分层的粒度要匹配团队规模和业务复杂度。小团队、单一业务场景,五层足够了,甚至可以把状态层和编排层合并。大团队、多业务线,才需要更细的拆分。分层的目的是降低认知负担和变更风险,如果分层本身成了负担,那就是过度设计。
我的建议是:先按五层起步,遇到具体的痛点再拆。比如发现记忆逻辑太复杂,再把它从编排层独立出来。不要一开始就追求完美的架构。
5.2 提示词该放哪一层
这是个高频争议点。我的答案是:提示词模板放模型层,提示词内容按用途分散。系统级的提示词(定义模型角色和输出格式)放模型层;业务级的提示词(定义具体任务)放编排层,作为参数传给模型层。
这样做的理由是:系统提示词是跨业务复用的,属于模型层的基础设施;业务提示词是随业务变化的,属于编排层的业务逻辑。混在一起会导致改业务提示词时误伤系统提示词。
5.3 状态管理的一致性难题
多步执行的 Agent 里,状态一致性是个硬骨头。比如 Agent 执行到第三步时失败了,前两步的副作用(比如已经调用了写接口)要不要回滚?
我的做法是:把有副作用的操作尽量后置,并且设计成幂等。读操作可以随便重试,写操作要么放在流程最后,要么带幂等键。同时,状态层要记录每一步的执行状态,支持从失败点恢复而不是从头重来。
5.4 观测埋点的最小集合
分层之后,埋点要覆盖每一层的边界。我总结的最小埋点集合是:每次模型调用的输入输出和 token 消耗、每次工具调用的参数和结果、编排层的每一步决策、状态层的读写操作、接口层的请求响应和总耗时。这些数据通过 trace_id 串起来,就是完整的执行链路。
没有埋点的分层,等于没有分层的黑盒。埋点是分层交付能真正发挥价值的前提。
6. 从分层到交付:一套可复用的检查清单
6.1 上线前的分层自检
在把 Agent 交付上线前,我会过一遍这份清单:
- 模型层:是否所有模型调用都经过统一接口?输出是否强制 schema 校验?重试策略是否明确?
- 能力层:每个工具是否有清晰的描述和参数 schema?异常是否被正确转换?写操作是否幂等?
- 编排层:主流程是否确定性可控?是否有明确的终止条件?降级策略是否定义?
- 状态层:三类记忆是否物理隔离?长期记忆是否有准入判断?状态是否可恢复?
- 接口层:对外契约是否稳定?是否有全链路 trace_id?错误码是否规范?
这份清单过一遍,能挡掉大部分上线后才会暴露的问题。
6.2 迭代时的分层影响分析
分层最大的价值在迭代时体现。当业务方提一个新需求,你能快速判断它影响哪几层:换个模型只动模型层,加个工具只动能力层,改个流程只动编排层。影响范围可控,是分层交付给团队带来的最实在的收益。
我在实际项目里有个习惯:每次需求评审时,先做一次"分层影响分析",明确这次改动涉及哪几层、每层改什么、层间契约要不要变。这个习惯让我们的 Agent 项目在半年迭代里几乎没有出现过"改 A 崩 B"的情况。
6.3 给团队的分层开发规范
如果要把分层交付推广到团队,我建议定几条硬规矩:跨层调用必须走契约、禁止反向依赖、每层必须有独立的单元测试、埋点必须覆盖层边界、提示词按用途分层存放。这几条规矩不需要多复杂,但能保证团队里每个人写的 Agent 都能拼到一起。
回到最开始那句话:AI Agent 工程化的核心,不是让模型更聪明,而是让系统可交付。分层交付就是实现这个目标最朴素也最有效的方法。别把智能糊进一锅,把它拆开,每一层管好自己的事,剩下的交给契约和测试。这套方法我在几个项目里反复验证过,越复杂的业务,分层的收益越明显。