写在前面 辣椒AI 最早是一个单体 Agent:提示词很长、工具很多、自主规划。跑了一段时间之后我们重构了它,做成了五层结构。这篇把每一层的职责和设计决策摊开来写。
一、从单体到分层
最初的架构很简单:
用户输入 → 一个全能 Agent(长提示词 + 多工具 + 自主规划) → 回复
在生产环境跑了真实流量后,几个问题越来越突出:
- 同一个输入两次运行路径可能不同,用户反馈不稳定
- 工具多了以后乱调、漏调、重复调用
- 出错时无法定位是哪一步崩的
- 优化一个能力(比如检索),可能影响其他能力
这些问题的根因都一样:职责太集中。 一个环节承载了太多不同的决策,改了 A 就会影响 B,测不全也防不住。
分层不是一个"好"设计,是一种"不得已"
每次有人问"你们的 Agent 架构设计为什么是分层的",我们的回答都一样:不是设计出来的,是撞出来的。
第一版上线后,连续三次线上故障(一次幻觉、一次工具超时、一次检索结果为空)的排查都极其痛苦——因为单体 Agent 的每一步都没有明确的输入输出边界,排查就是大海捞针。第三次故障处理完后,我们决定在下一版重构中把链路拆开。
这不是教科书上那种"你应该这样做架构"的设计,是"不拆开就管不住"的生存需求。
二、分层
请求
↓
层① Profile 路由 ←── 这个请求属于哪一类任务?
↓
层② 检索 ←── 从知识库里召回相关内容
↓
层③ 上下文构造 ←── 把检索结果、历史对话等拼成完整提示词
↓
层④ 模型推理 ←── 调用模型生成回复
↓
层⑤ 质量门控 ←── 回答合格吗?引用存在吗?
↓
回复(或拦截/降级)每一层只管一件事,输入输出结构化。下面逐层展开。
三、层①:Profile 路由
这一层的职责是把用户输入归类,决定后续走哪条处理路径。
归类依据包括:用户角色、所选的模式、历史交互特征、输入本身的内容特征。不靠模型自由规划。
目前的做法是为每一类请求定义好处理路径——用什么检索策略、用什么提示词模板、用什么模型、怎么校验输出。代价是灵活性下降,收益是行为可预测、成本可估算。
为什么不用模型做路由
用模型判断"这个请求应该怎么处理"看起来更灵活——模型可以处理你没见过的请求类型。
但问题是:
- 路由判断本身需要一次模型调用,增加了延迟
- 同一个请求两次可能被路由到不同的 Profile,结果不一致
- 新上线的 Profile 需要模型"学会"识别,过渡期不精准
我们选择规则 + 特征匹配的路由方式。规则能覆盖当前已知的任务类型,未知类型有兜底 Profile。新 Profile 上线时,路由规则同步更新——这是一个显式的、可测试的变更。
路由决策的三个层次
- 用户角色:管理员和企业用户的默认处理路径不同
- 选择的模式:用户选了"知识问答"模式还是"内容创作"模式
- 内容特征:输入本身的内容决定了走哪条路径
分层的好处是:下层不需要知道上层为什么把它归类到这里,它只需要知道"我是属于这个 Profile 的请求,我的处理规则如下"。
四、层②:检索
不是所有请求都需要检索。这一层判断是否需要、需要什么类型的检索、以及检索策略。
不同类型的请求,检索策略不同:
- 知识问答:语义检索(向量) + 关键词检索(BM25),两种结果融合排序
- 表格数据查询:需不需要查关系数据?
- 多材料综述:需要跨文档检索,对去重的要求更高
检索结果是一份结构化列表,每条包含:片段 ID、内容摘要、来源、权重、权限标记。
混合检索的融合排序
语义检索和关键词检索各有优势场景。融合之后需要对两边的结果合并去重并重新排序。
我们的做法是 Reciprocal Rank Fusion(RRF)——它不关心具体分数的量级,只需要知道每个结果的排名位置。语义结果排第 1 和关键词结果排第 3 的在 RRF 里得到类似的融合分,避免了"语义检索的分数是 0.92、关键词检索的分数是 12.5"这种不可比的问题。
五、层③:上下文构造
把检索结果、用户历史交互、当前问题、用户偏好设置拼成一个完整的提示词。
这里的核心约束:
- 控制长度——留给回答的空间要够。总长度等于拼接的上下文加上模型最大输出长度,必须保证回答不会被截断。
- 保持结构——而不是把所有内容揉成一段长文本。检索结果按来源分组、历史对话按时间排列、偏好设置放在末尾。
- 权限信息在检索层已经过滤——这一层只负责组装,不负责筛选。
上下文预算管理
对于不同 Profile,上下文的分配策略不同:
- 知识问答:检索结果占 60%,历史对话占 20%,偏好占 10%,余量 10%
- 多材料综述:检索结果占 80%,其他占 20%
- 简单对话:检索结果占 30%,历史对话占 50%,余量 20%
这个预算分配不是死的,会根据实际的 token 计数动态调整。
六、层④:模型推理
调用模型生成回复。这一层的复杂度不在调用本身(换模型厂商只是个配置切换),而在于:
- 流式输出控制——逐 token 推给前端,前端做打字机效果
- 超时与降级——超时后不是返回报错,而是返回"正在处理中,稍后查看"的提示
- 模型级错误处理——配额耗尽、内容安全拦截、输出截断,每种有各自的处理方式
不同 Profile 可以使用不同规格的模型——简单任务用小模型,复杂推理用大模型。这是分层架构最直接的收益之一。
七、层⑤:质量门控
这是最后一道防线。从多个维度检查回复质量:
- 引用校验——引用的片段 ID 是否存在?内容是否真实?是否越界?
- 事实一致性——回答和检索到的证据是否矛盾?
- 格式完整性——结构化输出是否符合预期格式?
- 敏感内容——有没有泄露不该说的信息?
不通过校验的回答会被标记,根据 Profile 配置决定是重新生成还是直接拦截。
质量门控的"先记录、后拦截"策略
新的校验规则上线时,先只在观测模式下记录,不拦截。跑一段真实流量后分析误伤率,再决定是否正式上线为拦截模式。这个策略让我们避免了很多次"一条规则上线后大量正常回答被拦截"的事故。
关于这套可观测能力的实现,详见《Agent 可观测性:用三张表回答"它当时到底做了什么"》。
八、每层独立可观测
每一层都产生结构化日志,记录输入、输出、耗时和状态。串联请求标识可以看出整个链路。
这带来的实际收益:
- 排障时能精确定位是哪一层的问题——不需要在大段文本日志里 grep
- 优化时能看出瓶颈在哪——是检索慢还是模型慢,数据不会骗人
- 成本分析时能按层、按 Profile 统计消耗——哪些请求最贵、哪些步骤最耗 token
九、分层的代价
没有免费午餐。分层带来了:
- 延迟增加。五层意味着五次内部串行处理,即使每层都很快,串在一起也增加了整体耗时。
- 更多组件要维护和监控。每一层都是一个独立的代码模块,有自己的配置、日志和错误处理。
- 错误会传递。上一层的小偏差到下层可能被放大。这是为什么我们在每层之间要求结构化的输入输出——便于做中间态的校验和告警。
延迟控制
五层的端到端延迟目标在 2 秒以内(不含模型推理时间)。主要通过:
- 每一层内部做超时控制,不无限等待
- 检索层做并行查询(语义 + 关键词同时跑)
- 上下文构造是纯 CPU 操作,耗时控制在 50ms 以内
实测下来,检索层是最大的延迟来源(因为要等网络返回),其他三层都在几十毫秒级别。
十、这套结构适合谁
如果你在做一个需要稳定运行、需要排障、需要持续优化的 Agent 系统,分层结构值得参考。
如果只是一个原型或内部工具,单体 Agent 完全够用。不要为了分层而分层——我们分是因为被实际问题逼到了这一步。
想聊 Agent 架构设计,欢迎联系我们。
想在你的业务里试试同样的做法?
告诉我们业务问题与数据现状,我们会给出方案建议与落地路径。
需要帮助?
联系我们的团队,获取贴合你业务场景的方案建议。