章节三十九:TurnPlanner实现

1. 概述

TurnPlanner:负责根据用户当前输入和对话状态,生成本轮对话计划 TurnPlan。

整体流程:

第一步:DialogueEngine 收到文本消息,调用 TurnPlanner.predict。

第二步:构建 prompt_inputs(把当前状态整理成提示词变量)。

第三步:渲染 planning/turn_plan_v1 提示词。

第四步:调用 LLM 生成 TurnPlan。

第五步:TurnPlanValidator 校验是否可直接执行。

第六步:如果校验通过,DialogueEngine 分发到对应 Handler;如果校验不通过,ClarifyResponder 生成澄清回复。

重要边界:

  • TurnPlanner:负责真实识别用户意图,可以识别出多个轨道
  • TurnPlanValidator:负责判断当前执行引擎是否能直接处理
  • DialogueEngine:只在校验通过后,分发到一个具体 Handler

当前系统支持三个轨道:

  • task:任务型流程,例如查订单、查物流、申请退款
  • knowledge:知识问答,例如商品信息、退款政策、配送规则
  • chitchat:闲聊,例如打招呼、询问助手身份

2. TurnPlan JSON 格式

LLM 输出的 TurnPlan 是一个 JSON 对象,顶层固定包含三个字段:task、knowledge、chitchat。

如果用户是在办理业务,填写 task:

// 功能说明:Task 类型的 TurnPlan 示例
// task.commands 包含要执行的命令列表,如 start_flow、set_slots 等

如果用户是在咨询知识,填写 knowledge:

// 功能说明:Knowledge 类型的 TurnPlan 示例
// knowledge.intents 包含命中知识意图ID列表

如果用户是在闲聊,填写 chitchat:

// 功能说明:Chitchat 类型的 TurnPlan 示例
// chitchat 为空对象即可

如果用户一句话同时表达多个意图,LLM 也可以同时填写多个轨道。但这种多意图 TurnPlan 会被 TurnPlanValidator 认定为当前引擎不能直接执行,然后引导用户澄清先处理哪一个。

3. TurnPlan 模型

与 JSON 对应的 Python 模型:

模型 说明
TaskTurnPlan 对应 task 字段,内部保存 commands
KnowledgeTurnPlan 对应 knowledge 字段,内部保存 intents
ChitchatTurnPlan 对应 chitchat 字段,当前不需要额外参数
TurnPlan 顶层模型,包含 task、knowledge、chitchat 三个可选字段

from_dict() 负责把 LLM 输出的 JSON 字典转换成对应模型。

# 功能说明:TurnPlan 数据模型
# 将 LLM 输出的 JSON 解析为 Python 对象
# 支持 task/knowledge/chitchat 三种轨道的组合

4. TurnPlanner 入口

TurnPlanner.predict() 是本组件的入口方法。

接收三类信息:

  • state:当前对话状态
  • flows:系统支持的任务流程
  • knowledge_intents:系统支持的知识意图

入口方法内部调用两个方法:

  • build_prompt_inputs():把当前状态、可用 flow、知识意图整理成提示词变量
  • _predict_from_prompt_inputs():使用提示词变量调用 LLM,并把输出转换成 TurnPlan
# 功能说明:TurnPlanner.predict() 入口方法
# 1. 构建提示词输入变量
# 2. 调用 LLM 生成 TurnPlan
# 3. 返回 TurnPlan 对象

5. 提示词设计

提示词的核心要求:

  • TurnPlan 顶层只允许 task、knowledge、chitchat 三个字段
  • 这三个字段的值必须是 JSON 对象或 null
  • 如果用户同时表达多个意图,可以同时填写多个轨道
  • 后续执行引擎一次只能处理一个轨道;如果输出多个轨道,系统会向用户澄清先处理哪一个
  • 只输出合法 JSON,不要输出 markdown 代码块

提示词中使用的参数:

参数 说明
available_flows_json 系统当前支持的任务流程列表。LLM 根据它判断用户是否要启动某个业务流程
knowledge_intents_json 系统当前支持的知识意图列表。LLM 根据它选择要查询哪类知识
active_task_json 当前正在执行的任务。如果用户继续补充信息,LLM 可据此生成 set_slots 等命令
interrupted_tasks_json 当前被暂停的任务列表。如果用户表达"继续之前的任务",LLM 可据此生成 resume_flow
focused_object_json 当前聚焦的业务对象(商品或订单)。帮助 LLM 判断用户是要办理对象相关业务,还是咨询对象相关知识
current_conversation 最近对话历史。让 LLM 判断当前意图时能参考上下文
user_message 用户本轮输入的原始文本。TurnPlan 的预测主要围绕这句话展开

6. 构造提示词输入

build_prompt_inputs() 负责把当前对话状态转换成提示词需要的变量。

这一步不会调用 LLM,也不会修改状态,只是做数据整理。

返回的变量包括:

  • current_conversation:最近对话历史,并追加当前用户消息
  • available_flows_json:系统支持哪些任务流程(去掉 steps 细节)
  • active_task_json:当前是否有正在执行的任务
  • interrupted_tasks_json:当前被暂停的任务列表
  • focused_object_json:当前聚焦的商品或订单
  • knowledge_intents_json:系统支持哪些知识意图
