Day09

一、复习回顾

电商小二智能客服解决什么问题?

背景:【在原来的基础上引入大模型改造】
电商平台原有的人工客服的缺点:人力成本,回答及时性和精准度,替代80%(用户问题的规律)
功能:用户可以通过自然语言实现修改地址,退款的等流程处理,以及商品参数查询,平台政策查询问答。

电商小二智能客服整体系统架构

电商业务后端 :提供了电商平台的数据和接口
电商前端:提供了购物等操作的界面,以及客服的问答界面
电商客服后端:接受并处理用户的消息

电商小二的智能客服分层设计

API层:FastAPI
历史对话查询接口
消息处理接口(聊天接口)
Service层:
对话消息的业务处理
查询用户的对话状态
调用engine层处理消息
更新用户的对话状态
Repository层:
load:根据用户id查询用户对话状态信息(json)-->DialogueState。
save:更新用户对话状态DialogueState-->JSON
Engine层:
1.准备会话(Session)(state.current_session_id)
2.创建本轮对话(state.pending_turn=Turn(turn_id,input_messsage))
3.处理消息
文本消息
意图识别:TurnPlan(task,knowledge,chitchat)
结构化命令校验
如果是task则调用taskhandler进行任务处理
如果是knowledge则调用knowledgehandler进行知识查询
如果是chit-chat则调用chit-chathandler进行闲聊
如果命令不合法则调用clarifyResponder组件进行澄清处理
对象消息
state.focused_object
判断对象是否能够用来填槽
如果能够填槽,则生成填槽指令调用taskhandler
如果不能填槽,调用clarifyresponder组件进行澄清
4.回填本轮对话(state.pending_turn=Turn(assistant_message))
5.提交本轮对话(state.current_session().turns)
6.返回处理结果(ProcessResult)
handler层:
taskhandler进行任务处理
knowledgehandler进行知识查询
chit-chathandler进行闲聊
clarifyResponder组件进行澄清处理

电商小二智能客服消息处理流程

文本消息处理流程图
对象消息处理流程图

文本消息处理-意图识别

结构化命令json

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

task:

{
	"task":{
		"commands":[
			{"command":"start_flow","flow":"flow_id"},
			{"command":"set_slots","slots":{...}},
			{"command":"cancel_flow"},
			{"command":"resume_flow","flow":"flow_id"},	
		]
	},
	"knowledge":null,
	"chitchat":null
}

konwlege:..
chitchat...

TurnPlan数据模型
调用LLM进行意图识别的提示词渲染(任务轨道流程信息,知识轨道的查询意图,state等...)

TaskHandler任务处理

yaml文件...

调用CommandProcessor组件进行命令处理
调用FlowExcutor组件进行流程推进

flow语法与解析
语法结构
数据模型
加载模型

CommandProcessor组件
StartFlowCommand
SetSlotsCommand
ResumeFlowCommand
CancelFlowCommand

ActionRunner组件
Action类
name属性
run方法
内置Action:ActionResponse、ActionListen
自定义Action:...
ActionRegistry类(self. _ actions)
ActionRunner类(Builder:action类的注册)

FlowExcutor组件

双层循环设计

外层循环
内层循环
获取任务上下文(系统任务优先,如果系统任务上下文为None,则获取用户任务上下文)
判断上下文是否为None,如果是None,表示既没有系统任务也没有用户任务,则返回ActionCall(action_name="action_listen")
如果不为None,则从当前上下文中获取当前flow的当前step。不同的step进行不同的处理
StartFlowStep:流程直接推进到下一步。
EndFlowStep:将当前任务上下文设置为None。
ActionFlowStep:先流程直接推进到下一步;再返回ActionCall。
CollectFlowStep:
先尝试自动填槽,再判断槽位是否有值;
如果没有值,调用system_collect_information系统任务;
如果有值,判断是否有校验条件;
如果没有校验条件,则将流程推进下一步;
如果有校验条件,则调用eval方法进行条件判断;
通过:进行流程推进;
不通过,槽位清空,判断是否需要生成错误提示:
不生成错误提示:直接进行下一轮循环
生成提示错误,则返回ActionCall。

