当前位置: 首页 > news >正文

Agent 事件驱动架构:用消息队列解耦 Agent 内部模块

Agent 事件驱动架构:用消息队列解耦 Agent 内部模块

一、"Agent 推理链里加一个日志步骤,推理慢了 40%"

Agent 的推理流程是同步串联的:分析意图 → 规划步骤 → 调用工具 → 记录日志 → 返回结果。"记录日志"步骤本身 50ms,但因它阻塞在推理链中,导致整个 Agent 的响应被延迟。更糟的是,日志写入失败会导致整个 Agent 请求失败。

Agent 内部不需要所有操作都是同步的。记录日志、更新统计数据、触发后续通知——这些操作不应该阻塞用户等待 Agent 回复。事件驱动架构把 Agent 内部的模块从"同步调用"变为"异步事件",核心推理链只保留真正必须等待的步骤。

二、Agent 的事件驱动架构

事件驱动后的效果:核心推理链只保留必须同步等待的操作(工具调用、模型推理),其他操作(日志、监控、通知、分析)全部异步化。Agent 响应延迟降低 30-50%,而且旁路模块的故障不影响主流程。

三、Go 实现:Agent 内部事件总线

package eventdriven import ( "context" "encoding/json" "fmt" "sync" "time" ) // ========== Agent 事件定义 ========== // AgentEvent Agent 事件 type AgentEvent struct { ID string `json:"id"` Type string `json:"type"` // task.started, tool.called, task.completed TaskID string `json:"task_id"` TraceID string `json:"trace_id"` Timestamp time.Time `json:"timestamp"` Payload json.RawMessage `json:"payload"` Metadata map[string]string `json:"metadata"` } // 事件类型常量 const ( EventTaskStarted = "task.started" EventToolCalled = "tool.called" EventReasoningStep = "reasoning.step" EventTaskCompleted = "task.completed" EventTaskFailed = "task.failed" ) // TaskStartedPayload 任务开始事件载荷 type TaskStartedPayload struct { UserID string `json:"user_id"` Message string `json:"message"` } // ToolCalledPayload 工具调用事件载荷 type ToolCalledPayload struct { ToolName string `json:"tool_name"` Params map[string]interface{} `json:"params"` Latency time.Duration `json:"latency_ms"` } // ========== 事件发布器 ========== // EventPublisher 事件发布接口 type EventPublisher interface { Publish(ctx context.Context, event *AgentEvent) error } // AsyncEventPublisher 异步事件发布器 type AsyncEventPublisher struct { handlers map[string][]EventHandler // 事件类型 → 处理器列表 eventCh chan *AgentEvent ctx context.Context cancel context.CancelFunc mu sync.RWMutex bufferSize int } type EventHandler func(ctx context.Context, event *AgentEvent) error func NewAsyncEventPublisher(bufferSize int) *AsyncEventPublisher { ctx, cancel := context.WithCancel(context.Background()) p := &AsyncEventPublisher{ handlers: make(map[string][]EventHandler), eventCh: make(chan *AgentEvent, bufferSize), ctx: ctx, cancel: cancel, bufferSize: bufferSize, } // 启动事件处理协程池 for i := 0; i < 4; i++ { go p.processEvents(i) } return p } // Subscribe 订阅事件 func (p *AsyncEventPublisher) Subscribe(eventType string, handler EventHandler) { p.mu.Lock() defer p.mu.Unlock() p.handlers[eventType] = append(p.handlers[eventType], handler) } // Publish 发布事件(非阻塞) func (p *AsyncEventPublisher) Publish(ctx context.Context, event *AgentEvent) error { select { case p.eventCh <- event: return nil default: // 缓冲区满 → 丢弃事件但不阻塞 Agent 主流程 return fmt.Errorf("事件缓冲区已满,事件被丢弃: %s", event.Type) } } // processEvents 事件处理循环 func (p *AsyncEventPublisher) processEvents(workerID int) { for { select { case <-p.ctx.Done(): return case event := <-p.eventCh: p.dispatch(event) } } } // dispatch 分发事件给所有订阅者 func (p *AsyncEventPublisher) dispatch(event *AgentEvent) { p.mu.RLock() handlers := p.handlers[event.Type] p.mu.RUnlock() for _, handler := range handlers { // 每个 handler 独立执行,互不影响 go func(h EventHandler) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := h(ctx, event); err != nil { // handler 失败不影响主流程,只记录日志 fmt.Printf("[EventBus] handler 失败: event=%s, err=%v\n", event.Type, err) } }(handler) } } // Close 关闭事件发布器 func (p *AsyncEventPublisher) Close() { p.cancel() } // ========== Agent 集成 ========== // EventDrivenAgent 事件驱动的 Agent type EventDrivenAgent struct { eventBus *AsyncEventPublisher modelClient ModelClient toolRegistry ToolRegistry } func NewEventDrivenAgent(bus *AsyncEventPublisher) *EventDrivenAgent { agent := &EventDrivenAgent{ eventBus: bus, } // 注册事件处理器(在 Agent 启动时) bus.Subscribe(EventTaskStarted, agent.handleTaskStarted) bus.Subscribe(EventToolCalled, agent.handleToolCalled) bus.Subscribe(EventTaskCompleted, agent.handleTaskCompleted) return agent } // RunTask 执行 Agent 任务(核心推理链) func (a *EventDrivenAgent) RunTask(ctx context.Context, taskID, userID, message string) (string, error) { // 1. 发布任务开始事件(不等待) startedPayload, _ := json.Marshal(TaskStartedPayload{ UserID: userID, Message: message, }) a.eventBus.Publish(ctx, &AgentEvent{ ID: fmt.Sprintf("evt-%d", time.Now().UnixNano()), Type: EventTaskStarted, TaskID: taskID, Timestamp: time.Now(), Payload: startedPayload, }) // 2. 核心推理(同步等待) plan, err := a.planSteps(ctx, message) if err != nil { a.publishFailed(ctx, taskID, err) return "", err } // 3. 执行工具调用 for _, step := range plan.Steps { // 发布推理步骤事件 a.publishReasoningStep(ctx, taskID, step.Description) // 调用工具(同步等待) start := time.Now() result, err := a.toolRegistry.Call(ctx, step.ToolName, step.Params) latency := time.Since(start) // 发布工具调用事件(不等待) toolPayload, _ := json.Marshal(ToolCalledPayload{ ToolName: step.ToolName, Params: step.Params, Latency: latency, }) a.eventBus.Publish(ctx, &AgentEvent{ ID: fmt.Sprintf("evt-%d", time.Now().UnixNano()), Type: EventToolCalled, TaskID: taskID, Timestamp: time.Now(), Payload: toolPayload, }) if err != nil { a.publishFailed(ctx, taskID, err) return "", err } _ = result } // 4. 生成最终回答 answer := "最终回答" // 5. 发布任务完成事件(不等待) completedPayload, _ := json.Marshal(map[string]string{"answer": answer}) a.eventBus.Publish(ctx, &AgentEvent{ ID: fmt.Sprintf("evt-%d", time.Now().UnixNano()), Type: EventTaskCompleted, TaskID: taskID, Timestamp: time.Now(), Payload: completedPayload, }) return answer, nil } // ========== 事件处理器(旁路模块) ========== func (a *EventDrivenAgent) handleTaskStarted(ctx context.Context, event *AgentEvent) error { var payload TaskStartedPayload json.Unmarshal(event.Payload, &payload) // 异步:更新用户活跃度统计 // 异步:记录请求日志 fmt.Printf("[Handler] 任务开始: task=%s user=%s\n", event.TaskID, payload.UserID) return nil } func (a *EventDrivenAgent) handleToolCalled(ctx context.Context, event *AgentEvent) error { var payload ToolCalledPayload json.Unmarshal(event.Payload, &payload) // 异步:记录工具调用频率 // 异步:更新工具调用延迟指标 fmt.Printf("[Handler] 工具调用: %s 耗时 %v\n", payload.ToolName, payload.Latency) return nil } func (a *EventDrivenAgent) handleTaskCompleted(ctx context.Context, event *AgentEvent) error { // 异步:发送通知 // 异步:更新分析数据 fmt.Printf("[Handler] 任务完成: %s\n", event.TaskID) return nil } func (a *EventDrivenAgent) publishFailed(ctx context.Context, taskID string, err error) { failedPayload, _ := json.Marshal(map[string]string{"error": err.Error()}) a.eventBus.Publish(ctx, &AgentEvent{ ID: fmt.Sprintf("evt-%d", time.Now().UnixNano()), Type: EventTaskFailed, TaskID: taskID, Timestamp: time.Now(), Payload: failedPayload, }) } func (a *EventDrivenAgent) publishReasoningStep(ctx context.Context, taskID, desc string) { stepPayload, _ := json.Marshal(map[string]string{"description": desc}) a.eventBus.Publish(ctx, &AgentEvent{ ID: fmt.Sprintf("evt-%d", time.Now().UnixNano()), Type: EventReasoningStep, TaskID: taskID, Timestamp: time.Now(), Payload: stepPayload, }) } // ========== 类型定义 ========== type Plan struct { Steps []PlanStep } type PlanStep struct { Description string ToolName string Params map[string]interface{} } type ModelClient struct{} type ToolRegistry struct{} func (tr *ToolRegistry) Call(ctx context.Context, name string, params map[string]interface{}) (interface{}, error) { return nil, nil } func (a *EventDrivenAgent) planSteps(ctx context.Context, message string) (*Plan, error) { return &Plan{}, nil }

