很多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做可视化看板。

采集事件清单

在这里插入图片描述

  1. Agent开始执行
  2. 每一轮LLM推理完成(输入token、输出token)
  3. 工具调用发起、工具调用返回/异常
  4. 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个信号,提前识别风险:

  1. P95思考轮次持续上涨:大概率用户提问容易触发多轮工具调用,甚至存在循环风险,需要增加最大轮次熔断;
  2. 单请求Token分布方差极大:说明请求之间成本差异巨大,不能用平均Token做容量估算;
  3. 工具调用次数突增:检索、数据库查询压力变大,向量库、下游接口容易被打挂;
  4. 正常结束率下降: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费用。


Logo

更多推荐