Skip to content

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实现.mdDialogueEngine设计.mdKnowledgeHandler实现.mdChitChatHandler实现.md,原文保留核心部分。代码来自 3.代码/customer-service-1020/atguigu/plan/task/handler.pyknowledge/handler.pychitchat/handler.py

📖 阅读优先级

等级章节说明
🔥 必须深入TurnPlanner 流程核心创新——LLM 做规划决策
🔥 必须深入Handler 三路分流核心架构——task/knowledge/chitchat
🟡 理解即可Clarify 澄清机制多意图时触发
🟢 了解即可Validator 校验判断规划是否可执行

一、整体流程

🔥【P0 必须理解】 以下是一条文本消息在 DialogueEngine 内完整的处理路径:

重要边界

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

二、TurnPlanner——LLM 规划器

2.1 TurnPlan JSON 格式

🔥【必须理解】 LLM 输出的规划结果是一个固定格式的 JSON,包含三个字段:taskknowledgechitchat

json
{
  "task": null,
  "knowledge": null,
  "chitchat": null
}

task 示例(用户要办理业务):

json
{
  "task": {
    "commands": [
      {"command": "start_flow", "flow": "refund_request"}
    ]
  },
  "knowledge": null,
  "chitchat": null
}

knowledge 示例(用户要咨询知识):

json
{
  "task": null,
  "knowledge": {
    "intents": ["refund_policy"]
  },
  "chitchat": null
}

chitchat 示例(用户闲聊):

json
{
  "task": null,
  "knowledge": null,
  "chitchat": {}
}

多意图示例(同时表达了多个诉求):

json
{
  "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。

python
@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。

python
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 在你预设的范围内做选择,而不是自由发挥。

jinja2
## 任务说明
你的任务是分析当前对话上下文,并生成一个 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 是最简单的——什么都不做,直接返回一个预定义的"兜底回复"。

python
class ChitChatHandler:
    async def handle(self, state: DialogueState, plan: ChitchatTurnPlan) -> BotMessage:
        # 直接返回通用兜底回复
        return BotMessage(text="你好,这里是 Atguigu 电商助手。我可以帮你查订单状态、查物流、了解商品信息,或者提交退款申请。")

四、Clarify 澄清机制

🟡【理解即可】 当 TurnPlan 出现多意图或无效意图时,ClarifyResponder 介入。

触发条件:

  1. TurnPlan 为空(三个字段都是 null)→ "抱歉,我没有理解你的意思"
  2. 多意图(同时填写了 task + knowledge)→ "你想先处理退款申请,还是先了解退款政策?"
  3. 意图不明确 → 引导用户补充信息

五、企业痛点-方案映射

痛点传统方案AI Agent 方案
用户一句话包含多个需求人工客服判断优先级LLM 规划 + Clarify 澄清
同类问题不同问法穷举关键词匹配LLM 自然语言理解意图
知识查询依赖客服记忆翻手册/问同事KnowledgeProvider 自动检索+LLM生成
闲聊干扰业务流程硬抛错误三路分流,闲聊独立兜底

六、本阶段文件索引

优先级文件路径说明
🔥 P0plan/turn_planner.pyatguigu/plan/turn_planner.pyTurnPlanner 实现
🔥 P0plan/models.pyatguigu/plan/models.pyTurnPlan 模型定义
🔥 P0plan/validator.pyatguigu/plan/validator.py规划校验器
🔥 P0engine/dialogue_engine.pyatguigu/engine/dialogue_engine.py引擎调度入口
🟡 P1task/handler.pyatguigu/task/handler.pyTaskHandler
🟡 P1knowledge/handler.pyatguigu/knowledge/handler.pyKnowledgeHandler
🟢 P2chitchat/handler.pyatguigu/chitchat/handler.pyChitChatHandler
🟢 P2clarify/responder.pyatguigu/clarify/responder.pyClarifyResponder

OPC 超级个体实战指南