Spring AI入门实战:从本地DeepSeek到RAG与Function Calling
2026/9/19 21:17:41 网站建设 项目流程

开工写这篇之前,我先说一句自己的判断:如果你是个 Java 后端开发者,过去一年已经被 ChatGPT、DeepSeek 和各种 AI 编程助手轰炸得有点焦虑,又不想为了接一个大模型接口就把原来的 Spring Boot 项目推倒重来,那 Spring AI 应该是目前让你“平滑上车”的最好选择之一。

这篇博客不是官方文档的翻译,也不是培训视频的笔记,而是我基于实际项目经验整理的一份入门实战指南。我会从 Spring AI 到底是什么讲起,然后拆解它的核心对象、常用功能、本地模型对接步骤,最后把我在真实项目中踩过的坑和排查思路一并倒出来。不管你之前有没有接触过 AI 开发,只要会用 Spring Boot 写接口,跟着这篇文章走一遍,基本就能在自己的业务代码里把大模型能力用起来了。

1. Spring AI 是什么:先搞清楚它在整个 AI 开发生态里的位置

1.1 从“模型 SDK 混乱”到“统一抽象”

很多人第一次听说 Spring AI 的时候,下意识会把它理解成“又一个封装大模型的 SDK”。这么说也不能算错,但我更愿意把它比作当年 JDBC 在数据库领域做的事情——在你面对 MySQL、Oracle、PostgreSQL 各不相同的客户端 API 时,JDBC 提供了一套统一的接口规范,让你写一份代码就能对接多种数据库。Spring AI 想做的是同样的事,只不过这次它统一的是大模型。

现在的实际情况是:OpenAI 有 openai-java,DeepSeek 有自己的 SDK,通义千问有 DashScope SDK,本地跑模型还有 Ollama 的 Java 客户端。你要是每个模型都接一遍,光是学这些 SDK 的用法、处理各家不同的鉴权方式和参数命名就够喝一壶的。更头疼的是,今天测试用 OpenAI,明天老板说要换成国产模型,业务代码几乎要重写。

Spring AI 的核心设计就是“接口 + 实现”。它定义了 ChatModel、EmbeddingModel 这些顶层接口,然后针对 OpenAI、Ollama、通义千问、DeepSeek 等各家模型分别提供对应的实现类。你在业务代码里面向的是统一的 ChatModel 接口,真正调用哪家模型,由配置文件里的参数决定。切换模型的时候,改配置、换依赖,业务代码几乎不用动。

我用一个场景帮大家直观理解一下:你写了一个聊天机器人服务,底层用的是 Spring AI 的 ChatModel 接口。一开始配置的是 OpenAI 的 GPT-4o,线上跑了一段时间觉得成本太高,想换成 DeepSeek 或者本地模型。如果你用的是 Spring AI,改的就是 application.yml 里 model 相关的几个配置项和 pom.xml 里的依赖坐标;如果你直接调 OpenAI 官方 SDK,那恭喜你,大半个 service 层都要重写。

1.2 核心对象:ChatModel 与 EmbeddingModel

Spring AI 抽象出来的核心对象并不多,入门阶段你只要先抓住两个:ChatModel 和 EmbeddingModel。

ChatModel 是最常用的,它封装了与大模型进行多轮对话的能力。你可以把它理解为“聊天模型的统一入口”,底层不管是 GPT、DeepSeek、通义千问,还是本地部署的模型,只要提供了对应的 Spring AI 实现,你调用的方法签名都是一样的。最基础的操作就像用 JdbcTemplate 执行 SQL 一样简单:注入 ChatModel,调用 call 方法,传入 prompt,拿到响应。

EmbeddingModel 则是做文本向量化的接口。它把一段文字转换成一串浮点数数组,也就是向量。向量化是 RAG(检索增强生成)的基础,后面我会专门聊到。简单来说,如果你想让大模型“读懂”你自己业务系统里的文档、商品信息、工单记录等私有数据,就需要用 EmbeddingModel 把文档切成小块并转成向量存进向量数据库,查询时再把用户的问题也转成向量,通过相似度检索找到相关内容送回给大模型。

