对话系统与多轮管理
本节定位
很多人一做聊天应用,第一反应是:
- 维护一个
history - 把历史一起塞给模型
这能做出最基础 demo,但离一个真正可用的对话系统还有很远。
这一节的重点, 就是把“多轮对话”这件事拆清楚。
学习目标
- 理解单轮问答和多轮对话系统的核心区别
- 理解会话状态、上下文窗口、澄清问题这些基本概念
- 看懂一个最小多轮对话管理器
- 明白为什么对话系统的关键不只是记历史,而是管状态
先建立一张地图
多轮对话这节最适合新人的理解顺序不是“把历史都塞进去”,而是先看清:
所以这节真正想解决的是:
- 多轮系统为什么比单轮问答难
- “有历史”为什么不等于“有状态”
一个更适合新人的总类比
你可以把多轮对话系统想成:
- 一个客服在和用户持续聊天
用户不会每一轮都把背景重新讲一遍。
所以客服必须记住:
- 现在聊的主题是什么
- 哪些关键信息已经知道
- 哪些信息还缺
如果只把聊天记录全堆在旁边,但没有真正整理状态,
这个客服还是会很容易答乱。
一、为什么多轮对话比单轮问答难得多?
1.1 单轮问答更像“一问一答”
例如:
- 用户问一句
- 系统回一句
这类系统即使没有长期状态,也能工作。
1.2 多轮对话真正难在哪?
因为后续轮次经常会省略信息:
- “退款政策是什么?”
- “那我已经学了 30% 还能退吗?”
第二句里的“那我”其实默认继承了第一轮主题。
如果系统不记得前文,就会理解不完整。
所以多轮对话真正难的地方不是“消息变多了”,而是:
上下文依赖和状态延续。
二、一个对话系统通常至少要管什么?
最少通常要管:
- 会话历史
- 当前主题
- 用户澄清信息
- 是否需要追问
也就是说,对话系统不仅要“生成回答”,还要管理:
这段对话现在处于什么状态。
三、一个最小对话管理器示例
def new_session():
return {
"history": [],
"topic": None
}
def add_turn(session, role, content):
session["history"].append({"role": role, "content": content})
session = new_session()
add_turn(session, "user", "退款政策是什么?")
add_turn(session, "assistant", "你是想看时间范围,还是资格条件?")
print(session)
3.1 这段代码虽然很小,但它已经在教什么?
它在教你:
- 对话系统天然就有状态
- 状态至少包括历史和当前主题
这就是从“单次调用模型”走向“对话系统”的第一步。
3.2 再看一个最小“状态流”示例
state = {
"topic": "refund",
"slots": {"progress": None},
}
user_message = "我已经学了 30% 还能退吗?"
if "30%" in user_message:
state["slots"]["progress"] = "30%"
print(state)
这个示例很适合初学者,因为它会帮助你看到:
- 对话系统真正要保留的,不只是原话
- 还包括结构化的状态
四、对话系统不是只会回答,还要会追问
4.1 为什么追问能力很关键?
因为用户输入很多时候是不完整的。
例如:
- “帮我查天气”
这时系统如果直接瞎猜城市,体验通常会变差。
更合理的做法是:
先补齐缺失信息。
4.2 一个最小追问示例
def dialog_step(session, user_message):
add_turn(session, "user", user_message)
if "天气" in user_message and "北京" not in user_message and "上海" not in user_message:
reply = "你想查哪个城市的天气?"
add_turn(session, "assistant", reply)
return reply
reply = f"系统正在处理:{user_message}"
add_turn(session, "assistant", reply)
return reply
session = new_session()
print(dialog_step(session, "帮我查天气"))
print(session["history"])
这已经体现出一个很关键的能力:
对话系统不只是答,还要能管理信息缺口。
4.3 为什么追问其实是一种“系统更稳”的表现?
很多新人会误以为:
- 系统越少追问越聪明
但真实产品里,很多时候恰恰相反:
- 先追问把条件补齐
- 往往比直接乱猜更可靠