02 对话状态管理
学习理念:DialogueState 是这个系统的心脏——每一条用户消息进来,第一件事就是加载状态;处理完毕,最后一件事就是保存状态。理解 DialogueState 的四组属性(用户任务上下文、系统任务上下文、聚焦对象、会话历史),你就理解了整个系统的数据模型。后续读所有代码时,都可以回到这里看数据结构定义。
海外对标:Rasa Tracker(对话跟踪器)、Google Dialogflow Session
本节 AI 替代率:~60% | 人工干预率:~40%
| 角色 | 能力范围 |
|---|---|
| 🤖 AI 擅长 | 生成 DialogueState 序列化代码、类关系图 |
| 👤 人类需理解 | 为什么需要用户任务 vs 系统任务分离、Session/Turn 的生命周期 |
📌 原文说明:以下内容来自
5.设计文档/对话状态设计.md,原文全部保留。这是理解所有代码的前提,建议在阅读任何代码文件之前先读完本篇。
📖 阅读优先级
| 等级 | 章节 | 说明 |
|---|---|---|
| 🔥 必须深入 | 2. 用户任务上下文 | 核心——理解 active_task vs paused_tasks 的切换 |
| 🔥 必须深入 | 5. 会话历史 | 核心——理解 Session/Turn 机制 |
| 🟡 理解即可 | 3. 系统任务上下文 | 知道 7 种子类型的存在即可 |
| 🟡 理解即可 | 4. 聚焦对象 | 知道前端传对象消息时怎么处理 |
| 🟢 了解即可 | 6. 处理中的轮次 | 两步提交流程,防止异常写入 |
1. 概述
DialogueState 是对话系统的核心数据结构,记录了与某位用户的完整对话上下文。它以 sender_id 为键持久化存储,每次消息处理前从数据库加载,处理完毕后存回。整个处理过程中,所有对话逻辑的读写都发生在同一个 DialogueState 实例上。
DialogueState 的属性分为四组:用户任务上下文、系统任务上下文、聚焦对象和会话历史,每组属性指向一类子结构。
🔥【P0 必须理解】 下面这张图是整个数据模型的核心。建议你把它当作阅读其他代码时的"地图"——随时回来查找字段含义。
以下各节依次说明四组属性及其子结构的设计意图。
2. 用户任务上下文
🔥【必须理解】 核心机制:同一时刻只有一个 active_task,被中断的压入 paused_tasks 列表。
active_task: TaskContext | None
paused_tasks: List[TaskContext]用户可以在对话中同时涉及多个业务任务,例如正在办理退款时临时插问一句物流状态。系统的处理方式是:同一时刻只有一个活跃任务(active_task),被中断的任务压入挂起列表(paused_tasks),新任务处理完毕后可恢复。paused_tasks 是一个有序列表,按挂起的先后顺序排列。
每个任务的执行进度封装在 TaskContext 中:
flow_id:标识正在执行的是哪条业务流程step_id:当前停在该流程的哪个步骤;None表示流程刚创建尚未推进slots:该流程已收集到的参数,随对话推进逐步填充
以退款申请流程为例,flow_id 对应配置文件中的流程名称 refund_request,step_id 对应流程内某个步骤的 id,slots 中的键来自该流程涉及的槽位定义:
# flow_config/user_flows.yml(节选)
slots:
order_number:
type: text
label: 订单号
refund_reason:
type: text
label: 退款原因
flows:
refund_request:
name: 退款申请
steps:
- id: start
type: start
next: ask_order_number
- id: ask_order_number # step_id = "ask_order_number"
type: collect
slot_name: order_number
response:
text: "请告诉我你的订单号。"
next: ask_refund_reason
- id: ask_refund_reason # step_id = "ask_refund_reason"
type: collect
slot_name: refund_reason
response:
text: "请简单说一下退款原因。"
next: refund_submitted
- id: refund_submitted
type: action
action: action_response
args:
text: "订单{{ slots.order_number }}的退款申请已提交,原因:{{ slots.refund_reason }}。"
next: end
- id: end
type: end用户说完"我要退款"之后,若已填写订单号、正在等待退款原因,此时 active_task 的状态为:
flow_id = "refund_request"
step_id = "ask_refund_reason"
slots = {"order_number": "123456"}slots 归属于 TaskContext 而非 DialogueState,因此不同任务的参数空间相互隔离——挂起某个任务时,其已填的 slots 完整保留,恢复时从原来的进度继续。
3. 系统任务上下文
🟡【理解即可】 系统任务是系统主动发起的交互——比如"我在收集订单号,请提供"。共有 7 种子类型,知道它们存在即可。
active_system_flow: SystemContext | None系统流程是由系统主动发起的一类特殊交互,用于向用户传递系统级通知,例如询问缺失参数、告知任务启动或完成、说明任务被打断等。
SystemContext 的基础结构与 TaskContext 相同,同样记录流程标识和当前步骤:
系统流程共有 7 种子类型,每种在基础字段之上扩展了各自所需的语义信息,以便响应模板在渲染文案时引用相关数据:
各子类扩展字段的设计说明:
| 子类 | 扩展字段 | 说明 |
|---|---|---|
CollectSystemContext | slot_name、response | 需要告知模板当前在收集哪个参数,以及向用户展示的提问内容 |
StartedSystemContext | started_flow_id、started_flow_name | 需要在通知文案中说明启动了哪个任务 |
ResumedSystemContext | resumed_flow_id、resumed_flow_name | 需要在通知文案中说明恢复了哪个任务 |
CanceledSystemContext | canceled_flow_id、canceled_flow_name | 需要在通知文案中说明取消了哪个任务 |
InterruptedSystemContext | interrupted_*、started_* 各一组 | 需要同时说明哪个任务被打断、哪个新任务被启动 |
CannotHandleSystemContext | reason | 可选地携带无法处理的原因 |
CompletedSystemContext | previous_flow_name | 需要在完成文案中提及刚刚结束的任务名称 |
4. 聚焦对象
🟡【理解即可】 用户在前端点击了"订单 A"→ 该订单自动成为 focused_object → 后续流程自动填充订单号槽位。
focused_object: FocusedObject | None用户在界面点击某张订单卡片,相当于告知系统"接下来的操作都与这张订单相关"。focused_object 记录这个被用户主动选中的业务对象,供后续流程自动填充相关槽位,免去重复询问。会话超时重置后,focused_object 随运行时状态一并清空。
type:对象类别,如"order"、"product",决定该对象能映射到哪些槽位id:对象的核心标识符,如订单号title:对象的展示名称,如"雪地靴 - 36码"attributes:扩展属性字典,供后续 Action 读取,如{"status": "已发货"}
5. 会话历史
🔥【必须理解】 Session 是"次"的维度(一次聊天时段),Turn 是"轮"的维度(一问一答)。用户长时间不说话再回来 → 新 Session 创建,旧的保留。
sessions: List[Session]
current_session_id: str | Nonesessions 永久累积所有历史会话。current_session_id 记录当前活跃 Session 的 ID,通过它在 sessions 列表中定位当前会话。
Session 将若干 Turn 按活跃时间窗口分组。用户长时间未发消息后再次进入时,开启新会话并重置运行时状态(任务、系统流程、聚焦对象全部清空),此前的历史记录保留。Session 的时间字段是判断这一边界的依据:started_at / last_activity_at / closed_at 均为 Unix 时间戳,closed_at 为 None 表示会话仍活跃。
Turn 是对话的最小单元,对应一次用户输入与机器人回复的完整交换。assistant_messages 是列表,因为机器人可能在一轮中依次发出多条消息。
6. 处理中的轮次
🟢【了解即可】 两步提交:先创建 pending_turn → 处理完成后才移入 sessions → 防止异常时写入不完整记录。
pending_turn: Turn | None每条用户消息进入处理流程时,系统立即创建一个 Turn 对象并暂存于 pending_turn。此时这条轮次尚未归入任何 Session,处于"处理中"状态。待本次消息的全部逻辑执行完毕后,pending_turn 才被移入当前 Session.turns,并重置为 None。
这种两步提交的方式确保了:若处理过程中发生异常,不完整的轮次不会被写入会话历史。pending_turn 不参与持久化,它是纯粹的请求内瞬态。
七、企业痛点-方案映射
| 痛点 | 传统方案 | AI Agent 方案 |
|---|---|---|
| 用户同时提多个需求时对话状态混乱 | 人工客服凭记忆跟进 | active_task + paused_tasks 挂起/恢复机制 |
| 长时间中断后无法恢复上下文 | 客服从头问起 | Session 按时间窗口分组,自动恢复 |
| 前端操作(点击订单)后还需反复输入 | 手动复制粘贴 | FocusedObject 自动补槽 |
| 异常导致对话数据损坏 | 日志排查困难 | pending_turn 两步提交,异常不写入 |
八、本阶段文件索引
| 优先级 | 文件 | 路径 | 说明 |
|---|---|---|---|
| 🔥 P0 | 对话状态设计.md | 5.设计文档/对话状态设计.md | 先读这篇——掌握数据模型 |
| 🔥 P0 | domain/state.py | 3.代码/customer-service-1020/atguigu/domain/state.py | 代码实现——状态类定义 |
| 🟡 P1 | domain/contexts.py | 3.代码/customer-service-1020/atguigu/domain/contexts.py | 上下文子类实现 |
| 🟡 P1 | repository/dialogue_state_repository.py | 3.代码/customer-service-1020/atguigu/repository/dialogue_state_repository.py | 序列化+持久化 |
| 🟢 P2 | models/dialogue_state.py | 3.代码/customer-service-1020/atguigu/models/dialogue_state.py | ORM 模型 |