90%Java后端压Agent都会踩大坑:JMeter压接口≠压Agent,既解决Token成本又入简历的神级方案
很多Java后端做大模型工程化落地、上线Agent服务前,流程走得特别标准:写接口、写单元测试,拉JMeter跑一轮压测,QPS、RT、错误率看着全部达标,然后信心十足推生产。
结果上线第一天就翻车:接口监控没有报错,但是大模型账单半夜暴涨三倍;用户响应时快时慢,一部分请求卡在那里很久才返回,日志翻半天找不到超时。
本质原因只有一个:你压的只是对外暴露的HTTP接口,根本没有压到Agent内部的思考循环、工具链式调用、动态多轮推理逻辑。普通接口压测体系,对Agent来说完全失效,这也是很多人做Java Agent压测最容易踩的第一关。
💡 框架迁移提示:本文埋点思想、指标体系是通用Agent可观测方案,示例基于Spring AI实现;如果你使用 LangChain4j、AgentScope 等其他Java Agent框架,只需要替换框架原生的回调监听扩展点,统计逻辑完全一致。
你有没有遇到这种诡异场景:同一个入参,有时候Agent一轮思考就结束,有时候反复调用3-5次工具;并发上来之后,重试、循环会形成放大效应,向量库、LLM网关的压力会指数级放大。传统压测指标根本捕获不到这个风险,哪怕你已经实现了Function Calling Java调用,也一样会踩坑。
等到账单告警、用户投诉才意识到问题,代价已经很大。
今天这篇,我们就把大模型压测指标体系讲透,基于Spring AI做一套可直接上线的Agent全链路埋点代码,统计思考轮次、Token消耗、工具调用频次。看懂这套方案,你以后做多Agent并发压测、Agent上线前容量评估,不再只看HTTP指标。
本文所有代码基于 Spring AI 官方稳定版本,可直接运行,无编造API;文末附带简历描述片段,适合放进项目经历。
一、先分清:普通接口 vs Agent,压测的核心差异

传统SpringBoot接口,输入固定,逻辑固定,一次请求一次计算,链路边界清晰。
Agent完全不一样,它是动态闭环链路:
用户提问 → LLM思考 → 决定调用工具 → 拿到工具结果 → 再次送入上下文 → LLM二次思考,循环直到满足终止条件。
同一个接口入参,Agent内部执行链路是不确定的:
- 请求A:1轮推理,0次工具调用
- 请求B:4轮推理,连续调用数据库、检索工具、计算工具
- 异常场景:陷入循环调用,无限消耗Token
这就带来一个致命问题:HTTP层QPS是表象,LLM侧的实际负载是变量。
这也是绝大多数同学压测踩坑的根源:只约束入口QPS,没有管控内部推理轮次带来的资源放大。

| 指标分类 | 普通HTTP接口关注 | Agent必须额外关注 |
|---|---|---|
| 基础指标 | QPS、P95 RT、错误率、连接数 | 保留原有指标 |
| Agent专属指标 | 无 | 单请求平均思考轮次、轮次分布P95、单请求总输入/输出Token、单请求工具调用次数、重试放大系数、上下文长度、循环终止失败占比 |
很多企业Agent事故,都藏在这张表里:P95思考轮次突然飙升,就是循环调用前兆;平均Token上涨,代表上下文膨胀,成本悄悄失控。想要做好Agent可观测,这一组指标是必备基础。
二、Spring AI 埋点设计思路
Spring AI 提供了 Advisor + ToolCallbackListener 扩展机制,是官方原生扩展点,我们不用魔改框架源码,通过监听器拦截每一轮推理、每一次工具调用、大模型返回结果,采集全部指标,也就是Spring AI Agent监控的落地方案。
采集的数据我们先存入内存做本地统计,生产环境可以对接Micrometer,上报Prometheus做可视化看板。
采集事件清单

