Spring Boot之Ollama本地大模型集成,一文搞定!

举报
bug菌 发表于 2026/01/13 10:55:22 2026/01/13
【摘要】 🏆本文收录于《滚雪球学SpringBoot 3》:https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。  本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。...

🏆本文收录于《滚雪球学SpringBoot 3》:https://blog.csdn.net/weixin_43970743/category_12795608.html,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
  
本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】https://blog.csdn.net/weixin_43970743/article/details/151115907,你想学习的都被收集在内,快速投入学习!!两不误。
  
若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》:https://blog.csdn.net/weixin_43970743/category_11599389.html,涵盖SpringBoot所有版本教学文章。

演示环境说明:

  • 开发工具:IDEA 2021.3
  • JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
  • Spring Boot版本:3.5.4(于25年7月24日发布)
  • Maven版本:3.8.2 (或更高)
  • Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
  • 操作系统:Windows 11

1) 为什么需要本地模型?隐私与成本考量

“上云不香吗?”当然香——但你要是做过企业内部系统,就会发现很多场景根本不是“香不香”,而是“能不能”。本地模型(Ollama 这类)常见动机基本就两类:

1.1 隐私/合规:数据不出内网就是硬道理

  • 你喂给模型的可能是:客服工单、合同、代码、报表、用户画像……这些东西一旦出网,哪怕有脱敏也会有人睡不着。
  • 本地推理的核心优势:原始数据不需要离开你的机器/局域网(你自己掌控网络边界、日志、存储)。

这里不是说云模型不安全,而是很多组织的合规要求会直接把“外发内容”判死刑。本地模型就变成了现实解。

1.2 成本:当调用量稳定增长时,本地推理更像“买断”

  • 云 API 通常是“按量计费 + 峰值焦虑”:一旦业务跑起来,你会开始盯着 token 用量像盯着心电图。
  • 本地模型更像:硬件成本一次投入 + 运行成本可预期。当然前提是你的场景允许“稍低一点的质量/更强的工程优化”。

我自己的经验是:如果你只是偶尔玩玩、或者质量必须顶配,那云更省心;但如果你是“内部问答/知识检索/代码辅助/批处理摘要”这类稳定高频场景,本地方案越做越划算(前提:你愿意把工程做好🙂)。

2) 本地运行 Llama 3 / Mistral

这一段我不搞花活,给你最小可用流程:拉取、运行、验证 API。

2.1 拉取并运行模型(命令行)

典型流程是先 pull/run(不同平台安装方式略有差异,你按官方安装完 Ollama 后直接用命令即可)。

# 运行(如果本地没有会自动拉取)
ollama run llama3

# 或者运行 mistral
ollama run mistral

跑起来后,你本地会有一个 Ollama 服务,默认提供 HTTP API(常见端口是 11434)。

2.2 先确认“流式输出”的数据格式:NDJSON

Ollama 的 API 流式返回是 newline-delimited JSON(NDJSON),也就是一行一个 JSON,Content-Type 通常是 application/x-ndjson。官方 streaming 说明写得很明确:像 /api/generate 这类端点默认就是流式响应,并采用 NDJSON。
GitHub 官方 API 文档也强调 /api/generate 是 streaming endpoint,会返回一系列 response,最后一条包含统计信息等。

这点非常关键:你后端要么逐行转发,要么就会把流读成“一个大字符串”,然后你就失去 streaming 的意义了(很多人第一次接就栽这儿😅)。

3) Spring Boot 连接本地 Ollama 服务

这里我给你两条路线:

  • 路线 A:Spring AI(省心、标准、Spring 风味)
  • 路线 B:直接 HTTP 调 Ollama API(最灵活、最贴近底层 streaming)

你项目要是“业务系统为主”,我建议优先 A;你要是“平台/网关/强定制”,B 更自由。

3.1 路线 A:用 Spring AI 集成 Ollama(强烈推荐入门先走这条)

Spring AI 官方文档明确提供了 Ollama Chat 的 Spring Boot 自动配置:引入 starter spring-ai-starter-model-ollama 就能用。

Maven 依赖(示例)

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

application.yml(示例)

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
    chat:
      options:
        model: llama3

Spring AI 的具体配置项会随版本演进,你以文档为准,但 starter 和“提供自动配置”这个事实是稳定的。

一个最小 Controller(非流式)

@RestController
@RequestMapping("/ai")
public class ChatController {

  private final org.springframework.ai.chat.client.ChatClient chatClient;

  public ChatController(org.springframework.ai.chat.client.ChatClient chatClient) {
    this.chatClient = chatClient;
  }

  @GetMapping("/chat")
  public String chat(@RequestParam String q) {
    return chatClient.prompt(q).call().content();
  }
}

这条路线的优势就是:你不用自己处理请求体结构、也不用手写 JSON 解析,Spring AI 会替你管理大部分细节。

3.2 路线 B:直接调用 Ollama HTTP API(更贴近 streaming NDJSON)

Ollama API 文档里有 generate/chat 等端点,并且支持流式对话请求。
如果你希望完全控制 prompt、system、工具调用、以及流式转发策略,直接 HTTP 会很舒服。

下面给一个 Spring Boot WebFlux 的示例:把 Ollama 的 NDJSON 流读出来,转成你自己的 SSE(给前端更容易消费)。