Spring AI 每个 Starter 包里都内置了对应模型实现的自动装配逻辑。比如你引入 spring-ai-starter-model-openai,Spring Boot 启动时就会自动创建 OpenAI 的 ChatModel、EmbeddingModel Bean。你不需要手动 new 任何实现类,直接用 @Autowired 或构造器注入就行,这一点和用 Spring Boot 操作 Redis、MySQL 的感觉是一样的。

1.3 为什么选 Spring AI 而不是直接调 SDK

市面上已经有不少大模型 SDK,很多人会问:我直接调 FastJSON + HttpClient 也能把请求发到模型那边,为什么要多引入一个框架?

我的看法是,Spring AI 的价值不在于“把请求发出去”这一步,而在于它把 AI 集成真正变成了 Spring 生态的一部分。主要体现在几个方面。

第一,统一配置与依赖管理。不同模型的 API 地址、密钥、超时时间、模型名称这些参数,在 Spring AI 里都收敛到 application.yml 中,通过 spring.ai.model 前缀统一管理。配置项被标准化之后,方便做多环境切换,测试环境用免费模型,生产环境切到商用模型,改配置即可。

第二,和 Spring Boot 的深度整合。Spring AI 的自动配置、Starter 机制、属性绑定、条件装配这些能力,都和你平时用的其他 Spring Boot 组件无缝衔接。你可以在项目里用 Spring 的 @Retryable 给模型调用加重试,用 Actuator 做健康检查,用 AOP 做调用日志埋点,这些和模型本身无关的工程能力,都是现成的。

第三,官方在持续推进高层抽象能力。比如 Prompt Template、结构化输出、向量数据库抽象、Function Calling、Agent 相关的支持,这些都是普通 SDK 不具备的。你直接调 SDK 可能也能实现同样的效果,但需要用大量胶水代码去处理,而 Spring AI 把这条链路打通了。

当然,Spring AI 也不是万能的。如果项目本身不是 Java 技术栈,或者你只需要在一个一次性脚本里简单调一次模型,那完全没有必要为了用 Spring AI 而用。它更适合的是那种“把 AI 能力作为后端服务的一部分,深度嵌入到业务系统中”的场景。

2. Spring AI 核心功能拆解:不只是“发个消息给大模型”

2.1 ChatModel 基础使用与 Prompt Template

先从一个最基础的例子看起。引入 Spring AI 的 OpenAI Starter(这里以 OpenAI 兼容协议为例,后面接 DeepSeek 也会用到),然后在 Service 里注入 ChatModel,调用 call 方法。

@Service public class ChatService { private final ChatModel chatModel; public ChatService(ChatModel chatModel) { this.chatModel = chatModel; } public String chat(String message) { return chatModel.call(message); } }

这段代码看起来简单得有点不太真实,但它确实能跑通。你需要做的就是在 application.yml 里配置好 api-key 和模型名称。底层发生了什么?Spring AI 会把你传入的字符串封装成一个 Prompt 对象,然后调用对应的模型实现,把模型的文本响应直接返回。

实际项目中,我们很少直接传一个裸字符串给模型,因为业务场景里通常需要把用户输入和其他背景信息拼到一起,生成完整的提示词。传统的做法是用字符串拼接,比如 String prompt = "你是一个客服助手,请回答:" + userMessage;如果提示词比较复杂,拼接起来不仅难看,还特别容易出错。

Spring AI 提供了 PromptTemplate 来替代手工拼接。它的用法和 Spring 的 RestTemplate 有点像,也支持占位符替换:

PromptTemplate promptTemplate = new PromptTemplate(""" 你是一个专业客服助手。 用户的问题是:{question} 请用简洁、友好的语气回答。 """); Prompt prompt = promptTemplate.create(Map.of("question", userQuestion)); String response = chatModel.call(prompt).getResult().getOutput().getText();

用占位符的好处是,提示词模板可以单独维护,甚至放到数据库或配置中心里动态加载。业务代码只负责传参数,不会因为提示词改动而跟着改,这个对于经常要调 prompt 的团队来说非常实用。

2.2 结构化输出:让大模型返回 JSON 对象而不是飘忽文本

调用大模型最让人头疼的问题之一,就是返回结果“不稳定”。你明明让它返回 JSON,它可能给你回一段带解释的文本,或者 JSON 格式不规范、字段名对不上。如果直接把模型的输出交给前端解析,随时可能崩。

Spring AI 提供了结构化输出(Structured Output)能力,让你可以直接把模型的输出映射成一个 Java 对象。核心工具是 BeanOutputConverter。看个例子:

record ProductInfo(String name, String category, BigDecimal price) {}

假设你有一个需求:让模型从一段商品描述中提取商品名称、分类和价格。传统写法大概是:把 JSON 结构说明写死在提示词里,然后把模型的返回结果用 Jackson 手动解析,中间还要处理 markdown 代码块包裹等问题。用 Spring AI 的写法是这样:

BeanOutputConverter<ProductInfo> converter = new BeanOutputConverter<>(ProductInfo.class); String formatInstruction = converter.getFormatInstruction(); PromptTemplate promptTemplate = new PromptTemplate(""" 从下面的商品描述中提取信息:{productDescription} 输出要求:{format} """, Map.of( "productDescription", description, "format", formatInstruction )); Prompt prompt = new Prompt(promptTemplate.createMessage()); ChatResponse response = chatModel.call(prompt); ProductInfo productInfo = converter.convert(response.getResult().getOutput().getText());

这里的关键是 converter.getFormatInstruction(),它会根据你的 Java 类型自动生成一段结构描述,告诉模型“你要返回符合这个结构的 JSON”。模型返回后,converter.convert 负责把 JSON 解析成 ProductInfo 对象。

我用这个功能做过商品信息抽取、用户反馈分类、工单结构化提取,整体体验是:比“纯提示词 + 手动解析”方案稳定得多,虽然偶尔还是会有模型输出不符合格式的情况,但配合重试机制、或者提示词里多给一个 few-shot 示例,基本能解决绝大多数问题。

2.3 向量库与 RAG:给大模型接上“外部记忆”

把大模型接进业务系统之后,你很快会遇到第二个问题:大模型的知识截止于训练数据,它不知道你公司内部的规章制度、商品库存、历史工单这些私有数据。你要么每次把相关资料拼进提示词,但超长上下文既贵又容易超出模型的 token 上限;要么用 RAG 方案,把文档向量化存进向量数据库,查询时先检索最相关的片段,再一起提交给模型。

Spring AI 对 RAG 的抽象主要体现在 VectorStore 接口上。它屏蔽了不同向量数据库的差异,无论你用的是 Redis、Pgvector、Milvus 还是 Chroma,面向业务代码的操作都是一样的:写入文档向量、相似度检索。

一个最简的 RAG 流程可以这样理解:

// 1. 把文档切块、向量化,写入向量库 List<Document> documents = List.of( new Document("员工请假制度:事假需提前一天申请,年假需提前三天申请。") ); vectorStore.add(documents); // 2. 用户提问时,先检索相关片段 List<Document> similarDocs = vectorStore.similaritySearch( SearchRequest.builder() .query("我明天有事想请假,需要提前多久?") .topK(3) .build() ); String context = similarDocs.stream() .map(Document::getContent) .collect(Collectors.joining("\n")); // 3. 把检索结果拼入提示词,再调用模型 PromptTemplate promptTemplate = new PromptTemplate(""" 基于以下资料回答用户问题: {context} 用户问题:{question} """, Map.of("context", context, "question", userQuestion));

RAG 解决的核心问题有两个:一是知识实时性,模型不懂的新信息可以通过文档更新来覆盖;二是幻觉问题,模型不再凭空发挥,而是基于检索到的资料生成答案。当然,RAG 的工程化落地比上面这个例子要复杂得多,比如文档切块的粒度、向量检索的阈值、多路召回的策略,都会影响最终效果,这个我后面会用专门的章节说明。

2.4 Function Calling:让大模型调用你的 Java 方法

如果说 RAG 是给大模型接上了“静态记忆”,那 Function Calling 就是给大模型装上了“手脚”,让它能主动调用外部工具。典型场景:用户问“帮我查一下订单物流进度”,模型本身并不知道你的订单系统里有什么数据,但它可以识别出用户意图,触发你注册好的查询方法,拿结果后再组织语言回复。

Spring AI 里实现 Function Calling 非常简单,核心就是 @Tool 注解。

