章节四十一:TaskHandler设计与实现
一、TaskHandler设计
1.1 设计理念
在电商客服系统中,很多业务任务都不是一轮对话就能完成的。比如用户说"我要退单",系统可能需要继续追问订单号、退款原因,然后再给出处理结果。这类任务天然是一个多步骤过程,系统把这类多步骤任务定义成flow。
flow被设计成可配置形式,使用YAML文件描述业务流程。这样新增或调整业务流程时,优先修改配置而不是直接改代码。
TaskHandler的职责是推进flow的执行,支持以下能力:
| 能力 | 说明 |
|---|---|
| 打断 | 当前正在处理一个flow时,用户可以临时切换到另一个flow |
| 恢复 | 被打断的flow可以从之前的位置继续 |
| 取消 | 用户不想继续时,可以取消当前flow |
1.2 Flow配置语法
设计思想:一个flow由一系列steps组成,每个step表示流程中的一个节点,通过next指向下一个step。
顶层字段:flows(根节点)、flow_id(唯一标识,如refund_request)、name(可读名称)、description(能力说明)、steps(步骤列表)。
基础step类型:
| type | 作用 |
|---|---|
| start | flow的入口,next指向第一个处理业务的step |
| end | flow的终点,next写空列表 |
| action | 执行一个动作 |
action_response:向用户回复消息。有三种回复模式:
- static:直接把text渲染后回复
- rephrase:先渲染text,再用prompt让大模型改写
- generate:不依赖固定text,直接用prompt让大模型生成
action_response支持变量引用:slots(已收集的信息)、context(系统上下文)。
action_listen:等待用户下一轮输入,通常跟在询问信息的action_response后面。
自定义action:处理项目自己的业务逻辑,如查询订单状态、查询物流信息、推荐相似商品。通常负责查询业务数据,后续再用action_response把结果回复给用户。
next跳转:
- 静态跳转:直接指定下一个step的id
- 条件跳转:根据条件(读取slots、context、flow_id、step_id)决定下一步,多个条件按顺序判断,命中第一个成立的分支
collect step:用来表达"当前流程需要某个slot"。如果slot已有值,直接进入next指定的step;如果没有值,询问用户并等待输入。collect把"问+等"的细节封装起来,让配置更关注业务需要什么信息。
1.3 SystemFlow
SystemFlow用来描述系统级交互:
| SystemFlow | 作用 |
|---|---|
| system_collect_information | 收集缺失的slot信息 |
| system_task_started | 新任务开始时通知用户 |
| system_task_resumed | 恢复暂停任务时通知用户 |
| system_task_interrupted | 任务被打断时通知用户 |
| system_task_canceled | 任务被取消时通知用户 |
| system_task_completed | 任务完成时通知用户 |
| system_cannot_handle | 系统无法处理时通知用户 |
二、TaskHandler实现
2.1 实现思路
TaskHandler是任务轨道的入口。不负责理解用户自然语言,也不负责读写数据库。只接收已经解析好的commands和当前DialogueState,然后推进任务流程。
内部依赖三个对象:
| 对象 | 职责 |
|---|---|
| CommandProcessor | 处理命令,修改DialogueState |
| FlowExecutor | 根据当前状态推进flow |
| ActionRunner | 执行flow中声明的action |
实现顺序:Flow定义和加载 -> Command定义和解析 -> CommandProcessor -> ActionRunner -> FlowExecutor -> TaskHandler。
2.2 Flow定义与加载
Flow配置对应的核心模型包括:FlowsList(所有flow和slot的集合)、Flow(单个流程)、FlowSlot(槽位定义)、FlowStep(步骤基类,包含StartFlowStep、CollectSlotStep、ActionFlowStep、EndFlowStep等子类)、FlowStepLink(跳转链接基类,包含StaticLink、ConditionalLink、FallbackLink)、ResponseDefinition(回复定义)、SlotValidation(槽位校验)。
FlowLoader从多个YAML配置文件加载配置,得到FlowsList对象。
2.3 Command定义与解析
Command由TurnPlanner调用LLM输出,常见类型:
- StartFlowCommand(启动任务):指定要启动的flow_id和初始slots
- SetSlotsCommand(写入槽位):设置要更新的槽位键值对
- CancelFlowCommand(取消任务):指定要取消的flow_id
- ResumeFlowCommand(恢复任务):指定要恢复的flow_id
CommandParser将LLM返回的JSON字符串解析为对应的Command对象列表。
2.4 CommandProcessor
CommandProcessor把Command应用到DialogueState上,主要修改三类状态:active_task、paused_tasks、active_system_flow。
各命令的处理逻辑:
start_flow(启动任务):
第一步:如有active_task,将其压入paused_tasks。
第二步:创建新的TaskContext,设置flow_id和初始slots。
第三步:将新的TaskContext设为active_task。
第四步:生成system_task_started通知,如有被打断的任务同时生成system_task_interrupted。
set_slots(写入槽位):
第一步:找到当前active_task。
第二步:将slots更新到active_task.slots中。
第三步:如果当前step是collect类型,将step_id推进到next指定的步骤。
cancel_flow(取消任务):
第一步:找到要取消的任务(active_task或paused_tasks中的某个)。
第二步:从状态中移除。
第三步:如果取消的是active_task,从paused_tasks中恢复最后一个任务。
第四步:生成system_task_canceled通知。
resume_flow(恢复任务):
第一步:从paused_tasks中找到目标任务。
第二步:将当前active_task压入paused_tasks。
第三步:将目标任务设为active_task。
第四步:生成system_task_resumed通知。
2.5 ActionRunner
ActionRunner负责执行flow step中声明的action。核心概念:
- ActionRegistry:action注册表,维护name到Action实例的映射
- Action:动作基类,每个action有唯一name,需实现run()方法
- ActionCall:一次action调用请求
- ActionResult:包含messages(机器人消息)和slot_updates(槽位更新)
内置action_response根据mode选择不同回复策略:static直接渲染模板,rephrase模板渲染后交给LLM改写,generate直接由LLM根据prompt生成。支持Jinja2模板变量插值。
2.6 FlowExecutor
FlowExecutor根据当前DialogueState推进flow:
第一步:读取active_task,获取当前flow_id和step_id。
第二步:从FlowsList中找到对应的Flow。
第三步:找到当前step,执行对应的动作或收集逻辑。
第四步:根据step的next配置决定下一步进入哪个step。
第五步:更新active_task的step_id。
2.7 TaskHandler总入口
TaskHandler是任务轨道的总入口,处理流程:
第一步:接收commands和DialogueState。
第二步:调用CommandProcessor.run()处理命令,修改状态。
第三步:调用FlowExecutor.run()推进流程。
第四步:调用ActionRunner.run()执行动作。
第五步:收集生成的BotMessage返回。