Spring AI 2.0实战:从多模型到Agent的一周学习路线
Spring AI 2.0 对 Java 开发者来说,最值得优先搞清楚的是整条学习线:多模型接入、Tools 函数调用、MCP 协议、Skills 复用、Agent 编排,这五个东西怎么串起来,而不是只学会某一个 API。这篇文章按一套一周能走完的实战路线写:先跑通单模型对话,再补结构化输出,然后依次接 Tools、接 MCP,再把 Skills 和 Agent 的关系理清,最后给常见报错和排查顺序。适合两类人:一是 Spring 很熟但没碰过 AI 的 Java 工程师,二是已经用 HTTP 或 Python 调过模型、想把能力沉淀到 Java 工程里的人。一周这个时间点比较诚实,指的是七天搭出一个带对话、工具调用、外部服务接入的完整项目,而不是把底层原理全部啃完。
1. Spring AI 2.0 先把 Java 集成的哪些脏活收了
1.1 没有 Spring AI 时,Java 接模型要重复做什么
很多 Java 项目最早接入大模型,路径非常原始:用 RestTemplate 或 WebClient 发 HTTP 请求,手动拼 JSON,手动解析返回,再手动处理流式输出。单个模型还能忍,一旦要换模型厂商,或者要支持好几个模型,代码就开始失控。
更麻烦的是函数调用。不同厂商对 tool calling 的请求体、参数格式、返回结构都不一样。模型说要查天气,你得先把工具声明转成 JSON Schema,再在返回结果里解析工具调用标记,自己维护这一轮对话的上下文。这套东西写一次可以,写两次就开始想抽公共层,抽完发现还是和具体厂商耦合。
Spring AI 解决的正是这一层重复劳动。它把模型接入、工具调用、结构化输出、上下文记忆、MCP 连接这些能力抽象成统一的 Spring 风格接口。对 Java 开发者来说,最大的收益不是少写几个类,而是整个团队可以用同一套写法去接不同模型,不用每来一个新模型就重新培训一遍集成方式。
1.2 2.0 这一层抽象到底抽象了什么
核心抽象可以拆成几块看:
- ChatModel 统一了对话模型接口。OpenAI、Ollama、DashScope 这类国内可访问的模型服务,在 Java 代码里都收敛成一个 ChatModel 或 ChatClient。
- ChatClient 提供流式 API,支持同步、流式、返回实体对象,日常开发基本都从它入口进。
- @Tool 注解把普通 Java 方法暴露给模型调用,不需要手动维护 JSON Schema。
- 结构化输出可以把模型返回的文本直接映射成 Java 实体类,省去自己写解析器的过程。
- Advisors 充当拦截器,可以在每次请求前后插入公共逻辑,比如注入系统提示词、记录日志、拼装记忆。
- MCP 相关 starter 负责连接外部 MCP Server,把远程工具注册进模型可见的 tool 列表。
这些能力单独看都不稀奇,关键是它们能组合。ChatClient 可以边用工具,边接 MCP,边做结构化输出,最后再由 Agent 逻辑决定调用顺序。这种组合能力才是 Spring AI 2.0 值得学的真正原因。
1.3 先别急着背 API,先建立三个预期
第一个预期:网上教程标题经常写“一周学完”,但真实目标是“一周跑通主链路”,也就是能做出一个可演示、可扩展的原型。原理部分后面再补,不影响你先跑起来。
第二个预期:Spring AI 迭代速度不慢,部分 API 在小版本之间会有调整。写代码时优先跟着官方文档或你本地拉到的 jar 包走,不要盲信一篇几个月前的文章里的包名。
第三个预期:多模型、MCP、Agent 这些词听起来很唬人,但落到工程里都只是“配置 + 接口 + 状态管理”。理解这一点,后面遇到报错就不会慌。
2. 一周实战路线:从 ChatClient 到 Agent 的顺序
2.1 前三天先解决“能对话、能结构化、能调用工具”
第一天只做一件事:把 ChatModel 跑通。选一个本地模型服务,或者用云端模型的 API,能通过 ChatClient 问一句话并且拿到完整回复就算过关。这一步的核心是确认依赖、配置、网络三个环节没问题。
第二天加结构化输出。让模型返回一个学生信息、订单信息之类的对象,直接用 Java 实体类接收。不要只输出字符串然后手动 substring,那样后面一定会出问题。
第三天加 Tools。写一个最简单的工具方法,比如根据城市名返回天气,让模型在对话中自动决定调用它。这里你会第一次理解“模型不会主动干活,它只会告诉你它想调用什么工具”。
2.2 后三天解决“能接外部系统、能复用技能、能编排任务”
第四天开始接触 MCP。先连接一个现成的 MCP Server,比如文件系统、数据库、设计稿这类工具,观察模型如何通过 MCP 拿到外部数据。重点不是自己写 Server,而是理解客户端连接、工具发现、调用流转。
第五天理清 Skills。你需要知道 Skills 和 RAG 的区别,也要知道 Skills 和 MCP 的区别。简单说,Skills 偏“模型该怎么做这件事”,MCP 偏“模型能连上哪些外部设备”。可以把 Skills 理解为一套可复用的提示词和验证规则包。
第六天做 Agent。不要第一次就写复杂的状态机,先实现一个最简单的循环:模型判断是否需要工具,需要就调用,调用完把结果塞回上下文,再让模型继续,直到给出最终答案。这个循环就是 Agent 的地基。
2.3 第七天整合项目时按什么标准验收
第七天不要开新功能,把前六天的东西整合成一个完整项目。比如做一个“智能客服 + 订单查询 + 知识库问答”的小应用,后端用 Spring AI,前端用 Vue 简单接一下。
验收标准建议按这个顺序看:
- 能连续对话,且对话历史不会越来越乱。
- 模型能在需要时调工具,工具返回异常时不会直接崩。
- 结构化输出字段稳定,不会偶尔多一个字段或少一个字段。
- 接 MCP 的调用延迟可接受,超时有兜底。
- 内存和 CPU 占用在可接受范围内,连续跑几十轮不卡死。
这一套走完,你对 Spring AI 2.0 的掌握度就已经超过“只会调接口”的阶段了。
3. 多模型接入:先本地后云端,配置优先于代码
3.1 入门用本地模型,最大的好处是能离线调试
想快速试错,我建议先跑本地模型,比如通过 Ollama 拉一个 7B 或 8B 参数级别的模型。原因很直接:不依赖外部网络,不消耗 API 费用,出问题时可以直接看模型日志,不会出现“不知道是网络问题还是代码问题”的尴尬局面。
本地模型对机器有要求。显存越大越好,至少 8GB 起步比较舒服;没有独立显卡也能跑,但速度会慢很多,只能用来验证流程。启动前先用ollama run在命令行里测一下,确保模型本身能回复,再把它接进 Spring AI。
这一阶段不要追求效果,追求链路通。链路通了,后面换云端大模型只是改配置的事。
3.2 云端模型统一走 ChatModel 配置
接入云端模型时,Spring AI 的写法大致是这样:引入对应 starter,配置 base-url、api-key、模型名,然后代码里还是用同一个 ChatClient。环境上能用哪个模型服务,就配哪个,配置示例大概长这样:
spring: ai: model: chat: dashscope dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus注意,这段配置只是示例,具体属性名要和你引入的依赖版本对齐。不同小版本之间,配置前缀可能有变化。我一般会先看一眼官方文档或 jar 包里的配置元数据,再写 application.yml。
还有一条必须养成习惯:api-key 不要写死在配置文件里,用环境变量或配置中心管理。项目一旦提交到仓库,密钥泄出去就不是小事。
3.3 切换模型后,必须重新验证的四个点
换模型不是改个模型名就结束。至少要从四个维度重新验证:
- 响应速度:不同模型的首字延迟差别很大,交互类场景对延迟敏感。
- 输出稳定性:同一个 Prompt,模型 A 可能规规矩矩返回 JSON,模型 B 可能带一堆解释文本。
- 工具调用质量:有的模型对 tool calling 支持得好,有的模型经常“假装调用”或调用参数错误。
- 失败模式:限流、超时、上下文超长,不同服务方返回的错误格式不一样,兜底逻辑要按实际返回调整。
也就是说,多模型接入的价值在于“可切换”,但切换之后必须把每个模型当作独立服务对待。不能因为接口统一就认为行为一致。
4. Tools 函数调用:把模型从“会聊天”变成“能干活”
4.1 为什么模型必须依赖工具才能完成真实任务
模型的知识是静态的,训练完之后不会自动知道今天的天气、最新的库存、用户的实际订单。它也没有权限去执行下单、发通知、改数据库这类操作。Tools 的出现,就是给模型开一个口子:遇到需要外部信息或执行动作的场景,模型返回一个工具调用请求,由你的 Java 代码真正执行,再把执行结果返回给模型继续推理。
这里有一个常见误解:模型“会写代码”不等于模型“会执行代码”。它只是从语义上理解这个工具能做什么,然后按照约定格式发出调用请求。真正执行的是你的方法。
4.2 用 @Tool 声明一个工具,最小步骤
用一个实例说明。假设我要写一个查询天气的工具,最小实现大概是:
@Component public class WeatherTools { private final WeatherService weatherService; public WeatherTools(WeatherService weatherService) { this.weatherService = weatherService; } @Tool(description = "根据城市名查询实时天气") public String getWeather(String city) { return weatherService.query(city); } }把这个类注册进 Spring 容器,ChatClient 配置好工具扫描,模型就能在对话中调用它。相比手写 JSON Schema,这套方式明显省事,但要注意:@Tool 的注解、包路径、工具注册方式在不同版本里有差异,示例代码是帮你理解思路,落地时以实际版本为准。
顺手提一句,这里说的 Tools 是 AI 函数调用里的工具,不是 Android SDK Tools、VMware Tools 那类系统工具。搜索时候很容易被这些词干扰,别绕进去。
4.3 工具描述、入参校验和返回值设计
工具方法名不重要,description 才是关键。模型选择工具时,主要靠 description 判断“这个方法适不适合当前任务”。写得模糊,模型就不会调用;写得准确,调用率会明显提升。
入参上,能做的校验一定要做。模型生成的参数有时候就是不对,比如城市名带了空格、日期格式不对、枚举值写错。方法内部先校验,失败时返回结构化错误信息,而不是直接抛异常把整个请求打崩。
返回值设计遵循一个原则:让模型直接能读懂。能返回字符串就返回字符串,能返回简单对象就返回简单对象。不要返回一个巨大的实体对象,模型在处理大量字段时更容易出错。必要时在返回前做裁剪,只保留模型需要的字段。
4.4 工具调用最容易翻车的三个场景
第一个场景是模型不调用工具。先看 description 是否足够清晰,再看工具是否真的被注册进了 ChatClient,最后看模型本身是否支持 function calling。
第二个场景是调用陷入死循环。模型调用工具、拿到结果、继续调用,反复不停。解决办法是设置最大迭代次数,超过就强制结束并返回当前信息。真实项目里这个限制必须有。
第三个场景是工具执行时间太长。模型在等工具结果时,如果接口超时,整个对话就卡住了。建议给外部调用设置独立的超时时间,并且让工具快速返回“查询中”这样的中间状态,不要死等。
5. MCP 协议:AI 应用连接外部系统的新标准
5.1 MCP 到底解决的是连接问题还是协议问题
MCP 全称 Model Context Protocol,解决的是 AI 应用和外部工具、数据源之间的连接规范问题。以前每接一个外部系统,就要写一套适配:接文件系统写一套文件读写,接数据库写一套查询,接设计协作工具再写一套 API。MCP 把这些统一成一套协议,Server 端暴露能力,Client 端发现并调用能力。
你可以把 MCP 理解成一个“插头标准”。有了这个标准,AI 应用不用针对每个外部工具单独定制接口,工具方也只需要实现一次 MCP Server,就能被所有支持 MCP 的客户端使用。
5.2 在 Spring AI 里接入 MCP Server 的落地路径
Spring AI 提供了 MCP Client 相关依赖,接入一个现成 Server 通常分三步:
第一步,引入 MCP Client starter,确认你的 Spring Boot 版本和 Spring AI 版本兼容。
第二步,配置要连接的 MCP Server。常见传输方式有两种,一种是通过本地进程启动的 stdio 方式,一种是走网络请求的 SSE 或 HTTP 方式。本地调试用 stdio 方便,部署到服务器上通常用网络方式。
第三步,启动应用,确认模型能“看到” MCP Server 暴露的工具,然后通过对话触发调用。
配置示例大致长这样,但属性名一定要以当前版本为准:
spring: ai: mcp: client: connections: - name: filesystem type: stdio command: npx args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]这段配置的意思是启动一个文件系统 MCP Server,把 /tmp 目录暴露给 AI 应用。真正接入时,注意 Server 要根据实际环境换成可用的命令和参数。
5.3 MCP 和 Tools 不是替代关系,是两层东西
MCP 和 Tools 经常被放在一起讲,但它们是两个层级的概念。Tools 是模型看到的“能力单元”,MCP 是能力单元从外部“运输”进来的协议。一个本地用 @Tool 写的方法,和远端通过 MCP 暴露的工具,最终都会变成模型可见的 tool 列表。
所以正确的理解是:Spring AI 负责把各种来源的工具统一成模型能理解的形式,MCP 负责解决“远端工具如何接入”的问题。两者是配合关系,不是二选一。
5.4 哪些工具适合用 MCP 暴露
适合用 MCP 暴露的是那些具有通用性的外部能力。比如文件读写、数据库查询、设计稿信息读取、文档内容提取、办公软件操作、科学计算工具等。现在很多设计协作工具、文档工具、数据分析软件都提供了 MCP Server,接一个就能让模型读取相关数据。
不太适合用 MCP 暴露的是那些内部强耦合的业务逻辑。这种功能直接用本地工具方法更合适,没必要多一层网络开销。
6. Skills 与 Agent:从单次问答到多步任务
6.1 Skill 和 MCP 经常被混在一起,区别其实很清晰
Agent Skill 和 MCP 是 Agent 生态里最容易混淆的两个概念。简单区分:
Skill 是一套可复用的能力包,里面包含提示词、操作步骤、示例和校验规则,作用是告诉模型“面对这类任务时应该怎么思考、怎么执行”。它改变的是模型的行为方式。
MCP 是一条连接通道,作用是让模型能访问外部工具和数据。它改变的是模型的能力边界。
打个比方:Skill 是工作手册,MCP 是插座。模型拿着工作手册知道该怎么做,通过插座才能接上外部设备。两者不冲突,反而经常一起用。
RAG 也顺带说清楚:RAG 是给模型提供知识文档,解决“不知道”的问题;Skill 是给模型提供做事方法,解决“不会做”的问题。一个管知识,一个管流程。
6.2 Agent 不是框架功能,是一种任务循环
很多人以为引入某个 Agent 框架就自动获得 Agent,实际不是。Agent 的本质是一个循环:模型根据当前目标判断下一步,如果需要外部信息就调用工具,拿到结果后更新上下文,再继续判断,直到完成目标或达到限制。
用伪代码表示就是:
while (step < maxSteps) { result = model.run(context) if (result.needTool) { toolResult = execute(result.toolCall) context.add(toolResult) continue } if (validate(result)) { return result } return handleFailure(result) }Spring AI 里实现这个循环,可以从最简单的方式开始:手动写一个 while 循环,结合 ChatClient 和工具调用返回结果。跑通之后再考虑引入更复杂的编排框架。比如有些基于 Spring AI Alibaba 的 Graph 项目,把多步任务做成流程图,适合任务链路复杂的场景,但那是提升阶段的事,不要第一步就上。
6.3 结构化输出先定义实体类,后面省很多事
Agent 跑完,最终结果要给业务系统用,就不能是自由文本。Spring AI 支持把模型输出映射到 Java 实体类,但前提是实体类字段描述要写清楚。
以订单结果为例:
public record OrderResult( @JsonPropertyDescription("订单号") String orderId, @JsonPropertyDescription("订单金额") BigDecimal amount, @JsonPropertyDescription("订单状态,枚举:CREATED, PAID, SHIPPED, DONE") String status ) {}让模型返回这个对象时,它会根据字段描述生成对应 JSON,再由框架转换回 Java 对象。字段描述越明确,返回越稳定。尤其是枚举值,一定要在描述里把可选项写全,不能指望模型猜。
如果偶尔解析失败,优先检查模型版本对 JSON 输出的支持,以及实体类字段和 Prompt 要求是否一致。不要一上来就怀疑框架,大多时候是描述没写清楚。
7. 常见报错与排查顺序
7.1 启动失败:版本矩阵是第一嫌疑
Spring AI 项目启动失败,最常见的原因不是代码写错,而是依赖版本不兼容。Spring Boot 版本、Spring AI 版本、Spring AI Alibaba 版本、MCP 相关版本,它们之间有对应关系,混用经常导致 Bean 创建失败或自动配置不生效。
排查顺序固定下来:先看完整堆栈,找到第一个异常;再看 pom.xml 或 build.gradle 里的版本约束;然后去官方文档确认当前 Spring Boot 对应的 Spring AI 版本。不要在启动失败时反复改业务代码,那是浪费时间。
7.2 内存不足:先分清是 JVM 还是模型服务
很多人跑 Spring AI 时遇到 OutOfMemoryError 之类的问题,第一反应是给 JVM 加内存。但在本地开发环境里,内存压力往往来自三个地方:
- Spring Boot 应用本身的 JVM 堆。
- Ollama 这类本地模型服务占用的内存或显存。
- IDE、容器、数据库等外部进程。
本地模型 + 开发环境 + 集成工具全挤在一台机器上,很容易内存紧张。解决办法是先看任务管理器,确认谁在吃内存。如果是模型服务,换小参数模型或降低并发;如果是 JVM,再调 -Xmx;如果容器内存上限不够,就要调容器配置。
还有一个容易忽略的点:日志文件、模型下载缓存、临时文件会慢慢占满磁盘。磁盘满的时候,表现也很像内存不足。
7.3 模型输出不对:从输入和工具描述查起
模型返回的结果不符合预期,不要先怀疑模型能力。按这个顺序查:
先看 Prompt 有没有把约束说清楚,尤其是“只能返回 JSON”“不要输出解释文字”这类要求。再看结构化输出实体类字段描述是否准确。接着看工具 description 是否足够明确。最后看上下文里是否残留了之前轮次的错误信息。
有时候模型调用了工具,但传进来的参数不对。最常见的翻车点是日期格式、城市名、ID 类型。处理办法是在工具方法入口加校验和归一化,模型传参不规范时,先修正再执行业务逻辑。
7.4 一套固定的排查流程
把整个项目的问题排查收敛成一张对照表,会省很多时间:
| 现象 | 优先排查 | 常见原因 |
|---|---|---|
| 应用启动失败 | 依赖版本、Bean 定义 | Spring Boot 与 Spring AI 版本不匹配 |
| 模型不回复 | 网络、API Key、模型名 | 服务地址不通或配置项名过期 |
| 模型不调用工具 | 工具注册、description | 工具没扫描进 ChatClient |
| 工具调用后死循环 | 迭代次数限制 | Agent 循环缺少最大步数 |
| 返回 JSON 解析失败 | 实体类描述、模型输出 | 字段描述不完整或模型加了额外文本 |
| 运行一段时间内存上涨 | JVM 堆、本地模型、日志 | 并发过高或模型服务占资源 |
| MCP 工具看不到 | Server 地址、传输方式 | stdio 命令不存在或网络 Server 没启动 |
真出问题时,先定位现象属于哪一类,再按对应行排查。不要一上来就改并发、换模型,那样只会引入更多变量。
一周走完这条线,你的收获应该是:能独立搭一个 Spring AI 项目,能用多模型配置应对不同环境,能把业务能力以 Tool 或 MCP 方式暴露给模型,能写一个带限制条件的简易 Agent,也能在出问题时按顺序定位是配置、输入还是依赖的问题。下一步再往深走,无非是记忆管理、任务编排、可观测性这些生产化话题。把这一周的基本功打牢,后面学什么都不慌。
