跳转到内容

02 - 事件驱动工作流引擎架构

02 - 事件驱动工作流引擎架构

学习目标

本章将带你分析 LlamaIndex 的 Workflow 引擎架构。你将学到:

  • 为什么将 Workflow 引擎拆为独立 PyPI 包
  • Core 中的 workflow/ 薄包装层设计
  • @step 装饰器如何构建事件流图
  • Event 类型系统(StartEvent → 自定义事件 → StopEvent)
  • Context 的 KV 存储与事件流推送
  • 事件驱动架构的学习曲线权衡

前置知识

项目实践

引擎拆包决策

LlamaIndex 的 Workflow 引擎实现在独立的 PyPI 包 llama-index-workflows(版本 >=2.14.0)中,而非 llama-index-core。Core 中的 llama_index/core/workflow/ 目录是薄包装层,只做 re-export:

llama-index-core/llama_index/core/workflow/__init__.py
from workflows.workflow import Workflow, WorkflowMeta # noqa
# (其他文件类似,从 workflows 包 re-export)

拆包原因

  1. 解耦:Workflow 引擎是通用组件,不依赖 LLM 或 RAG 相关代码
  2. 独立演进:引擎可以独立发版,不受核心包版本周期影响
  3. 可复用:其他项目可以不安装 LlamaIndex 直接使用 Workflow 引擎
  4. 减少核心包依赖:核心包保持精简

代价:用户需要理解两层结构(core 中的 shim + 实际 PyPI 包),调试时需要在两个包之间跳转。

@step 装饰器构建事件流图

@step 装饰器标记工作流中的步骤函数,引擎通过静态分析函数签名推断该步骤接受什么事件、产生什么事件:

from llama_index.core.workflow import step, Workflow, Context, Event
class MyWorkflow(Workflow):
@step
async def process_input(self, ctx: Context, ev: StartEvent) -> ProcessEvent:
# 接受 StartEvent,产生 ProcessEvent
data = await fetch_data(ev.query)
return ProcessEvent(data=data)
@step
async def generate_response(self, ctx: Context, ev: ProcessEvent) -> StopEvent:
# 接受 ProcessEvent,产生 StopEvent
result = await generate(ctx, ev.data)
return StopEvent(result=result)

@step 的静态分析机制

引擎将所有 @step 方法的输入/输出事件类型收集起来,构建有向图。事件沿着图的边流动——从 StartEvent 开始,到 StopEvent 结束。

Event 类型系统

LlamaIndex 的 Event 系统定义了标准的事件层次:

class Event: ... # 所有事件的基类
class StartEvent(Event): ... # 工作流启动
class StopEvent(Event): ... # 工作流终止
class HumanResponseEvent(Event): ... # 人机交互响应
class InputRequiredEvent(Event): ... # 需要用户输入
# 自定义事件
class MyCustomEvent(Event):
data: str
metadata: dict

设计模式:使用 Python 类型注解实现事件路由。引擎根据事件的 Python 类型将其分发到对应的 @step 方法。自定义事件只需继承 Event 并添加数据字段。

Context:运行时上下文

Context 是工作流运行时的核心,提供三个关键能力:

1. KV 存储 (ctx.store)

ctx.data["key"] = value # 存储
value = ctx.data["key"] # 读取

用于管理 Agent 的 memory、state、迭代计数等运行时状态。

2. 事件收集 (ctx.collect_events())

等待多个并行事件全部到达后才继续。这是实现并行工具调用的核心机制。

3. 事件流推送 (ctx.write_event_to_stream())

将中间状态(AgentInput、AgentStream、ToolCall 等)推送给订阅者。支持实时可观测性——前端可以订阅并显示 Agent 的推理过程。

事件流图的实际执行

以一个简单的 RAG 工作流为例:

引擎的执行流程:

  1. 识别所有 @step 方法的输入/输出事件类型
  2. 构建有向图:StartEvent → EmbedDone → Results → StopEvent
  3. 从 StartEvent 开始,沿着图的边触发步骤
  4. 每个步骤完成后,将输出事件推入事件队列
  5. 引擎将事件路由到下游的 @step 方法
  6. 到达 StopEvent 时结束

问题与规避

陷阱对策
@step 方法的参数类型不匹配确保输出事件类型与下游 @step 的输入事件类型严格匹配
Context.store 的类型不安全使用明确的键名和类型注解;考虑封装为 TypedDict
事件流图无法执行(孤立节点)检查所有事件类型是否都有对应的 @step 方法接收
调试困难使用 ctx.write_event_to_stream() 推送中间状态,或用 CallbackManager 追踪
Workflow 引擎 overhead生产环境中评估 Instrumentation 的性能影响

设计取舍

事件驱动 vs 函数调用式

维度事件驱动 Workflow函数调用式
并发天然支持(事件并行)需要手动管理 asyncio.Task
可观测性每步自动推送事件需要手动添加日志
状态持久化Context 可序列化需要手动序列化
调试需要理解事件流标准断点调试
学习曲线陡峭平缓

为什么选择事件驱动:LlamaIndex 需要将 Agent 循环、RAG 流水线、多 Agent 编排统一在一个抽象下。事件驱动架构支持这些场景的无缝组合。代价是用户需要学习新的事件流概念,而非使用熟悉的函数调用模式。

独立包 vs 内嵌核心

维度独立 PyPI 包 (llama-index-workflows)内嵌核心 (core/workflow)
依赖隔离好(核心包不依赖引擎细节)差(核心包耦合引擎)
调试难度高(跨包跳转)低(同一代码库)
可复用性高(其他项目可用)低(绑定 LlamaIndex)
版本管理复杂(两个包版本对应)简单(单一版本)

参考来源

  • llama-index-workflows PyPI 包
  • LlamaIndex Core workflow/ shim 文件:llama-index-core/llama_index/core/workflow/