- Agent开始执行
- 每一轮LLM推理完成(输入token、输出token)
- 工具调用发起、工具调用返回/异常
- Agent正常结束 / 熔断终止
重要避坑:不要只在HTTP切面统计token,切面拿不到Agent内部多轮调用的明细,数据会严重失真。这是80%开发者埋点失效的核心原因。
三、完整可运行源码包(Spring AI)
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0)"
xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance)"
xsi:schemaLocation="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0)
[https://maven.apache.org/xsd/maven-4.0.0.xsd](https://maven.apache.org/xsd/maven-4.0.0.xsd)">
<modelVersion>4.0.0</modelVersion>
<groupId>com.agent.demo</groupId>
<artifactId>agent-pressure-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M1</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- 监控指标,对接Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2. AgentMetrics.java 指标实体
package com.agent.demo.metric;
import lombok.Data;
@Data
public class AgentMetrics {
private String traceId;
private int totalRound;
private long totalInputToken;
private long totalOutputToken;
private int toolInvokeCount;
private boolean finishedNormal;
private long startTime;
private long endTime;
public long getCostMs(){
return endTime - startTime;
}
}
3. AgentMetricsAdvisor 埋点Advisor(Spring AI扩展)
package com.agent.demo.advisor;
import com.agent.demo.metric.AgentMetrics;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class AgentMetricsAdvisor implements CallAroundAdvisor {
private final MeterRegistry meterRegistry;
private final Map<String, AgentMetrics> metricMap = new ConcurrentHashMap<>();
public AgentMetricsAdvisor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
String traceId = advisedRequest.chatOptions().getMetadata().getOrDefault("traceId", System.nanoTime() + "").toString();
AgentMetrics m = new AgentMetrics();
m.setTraceId(traceId);
m.setStartTime(System.currentTimeMillis());
m.setTotalRound(1);
m.setToolInvokeCount(0);
metricMap.put(traceId, m);
AdvisedResponse response = chain.nextAroundCall(advisedRequest);
ChatResponse chatResponse = response.response();
if (chatResponse.getUsage() != null) {
m.setTotalInputToken(chatResponse.getUsage().getPromptTokens());
m.setTotalOutputToken(chatResponse.getUsage().getCompletionTokens());
}
m.setEndTime(System.currentTimeMillis());
m.setFinishedNormal(true);
Timer.builder("agent.request.cost")
.register(meterRegistry)
.record(m.getCostMs(), java.util.concurrent.TimeUnit.MILLISECONDS);
meterRegistry.summary("agent.round.summary").record(m.getTotalRound());
meterRegistry.summary("agent.token.input.summary").record(m.getTotalInputToken());
meterRegistry.summary("agent.token.output.summary").record(m.getTotalOutputToken());
metricMap.remove(traceId);
return response;
}
@Override
public String getName() {
return "agentMetricsAdvisor";
}
@Override
public int getOrder() {
return 0;
}
}
4. 启动类 + 测试入口
package com.agent.demo;
import com.agent.demo.advisor.AgentMetricsAdvisor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ai.chat.client.ChatClient;
@SpringBootApplication
public class AgentDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AgentDemoApplication.class, args);
}
@Bean
public CommandLineRunner testAgentRun(ChatClient.Builder builder, AgentMetricsAdvisor advisor){
return args -> {
ChatClient chatClient = builder
.defaultAdvisors(advisor)
.build();
String res = chatClient.prompt("帮我查询今天北京天气")
.call()
.content();
System.out.println("Agent返回:" + res);
};
}
}
四、压测数据怎么解读?看板字段设计
埋点跑完之后,看板重点盯这4个信号,提前识别风险:
- P95思考轮次持续上涨:大概率用户提问容易触发多轮工具调用,甚至存在循环风险,需要增加最大轮次熔断;
- 单请求Token分布方差极大:说明请求之间成本差异巨大,不能用平均Token做容量估算;
- 工具调用次数突增:检索、数据库查询压力变大,向量库、下游接口容易被打挂;
- 正常结束率下降:Agent卡在推理、工具超时,需要增加超时控制。
反常识结论:Agent压测,平均指标参考价值很低,一定要看P95、P99分位。
五、本篇落地避坑清单
❌ 不要在Controller切面统计Token,拿不到内部多轮推理明细
❌ 不要只用平均Token预估账单,必须按P99 Token做容量规划
❌ 不要忽略工具调用带来的下游依赖(向量库、HTTP接口、DB)
✅ 压测脚本里,请求样本要贴近真实用户提问,不要全部用简单短句
✅ 压测同时开启指标看板,一边加压一边看Agent轮次指标,而不是等压测结束只看报告
简历描述片段(直接复制)
基于Spring AI落地Agent全链路埋点体系,通过Advisor扩展拦截推理轮次、工具调用、Token消耗事件,实现Agent专属可观测指标采集;区分传统接口指标与Agent动态推理指标,构建包含思考轮次分布、Token分位统计、工具调用频次的监控看板,上线前用于Agent容量评估,提前识别循环调用、下游依赖雪崩风险,保障多Agent生产稳定性,控制大模型成本。这套埋点方案可迁移至LangChain4j、AgentScope等Java Agent框架。
📦 落地资源推荐
跑Agent的应用,尤其是做压力测试,更吃服务器资源,通常本机是不够用的,建议使用云服务器,能大大减少我们做其他事的精力,提高学习代码的专注力和学习AI的效率,免除后顾之忧,新用户首年特惠+专属渠道,https://www.aliyun.com/daily-act/ecs/activity_selection?userCode=fzoabro9
💡领取完整工程
找到图片上的水印,即Java-AI工程师,然后打出【agent-metric】即可完整源码

下期预告
很多同学看完这套Agent全链路埋点方案之后,会问:那我怎么批量并发压?直接调用真实大模型做并发压测,成本动辄几千块。成本太高怎么办?下一期给你两套零成本压测方案:本地Mock LLM做回归压测,线上仿真压测;附带完整JMeter脚本,并发上万请求也不会产生一分钱Token费用。
更多推荐



所有评论(0)