
大家好我是Java1234_小锋老师。最近用 Spring AI 2.0 做了几个 Agent 项目踩了不少坑也摸出一点门道。这篇文章不打算写成官方文档就是把我觉得最实用的十条经验整理出来配上关键代码方便你直接上手。写在前面Agent 到底在干什么很多人第一次接触 Agent会被各种名词绕晕。其实可以把它想成一件事用户提一个问题 → 大模型决定要不要调工具 → 工具跑完把结果还给模型 → 模型给出最终回答。Spring AI 2.0 最大的变化是把「工具调用循环」从各个 ChatModel 内部抽出来统一交给ChatClientToolCallingAdvisor管理。架构上更清晰调试也更容易。下面这张图用 Mermaid 描述了 Agent 一次完整对话的大致流程是否用户提问ChatClientAdvisor 链处理大模型推理需要调工具?执行 Tool返回最终答案用户看到结果经验一ChatClient 是入口别绕远路Spring AI 2.0 里推荐所有 Agent 场景都从ChatClient出发而不是直接调ChatModel。原因很简单ChatClient自带 Advisor 链、工具注册、流式封装你少写很多胶水代码。直接调ChatModel的话工具循环、记忆、RAG 都得自己拼。RestControllerRequestMapping(/agent)publicclassAgentController{privatefinalChatClientchatClient;publicAgentController(ChatClient.Builderbuilder){this.chatClientbuilder.defaultSystem(你是一个 helpful 的助手回答要简洁。).build();}PostMapping(/chat)publicStringchat(RequestBodyChatRequestrequest){returnchatClient.prompt().user(request.message()).call().content();}}application.yml里配好模型就行Spring Boot 会自动注入ChatClient.Builderspring:ai:openai:api-key:${OPENAI_API_KEY}base-url:https://dashscope.aliyuncs.com/compatible-mode/v1chat:options:model:qwen3.6-plustemperature:0.3经验二工具调用交给 ToolCallingAdvisor2.0 之前工具调用逻辑散落在各个 Model 实现里换模型容易出兼容问题。2.0 起ToolCallingAdvisor统一负责工具循环ChatClient会自动注册它。你只需要把工具传进去框架帮你跑完「模型 → 调工具 → 再喂给模型」这一圈ServicepublicclassWeatherAgent{privatefinalChatClientchatClient;privatefinalWeatherToolsweatherTools;publicWeatherAgent(ChatClient.Builderbuilder,WeatherToolsweatherTools){this.weatherToolsweatherTools;this.chatClientbuilder.build();}publicStringask(Stringquestion){returnchatClient.prompt().user(question).tools(weatherTools)// 注册工具循环由 ToolCallingAdvisor 处理.call().content();}}对应 Mermaid 流程用户: 北京今天天气怎样模型分析意图调用 getWeather 工具返回: 晴, 28°C模型组织自然语言回答北京今天晴, 气温 28°C别自己写 while 循环去解析 tool_calls除非你有非常特殊的业务需求比如要和 Prompt Cache 深度结合。大多数场景框架托管就够了。经验三用 Tool 定义工具少写样板代码定义工具最省心的方式写一个普通 Java 类方法上加Tool注解。ComponentpublicclassWeatherTools{Tool(description根据城市名查询当前天气例如北京、上海)publicStringgetWeather(ToolParam(description城市名称不含市字)Stringcity){// 实际项目里这里调第三方 APIreturnswitch(city){case北京-晴28°C东北风 2 级;case上海-多云26°C东南风 3 级;default-暂无 city 的天气数据;};}Tool(description查询指定城市未来三天的天气预报)publicListStringgetForecast(Stringcity){returnList.of(city 明天多云25~31°C,city 后天小雨22~27°C,city 大后天晴24~30°C);}}几点小建议description一定要写清楚模型靠它决定什么时候调、传什么参数参数用ToolParam补充说明减少模型传错值的概率返回值尽量是 String 或简单 POJO复杂对象模型不好消化经验四System Prompt 比换模型更管用做 Agent 时遇到问题第一反应往往是「换个更强的模型」。我的经验是先把 System Prompt 写好效果往往比换模型明显还省钱。this.chatClientbuilder.defaultSystem( 你是「小助手」一个面向企业内部员工的问答 Agent。 规则 1. 涉及公司制度、流程的问题必须先调用 searchKnowledge 工具检索不能凭记忆回答。 2. 不确定的信息明确说「我不确定」不要编造。 3. 回答用中文条理清晰必要时用编号列表。 4. 单次回答控制在 300 字以内除非用户要求详细说明。 ).build();Prompt 里写清楚「角色、边界、工具使用规则、输出格式」Agent 的稳定性会好很多。可以把 Prompt 抽到配置文件或数据库方便运营同学迭代不用每次改代码发版。经验五用 Advisor 链拆分职责Agent 很容易写成一个大 Service记忆在这里、RAG 在那里、日志又塞进来……代码很快变成一团。Spring AI 2.0 的Advisor 机制就是用来解耦的。常见组合Advisor作用MessageChatMemoryAdvisor自动读写会话历史QuestionAnswerAdvisorRAG 检索增强ToolCallingAdvisor工具调用循环框架自动注册自定义 Advisor审计日志、权限校验、敏感词过滤ConfigurationpublicclassAgentConfig{BeanChatClientagentChatClient(ChatClient.Builderbuilder,ChatMemorychatMemory,VectorStorevectorStore){varmemoryAdvisorMessageChatMemoryAdvisor.builder(chatMemory).build();varragAdvisorQuestionAnswerAdvisor.builder(vectorStore).build();returnbuilder.defaultAdvisors(memoryAdvisor,ragAdvisor).defaultSystem(你是企业知识库助手优先依据检索结果回答。).build();}}Advisor 的执行顺序很重要。想观察每一次工具调用的中间过程就把自定义 Advisor 的 order 设得比ToolCallingAdvisor更高数值更大这样它会在工具循环「内部」被触发。经验六流式输出要尽早接上Agent 调工具时用户可能要等好几秒。如果一直白屏体验很差。流式输出SSE应该尽早接上至少让用户看到「正在思考」或逐字输出的效果。GetMapping(value/chat/stream,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchatStream(RequestParamStringmessage){returnchatClient.prompt().user(message).tools(weatherTools).stream().content();}前端用 EventSource 或 fetch 读 SSE 就行。注意流式场景下如果有多轮工具调用中间会有几次「停顿」可以在 UI 上加一个 loading 状态别让用户以为卡死了。经验七会话记忆别自己拼 List多轮对话是 Agent 的基本能力。2.0 里直接用ChatMemoryMessageChatMemoryAdvisor别自己维护ListMessage再手动塞给模型。BeanChatMemorychatMemory(){returnMessageWindowChatMemory.builder().maxMessages(20)// 保留最近 20 条防止 token 爆炸.build();}// 调用时带上 conversationIdpublicStringchat(StringconversationId,Stringmessage){returnchatClient.prompt().user(message).advisors(a-a.param(ChatMemory.CONVERSATION_ID,conversationId)).call().content();}记忆窗口别设太大。对话一长token 费用和响应延迟都会上去。重要信息可以落库需要时再检索而不是全部堆在上下文里。经验八工具宁少勿滥新手容易犯一个错给 Agent 塞几十个工具觉得功能越多越好。实际效果是模型经常选错工具或者犹豫半天。我的做法是单 Agent 控制在5~8 个工具以内功能相近的工具合并比如searchOrderById和searchOrderByPhone合成一个带参数的searchOrder复杂流程拆成多个专用 Agent而不是一个「万能 Agent」// 不推荐工具太多模型容易懵.tools(tool1,tool2,tool3,tool4,tool5,tool6,tool7,tool8,tool9,tool10)// 推荐按场景分组每次只注册相关工具.tools(orderTools)// 订单 Agent.tools(inventoryTools)// 库存 Agent经验九可观测性要前置Agent 出问题时最难排查的是「模型为什么没调工具」或「为什么调错了工具」。所以日志和监控要在一开始就接上别等线上出事故再补。Slf4jComponentpublicclassAgentAuditAdvisorimplementsCallAdvisor{OverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){log.info(Agent 请求 | user{},request.prompt().getUserMessage());longstartSystem.currentTimeMillis();ChatClientResponseresponsechain.nextCall(request);log.info(Agent 响应 | 耗时{}ms | tokens{},System.currentTimeMillis()-start,response.chatResponse().getMetadata().getUsage());returnresponse;}OverridepublicStringgetName(){returnAgentAuditAdvisor;}OverridepublicintgetOrder(){returnOrdered.HIGHEST_PRECEDENCE100;}}Spring AI 也集成了 Micrometer配合 Prometheus Grafana 可以看 token 用量、调用延迟。Agent 是按 token 计费的这个监控直接关系到成本。经验十配置分层开发生产分开最后一个经验偏工程化开发和生产环境用不同的模型和参数别本地用 GPT-4o上线才发现成本和延迟扛不住。# application-dev.ymlspring:ai:openai:chat:options:model:gpt-4o-minitemperature:0.7# application-prod.ymlspring:ai:openai:chat:options:model:gpt-4otemperature:0.2# 生产环境温度低一点输出更稳定工具调用次数也可以加限制防止模型陷入无限循环spring:ai:chat:client:tool-calling:max-iterations:5# 最多 5 轮工具调用最后小结下哈Spring AI 2.0 把 Agent 开发里最难啃的「工具调用循环」收进了框架开发者可以更专注在业务本身。回顾这十条从ChatClient出发工具循环交给ToolCallingAdvisor用Tool快速定义工具先把 Prompt 写好Advisor 链拆分职责尽早接入流式输出用ChatMemory管理多轮对话工具少而精可观测性前置配置分层Agent 没有银弹但把这些基础打牢后面加 RAG、多 Agent 协作、人工审核环节都会轻松很多。如果你也在用 Spring AI 2.0 做 Agent欢迎交流踩坑经历。