Spring Boot 集成 Ollama:Java后端本地大模型对话实战
2026/9/24 18:20:20 网站建设 项目流程

本地大模型最近两年成了Java后端绕不开的话题。数据不出内网、按需部署、成本可控,这三点让 Ollama 成了本地跑大模型的首选,而 Spring AI 则是 Spring Boot 生态里接入 AI 最顺手的官方组件。这篇文章我完整梳理一遍:怎么装 Ollama、怎么拉模型、怎么用 Spring AI 把智能对话能力接进 Spring Boot 工程,全程给可直接复现的配置和代码。

这个方案适合谁?如果你是在做 Java 后端,想在项目里加一个不依赖云厂商的对话助手;或者你们环境对数据外发有要求,需要完全本地的推理服务;又或者你就是想在开发机上跑个千问、Llama,给内部工具做一个私人问答入口——这篇文章都适用。内容是我反复搭环境、调接口、踩坑之后沉淀下来的东西,照着做基本一遍过,不用再去翻一堆零散的官方文档。

1. 方案选型:为什么是 Ollama + Spring AI

1.1 Ollama 解决了本地部署的什么问题

先聊 Ollama。它本质上是一个大模型运行时管理器,把模型下载、启动、暴露 HTTP 接口这几件事全部封装好了。你不需要写 Transformer 推理脚本,不需要折腾 Python 环境,装好之后一条命令把模型拉下来、跑起来,就是一个完整的推理服务。

我在选本地推理方案的时候,不是没考虑过 LM Studio。LM Studio 更偏个人桌面使用,命令行和 Docker 环境不如 Ollama 干净,接口风格也没有 Ollama 这么贴近 OpenAI 规范。Ollama 的胜出点有三个:

  • 安装简单,Windows、macOS、Linux 都有安装包,Linux 上一条命令也能装;
  • 模型管理方便,ollama pull拉取、ollama ls查看、ollama rm删除,全命令行操作;
  • 接口兼容性好,原生支持 OpenAI 风格的/v1/chat/completions接口,这一点对接 Spring AI 非常关键。

1.2 Spring AI 是接入层的合理抽象

很多 Java 项目接大模型,习惯自己用 RestTemplate 或者 WebClient 拼 HTTP 请求,对着接口文档手撸 DTO、拼 JSON、处理流式响应。短平快接一个模型没问题,一旦要切换模型、做多轮对话、接 RAG、做 Agent,代码就会迅速膨胀,每个模型都要维护一套客户端逻辑。

Spring AI 是 Spring 官方推出的 AI 应用开发框架,核心思路是把对大模型的调用抽象成统一接口。你面向ChatClientChatModel这些高层 API 编程,底层接 OpenAI、Ollama、通义还是智谱,切换时只需要改配置和依赖,业务代码基本不动。

我知道有人会提 LangChain4j,功能也很全面,但论 Spring 生态的贴合度、自动装配能力以及上下文管理,Spring AI 更省心,毕竟是官方出品。Spring AI 1.0 GA 版本发布之后,API 趋于稳定,依赖坐标也清晰了,当前正是接入的好时机。

1.3 整体链路与版本搭配

整个调用链路非常短:

Spring Boot 应用 → Spring AI(Ollama Starter) → Ollama 本地服务 → 本地大模型

模型推理完全发生在本机或内网服务器,外部网络只负责把模型文件下载到本地。这个链路带来的直接好处是:对话数据不出内网,适合企业内部工具、金融政务类系统;不按 Token 计费,CPU 也能跑,部署成本可控;模型文件提前备好之后,离线环境也能运行。

版本搭配上,我实测下来这套最稳,直接贴给你:

组件推荐版本说明
JDK17 或 21Spring Boot 3.x 要求至少 17,21 表现更好
Spring Boot3.3.x 或 3.4.x当前主流稳定版本
Spring AI1.0.0 GA 或更高必须和 Boot 版本匹配
Ollama0.5.x 及以上新版本对 Function Calling 支持更完善

注意:Spring AI 1.0 GA 要求 Spring Boot 3.4 及以上,如果你还在用 Spring Boot 3.2 或 3.3,需要选用对应的 Spring AI 旧版本(0.8.x 那套依赖坐标)。动手前先把版本对应关系查清楚,能省掉大量排错时间。

2. 环境准备:Ollama 安装、模型选择与离线导入

2.1 三平台安装与联通性验证

Ollama 的安装基本没有难度,官网下载安装包按引导装完就行。我顺便把命令行的装法列出来,服务器环境经常用得上。

macOS:

brew install ollama

Linux:

curl -fsSL https://ollama.com/install.sh | sh

Windows 直接下载安装包,装完之后手动启动 Ollama 应用程序。

装好后,先确认服务状态:

ollama serve ollama --version

如果ollama serve已经在后台运行,直接访问http://127.0.0.1:11434能看到 Ollama 返回的信息,说明服务起来了。

这里有一个很容易忽略的点:Ollama 默认只绑定127.0.0.1:11434。如果 Spring Boot 和 Ollama 在同一台机器,没问题;如果 Spring Boot 部署在另一台服务器上,就必须在启动 Ollama 前设置环境变量OLLAMA_HOST=0.0.0.0,否则远端连不上,而且这个坑比较恶心,因为用本机 curl 测是通的,换一台机器就不行。

2.2 本地模型怎么选:从硬件出发的选型

这是所有人都会问的问题:本地部署大模型用哪个模型最佳?我的回答很实在——没有绝对最佳,只有匹配你硬件的选型。

我的判断标准是这么几条:

  • 显存 8G 以下,老老实实用 7B~8B 参数的量化模型,推荐qwen2.5:7b或者llama3.1:8b
  • 显存 16G,可以上 14B 参数的 Q4 量化版,比如qwen2.5:14b
  • 32G 及以上,再考虑 32B 或更大模型;
  • 纯 CPU 跑且内存 16G 以下,建议选qwen2.5:3bgemma2:2b,能用但首字延迟会偏高。

中文场景我首推通义千问系列。原因很简单:中文语料占比高,指令跟随能力好,输出质量在同尺寸下明显优于 Llama,而且量化后的模型体积控制得不错,Q4 量化的 7B 模型大概 4.7GB,普通开发机跑得动。

拉取模型的命令:

ollama pull qwen2.5:7b

拉完用ollama list检查,确认模型已经就位。

模型参数规模量化后体积建议硬件
qwen2.5:3b3B约 1.9GB任意开发机
qwen2.5:7b7B约 4.7GB8G 显存或 16G 内存
qwen2.5:14b14B约 9.0GB16G 显存
llama3.1:8b8B约 4.9GB8G 显存或 16G 内存

2.3 下载慢的稳妥解法:本地导入模型

ollama pull从官方模型库下载,网络状况不好的时候几 GB 的模型能下好几个小时,有时候下到一半断了还得重来。这个坑我估计每个人都踩过。

我的建议是:别在ollama pull上死磕,改用国内模型托管平台下载模型文件再导入 Ollama,整个过程完全可控。

具体操作分三步:

第一步,去魔搭社区(ModelScope)等平台找到同名模型的 GGUF 格式文件,搜索关键字用“qwen2.5 7b gguf”就能找到,下载到本地。

第二步,写一个 Modelfile,指明本地文件路径:

FROM ./qwen2.5-7b-instruct-q4_k_m.gguf

第三步,用 Ollama 从本地创建模型:

ollama create qwen2.5-7b -f ./Modelfile

创建完成后,ollama list里会出现这个模型,后续使用方式和pull下来的没有任何区别。

注意:如果目标环境完全离线,建议在开发机上先把模型导入一次,然后把整个模型目录拷贝到目标机器。模型默认存放在~/.ollama/models,目标机器上设置环境变量OLLAMA_MODELS指向该目录即可,省去重新下载的时间。

3. Spring Boot 工程搭建与智能对话代码实现

3.1 创建项目与引入依赖

项目创建我直接用 Spring Initializr,勾选 Spring Web 就够用。核心依赖是 Spring AI 的 Ollama Starter,建议同时加上 Spring Boot Starter Web 和 Validation,后面写对话接口会用到。

需要提醒的是,Spring AI 的依赖坐标在 1.0 GA 和之前的版本之间差别不小。我现在用的这套是最主流的,先通过 BOM 管理版本:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0-GA</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

然后在 dependencies 里加:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> </dependency>

如果是 Spring Boot 3.2 的老项目,Spring AI 1.0 装不上,需要回退到 0.8.1 那套依赖,但那版 API 跟现在差别比较大。我的建议是直接升级 Boot 版本,别在老版本上迁就。

3.2 配置文件:一个 application.yml 搞定

依赖引入之后,大部分接入工作都可以通过配置完成。application.yml里最核心的就是告诉 Spring AI 去连哪个 Ollama 服务、用哪个模型:

server: port: 8080 spring: application: name: local-chat-app ai: ollama: base-url: http://127.0.0.1:11434 chat: options: model: qwen2.5:7b temperature: 0.7 top-p: 0.9

