Skip to main content

对话助手 Agent 工程:从上下文装配到数据飞轮

这篇文档梳理一个对话式 Agent 的完整架构。目标系统需要联网取数、调用外部工具,并在多轮对话和跨会话之间保留用户信息。

有三个问题贯穿全文:上下文由哪个组件装配、记忆在什么时机读写、工具列表按什么规则裁剪。这三点决定了系统在功能持续增加之后是否仍然可控。线上大量「同一个问题问两遍、回答不一致」的偶发问题都源于此:某一轮的用户偏好因历史过长被裁掉,或者工具结果这一轮拼进 user 消息、下一轮拼进 system。

内容按一次请求的处理顺序组织:入口分诊、上下文装配、记忆读写、工具调用、深度研究编排、稳定性保障,然后是离线数据闭环,最后给出一份最小可用版本的取舍建议。全文共八张架构图,建议按顺序阅读,后文会反复引用前面的结论。

以下内容不在覆盖范围内:基座模型的训练与微调、RAG 的索引构建与切分策略、多模态输入的理解链路、安全与合规的策略设计。这几项各自都能单独成篇,这里只在与主链路交接的地方带过。

图中的 token 预算比例、轮次上限等数值均为示意值,用于说明各部分的相对量级,不是实测数据。

1 全景分层架构

请求进入后,编排层的第一个动作是分诊而不是回答。一类问题要求秒级响应(「现在还开门吗」),另一类允许较长耗时但要求覆盖完整(「六天行程怎么排」)。两者的延迟预算、状态管理和失败处理方式都不同,放在同一条链路上,响应快的那类会被慢的拖住。因此先在编排层把它们分开。

① 入口 ENTRY独立端 App全功能对话入口宿主 App 内嵌入口搜索 / 内容页 / 首页唤起多入口共用一套链路展示层差异化,编排不分叉HTTPS 请求 + SSE 流式回传② 接入 GATEWAY / BFF网关 / BFF鉴权 · 限流 · SSE 长连接 · 幂等会话路由 Session Routersession_id / conversation_id 落定策略 / 实验路由模型版本 · 场景策略 · 活动路由携带会话句柄与策略上下文进入编排③ 编排 AGENT ORCHESTRATION · 状态图编排Router意图分诊时效性 / 复杂度① 直答 · 经验型问答单轮 · 低延迟 · 内容侧检索② 工具型 ReAct 循环Think → Act → Observation③ Deep Research 多阶段图状态化任务编排(见 §07)Graph State任务树 · 中间产物引用表 · 节点进度跨节点上下文复用旁路小模型 · Suggestion追问推荐 · 独立裁剪后的 context window旁路小模型 · Topic会话标题 / 主题归类Query Rewrite · Safety改写补全 · 合规前置拦截编排节点调用底层能力④ 能力 CAPABILITYContext Builder拼装 · 裁剪Token 预算分配Tool Hub统一 RPC 工具协议注册 · 超时 · 重试Memory Service短期 / 会话 / 长期抽取 · 召回 · 遗忘Retrieval内容库 · POIWeb 搜索 · RerankCacheDR 结果缓存工具结果短 TTLmessages + tool schemas → 推理请求⑤ 模型 MODEL主对话模型(自研基座)对话 · 推理 · Tool CallingVLM 多模态理解图文笔记 · 截图理解旁路小模型Suggestion · Topic · 摘要器 · JudgeEmbedding / Rerank召回与精排读上下文 / 写轨迹⑥ 存储 STATE & STORAGESession StoreRedis · 热上下文TTL · 滚动摘要Conversation DB消息 · 工具轨迹卡片结构 · 反馈Long-term Memory用户偏好 · 事实卡置信度 · 时效Vector / Index记忆与内容向量Top-K 召回Trace StoreLangfuse Span离线消费入口可观测Langfuse + OTel模型调用 Span工具调用 Span核心节点执行链↓ 支撑Debug / Prompt 实验线上问题定位离线巡检取数稳定性监控告警限流保护降级兜底异常隔离↓ 目标核心链路按核心链路定义 SLOLLM Ops 干预活动路由Prompt 注入DR 缓存干预模型降级↓ 时效线上问题秒级处置↓ 面向产品 / 运营Playground版本 × 场景策略自由组合验效
图 1 · 七层结构。红色标出的是一次高时效提问真正会走满的那条链:入口 → 会话路由 → Router → ReAct → Tool Hub → 主模型。