3.2.1 后端:WebFlux 代理 Ollama 流式输出 → SSE 输出给浏览器

依赖

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

Controller:把 Ollama NDJSON 转成 SSE

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

import java.time.Duration;
import java.util.Map;

@RestController
public class OllamaStreamController {

  private final WebClient webClient;
  private final ObjectMapper mapper = new ObjectMapper();

  public OllamaStreamController() {
    this.webClient = WebClient.builder()
        .baseUrl("http://localhost:11434")
        .build();
  }

  @GetMapping(value = "/ai/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
  public Flux<String> stream(@RequestParam String q) {

    // Ollama /api/generate 默认流式 NDJSON(官方说明):contentReference[oaicite:5]{index=5}
    Map<String, Object> body = Map.of(
        "model", "llama3",
        "prompt", q,
        "stream", true
    );

    return webClient.post()
        .uri("/api/generate")
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(body)
        .retrieve()
        // 关键:把响应当作“逐行文本流”读,而不是一次性读完
        .bodyToFlux(String.class)
        .timeout(Duration.ofMinutes(2))
        .map(line -> {
          try {
            JsonNode json = mapper.readTree(line);
            // Ollama 响应里通常会有 token 增量字段(不同端点字段略有差异)
            // 这里示例取 response 字段(以实际返回为准)
            return json.path("response").asText("");
          } catch (Exception e) {
            return "";
          }
        })
        .filter(s -> !s.isBlank());
  }
}

为什么我建议 SSE?
因为浏览器天然支持 EventSource,你不用自己处理 chunk、也不用折腾复杂的流协议。SSE 本身就是一种标准的单向事件流。

4) 性能优化:流式响应(Streaming Response)的前后端实现

这一节是关键:很多人“接上了”,但体验像抽奖:有时候秒回,有时候卡成 PPT。通常不是模型坏了,而是你流式链路没打通或打得不顺。

4.1 后端优化要点(非常实用,不玄学)

(1) 不要把流读成整体

Ollama streaming 是 NDJSON,一行一条 JSON。
如果你用阻塞式客户端把 body 一次性读完,你就等于把 streaming “阉割”了。

(2) 选择 WebFlux/响应式,天然适合代理流

代理流式输出,本质是“IO 转发 + 少量解析”,响应式链路更容易做到高并发下的线程友好。

(3) 做好超时、断连与背压

  • 前端关掉页面、网络抖动时,后端必须能及时停止转发
  • 对长文本生成要设上限(token/时间)
  • 必要时增加队列或限流,避免单用户把 GPU/CPU 占满

4.2 前端实现:两种主流方式

方式 A:EventSource(SSE,最省心)

<script>
  const es = new EventSource("/ai/stream?q=" + encodeURIComponent("写一个三段式自我介绍"));

  es.onmessage = (e) => {
    // e.data 就是后端每次推送的增量 token
    document.querySelector("#out").textContent += e.data;
  };

  es.onerror = () => {
    es.close();
  };
</script>

<pre id="out"></pre>

SSE 的优势是简单直观,适合“流式文本输出”。

方式 B:Fetch + ReadableStream(更灵活,适合你要自定义协议)

如果你不想 SSE,而想直接消费 NDJSON 或自定义 chunk,可以用 Fetch 流读取。但实现会更啰嗦一些(要处理分片、换行、残留 buffer 等)。

一句话总结(但我不想装高深🙂)

Ollama 本地模型集成真正的“分水岭”不是“跑没跑起来”,而是你有没有把这三件事做好:

  1. 明确使用场景:隐私/成本是主驱动,别为了“本地”而本地
  2. 理解流式数据格式:NDJSON 是关键事实,不理解就会做出假 streaming
  3. 把 streaming 链路打通:后端用 WebFlux/SSE 转发,前端用 EventSource 逐字显示

🧧福利赠与你🧧

  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。

ps:本文涉及所有源代码,均已上传至Gitee:https://gitee.com/bugjun01/SpringBoot-demo 开源,供同学们一对一参考 Gitee传送门https://gitee.com/bugjun01/SpringBoot-demo,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗

🫵 Who am I?

我是 bug菌:

  • 热活跃于 CSDN:https://blog.csdn.net/weixin_43970743 | 掘金:https://juejin.cn/user/695333581765240 | InfoQ:https://www.infoq.cn/profile/4F581734D60B28/publish | 51CTO:https://blog.51cto.com/u_15700751 | 华为云:https://bbs.huaweicloud.com/community/usersnew/id_1582617489455371 | 阿里云:https://developer.aliyun.com/profile/uolxikq5k3gke | 腾讯云:https://cloud.tencent.com/developer/user/10216480/articles 等技术社区;
  • CSDN 博客之星 Top30、华为云多年度十佳博主&卓越贡献奖、掘金多年度人气作者 Top40;
  • 掘金、InfoQ、51CTO 等平台签约及优质作者;
  • 全网粉丝累计 30w+

更多高质量技术内容及成长资料,可查看这个合集入口 👉 点击查看:https://bbs.csdn.net/topics/612438251 👈️
硬核技术公众号 「猿圈奇妙屋」https://bbs.csdn.net/topics/612438251 期待你的加入,一起进阶、一起打怪升级。

- End -

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。