AI Agent 防飘移:把概率的 AI,关进确定的流程
AI Agent 防飘移:把概率的 AI,关进确定的流程
序
上一篇《Agent开发Easy,But测试Max》里我吐槽了一件事:Agent 是个黑匣子,难测、会飘。
但有读者(其实是我自己扪心自问)追了一句:测出来它飘了,然后呢?
光知道它飘,治不好飘。就像体检报告告诉你血脂高,但你照样顿顿红烧肉,那这张报告除了让你焦虑,啥用没有。
所以这篇接着聊——Agent 越用越偏,到底怎么治。
结论:
别再指望"把 AI 调聪明"了。AI 是个概率系统,你调不死它的不确定性。真正的解法是——在 AI 外面套一层确定性流程,把它框死。
这篇就讲这套"框子"怎么搭。
先看清敌人:Agent 是怎么飘的
(病因上一篇讲过,这里快速复盘一下,没看过的补个课。)
最致命的现象:越用越偏
我们在 physical-ai 项目里实测,Agent 的问答准确率随会话轮次的变化是这样的:
准确率
100% ┤●
85% ┤ ●
70% ┤ ● ← 整体大概在这
55% ┤ ● ← 聊到 20 轮,已经没法看
└──────────────────
第1轮 5 10 15 20+
第一轮准得像模像样,聊到第二十轮开始答非所问、调错工具、参数乱填。用户体感最差的就是这条下滑线。
三个根因(都是使用方式的锅)
顺着代码扒了一遍,根因就三个,而且全是结构性的:
| 根因 | 人话 | 类比 |
|---|---|---|
| 工具太多 | 把几十个工具一次性全摆给 AI,让它自己挑 | 仓库 56 件工具倒地上,让新人自己选 |
| 记忆不清理 | 整个会话历史(旧话题、过期数据)全塞给它 | 开会一直记着半小时前的岔题 |
| 一心三用 | AI 同时干"选工具 + 填参数 + 写回复"三件不确定的事 | 边打电话边填表边写报告 |
注意,这三个根因,没有一个是"AI 太笨"造成的。是我们把它用错了——给了它太多自由,又没帮它清理战场。
为什么"调 prompt""打补丁"治不好
面对飘移,人的本能反应是:
- 把提示词写到几千字,苦口婆心"千万别选错工具"
- 哪个参数出问题,就给它塞一张"同义词对照表"
- 出问题的地方加个
if拦一下
这些手段,全都治标不治本。
治标(打补丁):
─────────────────────
提示词越写越长
同义词表越加越多
哪里漏风补哪里
→ 只能堵已知的洞
→ prompt 越长,AI 越迷
治本(换架构):
─────────────────────
承认 AI 是概率系统
在外面套确定性流程
把 AI 压缩成"只填表单"
→ 从源头让飘移没机会发生
→ 自由度越小,跑偏率越低
补丁思路最大的问题是:你永远补不完。今天堵住"中文"这个洞,明天它给你飘个"英文";你堵了 setLanguage,它去调 setVolume。AI 的飘移是概率分布的尾巴,你堵不过来。
所以方向得反过来——不是去教 AI 别犯错,而是构造一个"它想犯都犯不了"的结构。
核心思想:用确定性,对冲概率性
一句话:
AI 不当决策者,只当受约束的执行单元。
把 AI 的职责,从"自由发挥的万事通",压缩成"在规定格子里填值的填表员"。剩下的判断、收敛、校验、兜底,全部交给确定性的工程代码。
这不是我拍脑袋。这套思路是业界做商用级 AI 产品的共识:
- 字节 Coze
- 阿里 百炼
- OpenAI Swarm
- Google Dialogflow
它们做企业级 AI 产品的底层范式都一样:意图识别 + 流程编排 + 表单填充。
这不是实验性想法,是被大规模商用验证过的成熟套路。
六道确定性防线
用户每说一句话,都先过六道关卡。关卡由工程代码把关(不靠 AI 自觉),跑偏在到达用户/设备之前就被拦下。