左侧六层是请求实际经过的路径。右侧三块不在请求路径上,但每一层都会向它们写入 Trace,或从它们读取配置(策略、降级开关)。

职责关键决策
入口独立端与宿主 App 内嵌等多入口入口差异只体现在展示层与可用工具集,不分叉后端编排
接入 / BFF鉴权、限流、SSE 流式回传、会话句柄落定、策略与实验路由会话 ID 在这一层确定,后面所有上下文读写都以它为键
编排状态化任务编排,Router 分诊到三条链路延迟敏感与深度研究物理隔离
能力上下文装配、统一工具协议、记忆服务、检索、缓存Context Builder 是唯一的 prompt 装配点,避免各节点分别拼接
模型主对话基座 + VLM + 一组旁路小模型旁路模型独立 context window,不与主链路抢 token 预算
存储热会话、冷对话、长期记忆、向量、Trace热冷分离;Trace 单独一路,是离线飞轮的取数口

2 一次对话的完整时序

这张图说明各组件之间的调用时序。其中三处容易被忽略:记忆在第 ⑤ 步一次性读出,不由模型自行查询;工具结果在第 ⑪ 步二次拼回上下文,由此构成 ReAct 循环;旁路小模型在第 ⑭ 步于轮末异步执行,不阻塞首 token。

接入上下文装配推理 + 工具输出轮末异步客户端网关 / BFFContext Builder主对话模型Tool Hub存储 / 记忆旁路小模型① 用户消息 + session / conversation id② 校验会话 · 加载 Conversation 元信息(标题 / 策略版本 / 最近轮次)③ 会话状态 + 上一轮工具上下文指针④ 触发上下文装配⑤ 一次性拉取:长期记忆 + 会话摘要 + 近 N 轮消息⑥ 记忆卡片 · Rolling Summary · 历史消息⑦ 裁剪 · Token 预算⑧ messages + tool schemas⑨ tool_calls(可并行)⑩ 校验 · RPC · 超时降级⑪ Observation(规范化 · 截断)⑫ SSE 流式 token + 结构化卡片⑬ 落库 assistant 消息 + 工具轨迹 + Trace⑭ 异步触发⑮ 会话标题(Topic)· 追问推荐(Suggestion)—— 独立小 context,不占主链路预算⑯ 异步摘要压缩 + 长期记忆抽取
图 2 · 单轮时序。实线为调用,虚线为返回或异步回调;自环表示节点内部处理。

用户感知到的延迟从「输出」阶段开始计算,因此 ①–⑧ 必须在首 token 之前完成,⑬–⑯ 全部推迟到轮末异步执行。⑨–⑪ 是可重复的循环,循环次数由 Reflect 判定,或在达到上限后强制终止。

为什么记忆不做成工具让模型自己查

做成工具意味着模型需要先判断是否查询、再执行查询、再生成回答,多出一整轮往返,首 token 延迟大约翻倍。而用户偏好在多数轮次中都会用到,为少数轮次牺牲全部轮次的延迟并不划算。因此长期记忆在装配阶段直接注入,只有需要精确检索历史事实的场景,才额外提供一个 recall 工具。

3 上下文装配与注入顺序

Context Builder 的输出是一串顺序固定的 message。这个顺序有明确依据:越靠前的内容越稳定,越靠后的内容越贴近当前这一轮的意图。收益是具体的:前几块跨轮不变,KV Cache 的前缀即可复用,同时降低成本和延迟。

上下文块 · 从上到下 = 拼装顺序来源注入时机变化频率预算(示意)System · 角色与安全边界人设 · 能力声明 · 拒答与合规��约束配置中心(可热更新)请求开始,跨轮固定版本级6%场景策略 / 活动路由片段LLM Ops 动态 Prompt 注入运维平台干预配置命中策略 / 活动时才注入策略级 · 秒级生效2%环境上下文 Env当前时间 · 定位城市 · 入口来源 · 端信息端上透传 + 请求头解析每轮请求进入时解析每轮1%长期记忆 Long-term Memory稳定偏好 · 事实卡片(带置信度)Memory Service · 用户维度会话首轮全量;后续增量 / 命中式低频(天级)5%会话摘要 Rolling Summary被挤出窗口的历史的压缩产物Session Store历史超出预算后才出现溢出时更新8%近 N 轮对话 History滑动窗口 · 含上一轮工具结论Conversation DB / Redis每轮装配,按剩余预算倒序裁剪每轮35%工具 / 检索观测 Observation规范化后的结构化结果,非原始 JSONTool Hub · Retrieval工具返回后二次注入(轮内可多次)轮内多次30%工具定义 Tool Schemas按意图裁剪后的工具子集,非全量列表Tool Registry每轮,由 Router 决定子集每轮(动态子集)10%当前用户输入 User原文 + Query Rewrite 补全结果客户端每轮最后拼入,紧邻生成位每轮2%输出约束 / 卡片协议结构化字段 · 引用格式 · 端上组件契约配置中心拼在序列末尾版本级1%拼装方向 ↓
图 3 · 上下文块的顺序、时机与预算。预算比例为示意值,用于说明压缩该压在哪两块。

红框表示每轮都会变化的部分,KV Cache 的可复用前缀到此为止;青框表示记忆来源;琥珀框表示一轮内可能多次追加的内容。比例为示意值,说明的是一个结论:History 和 Observation 占据主要开销,压缩应集中在这两块

装配完成后送进模型的序列大致是这个形状:

[
{ "role": "system", "content": "<角色定义与安全边界>" },
{ "role": "system", "content": "<命中的场景策略片段>" }, // 未命中策略时不出现
{ "role": "system", "content": "<Env:当前时间 / 定位城市 / 入口来源>" },
{ "role": "system", "content": "<长期记忆:命中的事实卡片 × N>" },
{ "role": "system", "content": "<会话摘要:Rolling Summary>" }, // 历史超出预算后才出现

{ "role": "user", "content": "<第 n-2 轮用户消息>" },
{ "role": "assistant", "content": "<第 n-2 轮回答>" },
{ "role": "user", "content": "<第 n-1 轮用户消息>" },
{ "role": "assistant", "tool_calls": ["<第 n-1 轮的工具调用>"] },
{ "role": "tool", "content": "<规范化后的 Observation>" },
{ "role": "assistant", "content": "<第 n-1 轮回答>" },

{ "role": "user", "content": "<当前轮输入,含 Query Rewrite 补全>" }
]

// tools: <按当前意图裁剪后的 schema 子集>
// 输出约束与卡片协议随 tools 一起下发

前五条跨轮不变,构成 KV Cache 的可复用前缀,这也是把它们排在最前面的原因。多数模型 API 只接受一条 system 消息,实际实现时这五块会按同样的顺序拼成一条,分开写只是为了说明各块的来源和生命周期不同。

预算不足时的裁剪顺序

  1. 优先压缩 Observation。 工具返回的原始 JSON 字段数量很大,地图、酒店、航班类尤其明显。先按白名单筛选字段、统一单位与时区,再按相关度保留 Top-K,最后才考虑硬截断。
  2. History 从最早的轮次开始丢弃,但保留结构。 被丢弃的轮次进入摘要器压缩为 Rolling Summary,否则用户追问「刚才你说的那家」时系统无法回答。
  3. 长期记忆不做全量注入。 按置信度和相关度召回若干条命中的事实卡片即可。
  4. System 与卡片协议不参与裁剪。 这两块缺失会直接导致输出格式错误,端上组件无法渲染。
旁路小模型的上下文是另外一套

追问推荐(Suggestion)、会话标题(Topic)这类任务运行在小模型上,上下文窗口远小于主模型,无法直接复用主链路的 messages。做法是为每个旁路任务单独定义一份最小输入:Topic 只需要前几轮的用户话术,Suggestion 只需要最后一轮问答和已展示的卡片类型。这份输入从主上下文投影得到,不与主链路共享。

4 记忆分层与读写时机

「上下文」和「记忆」在讨论中经常混用。两者的区分很明确:上下文是这一轮送入模型的内容,记忆是跨轮、跨会话持久化的内容。记忆分三层,每层的存活周期、存储介质、读取时机和写入时机都不同。

短期 · 轮内工作记忆 Turn / Working当前 user 消息工具 Observation中间推理 / Graph State本轮引用表存活:单轮(DR 场景内为一次任务) · 存储:进程内存 + Graph State · 轮结束即丢弃或落库会话级 · Conversation / Session近 N 轮消息窗口滑动窗口 · 热存 Redis摘要器(小模型)分段压缩 · 保实体Rolling Summary滚动更新 · 覆盖旧摘要超预算写回存活:会话生命周期(新建会话即隔离) · 存储:Redis 热 + Conversation DB 冷同时承载:会话标题(Topic 小模型产出)、本会话已用工具、用户在本会话内的临时约束("这次只看三亚")长期 · 用户级记忆 User-level稳定偏好口味 · 预算带 · 常驻城市行为与出行习惯同行人 · 节奏 · 忌口/无障碍事实卡片来源 turn · 置信度 · 时效 · 支持纠错与遗忘存活:跨会话长期 · 存储:Memory DB + 向量索引 · 冲突以「更新时间 + 置信度」消解每轮注入:近 N 轮 + 摘要会话首轮全量 / 后续命中式注入轮末同步落库异步抽取 · 去重 · 冲突消解
图 4 · 记忆的读写回路。左侧红色为注入路径(自下而上),右侧青色为沉淀路径(自上而下)。

左侧红色为读取路径,方向自下而上:长期记忆先注入,会话记忆随后。右侧青色为写入路径,方向相反,自上而下:轮内产物先落到会话层,会话内容再逐步提炼为长期事实。

这个方向不能反:模型在生成过程中不能直接写入长期记忆,不应保留任何这样的路径。否则一次幻觉就会永久污染用户画像,且事后难以定位。反映到用户侧,就是「越用越不懂我」。

读(注入)时机写(沉淀)时机失效方式
短期 / 轮内轮内即时,Observation 随工具返回追加不单独持久化,轮末随消息一起落库轮结束
会话级每轮装配时读窗口;窗口不足时补 Summary轮末同步写消息;超预算时异步触发压缩会话 TTL / 新建会话
长期 / 用户级会话首轮全量注入,后续按 query 命中式召回轮末异步抽取,经去重与冲突消解后入库用户主动删除 / 时效过期 / 置信度衰减

长期记忆写什么,以及怎么写

上表里「轮末异步抽取」这一步决定了记忆系统的实际质量,展开说明。

判定什么值得沉淀。 两个条件同时满足才写入:跨会话仍然成立,并且未来轮次用得上。「不吃香菜」两条都满足;「这次只看三亚」只在本次会话成立,属于会话级约束,写进长期记忆会让用户下次问别的城市时收到莫名其妙的限定。区分不了的时候倾向于不写,长期记忆的错误代价远高于漏记。

用受约束的抽取,不要让模型自由书写。 由小模型按固定 schema 输出结构化事实卡片,字段包括槽位、值、来源 turn、置信度、时效。schema 约束住输出,才能对结果做校验、去重和比对。让主模型用自然语言往记忆里追加文本,几轮之后就无法判断哪条是事实、哪条是推测。

冲突消解要有明确优先级。 同一槽位出现新旧冲突时,依次比较来源类型、更新时间、置信度:用户显式陈述优先于行为推断,新的优先于旧的,高置信度优先于低置信度。三者都相同才保留多值。

