Day07-电商小二-26-09-29
一、回忆
TaskHandler之步骤1:CommandProcessor:根据不同的Command设置state。
Flow数据模型task/flow/model。
输入之加载器loader:读取yaml文件,转为Flow对象
漏掉了的代码在加载器中把系统和用户合并
![[images/Pasted image 20260929083542.png]]
CommandProcessor组件设计图设计。
CommandProcessor之task/commands/processor处理命令。
二、内容
1.获取Command对象
2.使用CommandProcessor组件设置状态
3.使用FlowExecutor(双层循环设计)进行命令执行,并改state。
知识点1:策略模式(Strategy Pattern)
说明:
定义一系列算法(策略),把它们一个个封装起来,并且使它们可以互相替换。
Action 是策略接口,ActionResponse、ActionListen、LookupOrderStatusAction 是具体策略。ActionRunner 不需要知道具体执行的是哪个策略,统一调 run() 方法就行。
使用场景:
- 同一个动作有多种实现方式(比如回复用户有 static/rephrase/llm 三种模式)
- 需要在运行时动态切换算法
- 避免写一大堆 if-else 判断类型
代码复现:
# 策略接口
class Action(ABC):
@abstractmethod
async def run(self, state, args) -> ActionResult:
pass
# 具体策略 1
class ActionResponse(Action):
async def run(self, state, args): ...
# 具体策略 2
class LookupOrderStatusAction(Action):
async def run(self, state, args): ...
# 上下文(使用策略的地方)
class ActionRunner:
async def execute(self, action_call, state):
action = self.registry.get(action_call.action_name)
return await action.run(state, action_call.action_args) # 统一调用
知识点2:注册表模式(Registry Pattern)
说明:
用一个字典(注册表)把 "名字" 和 "实现类" 映射起来,运行时根据名字动态查找实现。
使用场景:
- 配置文件里只写字符串(比如 YAML 里写
action: action_lookup_order),运行时需要找到对应的类 - 需要动态扩展,新增实现不用改核心代码
- 插件化架构
代码复现:
class ActionRegistry:
def __init__(self):
self._actions = {} # 注册表:name → 实例
def register(self, action):
self._actions[action.name] = action
def get(self, action_name):
return self._actions[action_name] # 根据名字找实现
知识点3:(Dependency Injection)
说明:
把对象依赖的其他对象从外面传进来,而不是在内部自己创建。
使用场景:
- 方便单元测试(可以传 mock 对象进去)
- 解耦:使用方不关心依赖对象怎么创建的
- 同一个依赖可以被多个对象共享
代码复现:
class ActionRunner:
def __init__(self, action_registry: ActionRegistry):
self.action_registry = action_registry # 从外面传进来,不是自己 new
# 使用的时候
registry = ActionRegistry()
runner = ActionRunner(registry) # 注入依赖
知识 4:动态导入 + 反射(Dynamic Import + Reflection)
说明:
在运行时动态导入模块、获取模块里的类、实例化对象,而不是在代码顶部写死 import。
使用场景:
- 自动扫描某个目录下的所有插件并加载
- 插件化架构,新增插件不用改主代码
- 框架开发(比如 Django 的 app 自动发现)
代码复现:
import importlib
import inspect
import pkgutil
def registry_custom_actions(registry):
# 1. 动态导入包
package = importlib.import_module("task.action.custom.sub_action")
# 2. 遍历包下所有模块
for __, name, is_pkg in pkgutil.iter_modules(package.__path__):
module = importlib.import_module(name) # 动态导入
# 3. 反射:获取模块里所有类
for __, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, Action) and obj != Action:
registry.register(obj()) # 动态实例化并注册
知识 5:Jinja2 模板渲染
说明:
用模板引擎把字符串里的 {{变量}} 替换成实际值。
使用场景:
- 动态生成文本(邮件、消息、配置文件)
- 模板和数据分离,方便修改文案
- 支持条件、循环等复杂逻辑
代码复现:
from jinja2 import Template
text = "您的订单号是 {{ slots.order_number }},状态是 {{ slots.order_status }}"
data = {
"slots": {"order_number": "A123", "order_status": "已发货"}
}
rendered = Template(text).render(data)
# 结果:"您的订单号是 A123,状态是 已发货"
知识 6:LangChain LCEL 链式调用
说明:
LangChain Expression Language,用 | 符号把多个组件串成一条链,数据从左到右流动。
使用场景:
- LLM 应用开发:prompt → LLM → 输出解析
- 快速搭建处理管道
- 每个组件可以单独替换
代码复现:
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = PromptTemplate.from_template(prompt_text, template_format="jinja2")
chain = prompt | llm_client | StrOutputParser() # 链式组合
result = chain.invoke({"history": "...", "user_message": "..."})
# 数据流向:字典 → prompt 渲染 → LLM 生成 → 解析成字符串
知识 7:异步编程(async/await)
说明:
用 async def 定义异步函数,用 await 等待异步操作(比如 HTTP 请求、LLM 调用),等待期间可以去做别的事,不阻塞线程。
使用场景:
- I/O 密集型操作(HTTP 请求、数据库查询、文件读写)
- 同时处理多个请求
- Web 框架(FastAPI 原生支持 async)
代码复现:
class LookupOrderStatusAction(Action):
async def run(self, state, args) -> ActionResult: # async 定义
response = await http_client.get(url) # await 等待,不阻塞
dict_data = response.json()
return ActionResult(...)
# 调用的时候也要 await
result = await action.run(state, args)
知识 8:抽象基类(ABC + abstractmethod)
说明:用 ABC 做父类,用 @abstractmethod 标记抽象方法,子类必须实现这些方法,否则不能实例化。
使用场景:
- 定义接口规范,强制子类实现某些方法
- 防止直接实例化基类
- 多态:统一调用接口,具体实现由子类决定
代码复习:
from abc import ABC, abstractmethod
class Action(ABC):
@abstractmethod
async def run(self, state, args) -> ActionResult:
pass # 子类必须实现这个方法,否则实例化报错
class MyAction(Action):
async def run(self, state, args): # 必须实现
return ActionResult()
知识 9:dataclass (slots=True)
说明:@dataclass 自动生成 __init__、__repr__ 等方法;slots=True 限制实例只能有定义过的属性,更省内存、访问更快。
使用场景:
- 纯数据容器类(只有属性,没有复杂方法)
- 需要创建大量实例(比如几百个 FlowStep)
- 想要类型安全,防止乱加属性
代码复现:
@dataclass(slots=True)
class ActionResult:
message: List[BotMessage] = field(default_factory=list)
slot_updates: Dict[str, Any] = field(default_factory=dict)
# 不用写 __init__,直接 ActionResult(message=[], slot_updates={})
知识 10:假数据过渡(Mock Data)
说明:在外部接口还没准备好的时候,先用写死的假数据让流程跑通,后面再换成真实接口调用。
使用场景:
- 前后端并行开发,后端接口还没好
- 测试的时候不想真的调外部接口
- 演示的时候用固定数据,结果可控
代码复现:
# 现在:假数据
class LookupOrderLogisticsAction(Action):
async def run(self, state, args):
return ActionResult(
slot_updates={
"logistics_company": "顺丰快递", # 写死的
"tracking_number": "LH123546789", # 写死的
"logistics_status": "已签收", # 写死的
}
)
# 以后:换成真实接口
# response = await http_client.get(f".../logistics/{order_number}")
# return ActionResult(slot_updates=response.json())
三、总结
数据流转图
流程配置(YAML)里某一步配置了 action: action_lookup_order_status
↓
引擎拿到这一步的 action_name 和 args
↓
构造 ActionCall(action_name="action_lookup_order_status", args={...})
↓
ActionRunner.execute(action_call, state)
↓
├─ registry.get("action_lookup_order_status")
│ ↓ 找到 LookupOrderStatusAction 实例
│
└─ action.run(state, args)
↓
├─ 从 state.active_task.slots 拿参数(订单号)
├─ 调外部接口查数据
└─ 返回 ActionResult(message=[], slot_updates={"order_status": "已发货"})
↓
引擎拿到 ActionResult
↓
├─ 把 message 里的 BotMessage 发给用户
└─ 把 slot_updates 合并到 state.active_task.slots
ProcessOn绘图
暂时显示不了
浙公网安备 33010602011771号