概述
智能体中间件是在不修改智能体或模型代码的前提下,向智能体执行流程中的关键位置注入自定义逻辑(日志、追踪、输入改写、访问控制等)的机制。 AgentScope 暴露了 6 个 hook 位置外加一个工具提供 hook,覆盖了从外层 reply 流程一路下沉到底层模型 API 调用的全链路:以上 hook 均作用于智能体层面。若需要在单个工具实例上挂钩 —— 无论该工具是在智能体内部还是外部被调用 —— 请参见工具中间件。
- Onion(洋葱式)—— 中间件包裹下一层 handler,可以在
next_handler()前后插入逻辑、观察中间事件流。 - Transformer(变换式)—— 中间件之间串成流水线,前一个的输出作为后一个的输入,不存在「内层」概念。
- Tool source(工具来源)—— 不在运行时路径上的 hook。
Agent.__init__不会调用list_tools();需要显式从中间件中收集工具并自行传入 toolkit。
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 只包裹智能体运行时内部的工具执行;通过 external execution 在智能体外部执行的工具不会被 on_acting 追踪到。装备中间件
AgentScope 把一组 hook 装在一个类里 —— 同一个中间件类可以同时实现 6 个 hook 位置(外加可选的list_tools tool-provider hook)中的任意子集。把实例传入 Agent(middlewares=[...]) 即可装备:
内置中间件
AgentScope 目前支持如下的中间件实现:链路追踪
TracingMiddleware 为智能体全生命周期接入 OpenTelemetry 追踪。它在 on_reply、on_model_call、on_acting 三个位置打点,按层级生成 span。
使用前先在进程中注册 TracerProvider 与 OTLP 导出器:
TracingMiddleware 装到智能体上即可:
- Agent Reply Span
- Model Call Span
- Tool Execution Span
来自
on_reply:- Agent 名称、session ID、reply ID
- 输入消息与最终输出消息
- HITL 等待中的工具调用
- External execution 等待中的工具调用
TracerProvider 时,所有 hook 会直接短路到 next_handler(),不创建 span 也不计算属性 —— 几乎零开销。
Agent 收到
ExternalExecutionResultEvent(外部执行的工具结果)时,TracingMiddleware 会为每条外部执行结果合成一个补偿 span,让外部系统执行的工具也能保留完整可观测性。添加自定义 Span
如需在智能体生命周期内追踪自定义操作,可直接使用 OpenTelemetry Python SDK。获取一个作用域为 AgentScope 的 tracer,并将目标代码包裹在 span 中:TracerProvider 中配置的同一个 OTLP collector。
预算控制
ReplyBudgetControlMiddleware 为单次 reply 强制一个加权 token 预算。它累计统计一次 reply 内所有 reasoning 步骤的 token 消耗,一旦预算耗尽,便指示智能体立即收尾、不再调用任何工具。适用于为长链路、工具密集的 ReAct 循环设定成本或时延上限。
每次模型调用的加权成本按如下公式计算:
token_budget 时,middleware 会:
- 在智能体上下文最后一条 assistant 消息中追加一个
HintBlock(若不存在则新建一条AssistantMsg),提醒模型给出最终结论性回复。 - 把下一步 reasoning 的
tool_choice强制覆写为ToolChoice(mode="none"),阻止继续调用工具。
float
必填
单次 reply 允许的最大加权 token 成本。累计成本达到此阈值后,智能体会被指示立刻收尾、不再调用工具。
float
默认值:"1"
计算加权成本时应用于 input tokens 的乘数。
float
默认值:"1"
计算加权成本时应用于 output tokens 的乘数。通常将其设为高于
input_token_weight,以体现 output tokens 实际成本更高。str
预算超出时注入智能体上下文的提示消息。默认是内置的收尾 prompt,要求模型给出最终结论性回复、不再调用任何工具。
预算的作用域是单次 reply,而非智能体生命周期。每次新的 reply 都会从零计数,因此该限制对每次
agent(...) 调用独立生效。语音生成
TTSMiddleware 拦截智能体的文本输出并合成语音,将 DataBlockStartEvent / DataBlockDeltaEvent / DataBlockEndEvent 音频事件注入到事件流中。它在 on_reply 阶段拦截,观察每个 TextBlockDeltaEvent 和 TextBlockEndEvent。
输出事件流因模式而异:
标准模式 — 音频在文本块结束后输出:
DataBlockDeltaEvent.data 携带增量 base64 编码的音频 chunk;完整音频为所有 delta 解码后的字节拼接,按 block_id 关联。
模型路由
当不同类型的请求适合交给不同模型处理时(例如日常问答交给低成本模型,复杂推理交给更强的模型),ModelRouterMiddleware 可以为每次回复自动选择对话模型。
它的工作方式如下:
- 每次回复开始时,读取输入中最新一条用户消息的文本,交给路由模型从候选模型中选出一个;
- 本次回复期间把
agent.model替换为选中的模型,回复结束后恢复为智能体原来的模型; - 每次回复只选择一次,回复因用户确认等原因暂停后再恢复时,沿用之前的选择。
分类模型来自
agentscope.classifier 模块,它针对给定输入回答类型化的问题(二元判断、单选、评分),并给出每个答案的概率。目前内置的实现是基于 TypeSafe API 的 JevClassifierModel,使用前需要安装可选依赖:
安装 Jev 依赖
ClassifierModelBase | ChatModelBase
必填
用于选择候选模型的路由模型。
Sequence[ChatModelCandidate]
必填
候选模型列表。每个
ChatModelCandidate 包含 name(路由模型返回的名称,必须唯一)、model(选中时使用的对话模型)和 description(何时选择该模型)。str
路由问题的指令。默认为
"Select the most suitable chat model for responding to the user input."。长期记忆
AgentScope 以中间件的形式支持长期记忆,让智能体能够在多个会话间持久保存并调取信息。详见 长期记忆 章节。RAG
AgentScope 中知识库同样以中间件形式提供,允许智能体在推理时访问外部知识库。详见 RAG 章节。自定义中间件
继承基类MiddlewareBase 并实现所需位置的 hook 即可。
下面的示例用一个中间件覆盖了所有位置。每个 onion hook 都会收到一个 input_kwargs 字典,承载流入下一层的字段,通过 next_handler(**input_kwargs) 透传,也可以用关键字参数覆写其中某些字段:
控制智能体流程
on_reply 不只能观察事件,还可以通过「转发、替换或吞掉」事件改变整个 reply 的流程。其中,如果中间件收到 ReplyEndEvent 却不再 yield,Agent 会继续下一轮推理与行动;只有结束事件穿过整条中间件链时,reply 才真正结束。
下面的中间件在 Agent 第一次准备结束时追加一条检查指令,并强制再执行一轮:
执行顺序
洋葱型 hook(on_reply、on_reasoning、on_acting、on_model_call)—— 列表中第一个中间件处于最外层:
on_system_prompt)—— 中间件从左到右串行接力:
list_tools 不在每次 reply 的执行路径上,智能体不会自动调用它 —— 它是一个便捷接口,让中间件可以声明自己的工具。是否收集这些工具由组装 toolkit 的调用方决定。