用途
中间件(Middleware)用于处理应当跨多次运行一致生效的横切行为,且无需修改智能体、模型客户端或工具本身。常见使用场景包括:日志、追踪与计时;瞬时失败重试;在请求到达模型前裁剪历史;限制或估算 Token 用量;改写工具参数或结果;在工具运行前执行策略校验;审计人类输入请求。
四个钩子
BaseMiddleware 暴露四个异步钩子,只实现需要的即可:
| 钩子 | 包裹对象 | 用途 |
|---|---|---|
| on_turn(call_next, event, context) → ModelResponse | 整个智能体回合 | 总延迟、请求/响应检查、回合级策略 |
| on_llm_call(call_next, events, context) → ModelResponse | 每次 LLM API 调用 | 重试、日志、历史裁剪、请求改写、缓存 |
| on_tool_execution(call_next, event, context) → ToolResultType | 每次工具调用 | 参数校验、结果脱敏、失败回退、访问控制 |
| on_human_input(call_next, event, context) → HumanMessage | 每次 context.input() | 审计、改写提示、自动短路、限流 |
每个实例每回合创建一次,可在 self 上保存回合级状态。同一个实例可实现多个钩子。
内置中间件
可从 ag2.middleware 导入:
| 中间件 | 用途 | 构造参数 |
|---|---|---|
| LoggingMiddleware | 记录回合开始/结束、每次 LLM 调用、每次工具执行 | 无参数 |
| RetryMiddleware | 重试失败的 LLM 调用 | max_retries=N, retry_on=ExceptionClass |
| HistoryLimiter | 在 LLM 调用前限制事件数量 | max_events=N |
| TokenLimiter | 在 LLM 调用前按字符估算 Token 预算上限 | max_tokens=N, chars_per_token=4 |
| TelemetryMiddleware | OpenTelemetry GenAI span(参见 ag2-telemetry) | 参见 telemetry 技能 |
注册方式
智能体级
对每个回合生效:
from ag2 import Agent
from ag2.config import OpenAIConfig
from ag2.middleware import LoggingMiddleware, RetryMiddleware
agent = Agent(
"assistant",
config=OpenAIConfig(model="gpt-4o-mini"),
middleware=[
LoggingMiddleware(),
RetryMiddleware(max_retries=2),
],
)
调用级
为单个回合添加临时中间件,agent.ask(...) 和 reply.ask(...) 均接受该参数:
from ag2.middleware import TokenLimiter
reply = await agent.ask("Summarise the latest messages.", middleware=[LoggingMiddleware()])
next_turn = await reply.ask("Now answer in one paragraph.", middleware=[TokenLimiter(max_tokens=4000)])
调用级中间件追加在智能体中间件列表之后。
执行顺序
中间件按注册顺序运行,类似嵌套的 with 块。注册 [A, B, C] 时进入顺序为 A → B → C,退出顺序为 C → B → A:
enter A
enter B
enter C
<LLM call>
exit C
exit B
exit A
这在混合日志、改写与重试时很重要。若希望 RetryMiddleware 重试的是改写后的请求,改写应放在重试内部;若希望每次重试尝试都被单独记录,日志应放在重试内部。
编写自定义中间件
继承 BaseMiddleware 并实现需要的钩子:
import logging
from collections.abc import Sequence
from ag2 import Agent, Context
from ag2.config import OpenAIConfig
from ag2.events import BaseEvent, ModelResponse, ToolCallEvent
from ag2.middleware import BaseMiddleware, LLMCall, Middleware, ToolExecution
class AuditMiddleware(BaseMiddleware):
def __init__(self, event: BaseEvent, context: Context, logger: logging.Logger) -> None:
super().__init__(event, context)
self.logger = logger
async def on_llm_call(self, call_next: LLMCall, events: Sequence[BaseEvent], context: Context) -> ModelResponse:
self.logger.info("Calling model with %d events", len(events))
response = await call_next(events, context)
self.logger.info("Model returned: %s", response)
return response
async def on_tool_execution(self, call_next: ToolExecution, event: ToolCallEvent, context: Context):
self.logger.info("Executing tool: %s", event.name)
return await call_next(event, context)
agent = Agent(
"assistant",
config=OpenAIConfig(model="gpt-4o-mini"),
middleware=[
Middleware(AuditMiddleware, logger=logging.getLogger("ag2.audit")),
],
)
如果中间件需要 event 和 context 之外的构造参数,注册时用 Middleware(YourClass, ...) 包装。零配置中间件可直接传入(middleware=[LoggingMiddleware()])。
工具级 vs 智能体级
仅针对单个工具的行为(该工具输出的校验、脱敏、审批闸门),应使用工具中间件——在 @tool、@agent.tool 或 Toolkit 上设置 middleware=[hook]。内置的 approval_required()(参见 ag2-hitl)即为工具中间件。
智能体中间件运行在工具中间件之外:BaseMiddleware.on_tool_execution() 能看到包含工具级钩子在内的完整执行过程。
如何选择钩子
- on_turn → 关于整个请求/响应生命周期的行为。
- on_llm_call → 关于进入/离开模型内容的行为。
- on_tool_execution → 跨多个工具的工具安全/审计/结果塑形。
- 工具级中间件(非 BaseMiddleware)→ 单个工具定义的行为。
- on_human_input → 拦截 HITL 请求/响应。
深入阅读
- references/builtin_middleware.md — 各内置中间件的参数、常见场景配方与适用时机。
- website/docs/user-guide/middleware.mdx — 完整参考、顺序示例、自定义中间件准则。
- website/docs/user-guide/tools/tool_middleware.mdx — 每工具钩子(不同的心智模型——普通异步可调用对象,而非 BaseMiddleware)。
- OpenTelemetry 埋点请参见 ag2-telemetry。
常见陷阱
- 忘记为构造参数使用 Middleware(...):middleware=[AuditMiddleware](无包装)仅在类只需要 event 和 context 时可用,否则需包装:middleware=[Middleware(AuditMiddleware, logger=...)]。
- 改写顺序意外:中间件按注册顺序运行。若中间件 A 裁剪历史而 B 记录它,应注册 [A, B],使 B 看到裁剪后的视图。
- 调用级中间件不会替换智能体中间件:它是追加的,智能体中间件仍会运行。
- 一个中间件做五件事:保持钩子职责聚焦。将日志+重试+改写+策略放在一个类中难以推理与排序,应拆成多个实例。
- 在 on_tool_execution 中按 event.name 分支处理单个工具:这是坏味道;单个工具的行为用工具级中间件,on_tool_execution 留给横切策略。
- 把 OpenTelemetry 埋点写进自定义代码:已有 TelemetryMiddleware,参见 ag2-telemetry。