下面逐道拆。每道都讲清楚两件事:为什么需要(WHY) + 怎么做(HOW)。
第 1 道 · 意图边界:问什么答什么
WHY:不声明边界,AI 会对任何问题都"硬答"。你让它管设备,它连"今天天气怎么样"都想给你扯两句设备数据,硬凑。
HOW:系统提示词里把能力边界写死——"你只会设备管理"。超出范围的问题,礼貌拒答,不允许调用设备工具去凑答案。
系统提示词(节选):
你的能力范围:仅限【会议设备管理】(开关机、音量、信号源、语言)。
超出范围的问题(如查天气、写代码、闲聊),直接回复:
"这个问题我帮不上忙,我主要负责会议室设备控制。"
绝不允许调用设备工具来回答非设备问题。
这道关治的是答非所问。
第 2 道 · 意图分诊 + 工具收敛:别让它在一堆工具里挑花眼
WHY:工具越多,选错概率越高。前面说了,几十个里挑,远不如 3~5 个里挑准。
HOW:先用一次轻量分类,判定"用户到底要干嘛",再把对应业务的 3~5 个工具装给它。像医院分诊台,先领号再排队,别让病人自己满大楼乱窜。
// 伪代码
Intent intent = intentClassifier.classify(userMessage); // "设备控制·语言切换"
List<ToolCallback> tools = toolRouter.routeByIntent(intent);
// 只给 setLanguage、resolveDeviceOrGroup 这两个工具,其余全收掉
chatClient.prompt().toolCallbacks(tools)...
工具一收敛,AI 的选择空间从 N 选 1 降到 3 选 1,选对率立竿见影。
第 3 道 · 记忆隔离:别让旧话题串台
WHY:这是"越用越偏"的元凶。 历史不清理,旧话题会持续干扰当前判断。前 15 轮在聊查统计,第 16 轮让它切个语言,它脑子里还想着统计的上下文,能不串?
HOW:每次只带与当前问题强相关的最近几轮,无关的、过期的清掉。会话再长,AI 看到的都是干净短上下文。
// 伪代码:按当前意图裁剪上下文
ConversationContext ctx = dialogManager.loadContext(sessionId);
Intent cur = intentClassifier.classify(userMessage);
// 只保留和当前意图强相关的历史,无关的扔掉
List<Message> relevant = ctx.filterByIntent(cur).keepLast(5);
这道关直接把"越用越偏"那条下滑线拍平——会话长度不再影响准确率。
第 4 道 · 表单式填空:要的是精准裁剪,不是自由发挥
WHY:让 AI 自由生成参数,它会多填、少填、乱填。要的是"精准裁剪到必需字段"。
HOW:每个意图声明"我需要哪些字段",AI 只能填这些格子,缺必填项就反问用户(而不是自己瞎猜)。借助 function calling 的 schema 约束,参数被物理裁剪。
// 意图"语言切换"声明的表单
{
"name": "setLanguage",
"parameters": {
"type": "object",
"properties": {
"deviceKey": { "type": "string" },
"lang": { "type": "string", "enum": ["zh_CN", "en_US"] }
},
"required": ["deviceKey", "lang"]
}
}
AI 只能填这两个格子,多一个没有,少一个就回来问你。
第 5 道 · 参数质检 + 自动纠错:最后一道闸门
WHY:即使用了表单,AI 仍可能给你填个 lang: "English" 而不是协议要的 en_US。表单约束了"有哪些字段",约束不了"值对不对"。需要最后一道闸门。
HOW:参数下发前自动归一(中文/English → en_US)、校验范围(音量 999 直接拦截)。错了就把正确选项告诉 AI,触发自动重填。
// 伪代码:参数归一 + 校验
String lang = normalizeLanguage(rawLang); // "English" → "en_US"
validateRange("volume", volume, 0, 100); // 999 → 抛异常,带正确范围提示给 AI 重填
这道关治的是参数乱填,目标是0 非法值下发。
第 6 道 · 高危确认 + 兜底:最后的人肉保险丝
WHY:删除、重置这种不可逆动作,光靠前面五道还不够,必须有人工兜底;系统异常时还得能自保。
HOW:高危动作生成"红色确认按钮",用户点了才执行;飘移率超阈值时自动降级为只读模式。(本项目已经有一套 @DangerousOperation 的确认机制,这里直接复用加强。)
走一遍真实案例
光说设计不直观。拿一句话端到端走一遍。
📎 用户输入:「把会议室那台大屏切换成英文」 ⏱ 背景:这是该会话第 16 轮,前 15 轮在聊别的(查统计、发消息)。
新架构下,这句话的完整旅程
1. 用户输入:「把会议室那台大屏切换成英文」
│
2. ┌─ 关卡① 意图边界 ──────────────────────────┐
│ 判定:属于"设备管理"范围 → 放行 │
└──────────────────────────────────────────┘
│
3. ┌─ 关卡② 意图分诊 + 工具收敛 ───────────────┐
│ 意图 = "设备控制·语言切换" │
│ 只装载 2 个工具:resolveDeviceOrGroup, │
│ setLanguage │
└──────────────────────────────────────────┘
│
4. ┌─ 关卡③ 记忆隔离 ─────────────────────────┐
│ 检测到新意图(前 15 轮是别的任务) │
│ → 清掉无关历史,只带本轮 + 用户长期偏好 │
└──────────────────────────────────────────┘
│
5. ┌─ AI 调用(只剩这点自由度)─────────────────┐
│ 在 2 个工具里选 → 调 resolveDeviceOrGroup │
│ ("会议室那台大屏") │
│ 解析:唯一设备命中 → deviceKey=TEID_88FA │
└──────────────────────────────────────────┘
│
6. ┌─ 关卡④ 表单填空 ─────────────────────────┐
│ 按语言切换表单填参: │
│ { deviceKey: TEID_88FA, lang: "English" } │
│ (只这两个字段,无多余) │
└──────────────────────────────────────────┘
│
7. ┌─ 关卡⑤ 参数质检 ─────────────────────────┐
│ "English" 自动归一为 "en_US" → 校验通过 │
└──────────────────────────────────────────┘
│
8. ┌─ 关卡⑥ 高危确认 + 下发 ──────────────────┐
│ 生成红色确认按钮 → 用户点击确认 │
│ → 下发精准指令 lang=en_US │
│ ✓ 设备切换成功 │
└──────────────────────────────────────────┘
零跑偏、零非法值、与会话长度无关。
作为对比:旧架构下,同一句话会怎样
旧架构(自由发挥) 新架构(流程管控)
───────────────────── ─────────────────────
输入 + 前 15 轮历史全塞 输入先过 6 道关卡
几十个工具全摆出,自己挑 意图分诊后只给 2 个工具
可能被旧话题带偏,选了别的工具 无关历史已隔离,不受干扰
参数自由填,lang 可能是 按表单填空,只填必需字段
"中文"/"English"/"english" 参数质检归一为 en_US
直接下发,设备不认 → 失败/异常 确认后下发,精准成功
→ 跑偏、失败、事故风险 → 精准、稳定、可控
一个关键洞察
数一数新架构里 AI 真正"自由发挥"的环节——只剩第 5 步(在 2 个工具里选)和第 6 步(填 2 个字段)。其余全是确定性工程把关。
自由度越小,跑偏概率越低。这就是"根治"的本质。
和"测试"怎么配合:一个闭环
回到上一篇的话题。这套防飘移架构,和【黑匣子对话测试工具】是配套的,不是二选一:

- 流程管控:从源头让飘移"没机会发生"。这是预防。
- 测试工具:持续盯着还剩多少飘移,告诉你哪一关在漏水。这是体检。
- 测试报告反过来告诉你下一版该加固哪道关——闭环。
光有预防不体检,你不知道防线到底顶不顶用;光有体检不预防,天天看着血脂高顿顿红烧肉。两个都得有。
最后
错误的执念: 把 AI 调聪明,让它自己别犯错
正确的姿势: 把 AI 关进笼子,让它没机会犯错
这两件事看着像,其实差了一个维度的工程量。
开发 Agent Easy,但让 Agent 商用级稳定,才是真正吃功夫的地方。Spring AI 帮你把 Agent 跑起来,可没人帮你把它框住——这六道防线,得自己一砖一瓦砌。
所幸,砌防线的全是 Java 工程师的老本行:拦截器、参数校验、路由、状态机、确认流程。打败不确定性的,从来不是更强的 AI,而是更扎实的工程。
下篇预告:等【黑匣子对话测试工具】和这套防飘移架构都跑稳了,我打算把"测试 + 防飘移"两个工具的开源/落地情况一起写一篇。Agent 这摊事,开发只是入场券,质量才是护城河。