几个参数说明一下:

  • base-url:Ollama 服务地址,默认就是http://127.0.0.1:11434
  • model:默认模型名,必须和ollama list里看到的完全一致;
  • temperature:回答随机性,0 表示尽量确定,值越大越发散。客服问答建议 0.3~0.5,闲聊创作可以 0.8 以上;
  • top-p:核采样阈值,配合 temperature 一起调整,通常保持 0.9 左右。

如果你想让所有请求都带一套默认系统提示词,配置里也能预设,但我更推荐放在代码里,因为不同业务场景提示词差别很大,写死配置后期不好维护。

3.3 核心实现:ChatClient 一行出答案

Spring AI 1.0 之后最常用的是ChatClient,它是高层封装的入口,用法很像 WebClient 的链式调用。

先写一个普通对话接口:

@RestController @RequestMapping("/api/chat") public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @PostMapping public String chat(@RequestBody ChatRequest request) { return chatClient.prompt() .user(request.message()) .call() .content(); } }

ChatClient由 Spring AI 自动装配生成,直接构造器注入到 Controller 里就能用。ChatRequest是很简单的 DTO,一个message字段就够了,这里不展开。

调用逻辑跟平时调 OpenAI 的思维方式完全不同,你不需要关心 HTTP 报文、鉴权、模型参数这些细节,只要描述“用户问什么”,Spring AI 负责和 Ollama 通信并返回结果。

3.4 流式输出:让对话体验更像 ChatGPT

如果回答内容比较长,同步等待接口会非常难受。比如让模型写一段代码,调用要卡几十秒才返回,前端一直转圈,体验很差。更实际的做法是流式输出,用 SSE 实时展示生成过程。

@PostMapping("/stream") public Flux<String> chatStream(@RequestBody ChatRequest request) { return chatClient.prompt() .user(request.message()) .stream() .content(); }

Flux<String>返回后,Spring MVC 会自动以text/event-stream格式把内容推给前端。前端用 EventSource 或 fetch 流式读取,逐字渲染,体验和 ChatGPT 官网基本一致。

这个流式能力在本地模型场景下尤其值得优先实现。本地模型推理速度没有云端快,首字延迟可能有两三秒,如果还让用户等全部生成完才看到结果,用户大概率会以为系统挂了。流式输出至少让用户感觉到“模型在干活”。

3.5 多轮对话记忆:别让模型当“金鱼”

直接调大模型接口的初学者经常踩一个坑:模型没有记忆。每次调用都是无状态的,你告诉它“我叫张三”,下一句问“我叫什么”,它答不上来。

解决办法是把历史消息一起发给模型。Spring AI 里可以用ChatMemory管理会话历史:

@Configuration public class ChatConfig { @Bean public ChatMemory chatMemory() { return new InMemoryChatMemory(); } @Bean public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) { return builder .defaultSystem("你是一个智能助手,回答要简洁、准确。") .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build(); } }

MessageChatMemoryAdvisor会自动把上下文塞进每次请求,你需要做的只是在请求时带上会话 ID:

chatClient.prompt() .user(message) .advisors(a -> a.param(ChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)) .call() .content();

会话内存默认在 JVM 内存里,单机部署没问题。多机部署或需要跨实例共享上下文时,建议把ChatMemory换成 Redis 实现,Spring AI 有现成的扩展点。这里有一个细节:本地模型上下文窗口有限,qwen2.5:7b是 32K 上下文,但聊天记录塞太多既慢又费显存,所以我通常把历史控制在最近 10 轮左右,超出后丢最旧的消息。

3.6 系统提示词与结构化输出

对话类项目里,系统提示词决定了模型的“人设”。可以在运行时代码里动态指定,比写死在配置里灵活得多:

String response = chatClient.prompt() .system("你是一名中文客服,语气友好,回答不超过50字。") .user("请介绍一下退款流程") .call() .content();

另外一个很实用的能力是结构化输出。业务系统里经常需要模型返回 JSON,而不是一坨自由文本,比如解析用户意图:

public record Intent(String action, String target, String reason) {} Intent intent = chatClient.prompt() .user("帮我把空调温度调到26度") .call() .entity(Intent.class);

模型会按照 Java record 的字段名输出 JSON,并由 Spring AI 自动反序列化成对象。这个能力在实际业务对接里非常有用,省掉了自己写提示词让模型输出 JSON 再手写解析的一整套麻烦。

4. 进阶实践:超时控制、多模型切换与提示词模板

4.1 让对话接口可配置:超时与连接设置