@Service public class OrderTools { @Tool(name = "query_order_status", description = "根据订单号查询订单当前状态") public String queryOrderStatus(@ToolParam("订单号") String orderId) { // 调用自己的订单服务 return orderService.getStatusByOrderId(orderId); } }

然后在你调用模型时,告诉 ChatModel 这个工具在哪:

ChatClient chatClient = ChatClient.builder(chatModel) .defaultTools(new OrderTools()) .build(); String response = chatClient.prompt() .user("帮我查一下订单 20241111001 的状态") .call() .content();

整个过程的内部逻辑是:Spring AI 会把你带有 @Tool 注解的方法描述(包括方法名、参数、说明)转换成模型能理解的 JSON Schema,随着请求一起发给大模型。模型判断需要调用工具时,不会直接回答问题,而是返回一个工具调用请求;Spring AI 拦截到这个请求,反射调用你的 Java 方法;拿到方法返回结果后,再把它作为上下文回传给模型,最终生成面向用户的回答。

Function Calling 是把大模型从“聊天机器”升级为“业务执行者”的关键能力。以后你要是想做一个自然语言查天气、查库存、下单的 AI 助手,本质上都是靠 Function Calling 把用户的自然语言指令映射到具体业务方法上。

3. 对接本地部署的 DeepSeek:从 0 到 1 的完整实操

3.1 为什么选择本地部署模型

既然 Spring AI 可以对接这么多模型,那为什么我会特别强调“Spring AI 对接本地部署的 DeepSeek”这个场景?主要原因有三个。

一是数据私密性。很多企业内部数据不允许离开自己的内网环境,直接调用外部 API 过不了合规审查。本地部署模型后,所有请求都发生在本机或内网服务器,数据不出域。

二是零接口费用。本地模型跑起来后,推理费用基本只有电费,对于原型验证、测试环境、高频低价值场景特别友好。

三是学习成本低。你不需要注册各种云平台、申请 API Key,装好 Ollama、拉一个模型下来就能开始调,对刚接触 Spring AI 的开发者来说,这是上手成本最低的路径。

当然,本地部署的模型和云端大模型对比,在推理速度、生成质量上还是有差距的。所以我的建议是:本地部署适合开发调试和私有化场景,生产环境的大流量业务还是根据实际需求选云厂商模型。

3.2 环境准备:安装 Ollama 并拉取 DeepSeek 模型

本地跑 DeepSeek,我用的是 Ollama,因为它安装简单、命令少,而且天然兼容 OpenAI 的 API 协议,Spring AI 可以直接通过 OpenAI 兼容模式对接。

安装完成之后,拉取模型:

ollama pull deepseek-r1:7b

拉取时间取决于网络状况,模型包有几个 GB,耐心等待即可。拉完后验证一下服务是否正常,直接命令行测试:

ollama run deepseek-r1:7b "你好,请做一个自我介绍"

如果模型能正常回复,说明本地推理服务已经就绪。Ollama 默认监听 11434 端口,Spring AI 里的配置会用到这个地址。

3.3 Spring Boot 项目接入配置与代码实现

下面我们新建一个 Spring Boot 项目,引入 OpenAI Starter(因为 Ollama 兼容 OpenAI 协议,这里不需要专门的 Ollama Starter),然后在 application.yml 里把 base-url 指向本地服务。

pom.xml 中加入依赖(以 Spring Boot 3.3.x 为例):

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> <version>1.0.0</version> </dependency>

如果你用的 Spring Boot 版本是 3.4.x,建议换成 1.0.0 以上版本,具体版本兼容关系可以在 Spring AI 官方文档里查看,我后面也会讲到。

application.yml 配置:

spring: ai: openai: base-url: http://localhost:11434/v1 api-key: ollama chat: options: model: deepseek-r1:7b temperature: 0.7

这里有几个关键配置要解释一下。base-url 必须带上 /v1 后缀,因为 Ollama 的 OpenAI 兼容接口路径就是 /v1/chat/completions。api-key 随便填一个非空字符串就行,Ollama 本地服务不做鉴权,但 Spring AI 的自动配置会要求这个属性不能为空,否则启动报错。

接下来写一个 Controller 测试:

@RestController @RequestMapping("/api/chat") public class ChatController { private final ChatModel chatModel; public ChatController(ChatModel chatModel) { this.chatModel = chatModel; } @PostMapping public String chat(@RequestBody String message) { return chatModel.call(message); } }

启动应用后,用 curl 或者 Postman 发一个 POST 请求:

curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: text/plain" \ -d "用一句话介绍你自己"

如果配置没问题,你就能在响应里看到本地 DeepSeek 模型生成的回复了。整个过程其实就是:Spring AI 把请求包装成 OpenAI 协议格式,发给 Ollama 的本地服务,Ollama 转发给 DeepSeek 模型进行推理,结果原路返回。链路不复杂,但理解这条链路对后面排查问题非常有帮助。

3.4 连接远程 Linux 服务器的本地模型

有朋友可能会问:我不想把模型部署在本地 Windows 笔记本上,而是放在一台 Linux 服务器上,Spring Boot 应用在另一台机器,怎么连?

思路还是一样的,只是需要把 Ollama 服务暴露到局域网或内网。默认情况下,Ollama 只监听 127.0.0.1,需要修改环境变量让它监听所有网卡地址。Linux 下可以通过 systemd 配置:

# 设置环境变量 OLLAMA_HOST=0.0.0.0 # 重启 ollama 服务 systemctl edit ollama systemctl restart ollama

之后在 Spring Boot 的 application.yml 里,把 base-url 改成 http://服务器内网IP:11434/v1 即可。这里提醒一句:内网部署不需要担心外网暴露问题,但如果这台服务器有公网 IP,请务必做好防火墙策略,别把 Ollama 暴露在公网上,这个服务本身没有任何身份认证,裸奔在公网等于把自己家的模型推理能力开放给全网。

4. Spring AI Alibaba 与 Spring AI Skill:面向生产的选择

4.1 Spring AI Alibaba:国内云生态的官方适配

如果你关注过 Spring AI 的发布节奏,应该知道社区里还有一个非常有分量的子项目:Spring AI Alibaba。它是由阿里巴巴开源团队维护的 Spring AI 扩展模块,专门针对阿里云的“百炼”平台和通义千问系列模型做了深度集成。

它的核心价值是什么?首先,它让 Spring AI 对接国内云厂商模型的过程变得非常简单。你不需要自己去研究 DashScope 的鉴权细节,引入 spring-ai-alibaba-starter 之后,配置几个参数就能用通义千问、DeepSeek、Llama 等百炼平台上托管的模型。其次,它对生产环境的一些痛点做了补齐,比如更完善的重试机制、更稳定的模型调用链路、对阿里云内部组件(如阿里云 OSS、SchedulerX)的集成等。

一个典型的配置长这样:

spring: ai: dashscope: api-key: sk-xxxx chat: options: model: qwen-plus

本质上,它和前面用 OpenAI 兼容协议对接 Ollama 是同一套抽象模型,区别在于是谁提供了 ChatModel 的实现,以及这个实现的代码路径上做了哪些针对性优化。理解了这个点,你就能明白 Spring AI 生态里各个 Starter 之间的关系:它们都是 ChatModel 的具体实现,业务代码不感知差异。

4.2 DashScope 平台模型部署与对接要点

阿里云百炼平台(DashScope)上的模型不需要你自己部署,只需要在控制台开通服务、拿到 API Key,就能直接调用。Spring AI Alibaba 这个场景适用于不想维护本地模型、又希望用国内模型服务的生产项目。

具体对接流程不复杂:

  1. 开通百炼平台,创建一个 API-KEY。
  2. 在项目中引入 spring-ai-alibaba-starter。
  3. 在 application.yml 中配置 api-key 和 model 名称。
<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0</version> </dependency>
spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.8

如果同一个项目里既要接百炼,又要接本地 Ollama,可以通过 @Qualifier 或者 @Primary 区分不同的 ChatModel Bean,这个在 Spring AI 里是支持的。

4.3 Spring AI Skill:把模型能力封装成可复用的“技能”

最近“Spring AI Skill”这个词热度涨得很快。其实它不是 Spring AI 的一个独立框架,而是 Spring AI 在 1.0 版本之后逐步完善的一套“技能装配”思路:把 Prompt Template、Function Calling、RAG 这些底层的可编程单元,组合成一个面向业务的高层单元。

举个例子。你做了一个公司内部知识库问答机器人,如果是一段一段写代码,每次都要处理提示词模板、检索向量库、拼上下文。如果把这个能力封装成一个 skill,比如叫“hrPolicyAssistant”,那么在这个技能内部,你预定义了提示词模板、注册了查询考勤制度的工具方法、配置了员工手册的向量检索逻辑。调用方只需要传入用户问题,技能内部帮你完成整个编排。

@Component public class HrAssistantSkill { private final ChatClient chatClient; public HrAssistantSkill(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个HR政策助理,回答必须基于提供的资料。") .defaultTools(new HrPolicyTools()) .build(); } public String ask(String question) { return chatClient.prompt() .user(question) .call() .content(); } }

这个封装的意义在于:AI 能力的调用逻辑不再是散落在 Controller 和 Service 里的零散代码,而是按照“能力域”组织起来,每个技能边界清晰、可测试、可复用。后面系统里的 AI 能力越接越多,这种基于技能的组织方式会让代码维护轻松很多。

5. 实战经验与踩坑记录:Spring AI 常见问题排查

5.1 依赖版本与自动配置的坑

Spring AI 的版本迭代节奏非常快,从里程碑版本到 1.0.0 正式版,API 发生过一些调整。网上很多教程用的是旧版本写法,粘贴到你项目里可能编译不过或者运行报错。

我整理了几个典型的坑。

一个常见问题是“找不到 ChatModel 的 Bean”。这种情况多半是依赖引入不对,或者 Starter 包版本和 Spring Boot 版本不兼容。Spring AI 的每个版本都对 Spring Boot 版本有明确要求,比如 Spring AI 1.0.0 要求 Spring Boot 3.4.x,你如果用的是 3.2.x,就可能出现自动配置类没有被加载的情况。我的建议是:直接到 Spring Initializr 上勾选 Spring AI 依赖生成项目,用官方推荐的版本组合,别自己拼版本号。

还有一个问题是“启动成功,但调用时报 401 Unauthorized”。如果你配置的是 OpenAI 兼容协议对接本地或第三方服务,检查一下 api-key 是否为空字符串。Spring AI 在客户端初始化时如果发现 api-key 为空,可能会走某个默认的无效逻辑,表现形式就是请求发出去被对方拒了。本地 Ollama 对接时,api-key 填一个任意非空字符串就行。

5.2 模型名、参数与上下文管理的坑

Ollama 拉取的模型有具体标签,比如 deepseek-r1:7b。如果你在配置里写成 deepseek-r1 或者 deepseek-r1:latest,Ollama 可能返回 404,因为模型标签对不上。我踩过一次,排查了半天才发现是标签少写了一个版本后缀。建议配置后先直接在 Ollama 上ollama list看一眼准确的模型名。

temperature 参数也对结果影响很大。这个参数控制模型输出的随机性,取值 0 到 2,值越大答案越多变。做结构化输出、信息抽取时,建议把 temperature 调到 0 或 0.2,减少随机性带来的格式不稳定;做闲聊、创意写作可以调到 0.8 左右。参数不是越高越好,按场景调。

再就是上下文管理。默认情况下,Spring AI 的 chatModel.call(message) 是无状态的,也就是说模型不记得你上一条消息说了什么。很多新手做聊天机器人时发现模型“记忆力”很差,原因就在这里。你需要引入 Spring AI 的 ChatMemory 机制,它提供了消息窗口记忆功能,像 MessageWindowChatMemory 可以帮你维护最近 N 轮对话:

ChatMemory chatMemory = MessageWindowChatMemory.builder() .maxMessages(20) .build(); ChatClient chatClient = ChatClient.builder(chatModel) .defaultChatMemory(chatMemory) .build();

这里有个工程上要特别注意的点:如果系统是多人同时在线的,ChatMemory 必须按会话 ID 隔离,否则不同用户的消息会混在一起。生产环境建议把 ChatMemory 的存储实现放到 Redis 等外部存储中,而不是默认的内存实现,否则服务重启后所有对话上下文都丢了。

5.3 结构化输出不稳定的兜底策略

虽然 BeanOutputConverter 能解决大部分结构化输出问题,但在实际使用中我还是遇到过模型偶尔不按格式返回的情况,比如输出里带了解释文字、JSON 被 markdown 代码块包裹、或者字段值为 null。

我的兜底方案是组合拳:一是在提示词里明确“只输出 JSON,不要任何额外文字”,并给一个 few-shot 示例;二是对模型的原始输出做一次预处理,把 markdown 代码块剥掉;三是解析失败时做一次重试,重试次数控制在 2 到 3 次以内。重试逻辑用一个简单的循环就能实现,不需要引入额外框架。

int maxRetries = 3; for (int i = 0; i < maxRetries; i++) { String raw = chatModel.call(prompt).getResult().getOutput().getText(); String cleaned = raw.replaceAll("```json|```", "").trim(); try { return objectMapper.readValue(cleaned, ProductInfo.class); } catch (JsonProcessingException e) { // 记录日志,继续重试 } } throw new BusinessException("模型结构化输出多次解析失败,请稍后重试");

5.4 RAG 与向量化的几个实战建议

RAG 落地时我交过不少学费,最核心的体会是:检索质量决定回答质量。如果你检索出来的片段和问题不相关,提示词写得再好也没用。

文档切块的粒度是第一个影响因素。切得太小,单个片段信息量不足,召回结果不完整;切得太大,容易把不相关内容混在一起,还浪费 Token。我常用的策略是先按章节或段落粗切,再根据嵌入模型的最大输入长度二次调整,块大小控制在 300 到 500 字之间,块与块之间保留少量重叠,避免关键信息被切断。

相似度检索的阈值也要注意。topK 不是越大越好,你让人家回答一个问题,结果它翻了 10 段文档进来,里面只有两段是相关的,剩下的全是干扰信息,模型的回答反而变差了。我一般先设置 topK = 3,然后根据线上反馈调整;同时可以对相似度分数设一个下限,低于阈值就直接告知用户“库中没有找到相关资料”,而不是强行让模型编一个答案。

另外,不同嵌入模型的向量维度不同,如果你要切换本地 Ollama 的嵌入模型,就得注意向量数据库里已有的数据维度是否匹配。维度不一致时,相似度检索会直接报错,这是新手比较容易踩的坑。

6. 聊聊我对 Spring AI 现状与选型的一些思考

写了这么多,最后说点偏个人向的看法,不涉及任何厂商的立场,纯粹是这一年多来在多个项目里用 Spring AI 的一些体感。

Spring AI 目前的定位很清晰,它就是 Spring 生态在 AI 时代的“基础设施层”。它的目标不是跟 LangChain 这类框架比“谁的功能多”,而是让 Java 开发者用最熟悉的 Spring 姿势接入 AI 能力。这一点我觉得它做到了,而且完成度在不断提高。但从另一个角度看,Spring AI 的很多高级功能仍然处于快速迭代阶段,API 变动频率偏高,如果你要用它做大型生产项目,团队里最好有一个人能持续跟踪版本变化,提前评估升级影响。

我个人的建议是:如果你正在新起一个 Java 后端项目,对 AI 能力有刚需,那 Spring AI 可以作为首选方案,因为它帮你把模型接入、提示词管理、结构化输出、RAG、工具调用这些链路统一起来了,后面无论是换模型还是加能力,都留了足够的扩展空间。如果你的项目只是想在某个角落简单调一次大模型接口,几十行代码能解决的事,确实不必引入整个框架。

还有一件事我特别想强调:模型能力、提示词设计和代码工程三者的关系。Spring AI 解决的是“代码工程”这个层面,但最终效果的上限,还是由你选的模型和提示词质量决定的。同一个模型,提示词写得专业和随便写,效果差距极其明显。所以别把 Spring AI 当成“魔法框架”,它只是把工程复杂度降下来的工具,该打磨的提示词、该设计的检索策略,一个都不能少。

刚开始接触 Spring AI 的朋友,我建议你从小项目入手,先用 Ollama 跑一个本地模型,照着这篇文章的代码写一个最简单的聊天接口,感受一下整个链路跑通是什么感觉。之后再逐步加上 Prompt Template、结构化输出、RAG、Function Calling 这些能力。这个过程不需要花一分钱,就能把 Spring AI 的核心用法全部摸一遍。

最后分享一个我实际开发中养成的习惯:所有调用大模型的方法,我都会把请求参数、响应内容、耗时和 Token 消耗统一打到日志里。这么做刚开始会觉得啰嗦,但真的排查问题的时候就知道有多香了——模型返回内容对不对、是不是超时了、Token 消耗是不是异常膨胀,翻日志一目了然。AI 项目的排错逻辑和传统接口项目不太一样,模型的输出是不可控的,只有把过程数据完整记录下来,你才能知道问题出在提示词、出在检索、还是出在模型本身。这个习惯,从你写第一行 Spring AI 代码的时候就可以开始培养了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询