ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

压Agent别再烧钱跑大模型!0 Token开销|可写简历,这套Mock LLM仿真压测方案无敌

压Agent别再烧钱跑大模型!0 Token开销|可写简历,这套Mock LLM仿真压测方案无敌

上一篇我们聊透了一件反常识的事:直接拿JMeter压Agent对外HTTP接口,根本不算真正的Agent压测。Agent内部多轮推理、循环工具调用带来的资源放大效应,HTTP层监控完全感知不到。

很多同学看完埋点方案之后,马上遇到第二个拦路虎:想要验证这套埋点、验证Agent在高并发下会不会死循环、验证熔断逻辑,就得不停调用LLM接口。
随便跑一轮并发,几十上百块Token费用就出去了;如果要反复调试、回归测试,成本根本扛不住。

还有一类麻烦:开发环境没有外网LLM网关权限、内网隔离,想做Agent并发测试,连模型都调不通。这也是做大模型工程化落地时,绝大多数团队会卡很久的痛点。

💡 框架迁移提示:本文Mock仿真思路是通用方案,示例基于Spring AI实现;如果你使用 LangChain4j、AgentScope 等其他Java Agent框架,只需要替换框架对应的Model Provider实现,业务压测逻辑不变。

你有没有试过这种窘境:
想验证“连续调用检索工具会不会触发上下文超长”,压测跑5分钟,账单告警先来了;想测极限并发下的熔断、最大推理轮次限制,不敢开大并发,只能小流量手工测试,风险根本暴露不出来。

真实模型压测适合上线前最后一轮验收,而日常迭代、回归、容量预演,必须要有一套不依赖真实LLM的仿真方案。

今天这篇,我们搭建一套Spring AI下的Mock LLM能力,可控返回推理结果、可控模拟工具调用、可控模拟超时和异常;配合JMeter发起并发,和真实Agent调用链路完全一致,对接上一篇我们写的Agent全链路埋点,全程零Token消耗。

本文代码基于Spring AI稳定版本,可直接运行,无编造API;文末附带简历片段、JMeter脚本模板。

一、先分清:Mock LLM仿真压测 ≠ 简单Mock Controller

不少人的第一想法:直接在Controller层写Mock返回,把接口写死返回固定字符串。
这种方式非常廉价,但完全没有价值:

  • 绕过了Agent的思考逻辑、工具路由、上下文拼接、多轮循环
  • 无法复现Function Calling Java工具调用链路
  • 埋点采集的推理轮次、Token统计、工具调用指标全部无效

合格的Agent仿真Mock,必须侵入在Model层:
Agent框架照常走完整链路:prompt组装→传给Model→解析返回的tool_call→执行工具→把结果塞回上下文→下一轮推理;
只是模型的输出由我们程序控制,不走远程LLM接口。
链路和生产环境1:1对齐,只有模型返回内容是预制仿真数据,这样我们之前搭建的Agent可观测埋点体系,才能采集到真实有效的大模型压测指标。

测试类型优点短板适合场景
Controller层Mock实现最简单跳过Agent内部闭环,指标失真只测网关、鉴权
Model层Mock LLM仿真全链路保真、零成本、可模拟异常需要实现Model适配开发回归、容量压测、熔断校验
真实LLM压测真实效果Token成本高、限流不可控上线前最终验收

二、Spring AI Mock Model 设计思路

Spring AI 的核心抽象是ChatModel,我们自定义一个实现类MockChatModel,实现ChatModel接口。
我们可以灵活控制:

  1. 指定本轮返回普通文本回答,结束Agent流程
  2. 指定本轮返回Function Calling工具调用指令,驱动Agent继续循环
  3. 模拟模型超时、返回格式异常、返回超长上下文
  4. 自定义返回的token usage,用来模拟不同消耗的请求,测试指标看板

和上一篇的Advisor埋点完全兼容,不需要改动之前的Agent全链路埋点代码。

三、完整可运行源码

