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 (内存版) 来追踪和管理这些状态。

相关推荐
鲜于言悠9051 小时前
2026 AI Agent完整学习路线:6个阶段,从入门到可接商业项目
人工智能
Blockchina1 小时前
从一个 AI 助手到一支 AI 团队:用 Grok Bot 搭建自媒体内容流水线
人工智能
用户2215602767751 小时前
Dify 1.11.4 配置 LLM 深度思考:获取 reasoning_content 并支持前端渲染
人工智能
乱世刀疤1 小时前
WorkBuddy防踩坑指南
人工智能·workbuddy
开开心心就好2 小时前
PDF图片去水印软件,支持批量处理页面
前端·javascript·人工智能·智能手机·pdf·语音识别
深兰科技2 小时前
深兰科技亮相2026全球独角兽大会,获评“2026环卫机器人品类领袖企业”
人工智能·科技·jupyter·vim·腾讯会议·深兰科技·全球独角兽大会
HAHAXX82 小时前
2026智能自动化落地:通义灵码与Cursor加持,RPA融合生成式AI的工程化实践
人工智能·自动化·rpa
chuntian_tester2 小时前
AI自动化第1步【系统探索】
人工智能·测试工具·ai·自动化
月华路2 小时前
《模型不玄学》第29章 上线监控与再训练
人工智能·深度学习·机器学习