Day06-电商小二-26-09-28

一、内容

1.回顾:

定义TurnPlan数据模型,有三个属性,task,knowledge,chitchat。
属性task,依赖于TaskTurnPlan数据模型。
属性knowledge,依赖于KnowledgeTurnPlan数据模型
属性Chitchat,依赖于ChitchatTurnPlan数据模型

作用:来存储意图识别后生成的结构化命令

TaskHandler实现思路
设计:基于yaml+规则引擎
...

2.新增 YAML 加载器(task/flows/loader)

2.1 细节

目的:

核心原因:把 "业务流程逻辑" 从 Python 代码里抽出来,放到配置文件里。
以前写死在代码里:"用户问订单号 → 调接口查订单 → 回复用户",这三步写在 Python 里。
以后要加一个退款流程,就得改 Python 代码、重启服务。
现在改成 YAML 配置:1个业务流

好处:

- 产品 / 运营改流程不用找程序员
- 加新流程只需要加 YAML,不改代码
-
- 流程和代码解耦,引擎只负责 "按图索骥" 执行

加载方法:

整个加载链路分三层:
1.第1层:读文件(loader.py)
YAML 文件 → yaml.safe_load() → Python 字典(dict)
2.第2层:字典 → 领域对象(model.py 的 from_data)
裸字典 → FlowStep.from_data() → 具体的 Step 对象(Start/Action/Collect/End)
这里使用了工厂模式+多态(如下图):
关键设计:
- YAML 里写 type: collect,自动变成 CollectFlowStep 对象
- YAML 里写 type: action,自动变成 ActionFlowStep 对象
- 引擎以后只需要面对统一的 FlowStep 接口,不用关心具体是哪种 Step
3.第3层:组装(loader._load_flows)
所有 Step 对象 → 组装成 Flow 对象 → 放进 FlowList。
同时做一件事:把 collect step 用到的 slot_name,关联到全局 slots 定义。

工厂模型:+多态![[images/Pasted image 20260928112457.png]]

2.2 关键知识

yaml.safe_load() vs yaml.load()

用 safe_load() 而不是 load(),因为 load() 会执行任意 Python 对象(安全漏洞),safe_load() 只解析基本数据类型(dict/list/str/int)。

工厂模式(Factory Pattern)

FlowStep.from_data() 就是工厂 —— 根据传入的 type 字符串,决定造哪个子类的对象。调用方不需要知道有哪些子类,只需要传字典就行。

调用方: FlowStep.from_data({"type": "collect", "slot_name": "xxx"})
工厂内部: 查 FLOWSTEP_DICT → 发现是 CollectFlowStep → 调 CollectFlowStep.from_data()
返回: CollectFlowStep(...)

多态next跳转

YAML 里 next 有三种形态:

# 形态 A:字符串,直接跳
next: end

# 形态 B:条件判断
next:
  - if: "slots.get('product_id')"
    then: respond
  - else: missing_context

代码里对应三种 Link 类:

  • StaticLink → 直接跳
  • ConditionalLink → 条件跳(带 condition 表达式)
  • FallbackLink → else 分支
    以后引擎执行时,遍历 step.next,遇到 ConditionalLink 就判断条件,遇到 StaticLink 就直接跳。

为什么要分 system_flows 和 user_flows

  • system_flows.yaml:系统级流程(任务开始、中断、取消、收集信息等),由状态机自动触发,不需要用户说什么
  • user_flow.yml:业务流程(查订单、查物流、退款),由用户意图触发
    两个文件结构一样(都是 flows + steps),所以用同一个 FlowLoader 加载。

2.3 整体数据流图

system_flows.yaml / user_flow.yml
        ↓ yaml.safe_load
   裸字典 dict
        ↓ FlowStep.from_data() 工厂路由
   具体 Step 对象(Start/Action/Collect/End)
        ↓ 组装
   Flow 对象(包含 steps 列表 + slots 列表)
        ↓ 收集所有 flow
   FlowList(最终产物)
        ↓ 传给引擎
   引擎按 steps 一步步执行,根据 next 跳转