本地模型推理通常比云端 API 慢很多,7B 模型在纯 CPU 机器上回答一个问题可能要几十秒。这种情况下,HTTP 客户端默认超时时间完全不够用,必须显式设置。

Spring AI 的 Ollama 客户端底层使用自建 WebClient,通过配置可以调整超时和连接设置:

spring: ai: ollama: base-url: http://127.0.0.1:11434 connect-timeout: 5s read-timeout: 120s

connect-timeout是建立连接的超时,read-timeout是读取响应的超时。本地模型推理慢,read-timeout给到 120 秒比较稳妥。我见过不少项目因为没设置这两个参数,出现“答案还没生成完,客户端先超时断开”的问题,排查起来非常费劲。

4.2 多模型切换:一次接入,按需调用

实际项目里往往不止用一个大模型。比如线上环境用qwen2.5:14b保证回答质量,开发环境用qwen2.5:3b节省资源。Spring AI 支持预配置多个本地模型实例:

@Configuration public class OllamaConfig { @Bean public OllamaChatModel qwenSmallChatModel() { return OllamaChatModel.builder() .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) .defaultOptions(OllamaOptions.builder() .model("qwen2.5:3b") .build()) .build(); } @Bean public OllamaChatModel qwenLargeChatModel() { return OllamaChatModel.builder() .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) .defaultOptions(OllamaOptions.builder() .model("qwen2.5:14b") .build()) .build(); } }

然后按业务场景注入不同的 Bean 名使用。需要说明的是,如果只用一个模型,默认自动配置就够,完全不需要手写@Bean,只有多模型场景才需要手动定义。

这种“按需切换模型”的能力,是直接拼 HTTP 请求很难做到的,也是用 Spring AI 这类抽象带来的核心价值。

4.3 OpenAI 兼容接口的连带收益

Ollama 本身暴露了 OpenAI 兼容的/v1/chat/completions接口,理论上拿任何一套 OpenAI SDK 都能接入。但用 Spring AI 的好处是接口抽象统一,如果后续从本地 Ollama 平滑切换到云上的 OpenAI、通义或智谱,只需要调整依赖和配置,Controller 层代码不用动。

这一点我在一个内部知识问答项目里深有体会。最开始用 Ollama 跑,后来业务方要求对比云端模型的效果,我直接换了依赖坐标和配置文件,业务代码一行没改就完成了切换。这就是抽象层的价值——你今天花在 Spring AI 上的学习成本,后面会在无数次模型替换中赚回来。

4.4 让回答更“懂业务”:提示词模板

实际项目里,用户的提问通常需要包装成更结构化的提示词,而不是直接丢给模型。Spring AI 的PromptTemplate就是干这个的:

public String answerWithContext(String question, String context) { PromptTemplate template = new PromptTemplate(""" 请基于以下资料回答用户问题。 如果资料中没有相关信息,请明确说“资料中未找到相关内容”。 资料: {context} 问题: {question} """); Message message = template.createMessage(Map.of( "context", context, "question", question )); return chatClient.prompt(message).call().content(); }

{context}可以是产品说明、工单知识库内容。这个模板式提示词是接入 RAG 之前的必经步骤,先用起来,后续接向量数据库时只需要把“资料”换成语义检索的结果,业务层结构完全不用改。

5. 常见问题与排查技巧实录

这部分是我实际操作中踩过的真实坑,每一条都对应一个具体的线上或开发环境问题。

5.1 连接失败:Connection refused

Spring Boot 启动后调用接口报Connection refused: localhost/127.0.0.1:11434,八成是 Ollama 服务没启动。终端执行ollama serve启动即可。

如果是服务器部署,Ollama 和 Spring Boot 不在同一台机器,需要确认两方面:一是 Ollama 设置OLLAMA_HOST=0.0.0.0允许外部访问;二是 Spring AI 配置里的base-url要改成 Ollama 所在机器的实际 IP,而不是127.0.0.1。这两点缺一个都会报连接失败。

5.2 模型不存在:model not found

提示model "xxx" not found, try pulling it first,说明配置里的模型名和实际ollama list结果对不上。常见原因有两个:一是模型真的没下载;二是模型名写错了,比如qwen2.5:7bqwen2.5:7b-instruct是两个不同的模型名。排查方式就是执行ollama list看完整名称,再回去改配置。

5.3 中文流式输出乱码或乱序

流式输出时中文乱码,大部分是 HTTP 响应编码问题。确认接口返回头是Content-Type: text/event-stream; charset=utf-8,前端用response.text()按 UTF-8 解析,不要硬编码其他编码。Spring 的Flux<String>默认按 UTF-8 处理,我这边遇到乱码基本都是前端解析写错了导致的。

