AgentScope 2.0:5. Middleware —— 无侵入式智能体扩展机制深度解析
目录:
1. 面向生产环境的智能体工程平台
2. 快速上手 从零构建生产级智能体
3. Agent —— 智能体的核心抽象与工程化实践
4. Message & Event —— 消息模型与事件流深度解
5. Middleware —— 无侵入式智能体扩展机制深度解析
6. Model —— 统一模型接入层与容错机制深度解析
7. Permission System —— 权限控制系统深度解析
8. Tool —— 工具系统架构与生产级实践深度解析
9. Context —— 运行时上下文与状态管理深度解析
一、引言:为什么需要 Middleware?
在智能体生产化的过程中,开发者面临的典型需求是:
“我们想给 Agent 加上日志埋点、限流、Token 计费、敏感词过滤……这些需求有一个共同点:在 Agent 干活的特定时刻插一段自己的代码。”
AgentScope Java 1.x 用 Hook 实现这一需求,但扁平的 Hook 机制缺乏阶段划分、组合困难。2.0 用更通用的 Middleware(中间件) 全面取代 Hook,提供了结构化、可组合、无侵入的扩展机制。
Agent Middleware 是无侵入扩展机制:不改动 Agent、Model 源码,在执行链路关键节点插入横切逻辑。
典型场景包括:链路追踪、日志埋点、输入改写、权限拦截、限流、动态提示词、异常降级等。
二、核心设计:两类中间件模型
AgentScope Java 2.0 划分了两类中间件模型,覆盖不同的扩展需求:
2.1 Onion 洋葱模型
洋葱模型(Onion Model)是中间件执行的核心逻辑模式。请求先逐层穿过外层中间件到达核心逻辑,再反向逐层穿过外层中间件返回,形似洋葱的层级结构:
请求进入
│
▼
┌─────────────────────────────────────────┐
│ Middleware A (进入) │
│ ┌───────────────────────────────────┐ │
│ │ Middleware B (进入) │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ 核心逻辑 (Core) │ │ │
│ │ └───────────────────────────┘ │ │
│ │ Middleware B (离开) │ │
│ └───────────────────────────────────┘ │
│ Middleware A (离开) │
└─────────────────────────────────────────┘
│
▼
响应返回
**执行顺序:**进入 A → 进入 B → 执行 Core → 离开 B → 离开 A
适用场景:
- 链路追踪(进入时开 Span,离开时关 Span)
- 计时统计(进入时记录开始时间,离开时计算耗时)
- 异常兜底(进入时 try,离开时 catch)
- 优雅关闭(进入时注册,离开时清理)
2.2 Transformer 变换模型
变换模型(Transformer)关注的是数据流的改写:中间件接收输入数据,进行变换后传递给下一层:
Input → [Middleware A 变换] → [Middleware B 变换] → Core → Output
适用场景:
- 动态注入上下文(在 System Prompt 中追加时间、角色信息)
- 敏感词过滤(对输入/输出进行文本替换)
- 参数校验与改写(对工具调用参数进行规范化)
- 动态技能注入(根据用户身份注入不同的工具描述)
2.3 两类模型对比
| 维度 | Onion 洋葱型 | Transformer 变换型 |
|---|---|---|
| 核心思想 | 包裹(Wrap) | 变换(Transform) |
| 数据流 | 穿透后原路返回 | 单向流过,逐层修改 |
| 典型用途 | 追踪、计时、异常处理 | 输入改写、过滤、注入 |
| 类比 | Koa.js 中间件 / Servlet Filter | Unix 管道 / Map 函数 |
| 能否中断 | 可以(不调用 next) | 可以(返回空/异常) |
三、五个 Hook 挂载点位
AgentScope Java 2.0 将 Agent 执行生命周期划分为五个关键节点,每个节点前后都可以插入 Middleware:
agent.call(msg, rt)
│
① onAgent ← 整轮调用的起点
│
② onSystemPrompt ← 系统提示词拼好之后、发给 LLM 之前
│
③ onReasoning ← LLM 做推理、吐文字
│
④ onActing ← 工具调用执行
│
⑤ onModelCall ← 底层模型 API 调用
│
▼
返回结果
3.1 各挂载点详解
| # | 挂载点 | 位置说明 | 典型用途 |
|---|---|---|---|
| ① | onAgent | 整轮调用的起点/终点 | 设置日志上下文、绑定租户信息、初始化链路追踪、限流、计时 |
| ② | onSystemPrompt | 系统提示词拼好之后、发给 LLM 之前 | 动态注入时间/角色/业务上下文、技能描述 |
| ③ | onReasoning | LLM 推理阶段 | 审计、敏感词检测、Token 预算检查 |
| ④ | onActing | 工具调用执行阶段 | 权限检查、参数校验、沙箱策略、审批拦截 |
| ⑤ | onModelCall | 底层模型 API 调用 | 日志记录、Token 计费、重试策略、模型切换 |
3.2 完整执行流中的 Middleware 触发时序
用户请求到达
│
▼
┌── onAgent (Onion: 进入) ──────────────────────────────────────┐
│ │
│ ┌── onSystemPrompt (Transformer) ──────────────────────┐ │
│ │ 动态注入当前时间、用户角色、工作区摘要 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌── onReasoning (Onion: 进入) ─────────────────────────┐ │
│ │ │ │
│ │ ┌── onModelCall (Onion) ─────────────────────┐ │ │
│ │ │ 记录请求、检查限流、调用 LLM API │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ │ │
│ └── onReasoning (Onion: 离开) ─────────────────────────┘ │
│ │
│ [如果模型决定调用工具] │
│ ┌── onActing (Onion) ──────────────────────────────────┐ │
│ │ 权限检查 → 参数校验 → 执行工具 → 结果审计 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [循环回到 onReasoning 直到模型给出最终回复] │
│ │
└── onAgent (Onion: 离开) ──────────────────────────────────────┘
│
▼
返回最终响应
四、MiddlewareBase 接口
4.1 核心接口定义
MiddlewareBase 是所有中间件的基类/接口,提供五个可覆写的钩子方法:
package io.agentscope.core.middleware;
public abstract class MiddlewareBase {
/**
* ① Agent 整轮调用包裹(Onion 模型)
* 在 agent.call() 的最外层执行
*/
public Mono<Void> onAgent(AgentContext ctx, MiddlewareChain chain) {
return chain.next(ctx); // 默认直接穿透
}
/**
* ② 系统提示词变换(Transformer 模型)
* 在 system prompt 组装完成后、发送给模型前调用
*/
public Mono<String> onSystemPrompt(SystemPromptContext ctx) {
return Mono.just(ctx.getPrompt()); // 默认不变换
}
/**
* ③ 推理阶段包裹(Onion 模型)
* 在每轮 LLM 推理前后执行
*/
public Mono<Void> onReasoning(ReasoningContext ctx, MiddlewareChain chain) {
return chain.next(ctx);
}
/**
* ④ 工具执行包裹(Onion 模型)
* 在每次工具调用前后执行
*/
public Mono<Void> onActing(ActingContext ctx, MiddlewareChain chain) {
return chain.next(ctx);
}
/**
* ⑤ 模型 API 调用包裹(Onion 模型)
* 在底层 HTTP 请求前后执行
*/
public Mono<ModelCallResponse> onModelCall(
ModelCallRequest request, MiddlewareChain chain) {
return chain.next(request);
}
}
4.2 设计要点
| 特性 | 说明 |
|---|---|
| 响应式 | 所有方法返回 Mono,基于 Project Reactor |
| 链式调用 | 通过 chain.next() 传递控制权,不调用即中断 |
| 默认穿透 | 不覆写的方法默认直接放行,零开销 |
| 可选覆写 | 只需覆写关心的钩子,其余保持默认 |
4.3 注册方式
通过 Builder 注册中间件链:
ReActAgent agent = ReActAgent.builder()
.name("my-agent")
.model("dashscope:qwen-plus")
.sysPrompt("你是一个助手。")
// 注册中间件(按顺序执行)
.middleware(new OtelTracingMiddleware())
.middleware(new RateLimitMiddleware(100)) // 每分钟 100 次
.middleware(new SensitiveWordFilter())
.middleware(new DynamicSkillMiddleware())
.build();
五、内置 Middleware 实现详解
AgentScope Java 2.0 提供了多个开箱即用的内置中间件实现:
5.1 TaskReminderMiddleware —— 任务提醒
在 onSystemPrompt 阶段动态注入当前待办任务信息:
public class TaskReminderMiddleware extends MiddlewareBase {
@Override
public Mono<String> onSystemPrompt(SystemPromptContext ctx) {
String reminder = taskService.getActiveReminders(ctx.getUserId());
if (reminder != null && !reminder.isEmpty()) {
return Mono.just(ctx.getPrompt() + "\n\n[当前待办]\n" + reminder);
}
return Mono.just(ctx.getPrompt());
}
}
设计意图:让 Agent 在每次推理时自动感知用户的未完成任务,无需修改 Agent 核心逻辑。
5.2 OtelTracingMiddleware —— OpenTelemetry 链路追踪
在 onAgent 和 onModelCall 阶段创建/关闭 Span:
public class OtelTracingMiddleware extends MiddlewareBase {
private final Tracer tracer = GlobalOpenTelemetry.getTracer("agentscope");
@Override
public Mono<Void> onAgent(AgentContext ctx, MiddlewareChain chain) {
Span span = tracer.spanBuilder("agent.call")
.setAttribute("agent.name", ctx.getAgentName())
.setAttribute("user.id", ctx.getUserId())
.setAttribute("session.id", ctx.getSessionId())
.startSpan();
return chain.next(ctx)
.doFinally(signal -> span.end()); // Onion: 离开时关闭
}
@Override
public Mono<ModelCallResponse> onModelCall(
ModelCallRequest request, MiddlewareChain chain) {
Span span = tracer.spanBuilder("model.call")
.setAttribute("model.name", request.getModelName())
.startSpan();
return chain.next(request)
.doOnNext(resp -> {
span.setAttribute("tokens.prompt", resp.getPromptTokens());
span.setAttribute("tokens.completion", resp.getCompletionTokens());
})
.doFinally(signal -> span.end());
}
}
设计意图:生产环境必备的全链路可观测性,精确追踪每次模型调用的耗时与 Token 消耗。
5.3 GracefulShutdownMiddleware —— 优雅关闭
在 onAgent 阶段注册执行状态,支持滚动发布时等待在途请求完成:
public class GracefulShutdownMiddleware extends MiddlewareBase {
private final AtomicInteger inFlight = new AtomicInteger(0);
private volatile boolean shuttingDown = false;
@Override
public Mono<Void> onAgent(AgentContext ctx, MiddlewareChain chain) {
if (shuttingDown) {
return Mono.error(new ServiceUnavailableException("Agent is shutting down"));
}
inFlight.incrementAndGet();
return chain.next(ctx)
.doFinally(signal -> inFlight.decrementAndGet());
}
public void initiateShutdown() {
this.shuttingDown = true;
// 等待 inFlight 归零...
}
}
设计意图:支撑零停机滚动发布,避免在途 Agent 任务被强制中断。
5.4 DynamicSkillMiddleware —— 动态技能注入
在 onSystemPrompt 阶段根据用户角色/组织动态注入可用技能:
public class DynamicSkillMiddleware extends MiddlewareBase {
private final SkillRepository skillRepo;
@Override
public Mono<String> onSystemPrompt(SystemPromptContext ctx) {
// 根据用户组织获取可用技能
List<Skill> skills = skillRepo.getSkillsByOrg(ctx.getOrgId());
if (skills.isEmpty()) {
return Mono.just(ctx.getPrompt());
}
StringBuilder sb = new StringBuilder(ctx.getPrompt());
sb.append("\n\n[可用技能]\n");
for (Skill skill : skills) {
sb.append("- ").append(skill.getName())
.append(": ").append(skill.getDescription()).append("\n");
}
return Mono.just(sb.toString());
}
}
设计意图:多租户场景下,不同组织/用户看到不同的 Agent 能力集,无需为每个租户创建独立 Agent。
六、实战:自定义 Middleware 开发
6.1 日志审计中间件
public class AuditMiddleware extends MiddlewareBase {
private final AuditLogService auditService;
@Override
public Mono<Void> onAgent(AgentContext ctx, MiddlewareChain chain) {
long startTime = System.currentTimeMillis();
auditService.log(AuditEvent.builder()
.type("AGENT_CALL_START")
.userId(ctx.getUserId())
.sessionId(ctx.getSessionId())
.timestamp(Instant.now())
.build());
return chain.next(ctx)
.doFinally(signal -> {
long duration = System.currentTimeMillis() - startTime;
auditService.log(AuditEvent.builder()
.type("AGENT_CALL_END")
.userId(ctx.getUserId())
.sessionId(ctx.getSessionId())
.duration(duration)
.status(signal.toString())
.timestamp(Instant.now())
.build());
});
}
}
6.2 限流中间件
public class RateLimitMiddleware extends MiddlewareBase {
private final RateLimiter rateLimiter;
public RateLimitMiddleware(int permitsPerMinute) {
this.rateLimiter = RateLimiter.create(permitsPerMinute / 60.0);
}
@Override
public Mono<Void> onAgent(AgentContext ctx, MiddlewareChain chain) {
if (!rateLimiter.tryAcquire()) {
return Mono.error(new RateLimitExceededException(
"请求过于频繁,请稍后重试"));
}
return chain.next(ctx);
}
}
6.3 敏感词过滤中间件(Transformer 模式)
public class SensitiveWordMiddleware extends MiddlewareBase {
private final SensitiveWordDetector detector;
@Override
public Mono<String> onSystemPrompt(SystemPromptContext ctx) {
// 对输入进行敏感词检测
String prompt = ctx.getPrompt();
if (detector.containsSensitive(prompt)) {
return Mono.error(new ContentPolicyException("输入包含敏感内容"));
}
return Mono.just(prompt);
}
@Override
public Mono<Void> onReasoning(ReasoningContext ctx, MiddlewareChain chain) {
return chain.next(ctx)
.doOnNext(result -> {
// 对模型输出进行敏感词检测
String output = result.getTextContent();
if (detector.containsSensitive(output)) {
throw new ContentPolicyException("输出包含敏感内容");
}
});
}
}
6.4 Token 计费中间件
public class TokenBillingMiddleware extends MiddlewareBase {
private final BillingService billingService;
@Override
public Mono<ModelCallResponse> onModelCall(
ModelCallRequest request, MiddlewareChain chain) {
return chain.next(request)
.doOnNext(response -> {
billingService.charge(BillingRecord.builder()
.userId(request.getUserId())
.modelName(request.getModelName())
.promptTokens(response.getPromptTokens())
.completionTokens(response.getCompletionTokens())
.timestamp(Instant.now())
.build());
});
}
}
6.5 权限二次校验中间件
public class PermissionCheckMiddleware extends MiddlewareBase {
private final PermissionService permissionService;
@Override
public Mono<Void> onActing(ActingContext ctx, MiddlewareChain chain) {
String toolName = ctx.getToolCallName();
String userId = ctx.getUserId();
// 二次校验工具调用权限
PermissionDecision decision = permissionService.check(userId, toolName);
switch (decision) {
case ALLOW:
return chain.next(ctx);
case APPROVE:
// 触发人工审批流程
return requestApproval(ctx).then(chain.next(ctx));
case DENY:
return Mono.error(new PermissionDeniedException(
"工具 " + toolName + " 调用被拒绝"));
default:
return chain.next(ctx);
}
}
}
七、Middleware 与 Harness 的协作
7.1 Harness 层本身就是 Middleware 的叠加
官方文档明确指出:
Harness 在不改写推理循环的前提下,把这些问题各自的解法以 Middleware 与 Toolkit 的形式叠加到关键时机上,只叠加,不替换。
这意味着 HarnessAgent 的以下能力,本质上都是通过 Middleware 机制实现的:
| Harness 能力 | 对应 Middleware 挂载点 | 模式 |
|---|---|---|
| 上下文压缩(Compaction) | onReasoning | Onion |
| 记忆注入(MEMORY.md) | onSystemPrompt | Transformer |
| 技能加载(Skills) | onSystemPrompt | Transformer |
| 权限管控(Permission) | onActing | Onion |
| 沙箱隔离(Sandbox) | onActing | Onion |
| Session 持久化 | onAgent | Onion |
| 优雅关闭 | onAgent | Onion |
7.2 执行优先级
当多个 Middleware 注册在同一挂载点时,按注册顺序执行:
.middleware(new OtelTracingMiddleware()) // 最外层:追踪
.middleware(new GracefulShutdownMiddleware()) // 次外层:优雅关闭
.middleware(new RateLimitMiddleware(100)) // 限流
.middleware(new AuditMiddleware()) // 审计
.middleware(new PermissionCheckMiddleware()) // 权限
Onion 模型下,执行顺序为:
进入: Otel → Shutdown → RateLimit → Audit → Permission → Core
离开: Permission → Audit → RateLimit → Shutdown → Otel
八、从 1.x Hook 到 2.0 Middleware 的迁移
8.1 对比总结
| 维度 | 1.x Hook | 2.0 Middleware |
|---|---|---|
| 结构 | 扁平回调,无阶段区分 | 五个明确阶段 |
| 执行模型 | 单一(仅通知) | 双模型(Onion + Transformer) |
| 数据流控制 | 无法修改数据 | 可拦截、修改、中断 |
| 组合方式 | 注册顺序不明确 | 链式组合,顺序可控 |
| 响应式支持 | 有限 | 完整 Mono/Flux 支持 |
| 可测试性 | 难以单独测试 | 每个 Middleware 可独立单测 |
| 关注点分离 | 混杂 | 各居其层 |
8.2 迁移示例
1.x Hook 写法:
// 1.x: 扁平 hook,所有逻辑混在一起
agent.registerHook(HookType.PRE_REPLY, ctx -> {
// 日志 + 限流 + 权限... 全部堆在这里
});
2.0 Middleware 写法:
// 2.0: 关注点分离,各居其层
agent.builder()
.middleware(new TracingMiddleware()) // onAgent
.middleware(new RateLimitMiddleware()) // onAgent
.middleware(new PermissionMiddleware()) // onActing
.build();
九、设计哲学与最佳实践
9.1 核心设计原则
| 原则 | 说明 |
|---|---|
| 无侵入 | 不修改 Agent/Model 源码,纯外挂式扩展 |
| 单一职责 | 每个 Middleware 只做一件事 |
| 可组合 | 通过链式注册自由组合 |
| 可替换 | 每个 Middleware 可独立替换或移除 |
| 响应式原生 | 基于 Project Reactor,天然支持异步 |
| 默认穿透 | 不覆写即无开销 |
9.2 最佳实践
- 追踪类放最外层:确保所有内层异常都能被捕获
- 限流放权限前:先限流再鉴权,避免无效鉴权开销
- Transformer 类靠近 Core:数据变换越晚执行,越接近最终状态
- 避免在 Middleware 中做重 IO:会阻塞整个执行链
- 使用 doFinally 确保清理:Onion 模型的"离开"阶段必须执行清理逻辑
9.3 典型生产组合
// 生产环境推荐组合
HarnessAgent agent = HarnessAgent.builder()
.name("production-agent")
.model("dashscope:qwen-plus")
// === 可观测性层 ===
.middleware(new OtelTracingMiddleware())
.middleware(new MetricsMiddleware())
// === 治理层 ===
.middleware(new GracefulShutdownMiddleware())
.middleware(new RateLimitMiddleware(200))
// === 安全层 ===
.middleware(new SensitiveWordMiddleware())
.middleware(new PermissionCheckMiddleware())
// === 业务层 ===
.middleware(new DynamicSkillMiddleware())
.middleware(new TaskReminderMiddleware())
// === 计费层 ===
.middleware(new TokenBillingMiddleware())
.build();
十、总结
AgentScope Java 2.0 的 Middleware 机制,用两类模型(Onion + Transformer)覆盖了智能体扩展的全部场景,用五个挂载点(onAgent / onSystemPrompt / onReasoning / onActing / onModelCall)精确覆盖了 ReAct 循环的每个关键时机。
它的核心价值在于:
将"横切关注点"从 Agent 核心逻辑中彻底解耦,让推理循环保持纯净,让工程能力按需叠加。
对于 Java 开发者而言,这套设计与 Servlet Filter、Spring Interceptor、AOP 切面的思想一脉相承——用熟悉的模式解决新问题。从 Demo 到生产,Middleware 就是那座桥梁。
更多推荐



所有评论(0)