二、内容

1.电商小二-knowledgehandler轨道设计流程。

![[images/Pasted image 20261008103223.png]]
内容描述:
根据用户输入的内容,进行意图识别
意图识别会有5种选择

product_info
order_info
price_protected_policy
refund_policy
product_use_method

LLM会生成以上的命令中的一种
根据命令中的intents列表进入到轨道处理
intent_id查找到对应的provider_ids列表
根据provider_ids列表找到对应的类的实例
调用实例方法retrieve

provider类
ProductApiProvider类
OrderApiProvider类
RAGProvider
FAQProvide类

持久层...
找到切片chunks
调用llm生成机器回答给用户

代码实现:
provider里面
代码完成
代码测试
intents
代码字段
工厂映射
registry
迭代注册for
responder
提示jinjia2

2.DialogueEngine业务流程代码补全

三、总结

1.三轨道对话系统(knowledge + chitchat + planner + engine)


整体流程:

用户发消息
    ↓
TurnPlanner(意图识别)→ LLM 判断属于哪个轨道,返回 TurnPlan
    ↓
TurnPlanValidator(校验)→ 判断结果是否合法
    ↓
按轨道分发:
    ├─ Task → TaskHandler → 流程引擎
    ├─ Knowledge → KnowledgeHandler → 知识检索 + LLM 回答
    └─ Chitchat → ChitChatHandler → LLM 闲聊s
1.knowledge/intents.py - 知识意图定义
定义了KnowledgeIntent类
定义了KNOWLEDGE_INTENTS全局意图注册表

工厂模式的变体:这里用一个字典注册表来管理所有意图,LLM 返回意图ID,我们从字典里查到对应的配置。

2.knowledge/providers.py - 知识提供者
定义了KnowledgeChunk类 有content字段 存储知识内容文本
定义了KnowledgeProvider基类,有provider_id字段和retrieve抽象方法
定义了ProductAPIProvider类,继承KnowledgeProvider,重写抽象方法,调后端API查商品详情。

策略模式:每个 Provider 是一种知识获取策略(调API、查FAQ、查RAG),它们都继承同一个抽象基类,实现统一的 retrieve() 方法。

3.knowledge/registry.py - 提供者注册表
定义了KnowledgeProviderRegistry类,知识提供者注册表根据 provider_id 找到对应的 Provider 实例,定义get实例方法:根据ID获取Provider

工厂模式:注册表 + get 方法,和之前的 FLOWSTEP_DICT、COMMAND_CLASS_DICT 是同一个套路。

4. knowledge/handler.py - 知识轨道入口
定义了KnowledgeHandler类:知识轨道的入口:根据意图列表,调用对应的Provider检索知识,再生成答案
__init__:依赖注入:provider_registry,knowledge_responder,knowledge_intents组件从外面传进来
handle:  
	# 1.收集所有意图对应的 provider_ids
	# 2.去重(多个意图可能用到同一个provider)
	# 3.逐个调用Provider检索知识
	# 4.把知识片段交给 Responder,让LLM生成最终答案

协调者模式:KnowledgeHandler 协调了 Intent → Provider → Responder 三个环节,外部只需要调 handle()。

5. knowledge/responder.py - 知识回答生成
定义了KnowledgeResponder类,实现了respond方法
respond:
	# 1.加载提示词模板load_prompt
	# 2.构造提示词参数
	# 3.用 Jinja2 渲染模板
    # 4.调用 LLM,用 LCEL 链:llm | 输出解析器  

LCEL(LangChain Expression Language):用 | 把组件串成管道,prompt | llm | parser 像 Unix 管道一样。

6. chitchat/handler.py - 闲聊轨道
ChitChatHandler类:
	实现了handle方法:
		# 加载闲聊提示词模板
		# Jinja2 渲染 + LLM 生成回复