带时效的事实要能自动过期。 「下个月要去日本」是有效期很短的事实,到期后应当自动失效而不是永久留存。没有过期机制的记忆库会随使用时间不断积累噪声,而且这类噪声很难被发现。

每条事实都要能追溯和撤销。 记录来源 turn,用户说「我不是这个意思」时可以定位到具体记录并删除。这既是产品要求,也是排查记忆污染的唯一抓手。

5 会话数据实体模型

Session 和 Conversation 是最容易混用的两个概念。本文的定义是:Session 指一个用户在一台设备上的一段连续使用,对应登录态与活跃周期;Conversation 指一条对话线程,用户点击「新对话」即切换一条。一个 Session 下可包含多条 Conversation。长期记忆挂在 User 上,两者共享。

Useruser_id长期记忆 / 画像全局偏好设置跨端共享Sessionsession_id端 · 登录态活跃窗口 · TTL入口来源Conversationconversation_id一条会话线程标题(Topic)Rolling SummaryTurnturn_id一次问答回合意图 · 链路类型策略 / 模型版本Messagerole · content结构化卡片引用与出处用户反馈ToolCall / Spantool · argsresult · latency状态 · 重试次数Langfuse trace_id1:N1:N1:N1:N1:N长期记忆按 user_id 跨会话注入,不随 Conversation 隔离回归测试平台的 Transcript 即「一条 Conversation 下的完整 Turn 序列 + ToolCall 轨迹」,与线上实体结构同构,因此线上 Bad Case 可以直接转成�回归用例。
图 5 · 实体链条。Trace 挂在 ToolCall 与 Turn 上,是线上问题反查与回归用例转化的结构基础。

Trace 直接挂在 ToolCall 和 Turn 上,不单独设计一套结构。这样线上问题可以反向定位到具体轮次,并一键转换为回归用例。第 9 节的数据飞轮能够闭合,前提正是这里的结构一致。

6 工具协议与调用链路

统一工具协议要解决的不是「能否调通」,调通只是最低要求。它的价值在于新增一个工具不需要改动 Agent 代码:注册后即可使用,超时、重试、降级统一由 Hub 兜底,模型看到的 schema 由 Registry 生成。

用户输入"明早去古镇,会下雨吗"意图分诊时效性 / 是否需执行工具子集裁剪Registry 按场景出 schema主模型出参tool_calls(可并行多个)参数校验 / 归一化时间·地点·单位 → 补全默认值校验通过 → 提交调度Tool Hub · 统一 RPC 工具协议并发调度 · 超时 · 重试 · 熔断 · 鉴权 · 配额天气时段 · 降水 · 预警地图地理编码 · 距离路线规划多方式 · 时长POI营业 · 评分 · 排队酒店房态 · 价格 · 位置机票班次 · 余票 · 价格结果规范化 → Observation字段白名单 · 单位/时区统一 · 相关度 Top-K · token 截断 · 引用留痕降级兜底缓存结果 · 换源 · 无工具直答 + 显式说明超时 / 失败 / 熔断开启未满足 → 下一轮 ReAct(Think → Act → Observe)信息充分 → 终答终答生成SSE 流式文本 + 结构化卡片(端上组件渲染)
图 6 · 工具链路与 ReAct 回环。琥珀色为降级路径,业务节点对超时无感知。

图中有三个位置需要单独说明:

  • 裁剪发生在模型之前。 Registry 只提供当前场景相关的 schema。将几十个工具全量注入,一方面挤占上下文预算,另一方面显著提高误调用率。
  • 校验发生在 RPC 之前。 模型输出的参数不能直接采信。「明天下午」这类相对时间、指代不明的地名、缺失的默认值,都需要先归一化再发出。
  • 降级在 Hub 内部完成。 业务节点不感知超时,只会收到一个 Observation,必要时带上「数据暂缺」标记。

工具之间存在依赖时

