Skip to main content

概述

Agent middleware 是在不修改 agent 或 model 代码的前提下,向 agent 执行流程中的关键位置注入自定义逻辑(日志、追踪、输入改写、访问控制等)的机制。 AgentScope 暴露了 6 个 hook 位置外加一个 tool-provider hook,覆盖了从外层 reply 流程一路下沉到底层模型 API 调用的全链路: 三种类型的差别:
  • Onion(洋葱式)—— middleware 包裹下一层 handler,可以在 next_handler() 前后插入逻辑、观察中间事件流。
  • Transformer(变换式)—— middleware 之间串成流水线,前一个的输出作为后一个的输入,不存在「内层」概念。
  • Tool source(工具来源)—— 不在运行时路径上的 hook。Agent.__init__ 不会调用 list_tools();需要显式从 middleware 中收集工具并自行传入 toolkit。
下图展示这些 hook 在 agent 生命周期中的嵌套关系。on_system_prompt 嵌入在 on_reasoning 内部,因为它在 reasoning 步骤组装 system prompt 时被触发;on_compress_context 位于每轮 ReAct 顶部,在 reasoning 之前:
on_reply
ReAct loop(每一轮)
on_compress_context(上下文压缩判定)
on_reasoning
on_system_prompt(组装 system prompt)
on_model_call(模型 API 调用)
on_acting(每个工具调用一次)
当前 on_acting 只包裹 agent 运行时内部的工具执行;通过 external execution 在 agent 外部执行的工具不会被 on_acting 追踪到。

装备 Middleware

AgentScope 把一组 hook 装在一个类里 —— 同一个 middleware 类可以同时实现 6 个 hook 位置(外加可选的 list_tools tool-provider hook)中的任意子集。把实例传入 Agent(middlewares=[...]) 即可装备:
构造时 agent 会扫描每个 middleware 实例,按它实际实现了哪些 hook,把它分配到对应位置的执行列表中。未实现的位置自动跳过,不产生任何调用开销。

内置 Middleware

TracingMiddleware

TracingMiddleware 为 agent 全生命周期接入 OpenTelemetry 追踪。它在 on_replyon_model_callon_acting 三个位置打点,按层级生成 span。 使用前先在进程中注册 TracerProvider 与 OTLP 导出器:
随后把 TracingMiddleware 装到 agent 上即可:
每次 reply 会产出一棵嵌套 span 树,每个层级捕获的关键属性如下:
来自 on_reply
  • Agent 名称、session ID、reply ID
  • 输入消息与最终输出消息
  • HITL 等待中的工具调用
  • External execution 等待中的工具调用
未配置 TracerProvider 时,所有 hook 会直接短路到 next_handler(),不创建 span 也不计算属性 —— 几乎零开销。
Agent 收到 ExternalExecutionResultEvent(外部执行的工具结果)时,TracingMiddleware 会为每条外部执行结果合成一个补偿 span,让外部系统执行的工具也能保留完整可观测性。

添加自定义 Span

如需在 agent 生命周期内追踪自定义操作,可直接使用 OpenTelemetry Python SDK。获取一个作用域为 AgentScope 的 tracer,并将目标代码包裹在 span 中:
这些自定义 span 会与 AgentScope 内置的 span 一并上报,发送到 TracerProvider 中配置的同一个 OTLP collector。

TTSMiddleware

TTSMiddleware 拦截 agent 的文本输出并合成语音,将 DataBlockStartEvent / DataBlockDeltaEvent / DataBlockEndEvent 音频事件注入到事件流中。它在 on_reply 阶段拦截,观察每个 TextBlockDeltaEventTextBlockEndEvent
中间件根据 TTS 模型的模式自动适配: 输出事件流因模式而异: 标准模式 — 音频在文本块结束后输出:
实时模式 — 音频 chunk 与文本交替到达(并发合成):
每个 DataBlockDeltaEvent.data 携带增量 base64 编码的音频 chunk;完整音频为所有 delta 解码后的字节拼接,按 block_id 关联。

自定义 Middleware

继承 MiddlewareBase,只实现需要的 hook 即可,其它的不用管。 下面的示例用一个 middleware 覆盖了所有位置。每个 onion hook 都会收到一个 input_kwargs 字典,承载流入下一层的字段,通过 next_handler(**input_kwargs) 透传,也可以用关键字参数覆写其中某些字段:

执行顺序

Onion 类 hook(on_replyon_reasoningon_actingon_model_call)—— 列表中第一个 middleware 处于最外层
对于流式 / 产出事件的 hook,内层 middleware 先看到每一个 yield 出的事件:
Transformer 类 hook(on_system_prompt)—— middleware 从左到右串行接力
一次 reply 中各 hook 的整体执行顺序遵循 agent 生命周期:
list_tools 不在每次 reply 的执行路径上,agent 不会自动调用它 —— 它是一个便捷接口,让 middleware 可以声明自己的工具。是否收集这些工具由组装 toolkit 的调用方决定。

实用示例

计时 middleware

下面的 middleware 记录每次模型调用的耗时:

限速 middleware

下面的 middleware 在两次模型调用之间强制留出最小间隔:

动态 system prompt middleware

下面的 middleware 在 system prompt 中注入实时上下文:

模型回退 middleware

下面的 middleware 在主模型失败时切换到备用模型: