03 TurnPlanner 与 Handler 体系
学习理念:这是整个系统最核心的设计——DialogueEngine 收到一条文本消息后,TurnPlanner 用 LLM 判断"用户想干什么",然后分给对应的 Handler 去执行。 理解了这个"规划→分流→执行"的架构,你就掌握了 Agent 系统的核心设计模式。
海外对标:LangGraph Agent(LLM 规划→工具调用)、Rasa NLU(意图识别→Action 执行)
本节 AI 替代率:~55% | 人工干预率:~45%
| 角色 | 能力范围 |
|---|---|
| 🤖 AI 擅长 | 生成 TurnPlanner prompt 模板、TurnPlan JSON 解析代码 |
| 👤 人类需理解 | LLM 输出 JSON 格式的规划结果、多意图时如何触发澄清 |
📌 原文说明:以下内容来自
5.设计文档/TurnPlanner实现.md、DialogueEngine设计.md、KnowledgeHandler实现.md、ChitChatHandler实现.md,原文保留核心部分。代码来自3.代码/customer-service-1020/atguigu/plan/、task/handler.py、knowledge/handler.py、chitchat/handler.py。
📖 阅读优先级
| 等级 | 章节 | 说明 |
|---|---|---|
| 🔥 必须深入 | TurnPlanner 流程 | 核心创新——LLM 做规划决策 |
| 🔥 必须深入 | Handler 三路分流 | 核心架构——task/knowledge/chitchat |
| 🟡 理解即可 | Clarify 澄清机制 | 多意图时触发 |
| 🟢 了解即可 | Validator 校验 | 判断规划是否可执行 |
一、整体流程
🔥【P0 必须理解】 以下是一条文本消息在 DialogueEngine 内完整的处理路径:
重要边界:
TurnPlanner:负责真实识别用户意图,可以识别出多个轨道TurnPlanValidator:负责判断当前执行引擎是否能直接处理DialogueEngine:只在校验通过后,分发到一个具体 Handler
二、TurnPlanner——LLM 规划器
2.1 TurnPlan JSON 格式
🔥【必须理解】 LLM 输出的规划结果是一个固定格式的 JSON,包含三个字段:
task、knowledge、chitchat。
{
"task": null,
"knowledge": null,
"chitchat": null
}task 示例(用户要办理业务):
{
"task": {
"commands": [
{"command": "start_flow", "flow": "refund_request"}
]
},
"knowledge": null,
"chitchat": null
}knowledge 示例(用户要咨询知识):
{
"task": null,
"knowledge": {
"intents": ["refund_policy"]
},
"chitchat": null
}chitchat 示例(用户闲聊):
{
"task": null,
"knowledge": null,
"chitchat": {}
}多意图示例(同时表达了多个诉求):
{
"task": {
"commands": [
{"command": "start_flow", "flow": "refund_request"}
]
},
"knowledge": {
"intents": ["refund_policy"]
},
"chitchat": null
}🟡【理解即可】 多意图 TurnPlan 会被 Validator 认定为"不能直接执行"→ 触发 ClarifyResponder 询问用户"先处理哪个"。
2.2 TurnPlan 模型定义
🟡【P1 看注释就行】 LLM 输出的 JSON 会被解析为 Python 的 dataclass。
@dataclass
class TaskTurnPlan:
commands: list[Command] = field(default_factory=list)
@classmethod
def from_dict(cls, data: dict) -> "TaskTurnPlan":
return cls(commands=[Command.from_dict(c) for c in data["commands"]])
@dataclass
class KnowledgeTurnPlan:
intents: list[str] = field(default_factory=list)
@classmethod
def from_dict(cls, data: dict) -> "KnowledgeTurnPlan":
return cls(intents=data["intents"])
@dataclass
class ChitchatTurnPlan:
pass
@dataclass(slots=True)
class TurnPlan:
task: TaskTurnPlan | None = None
knowledge: KnowledgeTurnPlan | None = None
chitchat: ChitchatTurnPlan | None = None
@classmethod
def from_dict(cls, data: dict) -> "TurnPlan":
return cls(
task=TaskTurnPlan.from_dict(data["task"]) if data.get("task") else None,
knowledge=KnowledgeTurnPlan.from_dict(data["knowledge"]) if data.get("knowledge") else None,
chitchat=ChitchatTurnPlan() if data.get("chitchat") is not None else None,
)2.3 TurnPlanner 入口代码
🟡【P1 看注释就行】
predict()方法接收对话状态和可用 flows/intents,拼接 prompt 后调用 LLM。
class TurnPlanner:
async def predict(
self,
state: DialogueState,
flows: FlowsList,
knowledge_intents: dict[str, KnowledgeIntent],
) -> TurnPlan:
prompt_inputs = self._build_prompt_inputs(state, flows, knowledge_intents)
return await self._predict_from_prompt_inputs(prompt_inputs)这个方法接收三类信息:
state:当前对话状态(是否有 active_task、focused_object 等)flows:系统支持的任务流程列表knowledge_intents:系统支持的知识意图列表
2.4 提示词核心架构
🟡【理解即可】 TurnPlanner 的 prompt 把可用 flows 和知识意图作为变量传入,让 LLM 在你预设的范围内做选择,而不是自由发挥。
## 任务说明
你的任务是分析当前对话上下文,并生成一个 TurnPlan JSON。
TurnPlan 顶层只允许以下三个字段:
- `task`
- `knowledge`
- `chitchat`
## Task 结构
可用 flows:
{{ available_flows_json }}
## Knowledge 结构
允许的 intent:
{{ knowledge_intents_json }}
## 当前状态
### Active Task
{{ active_task_json }}
### Focused Object
{{ focused_object_json }}
### 最近对话
{{ recent_conversation_json }}三、三路 Handler 分流
3.1 Handler 路由逻辑
🔥【必须理解】 DialogueEngine 根据 TurnPlan 的结果分发给对应的 Handler。优先级:task > knowledge > chitchat。
TurnPlan 校验通过后:
if turn_plan.task is not None:
→ TaskHandler.handle() # 走任务流程
elif turn_plan.knowledge is not None:
→ KnowledgeHandler.handle() # 走知识问答
elif turn_plan.chitchat is not None:
→ ChitchatHandler.handle() # 走闲聊兜底
else:
→ ClarifyResponder # 无效意图 → 澄清3.2 TaskHandler——任务流程处理
🔥【必须深入】 TaskHandler 处理"有步骤、需要收集信息的业务任务"。详见下一篇文章。
职责:
- 执行 TaskTurnPlan 中的 commands(start_flow/set_slots/cancel_flow 等)
- 推进 Flow 执行器(FlowExecutor)一步步走完流程
- 将最终回复写回 pending_turn
3.3 KnowledgeHandler——知识问答
🟡【理解即可】 KnowledgeHandler 处理"用户想查信息"的请求,核心是 KnowledgeProvider 体系。
知识意图(knowlege intents):
| 知识意图 | 示例问题 | Provider 类型 |
|---|---|---|
product_info | "这件商品是什么材质?" | API(调电商后端) |
order_info | "这个订单现在什么情况?" | API(调电商后端) |
refund_policy | "退款政策是怎样的?" | FAQ |
shipping_policy | "多久发货?包邮吗?" | FAQ |
platform_rules | "平台有哪些限制规则?" | 知识库 |
general_ecommerce | "优惠券怎么用?" | 知识库 |
🟡【理解即可】 KnowledgeResponder 的流程:Provider 检索到原始信息 → LLM 基于这些信息组织自然语言回复。
3.4 ChitChatHandler——闲聊兜底
🟢【了解即可】 ChitChatHandler 是最简单的——什么都不做,直接返回一个预定义的"兜底回复"。
class ChitChatHandler:
async def handle(self, state: DialogueState, plan: ChitchatTurnPlan) -> BotMessage:
# 直接返回通用兜底回复
return BotMessage(text="你好,这里是 Atguigu 电商助手。我可以帮你查订单状态、查物流、了解商品信息,或者提交退款申请。")四、Clarify 澄清机制
🟡【理解即可】 当 TurnPlan 出现多意图或无效意图时,ClarifyResponder 介入。
触发条件:
- TurnPlan 为空(三个字段都是 null)→ "抱歉,我没有理解你的意思"
- 多意图(同时填写了 task + knowledge)→ "你想先处理退款申请,还是先了解退款政策?"
- 意图不明确 → 引导用户补充信息
五、企业痛点-方案映射
| 痛点 | 传统方案 | AI Agent 方案 |
|---|---|---|
| 用户一句话包含多个需求 | 人工客服判断优先级 | LLM 规划 + Clarify 澄清 |
| 同类问题不同问法 | 穷举关键词匹配 | LLM 自然语言理解意图 |
| 知识查询依赖客服记忆 | 翻手册/问同事 | KnowledgeProvider 自动检索+LLM生成 |
| 闲聊干扰业务流程 | 硬抛错误 | 三路分流,闲聊独立兜底 |
六、本阶段文件索引
| 优先级 | 文件 | 路径 | 说明 |
|---|---|---|---|
| 🔥 P0 | plan/turn_planner.py | atguigu/plan/turn_planner.py | TurnPlanner 实现 |
| 🔥 P0 | plan/models.py | atguigu/plan/models.py | TurnPlan 模型定义 |
| 🔥 P0 | plan/validator.py | atguigu/plan/validator.py | 规划校验器 |
| 🔥 P0 | engine/dialogue_engine.py | atguigu/engine/dialogue_engine.py | 引擎调度入口 |
| 🟡 P1 | task/handler.py | atguigu/task/handler.py | TaskHandler |
| 🟡 P1 | knowledge/handler.py | atguigu/knowledge/handler.py | KnowledgeHandler |
| 🟢 P2 | chitchat/handler.py | atguigu/chitchat/handler.py | ChitChatHandler |
| 🟢 P2 | clarify/responder.py | atguigu/clarify/responder.py | ClarifyResponder |