图中画的是并行扇出,但这只适用于彼此独立的工具。实际场景里依赖链很常见:模糊地名要先经地理编码拿到坐标,才能调路线规划;「现在还开门吗」要先检索到 POI,拿到营业时间才能判断。这类调用无法并行。

三种处理方式,代价不同:

方式做法代价
交给模型多轮 ReAct模型先调 A,看到 Observation 再决定调 B通用,不需要预先枚举组合;但每跳一次多一轮模型往返,延迟线性叠加
Hub 内封装复合工具把「地名 → 路线」整条链封装成一个工具,内部串行对模型只暴露一个 schema,延迟最低,误调用率也最低;但每个组合都要单独开发维护
Hub 按依赖分层调度模型一次给出多个 tool_calls,Hub 解析参数引用关系,同层并行、层间串行兼顾通用性和延迟;但需要在协议里表达参数引用,实现复杂度最高

选择依据是调用频次:高频且组合固定的走复合工具,长尾组合走多轮 ReAct。分层调度只有在依赖组合足够多、且延迟确实成为瓶颈时才值得投入。

工具域典型意图时效性归一化重点失败兜底
天气明天出门要不要带伞小时级相对时间 → 绝对日期时段;定位 → 城市区县短 TTL 缓存 → 换源 → 说明「实时数据暂不可用」
地图 / 路线规划从 A 到 B 怎么走、要多久分钟级模糊地名 → 地理编码;出行方式默认值补全降级为距离估算 + 提示手动确认
POI这家店几点关门、附近有什么天级 + 实时状态店名消歧(同名连锁);营业状态与评分口径统一回落到内容库检索
酒店这个价位有什么住的分钟级日期区间校验(入住 < 离店);人数房型默认值不编造房态,返回「需跳转查询」卡片
航班周五飞的还有票吗分钟级城市 → 机场码;单程往返判定同上,强制走真实链路,禁止模型幻觉价格
内容检索有没有人推荐过、别人怎么评价天级Query Rewrite + Rerank降低召回阈值重试 → 空结果显式承认

表中酒店和航班两行的「不编造」是硬性规则,不是建议。价格、房态、余票这类信息一旦出现幻觉,用户要为此付出真实成本。

7 Deep Research 编排

三条链路中只有 Deep Research 是有状态的。它不是单次模型往返,而是在一张图上多次往返,节点之间通过一份共享的 Graph State 传递中间结果。

入口:复杂 / 多约束问题("带家人去某地,六天怎么安排")Planning拆解任务树确定检索计划Clarify关键槽位是否缺失缺 → 只反问一次Search并行 subquery × N内容库 · Web · POI/地图Reflect覆盖度 / 冲突检查是否需要补搜Summary分片摘要去重 · 引用对齐Report结构化报告卡片 + 出处需澄清 → 反问用户后重规划(至多一次,避免打断体验)信息不足 / 有冲突 → 追加子查询(受轮次与耗时上限约束)Graph State · 共享状态任务树 · 已检索片段与去重指纹 · 引用表 · 各节点进度与耗时 · 断点续跑锚点读 / 写Deep Research 结果缓存命中 → 秒级直出;LLM Ops 可失效 / 预热命中直出
图 7 · Deep Research 状态图。两条回边均需硬上限约束,虚线为节点与共享状态之间的读写。

难点集中在两条回边上。Clarify 回边决定是否反问用户,Reflect 回边决定信息是否充分、要不要继续检索。两条都必须设置硬上限:澄清最多一次,补搜同时限制轮次和总耗时。没有上限约束时,开放性问题会导致持续循环。节点之间只通过 Graph State 交换数据,断点续跑与上下文复用同样依赖它。

工具型 ReActDeep Research 图
触发单点、高时效、执行型(「现在还开门吗」)多约束、需综合(「六天行程怎么排」)
状态轮内无状态,靠 messages 累积显式 Graph State,可断点续跑
延迟预算秒级,首 token 优先可到几十秒,需过程态反馈(进度卡片)
失败处理单工具降级,不影响整体回答节点级重试 + 缓存兜底,最差退回普通问答
输出形态短答 + 卡片结构化报告 + 引用

