Agent开发第4步:定义 RunEvent

在第3步中,我们成功让 Agent 跑通了"思考-调用工具-观察-回答"的闭环。但是在实际产品中(如 ChatGPT),用户不仅能看到最终答案,还能实时看到模型"正在调用网页搜索"、"正在执行代码"等中间状态。

为了将黑盒式的 Agent Loop 变得透明可观测,我们需要引入事件驱动(Event-Driven)的架构,第1步就是定义规范化的内部状态事件 (RunEvent)。

1. 为什么需要 RunEvent?

如果代码只是一个简单的 return final_answer,前端页面在几秒甚至十几秒的等待中只能显示一个无聊的 Loading 圈。 通过定义 RunEvent,Agent 可以在执行的不同阶段向外"广播"自己的状态。

事件流的好处:

  1. 前端反馈:实时更新 UI,如"正在思考..."、"工具执行中..."。
  2. 打字机效果:流式传输生成的文本 (Tokens)。
  3. 日志与调试:方便后端记录执行轨迹,追踪是哪一步出了错。

2. 核心生命周期事件图

一个典型的 Agent Run 生命周期会触发以下事件:

3. Python 模型定义

我们推荐使用 Python 的 pydantic 库来定义强类型的事件结构。这不仅便于序列化为 JSON 供前端消费,也能保证后端代码的严谨性。

python 复制代码
from enum import Enum
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field
from datetime import datetime

class RunEventName(str, Enum):
    """定义所有可能的事件类型"""
    RUN_STARTED = "run.started"
    RUN_COMPLETED = "run.completed"
    RUN_FAILED = "run.failed"
    
    TEXT_DELTA = "text.delta"
    
    TOOL_CALL_STARTED = "tool_call.started"
    TOOL_CALL_COMPLETED = "tool_call.completed"

class BaseRunEvent(BaseModel):
    """事件基类"""
    run_id: str = Field(..., description="当前运行实例的唯一ID")
    event: RunEventName = Field(..., description="事件类型")
    created_at: datetime = Field(default_factory=datetime.utcnow)

# 1. 运行开始与结束事件
class RunStartedEvent(BaseRunEvent):
    event: RunEventName = RunEventName.RUN_STARTED
    input_query: str

class RunCompletedEvent(BaseRunEvent):
    event: RunEventName = RunEventName.RUN_COMPLETED
    final_output: str

class RunFailedEvent(BaseRunEvent):
    event: RunEventName = RunEventName.RUN_FAILED
    error_message: str

# 2. 文本流式事件
class TextDeltaEvent(BaseRunEvent):
    event: RunEventName = RunEventName.TEXT_DELTA
    text: str = Field(..., description="增量文本 token")

# 3. 工具调用事件
class ToolCallStartedEvent(BaseRunEvent):
    event: RunEventName = RunEventName.TOOL_CALL_STARTED
    tool_name: str
    tool_args: Dict[str, Any]

class ToolCallCompletedEvent(BaseRunEvent):
    event: RunEventName = RunEventName.TOOL_CALL_COMPLETED
    tool_name: str
    tool_result: str

4. 在 Agent Loop 中触发事件

修改我们第3步中的 Agent Loop,将 print() 替换为触发(Yield)这些事件:

python 复制代码
from typing import Generator

class ObservableAgent:
    def run_stream(self, run_id: str, user_input: str) -> Generator[BaseRunEvent, None, None]:
        # 任务开始
        yield RunStartedEvent(run_id=run_id, input_query=user_input)
        
        try:
            # 伪代码:流式生成与工具调用拦截
            # ... 
            # 当模型流式吐出文字时:
            yield TextDeltaEvent(run_id=run_id, text="北")
            yield TextDeltaEvent(run_id=run_id, text="京")
            
            # 当模型决定调用工具时:
            yield ToolCallStartedEvent(run_id=run_id, tool_name="get_weather", tool_args={"location": "北京"})
            # 执行工具...
            yield ToolCallCompletedEvent(run_id=run_id, tool_name="get_weather", tool_result="晴天")
            
            # 任务完成
            yield RunCompletedEvent(run_id=run_id, final_output="北京今天是晴天。")
            
        except Exception as e:
            yield RunFailedEvent(run_id=run_id, error_message=str(e))

总结

通过定义规范化的 RunEvent,我们将 Agent 从一个阻塞的黑盒变成了一个透明的事件源。这种设计不仅解耦了核心逻辑与展示层,还为后续的功能扩展打下了坚实基础。

那么,产生的这些对话历史和状态事件应该存放在哪里呢?下一篇,我们将介绍如何实现 Run Store (内存版) 来追踪和管理这些状态。

相关推荐
A7bert7773 小时前
【SAM3部署至AGX Orin】环境配置→模型部署→问题记录
c++·人工智能·深度学习·ubuntu
I'm a winner3 小时前
《AI 赋能嵌入式开发:从 0 到全栈工程师》模块1|第3课时
人工智能·嵌入式硬件
周凡1233 小时前
从文档生成到可靠交付:基于 Dify 的流程化实践
人工智能
数据堂官方账号3 小时前
数据堂联合华为共筑 AI 数据价值新引擎
人工智能·华为·数据采集·具身智能·数据标注
Xuantong_903 小时前
拆解 Claude Sonnet5.5 底层设计:自适应思考、子智能体协同,Anthropic 如何重构中端大模型推理范式
人工智能·语言模型·重构
LX567773 小时前
制造业人学AI,按经验分三层
大数据·人工智能
babe小鑫3 小时前
金融工程专业秋招:金融类证书与数据分析类证书的搭配方案
大数据·数据库·人工智能
数字新视界3 小时前
2026机房动环监控选型指南:场景规模需求、市场发展占有率和竞争梯队报告解析
大数据·运维·网络·人工智能·嵌入式硬件·机房动环监控·机房环境安全
TK泰妞3 小时前
TikTok跨境电商提示词怎么找?Clipcat免费提示词资源与实用用法
大数据·人工智能
胡家伟++4 小时前
从一段经文到 5 分半水墨动画短片:全 AI 流水线完整实录(即梦 + TTS 克隆 + ffmpeg + 思维链重构)
人工智能·ffmpeg·aigc