最终目的:引擎拿到 FlowList 后,就像拿到一张流程图,从 start step 开始走,遇到 action 就调对应动作,遇到 collect 就问用户要槽位,遇到 end 就结束。整个对话流程的 "地图" 就是从 YAML 里加载出来的。

3.命令处理器(processor)

4.状态扩展(state新增方法)

5.知识点集合

知识点1:工厂模式 + 注册表字典

含义:用一个字典(注册表),把"字符串类型"映射到"具体类",实现根据配置动态创建对象。
创建原因:
- 不用写一大堆 if-else 判断类型
- 以后新增一种步骤类型,只要加个类,再往字典里注册一行就行
- 符合开闭原则:对扩展开放,对修改关闭

知识点2:### YAML 配置驱动

# user_flow.yml 里长这样
flows:
  refund_request:
    name: 退款申请
    steps:
      - id: step_start
        type: start
      - id: step_collect_order
        type: collect
        slot_name: order_id

加载器读出来,自动变成 Flow 对象,里面有 steps 列表,每个 step 是对应的类实例。
好处:
- 产品经理改流程,不用找开发改代码
- 流程逻辑和代码分离,好维护

知识点3:多态+instance分派

父类引用指向子类对象,用 isinstance() 判断具体是哪个子类,调对应的处理方法。

def _apply(self, command: Command, state, flow_list):
if isinstance(command, StartFlowCommand):
	self._handle_start_flow(command, state, flow_list)
elif isinstance(command, SetSlotsCommand):
	self._handle_set_slots(command, state)
elif isinstance(command, ResumeFlowCommand):
	self._handle_resume_flow(command, state, flow_list)
elif isinstance(command, CancelFlowCommand):
	self._handle_cancel_flow(command, state, flow_list)

Command 是父类,下面有 4 个子类。processor 不知道传进来的是哪个子类,就用 isinstance 判断一下,调对应的处理方法。

知识点4:dataclass(slots=True)

Python 3.10+ 的 dataclass 加 slots=True 参数,让类更省内存、属性访问更快。
使用原因:
- 每个 Flow / FlowStep / TaskContext 对象都很轻量
- 但 YAML 里可能有几十个流程、几百个步骤
- 加了 slots 之后,内存占用能省 30%~50%

知识点5:对话状态机的核心思路

把"对话现场"存在一个大对象里,每个命令执行后,修改这个对象的状态。
状态机的核心字段:

  • active_task:当前正在做什么业务
  • paused_tasks:之前被打断、挂起了哪些业务
  • active_system_task:当前要给用户播报什么系统提示
    状态转换:
启动新任务 → active_task 从 None 变成新任务
打断任务   → 旧任务进 paused_tasks,active_task 变成新任务
恢复任务   → 从 paused_tasks 拿出旧任务,放回 active_task
取消任务   → active_task 变成 None

知识点6:数据流方向

用户说的话
    ↓ (NLU 解析)
List[Command]  ← 一条条结构化命令
    ↓
CommandProcessor.process_command(commands, state, flow_list)
    ↓ 遍历每条命令,分派给对应的 _handle_xxx
    ↓ 修改 DialogueState 的各种字段
    ↓
把改完的 state 存回数据库

6.复习清单

7.问题➕解答

  1. 如果以后要新增一种 step 类型叫 confirm,需要改哪几个地方?
  2. 用户正在做退款任务,突然说"我要查订单",这时候 paused_tasks 里会多什么?active_task 变成什么?
  3. 为什么 FlowStepLink 要分成 ConditionalLink / FallbackLink / StaticLink 三种?
  4. 系统任务和业务任务有什么区别?为什么要分开存?

回答:

  1. ① 写一个 ConfirmFlowStep 类继承 FlowStep ② 往 FLOWSTEP_DICT 里注册一行 "confirm": ConfirmFlowStep
  2. paused_tasks 里会多一个 refund_request,active_task 变成 query_order_status
  3. 因为 next 跳转有三种情况:固定跳转、条件跳转、else 兜底跳转
  4. 业务任务是用户主动要办的事,系统任务是系统自动播报的提示,两者生命周期不同

二、安排

posted @ 2026-09-28 19:52  琳浪  阅读(6)  评论(0)    收藏  举报