博主简介CSDN博客专家「历代文学网」PC端可以访问https://lidaiwenxue.com/#/?__c1000总架构师首席架构师也是联合创始人16年工作经验精通Java编程高并发设计分布式系统架构设计Springboot和微服务熟悉LinuxESXI虚拟化以及云原生Docker和K8s热衷于探索科技的边界并将理论知识转化为实际应用。保持对新技术的好奇心乐于分享所学希望通过我的实践经历和见解启发他人的创新思维。在这里我希望能与志同道合的朋友交流探讨共同进步一起在技术的世界里不断学习成长。商务合作请搜索或扫码关注微信公众号 “心海云图”Spring AI-2.0 Agent 编程实战从 Tool Calling 到 MCP 协议附完整报文级日志一次真实完整的 Tool Calling 交互从 HTTP 请求报文到模型思维链全部可视化。前言2025-2026年大模型应用的范式正在发生根本性转变。单纯靠Prompt工程已经无法满足复杂业务需求——模型需要访问实时数据天气、股价、库存、调用外部系统数据库、API、ERP甚至自主编排多步任务。Spring AI 正是在这个背景下诞生的框架它为Java/Spring Boot生态提供了与 LLM 交互的标准抽象核心能力包括ChatClient— 统一对话客户端类似RestTemplate之于 HTTPTool Calling— 让 LLM 可以调用本地 Java 方法获取实时数据MCP 协议— 标准化的工具管理协议Anthropic 提出ReactAgent— 自动编排多步工具调用本文用一个完整的智能旅行规划助手项目带你从零理解这些概念的底层原理。最关键的是我会展示通过 RestClient 请求拦截器捕获的真实 HTTP 请求/响应报文让你看到Tool Calling的第一性原理。本项目github地址真实可直接运行的最小demohttps://github.com/lilinhai/spring-ai-alibaba-2.0-agent一、技术栈组件版本Spring Boot4.0.0Spring Framework7.0.1Spring AI2.0.0-M1spring-ai-alibaba2.0.0-M1.1JDK17大模型智谱 GLM-4.7⚠️ 注意Spring AI 2.0.0-M1 与后续 GA 版本的 API不兼容例如ToolExecutionEligibilityPredicate在 M1 中存在GA 版已移除。二、全流程架构用户 HTTP 请求 │ ▼ TravelController (REST API) │ ▼ ChatClient (统一对话入口) ├── .defaultSystem(提示词) ├── .defaultToolCallbacks(...) ├── .defaultAdvisors(...) └── .build() │ ▼ ZhiPuAiChatModel → ZhiPuAiApi → RestClient → HTTP → 智谱 API │ │ │ ← tool_calls ← ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ├── ToolCallingManager.executeToolCalls() │ ├── WeatherTool.getWeather(北京) ← Tool 反射调用 │ ├── AttractionTool.getAttractions(上海) │ └── HotelTool.getHotels(北京, 舒适) │ └── 递归 call() 带上工具结果送回 LLM 生成最终回答三、模型注册接入智谱 GLM依赖dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-zhipuai/artifactId/dependency配置spring:ai:zhipuai:api-key:your_api_key_herebase-url:https://open.bigmodel.cn/api/paaschat:options:model:glm-4.7-flash# ← 注意不是 spring.ai.zhipuai.chat.modeltemperature:0.7配置陷阱ZhiPuAiChatProperties中model字段位于NestedConfigurationProperty注解的options对象内所以正确路径是spring.ai.zhipuai.chat.options.model。如果配成spring.ai.zhipuai.chat.model模型会使用默认值glm-4-air已废弃导致 HTTP 429。四、ChatClient与 LLM 对话的统一入口基本用法// 基础版ChatClientchatClientChatClient.builder(chatModel).defaultSystem(你是一位专业的旅行规划助手。).build();// 带工具版ChatClientchatClientWithToolsChatClient.builder(chatModel).defaultSystem(你是一位专业的旅行规划助手。请使用提供的工具。).defaultToolCallbacks(toolCallbackProvider).build();// 同步调用StringresultchatClient.prompt().user(北京现在天气怎么样).call().content();// 流式调用FluxStringstreamchatClient.prompt().user(写一个上海一日游行程).stream().content();Builder 状态共享陷阱重要// ❌ 错误注入的 Builder 是同一个实例AutowiredChatClient.Builderbuilder;this.chatClientbuilder.defaultSystem(提示词A).build();this.chatClientWithToolsbuilder.defaultSystem(提示词B).defaultToolCallbacks(...).build();// chatClient 的 defaultSystem 和 tools 会被第二次调用污染// ✅ 正确分别创建独立 Builderthis.chatClientChatClient.builder(chatModel).defaultSystem(提示词A).build();this.chatClientWithToolsChatClient.builder(chatModel).defaultSystem(提示词B).defaultToolCallbacks(...).build();五、Tool Calling 第一性原理先看一张时序图User ChatModel LLM API Tool 方法 │ │ │ │ │ 1. 问天气 │ │ │ │─────────────────│ │ │ │ │ 2. SystemUser │ │ │ │ Tools定义 │ │ │ │─────────────────│ │ │ │ │ │ │ │ 3. 判断需要调用 │ │ │ │ getWeather │ │ │ │─────────────────│ │ │ │ │ │ │ │ 4. 执行工具 │ │ │ │──────────────────────────────────│ │ │ │ │ │ │ 5. 返回天气结果 │ │ │ │──────────────────────────────────│ │ │ │ │ │ │ 6. SystemUser │ │ │ │ Tool结果 │ │ │ │─────────────────│ │ │ │ │ │ │ │ 7. 返回最终回答 │ │ │ │─────────────────│ │ │────────────────│ │ │核心流程分为两轮 HTTP 交互第 1 轮发送 System Prompt 用户问题 所有工具定义JSON Schema第 2 轮将工具执行结果拼接回对话历史重新请求 LLM 生成最终回答下面看真实报文。第 1 轮请求——携带工具定义POSThttps://open.bigmodel.cn/api/paas/v4/chat/completions Content-Type:application/json{messages:[{content:你是一位专业的旅行规划助手。请使用提供的工具来帮助用户查询天气、景点、酒店等信息。,role:system},{content:北京现在天气怎么样,role:user}],model:glm-4.7,stream:false,temperature:0.7,tools:[{function:{description:获取指定城市的实时天气信息和未来天气预报,name:getWeather,parameters:{$schema:https://json-schema.org/draft/2020-12/schema,type:object,properties:{city:{type:string,description:城市名称例如北京、上海、广州}},required:[city]}},type:function},// ... getDistance, getAttractions, getHotels 等工具定义]}tools数组中的 JSON Schema 是从ToolToolParam注解的元数据自动生成的。第 1 轮响应——模型决定调用工具{choices:[{finish_reason:tool_calls,index:0,message:{content:我来帮您查询北京现在的天气情况。,reasoning_content:\n用户问的是北京现在的天气情况。我需要使用getWeather函数来获取北京的天气信息。函数需要一个city参数用户明确提到了\北京\所以我可以直接调用这个函数。,role:assistant,tool_calls:[{function:{arguments:{\city\: \北京\},name:getWeather},id:call_-7453302695756030892,type:function}]}}],usage:{completion_tokens:60,prompt_tokens:565,total_tokens:625,completion_tokens_details:{reasoning_tokens:39}}}字段含义finish_reason: tool_calls模型判定需要调用工具而非直接回答reasoning_contentGLM-4.7 的思维链展示了模型为什么选择这个工具和参数tool_calls[].function.arguments模型填好的参数 JSON 字符串tool_calls[].id工具调用 ID后续 tool 结果需要引用它reasoning_content是 GLM 特有的思维链输出对调试非常有用——你能看到模型是如何推理的。中间环节Spring AI 自动执行本地方法收到tool_calls后Spring AI 框架内部Executing tool call: getWeather Starting execution of tool: getWeather 【Tool 本地方法】查询天气: city北京 ← 反射调用 WeatherTool Successful execution of tool: getWeather Converting tool result to JSON. ← 序列化为 TOOL 消息DefaultToolCallingManager负责协调整个流程resolveToolDefinitions()— 解析工具定义MethodToolCallback.doCall()— 通过反射调用Tool方法DefaultToolCallResultConverter— 将返回值序列化为 JSON第 2 轮请求——工具结果回传 LLMPOSThttps://open.bigmodel.cn/api/paas/v4/chat/completions Content-Type:application/json{messages:[{content:你是一位专业的旅行规划助手。请使用提供的工具来帮助用户查询天气、景点、酒店等信息。,role:system},{content:北京现在天气怎么样,role:user},{content:我来帮您查询北京现在的天气情况。,role:assistant,tool_calls:[{id:call_-7453302695756030892,type:function,function:{name:getWeather,arguments:{\city\: \北京\}}}]},{content:\ 北京\\n️ 当前温度22°C\\n☁️ 天气状况晴转多云\\n 风速12 km/h\\n 湿度65%\\n 未来三天明天 24°C 晴后天 20°C 小雨\\n\,role:tool,name:getWeather,tool_call_id:call_-7453302695756030892}],model:glm-4.7,tools:[// ... 仍然包含所有工具定义]}此时messages数组包含4 条消息system— 系统提示词user— 用户原始问题assistant— 模型第一轮回答含tool_callstool— 工具执行结果通过tool_call_id与第 3 条关联注意第 2 轮请求仍然携带tools数组因为 LLM 可能需要再次调用其他工具。第 2 轮响应——模型输出最终回答{choices:[{finish_reason:stop,index:0,message:{content:北京现在的天气情况如下\n\n️ **当前天气**\n- 温度22°C\n- 天气状况晴转多云\n- 风速12 km/h\n- 湿度65%\n\n **未来三天预报**\n- 明天24°C晴天\n- 后天20°C小雨\n\n今天北京的天气还不错温度适宜不过后天会有小雨如果计划出行的话建议关注一下天气变化哦,role:assistant}}],usage:{completion_tokens:188,prompt_tokens:659,total_tokens:847,completion_tokens_details:{reasoning_tokens:88}}}模型基于工具返回的原始数据做了格式化加工后输出给用户——温度、天气状况、未来三天预报还有语气友好的提醒。在用户侧看来这就是一次普通的对话背后却经历了两轮 HTTP 交互 一次本地方法反射调用。核心要点总结两轮交互是标配第一轮 LLM 返回tool_calls第二轮在收到工具结果后返回stop。多个工具可能需要更多轮次。工具定义tools每次请求都需要附带因为 LLM 可能随时决定再次调用工具。reasoning_contentGLM 的思维链展示模型选择工具和参数的推理过程对调试极为有用。tool_call_id是关联纽带assistant 的tool_calls[].id与 tool 消息的tool_call_id一一对应。自动拼接对话历史ZhiPuAiChatModel.call()递归调用自身每次将上一轮的响应和新结果拼入messages。六、Tool 注解注册本地方法定义 Tool 类任何 Spring Bean 都可以加Tool方法不限于ServiceComponent// Service 也行publicclassWeatherTool{privatestaticfinalLoggerlogLoggerFactory.getLogger(WeatherTool.class);Tool(namegetWeather,description获取指定城市的天气预报)publicStringgetWeather(ToolParam(description城市名称,requiredtrue)Stringcity){log.info(【Tool】查询天气: city{},city);return city\n️ 当前温度22°C...;}}注册到 Spring 容器BeanpublicToolCallbackProvidertravelTools(WeatherToolweatherTool,AttractionToolattractionTool,HotelToolhotelTool){returnMethodToolCallbackProvider.builder().toolObjects(weatherTool,attractionTool,hotelTool).build();}MethodToolCallbackProvider自动反射扫描传入对象的Tool方法为每个方法创建一个MethodToolCallback实例。挂载到 ChatClientChatClient.builder(chatModel).defaultToolCallbacks(toolCallbackProvider).build();七、MCP 协议工具的标准化MCPModel Context Protocol是 Anthropic 提出的开放协议旨在为大模型应用提供标准化的工具/资源/提示管理接口。MCP Server两种注册方式方式一自动配置spring:ai:mcp:server:name:travel-mcp-servertransport:SSEcapabilities:tools:trueSpring AI 自动将所有ToolCallbackBean 注册为 MCP 工具。方式二编程式定义BeanpublicToolCallbackgetWeatherMcpTool(){returnFunctionToolCallback.builder(mcp_getWeather,(FunctionMapString,Object,String)input-{Stringcity(String)input.get(city);returnweatherTool.getWeather(city);}).description(获取指定城市的天气预报).inputSchema(jsonSchema(prop(city,城市名称,true))).inputType(Map.class)// 必须设置.build();}MCP Client手动连接外部 MCP 服务// 1. 创建 SSE 传输层McpClientTransporttransportHttpClientSseClientTransport.builder(mcpServerUrl).sseEndpoint(/mcp/v1/sse).build();// 2. 构建同步 MCP 客户端McpSyncClientmcpClientMcpClient.sync(transport).requestTimeout(Duration.ofSeconds(60)).build();// 3. 握手 发现工具mcpClient.initialize();ListToolsResulttoolsResultmcpClient.listTools();// 4. 转换为 ToolCallbackfor(McpSchema.ToolmcpTool:toolsResult.tools()){ToolCallbackcallbackFunctionToolCallback.builder(mcpTool.name(),input-{CallToolResultresultmcpClient.callTool(newCallToolRequest(mcpTool.name(),input));returnextractText(result);}).description(mcpTool.description()).inputType(Map.class).build();toolCallbacks.add(callback);}八、ReactAgent自动编排工具ReactAgent 基于 ReActReasoning Acting模式自动完成思考→调用工具→观察结果→再思考的循环。ReactAgentagentReactAgent.builder().name(travel_planner).model(chatModel).description(智能旅行规划助手).instruction( 你是一位专业的旅行规划师。请按照以下步骤规划行程 1. 使用 getWeather 查询天气 2. 使用 getAttractions 查询景点 3. 使用 getHotels 查询酒店 4. 综合所有信息输出完整行程计划 ).toolCallbackProviders(toolCallbackProvider).saver(newMemorySaver())// 支持多轮记忆.build();Agent 执行流程_START_ → 分析用户需求 ↓ _AGENT_MODEL_ → 思考需要调用哪些工具 ↓ _AGENT_TOOL_ → 调用 getWeather(北京) → 获取天气数据 ↓ _AGENT_MODEL_ → 思考下一步 → 需要查景点 ↓ _AGENT_TOOL_ → 调用 getAttractions(北京) → 获取景点列表 ↓ _AGENT_MODEL_ → 思考下一步 → 需要查酒店 ↓ _AGENT_TOOL_ → 调用 getHotels(北京, 舒适) → 获取酒店推荐 ↓ _AGENT_MODEL_ → 综合所有信息 → 输出完整行程 ↓ _END_九、常见踩坑记录1. ChatClient.Builder 状态泄漏注入的 Builder 是原型 Bean但build()返回的 Client 与 Builder共享同一个可变 RequestSpec 实例。后续修改 Builder 会污染已构建的 Client。解决方法每次使用ChatClient.builder(chatModel)工厂方法。2. 模型名称配置位置错误# ❌ 错误spring.ai.zhipuai.chat.modelglm-4.7-flash# ✅ 正确 — 多了一层 optionsspring.ai.zhipuai.chat.options.modelglm-4.7-flash3. SSE 流式输出中文乱码Windows 平台默认编码非 UTF-8。用OncePerRequestFilter强制设置response.setCharacterEncoding(StandardCharsets.UTF_8.name());4. FunctionToolCallback.inputType 必须设置// 否则启动报错 inputType cannot be null.inputType(Map.class)5. GLM-4.7 模型重复调用工具非 flash 版本在收到工具结果后可能再次调用相同工具。这是模型行为不影响最终结果。十、总结本文通过一个真实的项目完整演示了 Spring AI Agent 编程的四大核心场景场景核心机制基础对话ChatClient.prompt().call()流式对话ChatClient.prompt().stream()多轮记忆MessageChatMemoryAdvisorTool CallingToolMethodToolCallbackProviderMCP ServerFunctionToolCallbackBean 自动注册MCP ClientHttpClientSseClientTransport手动连接ReactAgentReAct 循环自动编排多工具最核心的收获是Tool Calling 的本质就是两轮 HTTP 交互——第一轮模型返回tool_calls决定要调用哪些函数、参数是什么第二轮将函数执行结果拼接回去让模型生成最终回答。理解了这层报文级别的原理你就能诊断绝大多数 Tool Calling 的问题。如果你想获取完整可运行的项目源码或者自己动手复现 Wire Log只需添加一个RestClientCustomizerBean —— 我们会在下一篇技术文章中介绍具体实现方式。项目源码基于 Spring Boot 4.0.0 Spring AI 2.0.0-M1 智谱 GLM-4.7