和 KnowledgeResponder 的区别:没有知识片段,直接根据历史和用户消息生成闲聊回复。

7. plan/planner.py - 意图识别
TurnPlanner类
	实现了predict方法:
		# 1.加载提示词模板
		# 2.构造提示词参数:把所有可用流程、意图、当前状态都告诉LLM
		# 3.Jinja2 渲染模板
		# 4.调用 LLM,用 JsonOutputParser 解析成 JSON
		# 5.把 JSON 转成 TurnPlan 对象

核心作用:用户发一句话,LLM 判断这句话属于哪个轨道(业务/知识/闲聊),返回结构化的 TurnPlan。

8. engine/dialogue_engine.py - 引擎入口
定义DialogueEngine类:
	__init__:依赖注入,组件从外面传进来
		task_handler
		knowledge_handler
		chitchat_handler
		turn_planner
		turn_planner_valid
	process方法:
		# 1.准备会话
		# 2.创建本轮对话
		# 3.处理消息
		# 4.回填 + 提交
		# 5.返回结果
	_handle_text_message:
		 # 1.意图识别:让LLM判断走哪个轨道
		 # 2.校验:判断TurnPlan是否合法
		 # 3.按轨道分发	 

策略模式 + 分发:根据 TurnPlan 的类型,选择对应的 Handler 来处理。

数据流转完整图
用户:"退款政策是什么?"
    ↓
DialogueEngine.process()
    ↓
TurnPlanner.predict()
    ├─ 加载 turn_plan.jinja2 模板
    ├─ 填充上下文(可用流程、意图、当前状态、对话历史)
    ├─ 调 LLM,返回 JSON:{"task": null, "knowledge": {"intents": ["refund_policy"]}, "chitchat": null}
    └─ 解析成 TurnPlan 对象
    ↓
TurnPlanValidator.validate()
    └─ 校验通过
    ↓
按轨道分发 → KnowledgeHandler.handle(["refund_policy"], state)
    ├─ 查 KNOWLEDGE_INTENTS["refund_policy"] → provider_ids=["faq.default", "rag.default"]
    ├─ 去重后调用两个 Provider 检索知识
    │   ├─ FAQProvider.retrieve() → 返回"未检索到相关问题(faq)"
    │   └─ RAGProvider.retrieve() → 返回"未检索到相关问题(rag)"
    ├─ 收集所有知识片段
    └─ KnowledgeResponder.respond()
        ├─ 加载 knowledge_respond.jinja2 模板
        ├─ 填充知识内容 + 历史 + 用户问题
        ├─ 调 LLM 生成答案
        └─ 返回 [BotMessage(text="退款政策是...")]
    ↓
DialogueEngine 回填消息、提交会话
    ↓
返回给前端

学习笔记

1.核心结论
  1. 三轨道设计:把用户消息分成三类——业务流程(Task)、知识咨询(Knowledge)、闲聊(Chitchat),每类走不同的处理链路。
  2. 意图识别是入口:用 LLM 把自然语言转成结构化的 TurnPlan JSON,决定走哪个轨道。
  3. 策略模式 + 注册表:KnowledgeProvider 是抽象基类,每个具体 Provider 是一种策略,注册表里按 ID 查找。
  4. Jinja2 模板渲染:所有提示词模板都用 Jinja2 渲染变量,不再用 LangChain 的 PromptTemplate。
  5. LCEL 管道:LangChain 用 | 把组件串成管道,llm_client | StrOutputParser()。
2.知识点详解
知识点 1:三轨道架构(Task / Knowledge / Chitchat)

说明: 把对话系统分成三个独立的轨道,每个轨道有自己的 Handler。用户发消息后,先由 TurnPlanner(意图识别器)判断属于哪个轨道,再分发到对应的 Handler 处理。
使用场景: 所有对话系统都需要做意图分类。如果不分轨道,所有消息都走同一个处理逻辑,会越来越混乱。分轨道后,每个轨道可以独立迭代,互不干扰。