8 稳定性与可观测

Agent 系统的可用性风险主要不来自服务宕机,而来自下游依赖过多、单点变慢即拖垮整体。一次回答可能串联 3 个工具调用、2 次模型往返和 1 次检索,其中任何一环的 P99 抖动,在用户侧都会放大成明显的等待。

失效点探测处置用户侧表现
单工具超时Hub 内超时计时 + 熔断器短 TTL 缓存 → 换源 → 剔除该 Observation回答仍生成,显式标注该项数据暂不可用
主模型超时 / 过载首 token 超时 + 队列水位降级到备用版本或小模型,必要时关闭工具能力走直答回答变简,但不空白
Deep Research 卡死节点耗时上限 + 总时长上限缓存兜底 → 用已有片段出局部报告 → 退回普通问答给出阶段性结论而非无限等待
流量突增网关 QPS / 并发限流按入口与场景分级限流,保住主入口低优场景排队或降级
异常会话单会话 token / 工具调用配额异常隔离,单会话熔断不影响集群该会话被限制,其他用户无感
效果退化线上巡检抽样 + LLM 评估器秒级干预:Prompt 注入、活动路由、模型降级、缓存失效问题在小时级而非发版周期内收敛

Span 分三层。 最外层是 Turn,覆盖一次问答的整体;中间层是 Node,记录 Planning、Search、Summary 各自的耗时;最内层是 Call,定位具体是哪次模型往返或哪个工具 RPC 变慢。Debug 工作台、Prompt 实验和线上排查共用这三层数据,下一节的离线飞轮也从这里取数。

降级本身必须可观测

上表里的每一条处置路径都会正常返回结果,因此在成功率、错误率这类指标上完全看不出来。系统可以长期运行在大面积降级的状态下,监控面板保持全绿,而用户实际收到的一直是简化过的回答。

三个要求:每条降级路径独立计数;Trace 上标记本次是否降级以及降级原因;降级率进主监控面板并单独设告警阈值,与可用性指标同等对待。排查时先看降级率,再看错误率,顺序反过来会浪费很多时间。

9 离线数据飞轮

线上运维、回归测试、标注评测经常被当作三套独立系统分别建设。放在同一张图上可以看出,它们是同一条流水线上的三个工位:线上产生 Trace,从 Trace 中筛出 Bad Case,Bad Case 转化为训练数据,训练出的新版本必须通过回归卡点才能回到线上

在线侧 ONLINE线上对话链路对话 · 工具 · Deep ResearchTracingLangfuse + OpenTelemetry 全量 Span线上巡检规则过滤 · 定时抽样 · LLM 评估器打分Bad Case / 负样本池低质 · 错误 · 工具误调用全量抽样命中LLM Ops 线上干预活动路由 · Prompt 注入 · 缓存 · 降级秒级处置AI Agent 自动化回归测试平台Task / Trial / Grader / Transcript 标准结构 · 灰盒全流程轨迹校验确定性校验 + LLM as Judge 双层 · 分级回归用例集 · PR 与发版卡点灰度发布小流量 · A/B 验证Playground 策略组合验效通过全量发布 → 回到线上,进入下一圈未通过 → 阻塞发布 · 回炉离线侧 OFFLINE标注评测平台V1 通用对话 / V2 ReAct 轨迹训练数据多轮 SFT 轨迹 · RL 负样本 · 仿真冷启Post-Training主模型 / 垂类 Agent 迭代候选模型版本待验证,尚未上线低质样本流入人工标注与偏好采集发版前强制回归
图 8 · 数据飞轮。中间红框是唯一闸门,左侧运维干预是一条不改模型的止血旁路。

上半圈是「线上 → 数据」,下半圈是「数据 → 模型」,中间的红框是唯一的闸门:新模型未通过回归即无法上线。左侧的运维干预是一条旁路,它不修改模型,只调整线上行为,用于在下次发版之前先控制影响面。