四、事件驱动架构的边界与风险

事件顺序性。异步事件不保证处理顺序。如果"任务完成"事件先于"工具调用"事件被处理,分析数据可能出错。需要依赖事件时间戳或序列号来保证分析的正确性。

缓冲区满时的丢弃策略。事件通道缓冲区满了,是丢弃、阻塞还是告警?推荐丢弃 + 告警——Agent 主流程的稳定性优先于事件记录的完整性。

测试复杂度增加。同步调用可以线性追踪,异步事件的处理发生在"别的地方"和"别的时间"。单元测试需要能捕获和断言发布的事件。

事件 Schema 的版本兼容。事件结构会随版本演化。新增字段需要向后兼容(老消费者忽略未知字段),删除字段需要渐进式迁移(先标记废弃再删除)。

五、总结

Agent 事件驱动架构的核心原则:核心推理链只保留必须同步等待的操作,其他旁路操作全部异步化。事件总线 + 发布订阅模式实现模块解耦,每个旁路模块独立订阅、独立失败。实施路径:先把最明显的可异步操作(日志、监控指标)从主流程中拆出来,确认稳定后再逐步迁移其他模块。

http://www.jsqmd.com/news/1201798/

相关文章:

  • 智能的PHP开发工具PhpStorm v2024.1全新发布——支持PHPUnit 11.0
  • 2026年水地源热泵品牌靠谱排行榜,选这几家准没错 - 官方资讯
  • 终极浏览器广告拦截指南:uBlock Origin让你的网络世界更干净、更安全、更快速
  • 雅典中国官方售后服务中心|地址与售后服务电话权威信息通告(2026年7月更新) - 亨得利官方服务中心
  • (十六)在判定假设下加密的安全证明(三)
  • Radare2高级功能:动态调试与漏洞利用实战
  • 计算机毕业设计之jsp新生儿疾病筛查信息系统
  • NeuroRebuild驱动的评标现场镜像孪生三维重构与全息智管平台
  • vue3中文字滚动代码
  • 上海翡翠回收避坑|5家主流平台实测对比!冰种/糯种翡翠这样卖最保值 - 奢侈品回收知识分享
  • 毕业论文不用“单工具硬扛”:8款AI工具组局,让研究想法3天“长成”答辩级论文 - 掌桥科研-AI论文写作
  • jspm足球联赛管理系统
  • 生命涌现的小龙虾技能之【Pet Picky Eater Detection | 宠物选择性拒食识别】简介
  • otel-desktop-viewer 与 Jaeger、Prometheus 对比:本地调试工具选型指南
  • Fiddler中文版插件推荐:10个必备扩展提升工作效率
  • 美度中国官方售后服务中心|全新电话和完整维修地址权威信息声明(2026年7月更新) - 亨得利钟表维修中心
  • 从零开始构建类似 otel-desktop-viewer 的工具:OpenTelemetry Collector 扩展开发
  • jspm美食推荐系统
  • 如何用Loop在5分钟内彻底改变你的macOS窗口管理体验
  • 2026年数据分析工具五大推荐 - 科技焦点
  • 卡地亚中国官方售后服务中心|服务热线及全部维修地址权威信息通知(2026年7月更新) - 卡地亚官方售后中心
  • Czkawka终极指南:如何用免费开源工具轻松清理重复文件
  • Cervus性能优化指南:7个技巧提升WASM内核执行效率
  • MinerU深度解析:3种核心技术路径破解复杂文档解析难题
  • 大模型多租户公平计费方案:Token 不再均摊的成本分摊模型
  • 计算机毕业设计之基于springboot的在线考试管理系统
  • 2026年7月重庆新房装修公司哪家好?——本土头部装企深度横评与选购指南 - 品牌鉴赏官2026
  • Vegas源码解析:如何构建类型安全的DSL系统
  • django-paypal信号机制全解析:如何优雅处理支付状态变更通知
  • 物理遮挡消融:基于空间折叠技术的视频孪生穿透式透明治理底座