# 功能说明:build_prompt_inputs() 提示词输入构建
# 将 DialogueState 中的各种状态信息格式化为提示词模板变量
# 使用 json.dumps 将对象序列化为 JSON 字符串

7. 调用 LLM

_predict_from_prompt_inputs() 负责真正调用 LLM。

使用 LangChain 的链式写法:prompt -> llm -> JsonOutputParser。

执行结果是 JSON 字符串,先用 json.loads() 转成字典,再通过 TurnPlan.from_dict() 转成 TurnPlan 对象。

# 功能说明:_predict_from_prompt_inputs() LLM 调用
# 加载 turn_plan 提示词模板(Jinja2 格式)
# 组装 LangChain 调用链:prompt | llm | JsonOutputParser
# 异步调用大模型,解析输出为 TurnPlan 对象

8. TurnPlanValidator

8.1 校验结果定义

TurnPlanValidator 的返回值是 TurnPlanValidationResult,表达当前 TurnPlan 是否可以执行,以及校验失败的原因。

ClarifyReason 枚举定义了各种失败原因:

原因 说明
MISSING_TRACK TurnPlan 没有命中任何轨道,系统无法判断本轮应该做什么
MULTIPLE_TRACKS TurnPlan 同时命中了多个轨道,执行引擎一次只能处理一个
MISSING_TASK_COMMANDS 命中了 task 轨道,但 commands 为空,不知道具体执行什么命令
MISSING_KNOWLEDGE_INTENT 命中了 knowledge 轨道,但 intents 为空,不知道查询哪类知识
MISSING_FOCUSED_OBJECT 知识意图需要聚焦对象,但当前状态中没有合适的 focused_object
OBJECT_REQUIRES_INTENT 用户只发送了对象,但没有说明想做什么

8.2 校验逻辑

整体流程:

第一步:获取活跃轨道列表(_active_tracks)。

第二步:如果没有活跃轨道,返回 MISSING_TRACK。

第三步:如果有多个活跃轨道,返回 MULTIPLE_TRACKS。

第四步:取唯一轨道,根据轨道类型分别校验:

  • task 轨道:校验 commands 是否为空
  • knowledge 轨道:校验 intents 是否为空,以及是否需要聚焦对象
  • chitchat 轨道:直接通过
# 功能说明:TurnPlanValidator.validate() 校验入口
# 1. 检查活跃轨道数量
# 2. 根据轨道类型调用对应校验方法
# 3. 返回 TurnPlanValidationResult(valid + reason)

task 校验:检查 commands 是否为空。具体 command 是否能执行,由后续 TaskHandler 和 CommandProcessor 处理。

knowledge 校验:检查两件事:intents 不能为空;如果某个 intent 要求聚焦对象,则当前状态中必须有对应类型的 focused_object。

例如:product_info 需要当前聚焦商品;order_info 需要当前聚焦订单;refund_policy 这类规则咨询不一定需要聚焦对象。

9. ClarifyResponder

9.1 概述

ClarifyResponder 负责把澄清原因转换成最终的 BotMessage。

接收两类信息:state(当前对话状态)和 reason(澄清原因)。

整体处理思路:

第一步:根据 reason 和 state 生成一条基础澄清话术(build_clarify_message)。

第二步:读取当前对话历史和聚焦对象。

第三步:调用 clarify/rewrite 提示词,把基础话术改写成自然的客服回复。

第四步:返回 BotMessage。

职责分离:TurnPlanValidator 只负责判断失败原因,ClarifyResponder 负责生成基础澄清话术并改写成自然回复。

9.2 澄清提示词

提示词要点:

  • 角色:中文电商客服助手,语气自然、友好、简洁
  • 任务:把系统澄清提示改写成更自然的一句话
  • 要求:不要扩写,不要新增信息,不要改变澄清意图

提示词变量:

变量 说明
reason 澄清原因枚举值
clarify_message 基础澄清话术
focused_object 当前聚焦对象描述
history 当前会话历史
user_message 用户本轮输入文本

9.3 基础话术生成

build_clarify_message() 根据 reason 生成基础澄清话术:

原因 基础话术
MULTIPLE_TRACKS 你这次同时提到了多个方向。我们先处理一个,你想先办业务还是先咨询信息呢?
MISSING_FOCUSED_OBJECT 请先发送你想咨询的商品或订单,我再继续帮你看。
MISSING_KNOWLEDGE_INTENT 你是想了解商品信息、订单信息,还是售后配送规则呢?
MISSING_TRACK 你是想先处理业务问题,还是先咨询信息呢?
MISSING_TASK_COMMANDS 你这次是想办理什么业务呢?比如查订单、查物流,或者申请退款。
OBJECT_REQUIRES_INTENT 我已经收到这个订单/商品了。你想查状态、查物流,还是申请退款/了解信息呢?
# 功能说明:ClarifyResponder 澄清回复生成器
# 1. 根据 reason 生成基础澄清话术
# 2. 加载对话历史和聚焦对象
# 3. 调用 clarify_respond 提示词让 LLM 改写为自然回复
# 4. 返回 BotMessage
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