「应对能力」不是一个能力,是一堆具体毛病的集合:答非所问、丢了信息、重复追问、跟不上改主意、 一句话两个请求只接住一个。 这一节把六种手段拆开看,重点放在两个能立刻见效的:槽位追踪 + 主动追问,和错误处理。
上一节管的是「上下文里放什么」,这一节管的是「拿着这段上下文怎么做事」。 状态机与流程控制,17 课 编程原理 里讲过; 这一节讲的是把对话当成一个带状态的过程,而不是一串独立的问答。
一、针对性的数据集与微调
先别急着「提升能力」,先把毛病列出来。列不出来,就没有验收标准。 每一类毛病找二十条真对话,标出正确答法,进训练集。 这比再多喂一万条通用数据有效得多——通用数据治不了具体毛病。 标的时候要标「这一轮该做什么」,不只是「该答什么」。缺参数时该追问,比给一个猜出来的答案更对。
二、实时反馈与自适应性
三道闸:置信度低就换个说法重问,超时就给模板答案,连续两轮没进展就转人工。 阈值不要拍脑袋。拿一个月的线上日志,把「误转人工率」定在业务能接受的那个点上,再回头定阈值。 这三道闸的意义不在准确率,在兜底。多轮系统最怕的不是答错,是答错之后一直错下去。
三、强化学习优化
RLHF 在多轮上的难点是奖励稀疏:这一句答得好不好,往往要等三轮之后才知道。 两种常见处理:把奖励摊到每一轮(过程奖励),或者用偏好对做 DPO,绕开奖励模型。 这条路成本高,一般是有单轮基础之后才做。单轮都答不好,做多轮强化学习是浪费算力。
四、上下文感知与个性化
上下文感知是「知道现在说到哪了」,个性化是「知道这个人是谁」。两件事经常被混为一谈。 前者靠槽位和状态机,后者靠用户画像与历史偏好。两样都是工程,不是模型能力。 演示里的策略 B 和 C 就是上下文感知的最小版本:记住已收的槽位,只问缺的那个。 它不需要更强的模型,只需要一张表。
个性化的风险是过度。 给老客户省掉确认步骤很贴心,给新客户省掉就是事故——判断依据要能说清,否则不要自动做。
五、多模态与多任务
多模态是让模型看图、听声;多任务是让一个模型干几种活。两样都会让训练难度上一个台阶。 多任务的风险是互相打架:两个任务的输出格式不一样,混训会让格式变得不稳定,表现是「时好时坏」。 现在的通行做法是「一个基座 + 多个 LoRA」,用哪个挂哪个。比混训稳,也比混训省。
六、错误处理与恢复策略
三类失败要分开:答不出、答错、答跑题。三类的处理方式完全不同。 答不出就追问,或者承认不知道;答错要能被发现(给可验证的字段、给来源);答跑题靠意图识别拦下来。 恢复比预防更重要。 多轮对话里没有一次不错的系统,只有错了能拉回来的系统。评价一个系统, 看它出错之后第几轮能回到正轨。
七、动手 Demo:三个槽位分三轮给,看追问答几次才凑齐
场景:用户分三轮说出部位、预算、时间。系统要凑齐这三个槽位才能出方案。 对比三种策略:不记历史、记历史按缺追问、把槽位压成一句写进系统提示。 最后再看一句带两个意图的话会怎么丢。
SLOTS = [(u'部位', (u'外立面', u'楼顶', u'入口')),
(u'预算', (u'万', u'预算')),
(u'时间', (u'下周', u'月底', u'明天'))]
# 策略 B:每轮抽槽位,缺什么问什么
got = {}
for t in UTTER:
got.update(extract(t))
miss = [n for n, _k in SLOTS if n not in got]
print(u'、'.join(got), miss[0] if miss else u'(不用追问)')
# 策略 C:把已收槽位压成一句系统提示
st = u'已知:部位=外立面;预算=二十万;时间=下周。'
实跑输出:
--------------------------------------------------------------------
三个槽位分三轮给,看追问答几次才凑齐
--------------------------------------------------------------------
① 场景:一句话给一个槽位
--------------------------------------------------------------------
| 轮 | 用户说 |
|:---:|---|
| 1 | 外立面那块灯光太暗了,得改 |
| 2 | 预算大概二十万左右 |
| 3 | 下周就得进场,来得及吗 |
三句话分别给了部位、预算、时间。真机上还会夹着闲聊、反问、改主意。
② 策略 A:不记历史,每轮独立处理
--------------------------------------------------------------------
| 轮 | 输入 | 本轮抽到 | 系统追问 | 算无效 |
|:---:|---|---|---|:---:|
| 1 | 外立面那块灯光太暗了… | 部位 | 请问要改的是哪个部位? | 是 |
| 2 | 预算大概二十万左右… | 预算 | 请问要改的是哪个部位? | 是 |
| 3 | 下周就得进场,来得及… | 时间 | 请问要改的是哪个部位? | 是 |
每轮都抽到了当轮那个槽位,但一个都没留住——第三轮结束时手里还是空的。
追问 3 次,三次问的都是「哪个部位」。
! 这就是「无状态」的代价:每一轮都从零开始,用户被问了三次同一个问题。
很多「这机器人怎么这么笨」的抱怨,根子在这里,不在模型。
③ 策略 B:记历史,缺什么问什么
--------------------------------------------------------------------
| 轮 | 已收槽位 | 系统追问 | 还缺 |
|---|---|---:|:---:|
| 1 | 部位 | 预算大概多少? | 2 |
| 2 | 部位、预算 | 什么时候要进场? | 1 |
| 3 | 部位、预算、时间 | (不用追问) | 0 |
三轮凑齐 3 个槽位,追问 2 次,每次问的都是当时真缺的那个。
! 注意第二轮的追问问的是「预算」而不是「部位」——因为部位已经在第一轮拿到了。
追问的价值不在问,在于不问已知的。
④ 策略 C:把已收槽位压成一句写进系统提示
--------------------------------------------------------------------
| 策略 | 第 3 轮上下文 | 槽位 | 无效追问 |
|---|---:|---:|---:|
| A 无状态 | 16 | 0 / 3 | 3 |
| B 记住原话 | 50 | 3 / 3 | 1 |
| C 槽位压缩 | 77 | 3 / 3 | 1 |
A 的上下文最省,因为它只看当前这一句——省下来的都变成了无效追问。
C 比 B 多 27 token,那是「把槽位再写一遍」的成本。
! 轮数一多,这个差别就翻过来了——原话每轮涨,槽位只涨字段。十轮之后 C 会明显更省。
⑤ 一句话两个意图
--------------------------------------------------------------------
| 处理方式 | 识别到 | 完成 |
|---|---|:---:|
| 单意图处理 | 只接第一个 | 1 / 2 |
| 双意图处理 | 改方案、报价 | 2 / 2 |
这句话里有两个意图。单意图的处理只接住第一个,报价那个被静默丢掉。
! 用户不会说「我有两个请求」。一旦你只处理一个,他不会重问,他会觉得你没听懂。
所以「一句话多意图」这句检查要写进评测表。
①三句话分别给了一个槽位。真实对话比这乱得多:夹着闲聊、反问、临时改主意,而且用户经常一次给两个。 ②策略 A 不记历史:三轮都问「哪个部位」,三次全是无效追问。 很多「这机器人怎么这么笨」的抱怨,根子在这里,不在模型。 注意它每一轮其实都抽到了槽位(部位、预算、时间各一次),只是没存下来。抽得到和留得住,是两件事。 ③策略 B 记历史:追问两次,每次问的都是当时真缺的那个。 价值不在「问」,在「不问已知的」——这一点让人觉得系统在听。 ④三种策略一起看:A 的第 3 轮上下文最省(只有当前一句),省下的全变成了无效追问; C 比 B 多 27 token,那是把槽位再写一遍的成本。 轮数一多这个差别就翻过来:原话每轮都涨,槽位只涨字段。十轮之后 C 会明显比 B 省。 ⑤一句话两个意图:单意图处理只接住「改方案」,报价被静默丢掉。 用户不会说「我有两个请求」,他不会重问,他只会觉得你没听懂。 所以「一句话多意图」要写进评测表。它是那种不漏在指标上、只漏在体感上的毛病。
30 秒实验
- 给 SLOTS 加一个「联系人」,但三轮话里都没提。 看策略 B 会追问几次——这就是「必填项缺失」时系统卡住的样子。
- 把策略 B 的追问改成「一次把所有缺的都问一遍」,看重填次数从 2 降到 1, 但用户的认知负担变成多少?
- 改
extract(),让它在同一句里抽两个槽位(比如「外立面二十万」),重跑策略 B。 看看实际需要几轮。
常见误区
- 以为换个更大的模型就能解决。 无状态的问题换多大模型都一样——它连上一句说了什么都不在输入里,再强也没用。
- 只做预防不做恢复。多轮系统一定有出错的时候,没有回退、没有转人工,一次失败就丢掉整个会话。
- 把个性化做成「猜」。猜错了比不问更糟:客户以为你记着,其实你记的是别人的。 凡是自动跳过的步骤,都要能说清依据。
自测三问(点开对答案)
1. 为什么「不记历史」的系统会重复追问同一个问题? —— 因为它每一轮都从零开始,输入里只有当前这一句。 上一轮拿到的部位不在输入里,它就会认为还缺,于是再问一遍。这不是笨,是信息没给到。 2. 槽位压缩为什么轮数越多越划算? —— 因为原话的长度随轮数线性涨,而槽位只涨字段名。十轮之后原话上千 token,槽位还是那几十个。 交叉点通常在五六轮。 3. 「一句话两个意图」为什么容易漏? —— 因为大多数系统按「一次一个意图」设计,第二个意图既不报错也不进日志。 用户不会重问,只会觉得没被听懂,所以你连 badcase 都收不到。手段讲完了,下一节回答那个绕不开的问题:怎么知道它到底变好了没有。
- ☐ (本篇完,下一篇 4.5 模型评估与持续改进)
相关笔记
- 17 课 编程原理:状态机与流程控制
- 存历史:槽位与摘要怎么选
- 42 课 图解人工智能:强化学习与人类反馈
- 🦙 Llama 大模型实践 · 索引