还有一种情况是字序不对,文字一段段地出来但顺序偶尔颠倒,这通常是前端对 SSE 分帧处理不当,把多个数据块拼接顺序搞错了。处理思路是严格按照 SSE 的data:帧格式解析,每个事件单独处理,不要跨帧拼接。

5.4 推理性能慢和资源占用过高

本地模型首字延迟高,常见原因有三个:

  • 模型太大,硬件撑不住。7B Q4 模型在 Apple Silicon 上大概每秒 20~30 token,CPU 机器更慢;
  • GPU 没被用上。NVIDIA 显卡需要 CUDA 版 Ollama,macOS 上如果没有正确启用 Metal 加速,默认 CPU 跑会很慢;
  • 模型常驻内存。Ollama 默认模型加载后 5 分钟无请求自动释放,但高并发情况下多个模型可能同时驻留,内存瞬间被吃满。

排查时先执行ollama ps看当前模型是否在内存中,再用系统资源监控确认瓶颈是 CPU 还是内存,最后决定是升级硬件还是换小模型。想要控制内存占用,可以设置OLLAMA_KEEP_ALIVE=0让模型回答完立即释放,缺点是每次请求都要重新加载模型,冷启动时间变长。

5.5 并发场景下的请求排队问题

本地模型一次通常只能跑一个推理任务,并发请求会排队,延迟飙升属于正常现象。生产环境能做的事情有三件:接口层做限流,控制并发数;用异步化或消息队列削峰,把对话任务排队处理;多卡或多机部署,通过负载均衡分散请求压力。

我之前一个项目上线后高峰期接口超时率很高,排查根因不是 Java 代码问题,而是 Ollama 单实例的处理能力上限。后来加了限流和异步化,服务质量才恢复正常。

5.6 版本不兼容的诡异报错

Spring Boot 和 Spring AI 版本不匹配时,会出现一些看起来特别奇怪的异常,比如NoSuchMethodErrorClassNotFoundException、Bean 创建失败等等。遇到这类问题,先查版本号:

  • Spring AI 1.0.0 GA 需要 Spring Boot 3.4 及以上;
  • Spring AI 的里程碑版本(M1、M2 这种)不建议上生产;
  • 所有 Spring AI 相关依赖统一走 BOM 管理,不要多个版本混用。

我踩过最大的坑是 Spring Boot 3.3 加 Spring AI 1.0.0-M3,各种诡异报错排了两天,最后把 Boot 升到 3.4 才彻底解决。版本问题一定要在最开始就确认好,别抱着侥幸心理。

6. 扩展:从智能对话到知识库问答

6.1 RAG 的接入思路

对话能跑通之后,最值得做的扩展就是接入向量数据库,实现知识库问答。RAG(检索增强生成)的思路不复杂:

  1. 把文档切片,用 Embedding 模型转成向量存入向量数据库;
  2. 用户提问时,先把问题转成向量,在库里做相似度检索,找出最相关的文本片段;
  3. 把检索到的片段作为上下文,拼进提示词模板,交给对话模型生成回答。

这里有一个好消息:Embedding 模型同样可以用 Ollama 跑,比如nomic-embed-textbge-m3,和对话模型共用一个服务,不需要额外部署一套推理环境。

6.2 向量数据库怎么选

Spring AI 对向量数据库做了抽象,Redis、Milvus、PgVector 都有对应的 Starter。我的建议是从 Redis 的向量检索开始试,部署成本最低,功能对内部工具来说已经够用,后面规模大了再迁移到 Milvus 这类专业向量库。

接入之后,知识库问答的链路就是第 4.4 节那个提示词模板的升级版:把{context}从“手动传入的文本”替换成“向量检索返回的相关文档片段”。其他逻辑完全复用,这就是前面把提示词模板单独抽出来的价值所在。


这套东西我陆陆续续调了一周多,最深的体会是:本地大模型的落地难点不在模型本身,而在工程链路的长尾问题——版本兼容、超时处理、并发控制、上下文管理,每一样都比“把接口跑通”更花心思。用 Spring AI 把这层抽象用好,后面无论是换模型、加 RAG 还是做 Agent,都能省下大量重复劳动。

最后再分享一个小技巧:开发阶段先不做任何业务包装,直接在 Controller 里暴露原始请求参数,用 Postman 或浏览器把同步和流式接口都调通,确认模型侧没问题之后,再往上叠加提示词模板、会话记忆、多模型切换这些能力。这样每一步出问题都能快速定位到是模型、配置还是代码的锅,排错成本会低很多。

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

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

立即咨询