Skip to content

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_requeststep_id 对应流程内某个步骤的 idslots 中的键来自该流程涉及的槽位定义:

yaml
# 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 种子类型,每种在基础字段之上扩展了各自所需的语义信息,以便响应模板在渲染文案时引用相关数据:

各子类扩展字段的设计说明:

子类扩展字段说明
CollectSystemContextslot_nameresponse需要告知模板当前在收集哪个参数,以及向用户展示的提问内容
StartedSystemContextstarted_flow_idstarted_flow_name需要在通知文案中说明启动了哪个任务
ResumedSystemContextresumed_flow_idresumed_flow_name需要在通知文案中说明恢复了哪个任务
CanceledSystemContextcanceled_flow_idcanceled_flow_name需要在通知文案中说明取消了哪个任务
InterruptedSystemContextinterrupted_*started_* 各一组需要同时说明哪个任务被打断、哪个新任务被启动
CannotHandleSystemContextreason可选地携带无法处理的原因
CompletedSystemContextprevious_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 | None

sessions 永久累积所有历史会话。current_session_id 记录当前活跃 Session 的 ID,通过它在 sessions 列表中定位当前会话。

Session 将若干 Turn 按活跃时间窗口分组。用户长时间未发消息后再次进入时,开启新会话并重置运行时状态(任务、系统流程、聚焦对象全部清空),此前的历史记录保留。Session 的时间字段是判断这一边界的依据:started_at / last_activity_at / closed_at 均为 Unix 时间戳,closed_atNone 表示会话仍活跃。

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对话状态设计.md5.设计文档/对话状态设计.md先读这篇——掌握数据模型
🔥 P0domain/state.py3.代码/customer-service-1020/atguigu/domain/state.py代码实现——状态类定义
🟡 P1domain/contexts.py3.代码/customer-service-1020/atguigu/domain/contexts.py上下文子类实现
🟡 P1repository/dialogue_state_repository.py3.代码/customer-service-1020/atguigu/repository/dialogue_state_repository.py序列化+持久化
🟢 P2models/dialogue_state.py3.代码/customer-service-1020/atguigu/models/dialogue_state.pyORM 模型

OPC 超级个体实战指南