Spring AI 入门到上手:用 Java 把大模型接进业务(DeepSeek / Ollama / RAG / Function Calling 一篇搞懂)
适用人群:做 Java / SpringBoot 的同学,想把大模型接到系统里:智能客服、知识库问答、内容生成、自动审核/助手等。
技术栈:Spring Boot 3.x + JDK 17 + Spring AI(OpenAI 协议)+ Redis/Milvus(可选)+ ES(可选)
1. Spring AI 到底解决什么问题?
很多人第一次接大模型,会直接HttpClient/RestTemplate去调某个平台的 API。短期能跑,但很快会遇到几个问题:
- 供应商绑定严重:今天用 DeepSeek,明天要切通义/Claude/Ollama,代码改一大片。
- Prompt、上下文、流式输出、工具调用都得自己拼装,容易越写越乱。
- RAG(知识库问答)这条链路(读取→切分→向量化→入库→检索→拼 Prompt→回答)自己搭很耗时。
- 工程化能力缺失:监控、重试、日志、超时、降级、统一配置,不好统一治理。
Spring AI 的价值就是:把“调用大模型”这件事抽象成 Spring 风格的组件。你写业务时关心的是:
- 我是聊天(Chat)还是 Embedding?
- 要不要流式?
- 要不要函数调用(Tools)?
- 要不要接向量库做 RAG?
而不是天天和不同供应商的请求结构搏斗。
2. 快速跑通:用 DeepSeek(OpenAI 协议)做一次对话
2.1 版本建议(很重要)
- Spring Boot:3.x
- JDK:17
- Spring AI:建议选同一条版本线(用 BOM 管)
Spring AI 版本迭代快,建议把版本锁在 BOM 上,避免依赖互相“打架”。
2.2 Maven 依赖(OpenAI 协议 Starter)
<properties><java.version>17</java.version><spring-ai.version>1.0.0-M5</spring-ai.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>${spring-ai.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId></dependency></dependencies>2.3 application.yml 配置(以 DeepSeek 为例)
server:port:8899spring:application:name:spring-ai-deepseek-demoai:openai:api-key:sk-xxxxxbase-url:https://api.deepseek.comchat:options:model:deepseek-chattemperature:0.7关键点:DeepSeek 提供 OpenAI 风格接口,所以用
spring-ai-openai-*就能接。
2.4 最小 Controller
importjakarta.annotation.Resource;importorg.springframework.ai.openai.OpenAiChatModel;importorg.springframework.web.bind.annotation.*;@RestControllerpublicclassChatController{@ResourceprivateOpenAiChatModelchatModel;@GetMapping("/ai/generate")publicStringgenerate(@RequestParam(defaultValue="hello")Stringmessage){returnchatModel.call(message);}}访问:GET http://localhost:8899/ai/generate?message=用一句话解释Spring%20AI
3. ChatClient vs ChatModel:项目里我更推荐用 ChatClient
3.1 为什么?
ChatModel更偏底层(Prompt、Options 你都得自己组织)ChatClient更像“业务层 API”,链式写法清晰,更适合 Controller/Service
3.2 ChatClient 最常用的 3 种能力
A) 普通对话(最常用)
importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.web.bind.annotation.*;@RestControllerpublicclassChatClientController{privatefinalChatClientchatClient;publicChatClientController(ChatClient.Builderbuilder){this.chatClient=builder.build();}@GetMapping("/chat")publicStringchat(@RequestParam(defaultValue="你是谁")Stringmessage){returnchatClient.prompt().user(message).call().content();}}B) 角色设定(System Prompt)
把“人设”固定住,避免每次都写一大段提示词:
importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassAiConfig{@BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.defaultSystem("你是一个资深Java开发助手,回答要简洁、可落地,必要时给代码。").build();}}C) 流式输出(体验会好很多)
适用于:前端打字机效果、长回答、模型推理慢的场景。
importreactor.core.publisher.Flux;importorg.springframework.web.bind.annotation.*;@RestController@RequestMapping("/ai")publicclassStreamController{privatefinalChatClientchatClient;publicStreamController(ChatClientchatClient){this.chatClient=chatClient;}@GetMapping(value="/stream",produces="text/plain; charset=UTF-8")publicFlux<String>stream(@RequestParamStringmessage){returnchatClient.prompt().user(message).stream().content();}}注意:很多 API 测试工具不容易观察流式效果,浏览器/前端更直观。
4. Function Calling(工具/函数调用):让模型“会办事”,不是只会聊天
大模型擅长“生成”,但它不知道你的业务数据。
Function Calling 的作用:模型发现需要数据 → 调用你提供的函数 → 你返回真实结果 → 模型基于真实数据再组织回答。
典型场景:
- 查用户余额、查订单、查基金净值、查审批状态
- 触发“创建工单 / 发通知 / 写入数据库”(是否允许要自己控制)
4.1 定义一个函数(Spring Bean + Function)
importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Description;importjava.util.function.Function;@ConfigurationpublicclassToolsConfig{publicrecordAddReq(inta,intb){}publicrecordAddResp(intresult){}@Bean@Description("计算两个整数的加法")publicFunction<AddReq,AddResp>add(){returnreq->newAddResp(req.a()+req.b());}}4.2 在 ChatClient 里启用函数
importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.web.bind.annotation.*;@RestControllerpublicclassToolController{privatefinalChatClientchatClient;publicToolController(ChatClient.Builderbuilder){this.chatClient=builder.build();}@GetMapping("/tool")publicStringtool(@RequestParamStringmessage){returnchatClient.prompt().system("你是计算助手,遇到加法问题必须调用 add 函数。").user(message).functions("add").call().content();}}你问:2+3 等于多少
模型会决定调用add,再把结果组织成自然语言输出。
实战建议:
- 工具调用要白名单控制,不要让模型随便调用敏感操作。
- 工具返回结果要结构化(record/POJO),便于模型理解。
5. 接入 Ollama:本地跑模型(离线/内网/数据敏感场景)
如果你需要:
- 研发内网、不能出网
- 数据敏感,不想把内容发到公网
- 成本控制,想本地推理
Ollama 就很合适。
5.1 依赖
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId></dependency>5.2 配置
spring:ai:ollama:base-url:http://localhost:11434chat:options:model:deepseek-r1:1.5btemperature:0.75.3 使用方式和 ChatClient 一样
你换了底层模型供应商,但业务代码几乎不动,这就是 Spring AI 的工程价值。
6. RAG(知识库问答):让回答“有依据”,减少胡编
很多业务不是“开放聊天”,而是:
- 产品手册问答
- 内部制度问答
- 工单历史/FAQ 问答
- 研发文档/接口文档问答
这类场景最怕“编造”。RAG 的思路是:先检索到真实资料,再让模型基于资料回答。
6.1 最小 RAG 组成
- Embedding 模型:把文本转向量
- VectorStore:存向量并支持相似度检索
- Advisor:把检索结果自动拼进 Prompt
6.2 代码示例(思路版)
importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;importorg.springframework.ai.vectorstore.VectorStore;importorg.springframework.web.bind.annotation.*;@RestController@RequestMapping("/rag")publicclassRagController{privatefinalChatClientchatClient;privatefinalVectorStorevectorStore;publicRagController(ChatClientchatClient,VectorStorevectorStore){this.chatClient=chatClient;this.vectorStore=vectorStore;}@GetMapping(value="/chat",produces="text/plain; charset=UTF-8")publicStringchat(@RequestParamStringq){returnchatClient.prompt().user(q).advisors(newQuestionAnswerAdvisor(vectorStore)).call().content();}}生产实践要点:
- 文档入库前要做“切分”(chunk),并保存 metadata(来源、标题、更新时间)
- 检索要加阈值,避免捞到不相关内容
- System Prompt 要明确:“资料没有就说不知道,不要编”
7. 常见坑与实战建议(非常关键)
- 超时要配:模型响应时间不稳定,网关/客户端都要适当放宽,避免接口总 504。
- 流式返回要注意:前端最好用 SSE/Fetch Stream,别用传统一次性 JSON。
- Prompt 不要堆太长:长不是问题,乱才是问题。把角色、约束、输出格式写清楚,比堆背景更重要。
- 函数调用要控权限:模型“能调用什么”必须严格控制。
- 日志与审计:至少把请求参数、模型响应、工具调用记录下来(脱敏)。
- 成本与限流:生产必须做配额、限流、缓存(同问同答可以缓存)。