结构化事件建模与处理策略
为什么每个输入都要建模为结构化事件?
- 前提: 通用 Agent 的输入不只来自用户本人——第三方发来的消息不是用户发给 Agent 的,但 Agent 需要理解、评估重要性并决定如何介入;
- 泛化来源: n8n 等工作流平台的触发器生态中,Webhook、定时器、邮件、数据库变更、文件监听,每一种触发器都是 Agent 感知世界的一个“感官”;
- 目标: 异构事件统一建模后,Agent 能以一致方式处理不同来源的刺激,紧急度判定与处理策略都建立在这一统一建模之上;
- 安全意义: 只有维度清晰,才能避免把用户输入误当成工具结果,或把藏有指令的工具结果误认为用户指令,从而防止提示注入;
结构化事件包含哪些维度?
- 来源(谁): 用户本人、联系人、陌生人、系统通知;
- 渠道(方式): 电话语音、短信、即时消息、邮件、社交媒体、定时器触发、异步工具调用结果、命令行监控状态更新;
- 内容(什么): 消息文本、情感色彩、紧急程度、是否需要回复;
- 上下文(背景): 是对之前某个对话的回复还是新发起沟通,与当前任务的关联;
{
"source": { "type": "email", "sender": "client@example.com" },
"channel": "gmail_webhook",
"content": { "subject": "退款请求", "body": "订单 #12345 希望退款..." },
"context": { "priority": "high", "customer_tier": "vip", "related_orders": ["#12345"] }
}
- 多线程认知: Agent 还需理解多个对话线程之间的关联——第三方消息如何影响用户情绪、用户在不同对话中扮演什么角色、何时需要综合多线程信息给出建议;
事件在事件循环的哪个位置被消费?
- 骨架: 事件循环每轮从输入队列取出若干事件,追加到轨迹,调用一次 LLM,执行工具,再回到循环开头;
- 安全点(safe point): 一段推理结束或一次工具返回的位置,是事件可以被消费的边界;
- 同步接口实现: 事件在每轮循环边界被消费——LLM 推理中、工具执行中新到的事件先在队列等待,到安全点再处理;
- 原生异步: 允许在模型仍在思考或输出时接收新要求,由系统选择合适时机继续处理,边界由不同层管理;
- 取消的真实边界: 工具取消仍需执行器响应取消信号(类似 Go 中对
ctx.Done()的检查),接收一条“停止”消息本身不会撤销已经发生的动作; - 取消点要求: 必须是工具或推理能够安全收尾的位置;未完成的工具结果用显式占位符表示,不能伪造成功;
三种事件处理策略如何选择?
| 策略 | 适用事件 | 核心机制 |
|---|---|---|
| 取消式(Cancellation-Based) | 紧急事件 | 为紧急事件提前制造安全点:取消流式响应或发取消信号 → 清空待处理队列 → 事件与紧急事件一起追加到轨迹末尾 → 立即重新调用 LLM 评估局势 |
| 队列式(Queued) | 常规事件 | 事件入队尾不打断当前操作 → 等 LLM 完成推理、同步工具执行完毕 → 任一工具返回 tool.result 时检查队列并一次性追加 → LLM 综合处理 |
| 并行式(Parallel) | 独立轻量查询 | 在独立推理会话中执行并立即返回,查询与响应追加到主轨迹并标记“与主任务并行执行”,避免 LLM 混淆 |
- 取消式例子: 用户在 Agent 执行可能错误的操作时输入“停!我说错了”,Agent 立即看到新输入、重新理解真实意图;
- 队列式的批处理收益: Agent 调用搜索工具、等待期间用户补充“只看最近一个月的结果”,搜索结果返回时两个事件一起呈现,避免不必要的往返;
- 并行式的三个特征: 与主任务无关、需要快速响应、执行成本低——既不该取消式打断主任务,也不该让用户久等;
紧急度如何判定?
- 紧急事件类型: 用户中断
user.interrupt、监督指令supervisor.instruction、Agent 间中断agent.interrupt、标记为紧急的外部触发器(系统告警、支付失败); - 非紧急事件类型: 常规用户输入
user.input、Agent 输入agent.input、工具结果tool.result、定时器触发timer.trigger、常规外部触发器; - 硬编码规则的局限: 事件语义决定处理方式——“马上停下来”用取消式、“今天天气怎么样”用并行式、“报告需要用中文发给我”用队列式,固定规则难以覆盖;
- 推荐方案: 使用轻量级分类 LLM 作为事件路由器,在事件到达时快速判断应采用哪种策略;