本节摘要:对话不是自由聊天,是"有状态的过程"。本节讲清状态机模型(对话走到哪一步)、流程设计(主流程与分支)、以及"状态设计决定可控性"的核心理解——任务型对话的骨架。
阅读完本节,你应当能够:
"订机票的对话是怎么组织的?"——不是随机聊天,是一步步收集信息的状态过程:先问目的地、再问日期、然后确认。对话系统用"状态"跟踪走到哪一步——像表单的进度条,只是用对话填写。
状态设计是对话的骨架:

💡 关键直觉:状态 = "已收集什么 + 还缺什么"——状态设计对了,流程自然清晰:缺什么就问什么,齐了就推进。
| 要素 | 含义 | 例子 |
|---|---|---|
| 已收集 | 拿到了什么 | 目的地 |
| 还缺 | 差什么 | 日期 |
| 进度 | 走到哪步 | 确认阶段 |
| 分支 | 可跳转方向 | 改口回退 |
第一步 列信息:完成任务需要哪些信息 第二步 排顺序:按询问的自然顺序 第三步 画状态:每个信息是一个状态 第四步 补分支:改口、跳步、回退
⚠️ 常见坑:流程画成"直线"。真实用户会跳步、改口、答非所问——流程必须设计分支与回退,直线流程一遇意外就卡死。
信息是否覆盖完整 顺序是否自然 改口能否回退 异常是否有兜底
骨架搭好了,下一节加温度——角色与语气设计。
前文讲了状态的概念,这里给出任务型对话里最常用的落地形式——状态表 + 转移条件,用一个订餐例子从头到尾走一遍。
设目标是为餐厅机器人设计"点餐 → 确认 → 下单"流程,需要收集的信息有三个:菜品、份数、是否打包。先列一张状态表,每一行是一个状态,列清楚"进入条件"和"离开条件":
状态名 | 已收集 | 还缺 | 可转移动作 初始 | 无 | 菜品、份数 | 收到点餐意图 → 点餐收集 点餐收集 | 菜品 | 份数 | 份数齐 → 确认 确认 | 菜品、份数 | 打包偏好 | 用户确认 → 下单完成 下单完成 | 全部 | 无 | 结束 / 再点一份 → 回到点餐收集 异常回退 | — | — | 任何步骤用户反悔 → 回到上一步
这张表就是策略层的依据:DM 看到当前状态,查表就知道"接下来该问什么、能去哪"。关键设计点是转移条件要写清楚。比如"点餐收集"状态,用户说"来两份牛肉面"应直接同时填掉菜品和份数;用户说"那换面吧"则要识别成"改口"——覆盖更新菜品而不是新增一个订单。
下面给一个简化版的转移逻辑示意:
# 概念示意:根据状态表决定动作 def next_action(state): if "菜品" not in state["slots"]: return ask("请问您想点什么?") if "份数" not in state["slots"]: return ask("来几份?") if not state["confirmed"]: return confirm("两份牛肉面,打包吗?") return execute("下单")
这个函数虽然简单,但它体现了状态设计的全部价值:代码不用关心"用户前面说了什么",只看当前状态。复杂对话(跳步、改口、回退)最后都收敛为"状态表的转移",这就是状态设计让对话可控的原因。真实项目还会给每个状态配"超时返回"和"多次失败兜底",但骨架就是这张表。
拿前文"点餐 → 确认 → 下单"的流程,自己动手补全它的状态表和转移条件,再试着加一个"修改订单"的需求:用户完成下单后又想加一份菜。请在原状态表上新增状态或转移,并写明"加菜"的进入条件(下单完成状态下收到加菜意图)与数据更新方式(把已下订单的明细重新加载进点餐收集状态)。写完后检查两件事:一是改口/加菜不会重复扣费或重复下单——转移条件里要写明"更新已有订单,而非新建订单";二是所有状态都有出口(用户中途放弃怎么办)。把这张表画完,你就完成了任务型对话最核心的工程动作——流程设计。它比任何模型都更能决定系统能不能稳定办成事。