1. pom.xml(昨天的Pom,无需新增依赖)
<?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> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>${spring-ai.version}</version> </dependency> <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. MockChatModel.java(核心仿真模型)
package com.agent.demo.model; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionMessage; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionMessage.Role; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionTool; import org.springframework.ai.openai.api.OpenAiApi.FunctionToolCall; import org.springframework.stereotype.Component; import java.util.List; @Component public class MockChatModel implements ChatModel { // 可配置:是否返回工具调用,制造多轮循环 private boolean enableMockToolCall = true; @Override public ChatResponse call(Prompt prompt) { if(enableMockToolCall){ // 仿真:返回工具调用,驱动Agent继续调用工具,形成多轮链路 FunctionToolCall toolCall = new FunctionToolCall("queryWeather", "{\"city\":\"北京\"}", "call_1"); ChatCompletionMessage msg = new ChatCompletionMessage("", Role.ASSISTANT, List.of(toolCall), null); Generation gen = new Generation(ModelOptionsUtils.toJsonString(msg)); return new ChatResponse(List.of(gen)); }else{ // 仿真:直接返回回答,本轮结束 Generation gen = new Generation("北京今天晴,气温22~28℃"); return new ChatResponse(List.of(gen)); } } public void setEnableMockToolCall(boolean enableMockToolCall) { this.enableMockToolCall = enableMockToolCall; } }
3. 切换模型配置(区分测试环境/生产环境)
package com.agent.demo.config; import com.agent.demo.model.MockChatModel; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration public class ChatModelConfig { @Value("${spring.ai.openai.api-key:}") private String apiKey; // test环境使用Mock模型,压测、回归 @Bean @Profile("test") public ChatModel mockChatModel(){ return new MockChatModel(); } // prod环境使用真实OpenAI模型 @Bean @Profile("prod") public ChatModel realChatModel(OpenAiChatModel.Builder builder){ return builder.apiKey(apiKey).build(); } }
4. JMeter测试脚本使用说明

JMeter只需要正常POST请求访问Agent对外接口,和压线上Agent接口写法完全一致:

  • 请求体:用户提问文本
  • 线程组:设置并发线程数、循环次数
  • 后置处理器:不需要特殊改造
  • 配套监控:接入我们D1写的埋点指标(Prometheus + Grafana看板)>

测试玩法:

  1. 开启enableMockToolCall=true,持续触发工具调用,模拟多轮推理,测试循环熔断、轮次上限
  2. 开启enableMockToolCall=false,单轮直接返回,对比两组指标差异
  3. 调高并发,验证线程池、上下文存储、trace链路是否泄露

四、压测验证重点校验清单

✅ 并发下埋点指标正常上报:思考轮次、工具调用次数、Token统计持续写入看板
✅ 设置最大推理轮次熔断后,不会出现无限循环
✅ 大量并发下TraceId不混乱、指标不会串请求
✅ 模拟异常返回,Agent异常分支、错误告警逻辑可以正常触发

❌ 不要只看接口RT和错误率,重点看Agent层指标
❌ 不要用Controller Mock替代Model层仿真,指标完全无效
❌ 测试环境和生产环境的Agent超时、最大轮次、工具配置必须保持一致,否则压测结论无参考价值

简历描述片段

基于Spring AI实现Model层Mock LLM仿真方案,搭建零成本Agent压测环境;仿真真实Function Calling调用、多轮推理链路,和生产Agent执行链路对齐;配合JMeter并发测试,对接Agent全链路埋点体系,在不消耗大模型Token的前提下,完成Agent循环熔断、容量、异常场景回归,降低大模型工程化测试成本。

📦 落地资源推荐

跑Agent的应用,尤其是做压力测试,更吃服务器资源,通常本机是不够用的,建议使用云服务器,能大大减少我们做其他事的精力,提高学习代码的专注力和学习AI的效率,免除后顾之忧,新用户首年特惠+专属渠道,https://www.aliyun.com/daily-act/ecs/activity_selection?userCode=fzoabro9。

💡领取完整工程

找到图片上的水印即Java-AI工程师,打出【agent-metric】即可,本篇完整Mock源码+JMeter脚本模板,后台关键词

下期预告

下一篇,我们聚焦压测之后的问题:Agent循环调用、上下文膨胀的熔断策略,教你写生产可用的多层熔断 + 上下文裁剪策略,防止上线后偷偷无限循环吃掉账单。从源头锁死成本上限。


RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进