AgentScope-Java 入门:用 Middleware 审计 Agent 调用
前面几篇,我们已经把 Review Copilot 做成了一个可运行的本地 Git diff 评审助手:
- 第 1 篇搭好 Spring Boot + Vue 项目骨架。
- 第 2 篇用 SSE 展示评审进度。
- 第 3 篇读取 Git diff、执行规则检查,并加入最小 Agent diff 摘要。
- 第 4 篇保存任务状态和 Markdown 报告。
- 第 5 篇收紧只读权限边界。
这一篇回到 AgentScope-Java 的另一个核心能力:Middleware。
目标很明确:
让我们能看清 Agent 调用了什么、用了多久、是否失败,以及整个评审流水线最终生成了多少 findings。
第 6 篇不再把所有模型 provider、prompt 解析和前端发布细节都塞进来。模型调用链路只讲必要背景,重点放在审计。
本篇对应代码
本篇对应分支:
bash
git checkout chapter/06-audit-middleware
完成后合并回 main,并打 tag:
bash
git tag chapter-06-complete
项目仓库:
text
https://github.com/ynzz-j/agentscope-review-copilot
为什么需要审计
Agent 应用和普通 CRUD 应用不一样。
普通接口出问题时,我们通常看:
- HTTP 状态码。
- 入参。
- SQL。
- 业务日志。
Agent 应用还要额外关心:
- 模型是否真的被调用。
- 调用了几次模型。
- 每次调用有多少消息。
- 是否带了工具定义。
- 调用耗时多久。
- 模型调用失败时错误是什么。
- 最终生成了多少结构化发现项。
否则很容易出现一种假象:
页面有结果,但结果其实全来自规则检查,模型根本没有参与。
所以第 6 篇要补上可观测性。
当前评审链路
配置模型 provider 后,Review Copilot 里会有两类 Agent 调用。
第一类是第 3 篇加入的最小 Agent 摘要:
text
Git diff
-> DiffSummaryService
-> ReActAgent.call()
-> DIFF_SUMMARIZED 事件
第二类是完整模型评审:
text
Git diff + 文件上下文 + 规则 findings
-> ModelReviewService
-> ReActAgent.call()
-> 解析模型 JSON
-> 合并 ReviewFinding
未配置 provider 时,这两类模型调用都会跳过,确定性规则检查仍然执行。
这条约束不变:
不指定默认模型提供商。DashScope、OpenAI、Anthropic、Gemini、Ollama 或其他 RC4 支持方式,都在实际项目配置时显式决定。

