跳转到内容

03 - 事件驱动 Agent 循环

03 - 事件驱动 Agent 循环

学习目标

本章将带你深入 LlamaIndex 的 Agent 循环实现。你将学到:

  • Workflow 引擎的事件驱动状态机架构
  • @step 装饰器如何构建事件流图
  • ReActAgent 的 Thought-Action-Observation 循环
  • FunctionAgent 的原生 function calling 路径
  • 并行工具调用的等待-聚合模式
  • Context 状态管理机制

前置知识


项目实践

Agent 架构概览

LlamaIndex 的 Agent 系统建立在 Workflow 引擎之上。所有 Agent 类型继承自 BaseWorkflowAgent,它同时继承了 Workflow(事件驱动能力)和 Pydantic BaseModel(验证与序列化)。

Workflow 引擎:事件驱动状态机

与传统的 while True 循环不同,LlamaIndex 的 Agent 循环是事件驱动状态机。每个步骤用 @step 装饰器标记,引擎通过静态分析函数签名推断该步骤接受什么事件、产生什么事件。

核心步骤的事件流:

每个 @step 方法定义了一条状态转换边:输入事件类型 → 处理逻辑 → 输出事件类型。引擎将所有 @step 方法组装成有向图,事件沿着图的边流动。

ReActAgent:文本解析式推理循环

ReActAgent 实现了经典的 ReAct (Reasoning + Acting) 循环,基于文本格式解析。

循环流程

  1. LLM 输入格式化:使用 ReActChatFormatter 组合 system header、工具描述、对话历史、当前推理步骤
  2. LLM 调用llm.achat() 获取响应
  3. 输出解析ReActOutputParser 尝试匹配两种格式:
    • Action:ActionReasoningStep(需要调用工具)
    • Answer:ResponseReasoningStep(给出最终回答)
  4. 工具执行:如果是 Action,创建 ToolSelection 并执行
  5. 观察注入:工具结果包装为 ObservationReasoningStep 追加到推理链

推理步骤类型

类型触发条件内容
ActionReasoningStepLLM 输出包含 Action:thought + 工具名 + 参数
ObservationReasoningStep工具执行完成observation(工具输出)
ResponseReasoningStepLLM 输出包含 Answer:thought + 最终回答

ReAct 提示词模板(伪代码格式):

你是 designed to help with tasks using tools.
## 工具
> Tool Name: tool_name
> Tool Description: 工具描述
> Tool Args: 参数 schema
## 输出格式
Thought: <你的推理>
Action: tool_name
Action Input: {"key": "value"}
Observation: tool_response
重复直到你能回答:
Thought: 我不需要工具就能回答。
Answer: <你的回答>

FunctionAgent:原生 Function Calling

FunctionAgent 与 ReActAgent 的关键区别在于它依赖 LLM 的原生 function calling 能力,不需要文本格式化的 Thought-Action-Observation 循环。

前置条件llm.metadata.is_function_calling_model 必须为 True

take_step 流程(伪代码):

async def take_step(self, ctx, input):
# 1. 组装输入 + scratchpad
llm_input = build_input(ctx, input)
# 2. 调用 LLM 原生 function calling
response = await llm.achat_with_tools(
chat_history=scratchpad,
tools=self.get_tools(input),
allow_parallel_tool_calls=self.allow_parallel_tool_calls
)
# 3. 从响应中提取 tool_calls
tool_calls = llm.get_tool_calls_from_response(response)
# 4. 追加到 scratchpad
scratchpad.append(response.message)
return AgentOutput(tool_calls=tool_calls)

与 ReActAgent 对比

维度ReActAgentFunctionAgent
工具调用协议文本格式 (Thought-Action-Observation)OpenAI function calling
适用 LLM所有 LLM仅支持 function calling 的 LLM
并行工具调用不支持(串行解析)支持(allow_parallel_tool_calls
提示词开销高(工具描述格式化为文本)低(JSON schema 传递)
容错能力中(LLM 格式错误时可重试)高(LLM 保证 JSON 格式)

并行工具调用的等待-聚合模式

当 Agent 需要调用多个工具时,LlamaIndex 使用 ctx.collect_events() 实现并行调用和结果聚合:

关键设计:所有工具调用并行发出collect_events 等待全部结果到达后才进入聚合。避免了传统循环中工具串行调用的延迟累积。

Context 状态管理

Context 对象提供 KV 存储 ctx.store,管理 Agent 的运行时状态:

  • memory:对话历史列表
  • state:自定义状态字典
  • max_iterations:最大迭代次数
  • iteration_count:当前迭代计数器

early_stopping_method:达到最大迭代次数时的处理方式:

  • "force":抛出异常终止
  • "generate":让 LLM 生成最终回答后正常结束

AgentWorkflow.from_tools_or_functions() 工厂

这个工厂方法根据 LLM 是否支持 function calling 自动选择 Agent 类型:

# 伪代码
agent = AgentWorkflow.from_tools_or_functions(
tools_or_functions=[search_tool, calculator],
llm=my_llm # 如果 my_llm 支持 function calling → FunctionAgent,否则 → ReActAgent
)

问题与规避

陷阱对策
ReActAgent 的 LLM 输出格式不正确在系统提示中明确 Thought/Action/Answer 格式;解析器会自动重试
FunctionAgent 在不支持 function calling 的 LLM 上失败使用 from_tools_or_functions() 工厂自动回退到 ReActAgent
工具调用无限循环设置 max_iterations 并选择 "generate" 作为 early_stopping_method
并行工具调用的结果顺序不确定不要在 Agent 逻辑中依赖工具执行顺序
Context.store 在多 Agent 间泄漏状态每个 Agent 使用独立的 memory 键,或手动管理 state 隔离

设计取舍

事件驱动 vs 简单 while 循环

维度事件驱动 Workflow简单 while 循环
并发工具调用天然支持需要手动管理 asyncio.Task
状态持久化Context 可序列化需要手动序列化
可观测性每步自动推送事件需要手动添加日志
学习曲线陡峭(理解事件流图)平缓(标准编程模式)
灵活性高(可组合、可路由)低(固定循环结构)

为什么选择事件驱动:LlamaIndex 需要将 Agent 循环与 RAG 流水线、多 Agent 编排等复杂场景集成。事件驱动架构支持这些高级场景,代价是入门门槛更高。

参考来源