工位输入输出
线上运维平台(发现 + 止血)Trace、规则、抽样策略巡检结论、干预配置(秒级生效)、策略组合验效结果
标注评测平台(把问题变成数据)Bad Case、训练中间数据、仿真样本人类偏好数据、多轮 SFT 轨迹、RL 负样本
自动化回归平台(防止退化回到线上)候选模型版本 + 分级用例集通过 / 阻塞结论、轨迹与提示词一致性校验报告
为什么回归不能只看最终答案

最终输出的文本可能只是恰好正确。工具调用有误、Prompt 拼接时丢失字段、检索结果未进入上下文,这些问题在只比对最终答案的黑盒测试中完全不可见,换一个 case 就会暴露。

因此校验需要下沉到轨迹层:工具的调用顺序、Agent 出参与模型入参的一致性、生成内容与检索结果的合规性。可以固化的部分用确定性断言,无法固化的交给 LLM as Judge。这也是第 5 节要求 Transcript 结构必须与线上实体保持一致的原因。

10 最小可用版本

前面九节描述的是完整形态。多数系统不需要一次建齐,下面是按依赖关系拆出的骨架与可后置项。

必须先有的四块。 缺任何一块,后面每加一个功能都会成倍增加返工成本:

  1. 单一的 Context Builder。 哪怕它只做「system + 近 N 轮 + 当前输入」这一件事,装配点唯一这个约束要从第一天立住。事后再把散落各处的拼接收拢,是这套系统里最贵的重构。
  2. 会话级记忆。 滑动窗口即可,摘要可以后补。没有它,多轮对话根本不成立。
  3. 工具注册表加参数校验加超时兜底。 即使只接两三个工具也要走这套,否则第四个工具接入时一定会绕过它。
  4. Turn 级 Trace。 没有 Trace,线上问题只能靠复现,而 Agent 的问题大多复现不了。

可以后置的部分,以及判断什么时候该补:

能力什么时候补触发信号
长期记忆复访率起来之后用户开始抱怨「每次都要重新说一遍」
Rolling Summary长会话占比升高之后出现因超窗导致的上下文丢失
分诊 Router链路超过一条之后快慢请求互相拖累,P99 被少数慢请求拉高
Deep Research工具型 ReAct 明显不够用之后出现大量多约束、需综合的问题,ReAct 转很多圈仍答不全
旁路小模型主链路预算紧张之后追问推荐、会话标题挤占主模型上下文
自动化回归平台用例攒到几十条之后人工回归开始成为发版瓶颈,或出现重复的退化问题

顺序上有一条硬约束:回归能力要在开始频繁迭代模型之前具备。用例可以慢慢攒,人工执行也可以,但「改了什么、有没有退化」这个问题必须一直有答案。等到已经退化了才开始建,你会缺少判断基线。

11 六条通用结论

以下几条在其他业务场景中同样成立:

1. 分诊放在链路最前端。 用一条链路兼容所有意图,最终会由最慢的路径决定全部请求的延迟。

2. prompt 的装配点只能有一个。 多个节点各自拼接时,没有任何一处掌握 token 的全局用量。线上迟早出现「特定条件组合下超窗」的问题,且难以复现。

3. 上下文按稳定性排序,而非按重要性排序。 重要性决定内容是否保留;稳定性决定 KV Cache 能否复用,后者直接对应成本和延迟。

4. 记忆的写入必须异步且单向。 模型可以读取,但不能在生成过程中写入。把抽取放在轮末的独立通道,才有位置做去重、冲突消解和信息过期处理。

5. 模型负责提议,系统负责裁决。 参数校验、鉴权、超时和降级全部在系统侧完成。Lobster0 中的权限模型是同一原则的另一种表达。

6. 缺少灰盒回归,迭代就失去了确定性。 答案正确不代表过程正确。只看最终结果的评测,往往要到下一个版本才会发现上一个版本引入的问题。EvalHub 中「跑失败和做错了必须分开记录」说的是同一件事。

相关阅读