ModelReviewService 的核心链路
第二类 Agent 调用是完整模型评审。它的核心做了三件事:构建评审 prompt、调用 ReActAgent.call()、解析 JSON 输出。
关键代码:
java
// ModelReviewService.java
public List review(
ReviewJob job,
GitDiffTool.GitDiffResult diff,
Map contexts,
List ruleFindings) {
String prompt = buildReviewPrompt(diff, contexts, ruleFindings, job.focusCategories());
RuntimeContext runtimeContext = RuntimeContext.builder()
.sessionId(job.sessionId())
.build();
try (ReActAgent agent = agentFactory.createModelOnlyReviewer(job.sessionId())) {
Msg response = agent.call(prompt, runtimeContext).block(MODEL_CALL_TIMEOUT);
if (response == null) {
return List.of();
}
return parseFindings(response.getTextContent());
}
}
Prompt 里约束模型只输出 JSON 数组:
java
private String buildReviewPrompt(
GitDiffTool.GitDiffResult diff,
Map contexts,
List ruleFindings,
List focusCategories) {
return """
你是代码评审专家。基于以下输入生成结构化发现项。
## Git Diff
%s
## 规则检查已发现的问题(供参考,不要重复)
%s
## 重点关注类别
%s
请只输出 JSON 数组,不要输出其他内容。
每个元素包含:severity, category, file, line, evidence, impact, suggestion, confidence
""".formatted(
diff.diffText(),
formatRuleFindings(ruleFindings),
formatCategories(focusCategories));
}
模型输出不一定完全合规,所以解析需要容错:
java
private List parseFindings(String modelOutput) {
// 1. 提取第一个 [ 到最后一个 ] 之间的内容
String json = extractJsonArray(modelOutput);
if (json == null) {
return List.of();
}
try {
// 2. 反序列化为 ReviewFinding 列表
return objectMapper.readValue(json, new TypeReference>() {});
} catch (Exception e) {
// 3. 解析失败不中断流程,规则 findings 仍然可用
return List.of();
}
}
这里有两个设计决策值得注意:
- prompt 里带上规则 findings 作为参考:让模型知道哪些问题已经被规则发现,避免重复输出。
- 解析失败时返回空列表而非抛异常:模型评审是规则检查的补充,不应该因为模型输出不合规而中断整个评审。
完整的 ModelReviewService 实现(包括 formatRuleFindings、extractJsonArray 等辅助方法)可以在仓库 chapter/06-audit-middleware 分支中查看。
AuditRecord:先定义审计数据
先定义一条审计记录应该包含什么:
java
public record AuditRecord(
String type,
String subject,
Status status,
int messageCount,
int toolCount,
int findingCount,
long elapsedMs,
String error,
Instant timestamp) {
public enum Status {
SUCCESS,
FAILED
}
}
字段含义:
| 字段 | 含义 |
|---|---|
type |
审计类型,例如 agent_model_call 或 review_pipeline |
subject |
审计对象,例如 agent 名称或 review job id |
status |
成功或失败 |
messageCount |
模型调用消息数量 |
toolCount |
模型调用可见工具数量 |
findingCount |
评审流水线生成的 finding 数量 |
elapsedMs |
耗时 |
error |
失败时的错误信息 |
timestamp |
审计时间 |
这里故意把模型调用审计和评审流水线审计放进同一个结构里。
好处是后续可以统一写入日志、数据库或可观测平台。
ReviewAuditSink:先做可替换出口
不要把审计逻辑写死在 Middleware 里。
先定义接口:
java
public interface ReviewAuditSink {
void record(AuditRecord record);
}
当前实现可以只是写日志:
java
@Component
public class LoggingReviewAuditSink implements ReviewAuditSink {
private static final Logger log = LoggerFactory.getLogger(LoggingReviewAuditSink.class);
@Override
public void record(AuditRecord record) {
if (record.status() == AuditRecord.Status.SUCCESS) {
log.info(
"{} subject={} messages={} tools={} findings={} elapsedMs={}",
record.type(),
record.subject(),
record.messageCount(),
record.toolCount(),
record.findingCount(),
record.elapsedMs());
return;
}
log.warn(
"{} subject={} messages={} tools={} findings={} elapsedMs={} error={}",
record.type(),
record.subject(),
record.messageCount(),
record.toolCount(),
record.findingCount(),
record.elapsedMs(),
record.error());
}
}
后续如果要落数据库,只需要换 ReviewAuditSink 的实现。
AuditingMiddleware:审计模型调用
AgentScope-Java 的 Middleware 可以包住模型调用。
本项目实现 onModelCall:
java
@Component
public class AuditingMiddleware implements MiddlewareBase {
private final ReviewAuditSink auditSink;
@Override
public Flux onModelCall(
Agent agent,
RuntimeContext ctx,
ModelCallInput input,
Function> next) {
long started = System.nanoTime();
int messageCount = input.messages() == null ? 0 : input.messages().size();
int toolCount = input.tools() == null ? 0 : input.tools().size();
return next.apply(input)
.doOnComplete(() -> auditSink.record(AuditRecord.modelCall(
agent.getName(),
AuditRecord.Status.SUCCESS,
messageCount,
toolCount,
(System.nanoTime() - started) / 1_000_000,
null)))
.doOnError(error -> auditSink.record(AuditRecord.modelCall(
agent.getName(),
AuditRecord.Status.FAILED,
messageCount,
toolCount,
(System.nanoTime() - started) / 1_000_000,
error.toString())));
}
}
这段代码不关心业务逻辑。
它只回答一个问题:
AgentScope 的模型调用有没有发生,结果如何。
如果配置了 provider,但日志里没有 agent_model_call,就说明模型没有走到 AgentScope 调用链路。
ReviewService:审计整条评审流水线
Middleware 能审计模型调用,但它不知道最终生成了多少 findings。
这个信息属于业务流水线,所以放在 ReviewService 里记录。
成功时:
java
auditSink.record(AuditRecord.reviewPipeline(
job.id(),
AuditRecord.Status.SUCCESS,
findingCount,
(System.nanoTime() - started) / 1_000_000,
null));
失败时:
java
auditSink.record(AuditRecord.reviewPipeline(
failed.id(),
AuditRecord.Status.FAILED,
findingCount,
(System.nanoTime() - started) / 1_000_000,
e.toString()));
这样我们能同时看到两层信息:
| 审计类型 | 记录什么 |
|---|---|
agent_model_call |
AgentScope 模型调用是否成功、消息数、工具数、耗时 |
review_pipeline |
评审任务是否成功、生成 findings 数量、总耗时、错误 |
这比只看最终页面结果更可靠。
和 Spring AI Advisor 的区别
如果你熟悉 Spring AI,可以把 AgentScope-Java Middleware 类比为 Spring AI Advisor。
但两者的关注点不同。
Spring AI Advisor 更贴近 ChatClient 调用链:
- prompt 增强。
- 上下文注入。
- 日志记录。
- RAG 检索。
- 调用前后处理。
AgentScope-Java Middleware 更贴近 Agent 执行过程:
- model call。
- tool call。
- runtime context。
- agent event。
- 多轮执行链路。
Review Copilot 里我们用 Middleware 做模型调用审计,用业务 service 做流水线审计,两者分工清楚。
验证测试
后端测试:
powershell
cd D:\workspace\whd\ynzz\articles\agentscope-review-copilot\backend
mvn test
本篇重点看两个测试:
DiffSummaryServiceTest:确认 diff 摘要通过ReActAgent.call()执行,并携带sessionId。AuditingMiddlewareTest:确认模型调用成功、失败都会生成审计记录,并覆盖findingCount。
测试通过后,可以再跑完整验证脚本:
powershell
cd D:\workspace\whd\ynzz\articles\agentscope-review-copilot
.\scripts\verify.ps1 -SkipFrontendInstall
运行时如何观察
启动后端:
powershell
cd D:\workspace\whd\ynzz\articles\agentscope-review-copilot\backend
mvn spring-boot:run
配置模型 provider 后发起评审。
日志里应该能看到类似记录:
text
agent_model_call subject=AgentScope Review Copilot messages=... tools=0 findings=0 elapsedMs=...
review_pipeline subject=... messages=0 tools=0 findings=... elapsedMs=...
如果没有配置 provider,模型调用审计不会出现,但 review_pipeline 仍然会记录规则检查结果。
常见问题
为什么 findingCount 不放在 Middleware 里
因为 Middleware 只知道模型调用输入和输出过程,不知道业务上最后合并了多少 findings。
Review Copilot 的 findings 来自两部分:
- 规则检查。
- 模型评审。
合并逻辑在 ReviewService,所以 finding 数量应该在业务流水线层记录。
为什么 toolCount 现在经常是 0
模型评审阶段使用的是 createModelOnlyReviewer,没有把 Toolkit 交给模型。
这是刻意设计。
本项目先由服务端确定性读取 diff 和源码上下文,再把受控输入交给模型。这样能避免模型绕过权限边界自主读取文件。
工具本身仍然保留 @Tool 元信息,后续要扩展 Agent 自主工具调用时可以继续使用。
为什么第 6 篇不展开所有 provider 配置
provider 配置不是第 6 篇的重点。
这一篇只需要知道:
- 不指定默认 provider。
- 显式配置 provider 后才调用模型。
- 模型调用会经过 Middleware 审计。
具体 provider 细节放在 README 和项目配置里即可。
本篇小结
这一篇完成了:
ModelReviewService核心链路(prompt 构建 + JSON 解析容错)。AuditRecord。ReviewAuditSink。LoggingReviewAuditSink。AuditingMiddleware。- 模型调用成功/失败审计。
- 评审流水线成功/失败审计。
- finding 数量和耗时记录。
- diff 摘要 Agent 调用的测试覆盖。
到这里,Review Copilot 不只是能跑,还能回答"Agent 到底有没有工作、工作结果如何"。
下一篇,我们完成前端体验、中文 README、GitHub 发布和整个系列的扩展路线。
系列导航
| 篇目 | 标题 | 状态 |
|---|---|---|
| 1 | AgentScope-Java 入门:搭建 Review Copilot 项目骨架 | 已发布 |
| 2 | AgentScope-Java 入门:用 SSE 展示评审任务进度 | 已发布 |
| 3 | AgentScope-Java 入门:让评审助手读懂 Git diff | 已发布 |
| 4 | AgentScope-Java 入门:保存评审状态并生成 Markdown 报告 | 已发布 |
| 5 | AgentScope-Java 入门:给代码评审助手加上只读安全边界 | 已发布 |
| 6 | AgentScope-Java 入门:用 Middleware 审计 Agent 调用 | 本文 |
| 7 | AgentScope-Java 入门:完善 Vue 前端、发布 GitHub,并规划下一步 | 待发布 |
作者:亦暖筑序
系列仓库:https://github.com/ynzz-j/agentscope-review-copilot
AgentScope-Java 版本:2.0.0-RC4