知识点 2:策略模式(Strategy Pattern)

说明: 定义一族算法,把它们一个个封装起来,并且使它们可以互相替换。这里的"算法"就是不同的知识获取方式:调商品API、调订单API、查FAQ、查RAG。它们都继承同一个抽象基类 KnowledgeProvider,实现统一的 retrieve() 方法。
KnowledgeProvider(抽象基类)
├─ ProductAPIProvider → 调商品API
├─ OrderAPIProvider → 调订单API
├─ FAQProvider → 查FAQ
└─ RAGProvider → 查RAG
使用场景: 当有多种实现方式,而且需要动态切换时用策略模式。比如知识来源可能是API、数据库、向量库、文件,每种都是一个策略。新增知识来源时,只需要新增一个类,不需要改现有代码。

知识点 3:注册表模式(Registry Pattern)

说明: 用一个字典把 ID 和对应的实例/类关联起来,根据 ID 快速查找。和之前的 FLOWSTEP_DICT、COMMAND_CLASS_DICT 是同一个套路。
使用场景: 当你有很多组件,需要根据字符串ID找到对应的组件时用注册表。比如:

  • 流程类型 → 流程步骤类(start/action/collect/end)
  • 命令类型 → 命令类(start_flow/set_slots/cancel_flow/resume_flow)
  • 知识提供者ID → Provider实例(api.product/api.order/faq.default/rag.default)
知识点 4:Jinja2 模板渲染

说明: 提示词里有很多动态变量(对话历史、用户问题、可用流程列表等),需要用模板引擎把变量填进去。Jinja2 是 Python 最常用的模板引擎,用 {{ 变量名 }} 表示占位符。
为什么不用 LangChain 的 PromptTemplate? 之前踩过坑:LangChain 的 PromptTemplate 对 Jinja2 双大括号语法支持不好,渲染出来变量还是 {{ }} 原样。所以直接用 Jinja2 自己渲染,更可控。
使用场景: 所有需要动态生成文本的地方都可以用模板引擎。除了提示词,还有邮件模板、HTML页面、报告生成等。

知识点 5:LCEL(LangChain Expression Language)

说明: LangChain 提供的一种声明式语法,用 | 把组件串成管道,像 Unix 管道一样。左边的输出就是右边的输入。
管道结构:
 Prompt / 字符串 → LLM → OutputParser
      ↓             ↓         ↓
  提示词文本   大模型回复   解析后的结果
使用场景: 调用 LLM 时,通常需要三步:构造提示词 → 调LLM → 解析输出。用 LCEL 把这三步串成一条链,代码更简洁。

知识点 6:依赖注入(Dependency Injection)

说明: 类需要的组件不从内部创建,而是从外部传进来。这样做的好处是:

  1. 方便测试:测试时可以传假的(mock)组件
  2. 解耦:类不需要知道具体实现,只需要知道接口
  3. 统一配置:所有组件在 builder.py 里统一创建和配置
    使用场景: 大型项目里,类之间依赖关系复杂,如果每个类自己创建依赖,会导致耦合严重、难以测试。用依赖注入,在最外层统一组装所有组件。
知识点 7:异步编程(async/await)

说明: Python 的异步编程模型,用来处理 I/O 密集型任务(调API、调LLM、读数据库)。async def 定义异步函数,await 等待异步操作完成。
为什么要用异步? 调 LLM 要等几秒,调API也要等。如果用同步代码,等的时候整个程序都卡住了。用异步,等的时候可以去处理其他请求。
使用场景: 所有涉及网络请求的地方都应该用异步:调LLM、调后端API、查数据库。

3.场景问题与解决方案

问题 1:LLM 返回的意图不在配置里怎么办?

场景:LLM 随便返回了一个意图ID,比如 "price_protection",但 KNOWLEDGE_INTENTS 里没有这个意图。
解决方案: 遍历意图列表时加判断,跳过未知意图:

问题 2:多个意图用到同一个 Provider,重复调用怎么办?

场景:用户问"退款政策和退货政策有什么区别",LLM 返回两个意图 ["refund_policy", "return_policy"],这两个意图都需要调 FAQProvider 和 RAGProvider,会重复调用两次。
解决方案: 收集完所有 provider_ids 后用 set() 去重:

问题 3:查订单信息,但用户还没选中订单怎么办?

场景:用户直接说"我的订单到哪了",但 state.focused_object 是 None,没有选中订单。
解决方案: Provider 里先判空,没有聚焦对象就返回提示:

问题 4:提示词模板里的变量渲染不出来怎么办?

场景:模板里写了 {{ user_message }},但渲染后还是原样,没有被替换成实际内容。
解决方案:

  1. 确认用的是 Jinja2 的 Template().render(),不是 LangChain 的 PromptTemplate
  2. 确认 prompt_inputs 里的 key 和模板里的变量名完全一致
  3. 加打印调试,看 rendered_prompt 到底是什么

4.术语表

术语 含义
TurnPlan 一轮对话的计划,包含三个轨道:task/knowledge/chitchat
Intent 意图,用户想干什么
Provider 知识提供者,负责检索知识片段
Chunk 知识片段,一段文本
Responder 回答生成器,根据知识片段生成最终回复
Handler 轨道处理器,每个轨道一个入口
LCEL LangChain Expression Language,LangChain 的管道语法
注册表 字典形式的 ID → 实例 映射表
策略模式 多种实现方式可互换,都继承同一个接口

四、训练

DialogueEngine
第一步:
进入会话,获取当前会话id,
创建本轮对话,开始新的对话,生成uid,获取用户输入信息,我要查询商品
进行判断,用户信息是文本还是对象?

第二步:处理文本或者对象
处理文本:
意图识别 本轮计划是处理哪个?是在任务轨道
校验结果,进行本轮计划校验,判断校验结果是什么?
根据校验结果进行分发处理,
true,task,然后进行任务处理handle()
返回message
第一步:
回填本轮对话信息信息
把本轮对话提交到当前会话中
返回封装的过程结果中。
![[images/Pasted image 20261008201413.png]]


最后怎么返回给用户回复的?
1.引擎内部返回ProcessResult数据对象,包含了用户id,用户消息id,机器回复几条消息,每条消息内容是什么?
2.dialogue_service模块把state中的数据存在数据库持久化
Service 层做了两件事:

  1. 调引擎处理消息
  2. 把更新后的对话状态存回数据库(下次用户再来,能接着上次的聊)
    3.API层Routers
    FastAPI识别ProcessResult对象,自动Pydantic把其序列化为json发送给前端
    4.前端的json
{ 
	"sender_id": "user_123", 
	"message_id": "msg_456",
	"messages": [ 
	{
		"text": "退款政策是..."
	}
] }

引擎处理完 → 封装成 ProcessResult 对象 → Service 层存状态 → API 层返回 → FastAPI 自动转 JSON → 前端渲染


三个轨道是怎么分发的?
三个轨道是经过意图识别分发的。
在收到用户信息之后,使用predict方法,需要传流程列表,知识库意图字典,和会话状态
加载提示词模板
构造提示词参数
调用jinja2进行渲染
调用llm
最后大模型返回json对象转换成turnplan对象
根据turnplan返回的结果进行校验,
使用validate方法:传递对象,和会话状态以及知识库字典
进行单轨道校验,
长度为0说明缺少轨道
长度为大于1说明是多轨道
进行内容校验
判断是否存在知识库意图
存在,则获取意图,获取意图对象,没有就澄清
判断是否是任务轨道
存在命令则校验成功,不存在则校验不成功,
经过校验,分发最后返回message信息。

posted @ 2026-10-08 21:05  琳浪  阅读(9)  评论(0)    收藏  举报