
上一篇我们聊透了一件反常识的事直接拿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接口。我们可以灵活控制指定本轮返回普通文本回答结束Agent流程指定本轮返回Function Calling工具调用指令驱动Agent继续循环模拟模型超时、返回格式异常、返回超长上下文自定义返回的token usage用来模拟不同消耗的请求测试指标看板和上一篇的Advisor埋点完全兼容不需要改动之前的Agent全链路埋点代码。三、完整可运行源码1. pom.xml昨天的Pom无需新增依赖?xml version1.0 encodingUTF-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) modelVersion4.0.0/modelVersion groupIdcom.agent.demo/groupId artifactIdagent-pressure-demo/artifactId version1.0-SNAPSHOT/version parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.0/version relativePath/ /parent properties java.version17/java.version spring-ai.version1.0.0-M1/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement /project2. 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看板测试玩法开启enableMockToolCalltrue持续触发工具调用模拟多轮推理测试循环熔断、轮次上限开启enableMockToolCallfalse单轮直接返回对比两组指标差异调高并发验证线程池、上下文存储、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?userCodefzoabro9。领取完整工程找到图片上的水印即Java-AI工程师打出【agent-metric】即可本篇完整Mock源码JMeter脚本模板后台关键词下期预告下一篇我们聚焦压测之后的问题Agent循环调用、上下文膨胀的熔断策略教你写生产可用的多层熔断 上下文裁剪策略防止上线后偷偷无限循环吃掉账单。从源头锁死成本上限。