Spring AI入门:Java开发者的大模型集成实践指南
1. 为什么是 Spring AI,而不是直接调用 OpenAI SDK?
我第一次在团队内部技术分享会上提出“用 Spring AI 写第一个 AI 应用”时,后座一位写了八年 Java 的老哥直接举手:“不就是发个 HTTP 请求?Spring Boot 自带 WebClient,三行代码搞定,为啥要多套一层?”——这问题太真实了。它精准戳中了绝大多数 Java 开发者面对新框架时的第一反应: 怕重复造轮子,更怕引入新坑 。
但三个月后,他主动找我,说正在把项目里原来手写的 OpenAI 调用模块,一整块替换成 Spring AI。不是因为“时髦”,而是因为他在给一个金融风控服务加多模型 fallback 逻辑时,被自己写的那套“if-else 切模型 + 手动序列化 + 线程池管理 + 异常重试 + token 统计”代码逼疯了。他当时贴给我一段截图:一个 ChatClient 的封装类,光构造函数参数就传了 7 个——API Key、Base URL、超时时间、重试策略、日志开关、响应拦截器、自定义 ObjectMapper……而这些,全是为了让同一个 sendMessage() 方法,在对接 Qwen、DeepSeek 和本地 Ollama 时,能勉强跑通。
这就是 Spring AI 的真实价值锚点: 它不解决“能不能调通大模型”这个最底层问题,而是系统性地解决“在企业级 Java 工程中,如何可持续、可维护、可观测、可灰度地使用大模型”这个工程问题 。它不是另一个 SDK,而是一套面向生产环境的 AI 基础设施抽象层。
它的核心设计哲学,和 Spring Data JPA 一脉相承:你写业务逻辑时,只关心“我要查什么数据”或“我要问什么问题”,至于底层是 MySQL 还是 PostgreSQL,是 OpenAI 还是 Ollama,是同步调用还是流式响应,是 JSON 还是 SSE 协议——这些都由 ChatModel 、 EmbeddingModel 、 AudioModel 等接口背后的实现类去适配。你只需要在 application.yml 里改一行 spring.ai.ollama.chat.model: qwen2:7b ,整个应用的对话模型就无声无息地切换了,连测试用例都不用动。
这背后是 JDK17+ 的强类型泛型、Spring Boot 的自动配置(Auto-Configuration)机制、以及 Project Reactor 的非阻塞流支持共同构建的护城河。比如,当你声明一个 @Bean ChatClient chatClient(ChatModel model) ,Spring AI 会自动注入一个预置了重试、熔断、指标埋点、请求/响应日志(可开关)的客户端实例。你拿到的 ChatClient ,本质上是一个经过 Spring 生态深度武装的“AI 通信兵”,而不是裸奔的 HttpClient 。
所以,别再纠结“它比原生 SDK 多几行代码”。真正该问的是:当你的 Spring Boot 项目明天要接入阿里云百炼、后天要切到私有部署的 Llama 3、大后天要给客服机器人加上语音转文字能力时,你希望是改 3 行配置,还是重构 300 行网络调用代码?Spring AI 的答案很朴素: 让 AI 调用,回归到 Spring 开发者最熟悉的“声明式编程”范式里来 。
提示:Spring AI 并非取代 OpenAI 官方 Java SDK。官方 SDK 更适合做 POC 或极简集成;而 Spring AI 是为中大型 Java 项目准备的“AI 中间件”。就像你不会在 Spring Boot 项目里手写 JDBC 连接池一样,你也不该在需要长期维护的项目里,手写大模型的调用胶水代码。
2. 环境筑基:JDK17、Spring Boot 3.x 与 Ollama 的三位一体
跑通第一个 Spring AI 应用,90% 的失败案例,都卡在环境这一环。不是代码写错了,而是环境没对齐。我见过太多人对着 UnsupportedClassVersionError 抓耳挠腮两小时,最后发现只是 JDK 版本没切到 17。所以,我们得像搭乐高一样,严丝合缝地拼好这三块基石:JDK17、Spring Boot 3.x、Ollama。
2.1 JDK17:不是“推荐”,而是硬性门槛
Spring AI 1.0.x 系列(当前稳定版)明确要求 JDK 17 或更高版本 。这不是一个“建议兼容”的软性要求,而是由其底层依赖决定的硬性约束。关键原因有两个:
第一, Records 类型的深度使用 。Spring AI 的核心数据结构,如 ChatResponse 、 EmbeddingResponse 、 AudioResponse ,大量采用 record 关键字定义不可变数据载体。 record 是 JDK14 引入、JDK17 正式成为标准特性的语法糖。它带来的不仅是代码简洁,更是语义安全——编译器强制保证这些对象的不可变性,这对 AI 响应这种“一次生成、多次消费”的场景至关重要。如果你强行用 JDK11 运行,编译器会直接报错 error: records are not supported in -source 11 。
第二, java.util.concurrent.StructuredTaskScope 的依赖 。Spring AI 在实现并行调用(如同时调用多个 Embedding 模型)时,采用了 JDK19 引入、但在 JDK17 的 jdk.incubator.concurrent 模块中已提供预览版的结构化并发 API。虽然 Spring AI 当前版本主要用 CompletableFuture ,但其未来演进路径已锁定在此。这意味着,选择 JDK17,你不仅满足了当下需求,更买到了未来升级的“入场券”。
安装建议:直接去 Oracle 官网 或 Adoptium 下载 JDK17 LTS 版本。Windows 用户务必在安装后检查 JAVA_HOME 环境变量是否指向 jdk-17.x.x 目录,并在 PATH 中加入 %JAVA_HOME%\bin 。验证命令:
java -version
# 输出应为:java version "17.x.x" ...
2.2 Spring Boot 3.x:WebFlux 与 Jakarta EE 9 的必然选择
Spring AI 是 Spring 官方项目,其所有 Starter 都基于 Spring Boot 3.x 构建。这带来两个不可绕过的事实:
- 必须使用 Spring Boot 3.0+ 。Spring Boot 2.7 已于 2023 年 11 月停止维护,且其底层的 Spring Framework 5.x 不支持 Spring AI 所需的响应式流(Reactive Streams)规范。
- 默认 Web 容器是 Netty(WebFlux),而非 Tomcat(MVC) 。这是为了原生支持大模型的流式响应(Streaming)。当你调用
chatClient.stream()时,Spring AI 会通过Flux<ChatResponse>将每个 token 的增量响应推送到前端,而不是等整个回答生成完毕才返回。这要求你的 Controller 必须是@RestController+Flux返回类型,而非传统的@ResponseBody。
创建项目最稳妥的方式,是访问 start.spring.io ,选择:
- Project:Maven
- Language:Java
- Spring Boot:3.3.x(最新稳定版)
- Dependencies:Spring Web、Spring AI Ollama(注意:不是 “Spring AI Core”,而是具体实现)
生成的 pom.xml 会自动包含:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
这个 Starter 不仅引入了核心依赖,还内置了针对 Ollama 的自动配置类 OllamaAutoConfiguration ,它会扫描 application.yml 中的 spring.ai.ollama.* 配置,并为你准备好 OllamaChatModel 、 OllamaEmbeddingModel 等 Bean。
2.3 Ollama:本地大模型的“即插即用”运行时
Ollama 是目前 Java 开发者入门 AI 最友好的本地模型运行时。它把复杂的模型加载、GPU 显存管理、HTTP API 封装成一个简单的命令行工具。你不需要懂 CUDA、不需要配 Docker、甚至不需要手动下载几十 GB 的模型文件——一条命令,模型就跑起来了。
安装步骤极其简单:
- Mac :
brew install ollama,然后ollama run qwen2:7b - Windows :下载 Ollama Windows 安装包 ,双击安装,然后在 PowerShell 中执行
ollama run qwen2:7b - Linux :
curl -fsSL https://ollama.com/install.sh | sh,然后ollama run qwen2:7b
注意:
qwen2:7b是通义千问 2 的 7B 版本,对显存要求低(8GB RAM 即可),中文理解优秀,是入门首选。如果你的机器显存充足(≥16GB),可以换qwen2:14b或llama3:8b。
安装完成后,Ollama 会在本地启动一个 HTTP 服务,默认地址是 http://localhost:11434 。你可以用 curl 验证:
curl http://localhost:11434/api/tags
# 返回所有已下载模型的列表
国内用户常遇到的问题是 ollama run qwen2:7b 下载极慢。这不是网络问题,而是 Ollama 默认从 Hugging Face 下载,而 HF 在国内没有 CDN 加速。解决方案是配置国内镜像源。在 ~/.ollama/modelfile (Windows 是 %USERPROFILE%\.ollama\modelfile )中添加:
FROM https://mirrors.tuna.tsinghua.edu.cn/hugging-face/models/Qwen/Qwen2-7B-Instruct/gguf/qwen2-7b-instruct.Q4_K_M.gguf
或者,更通用的方法是设置环境变量(推荐):
# Linux/macOS
export OLLAMA_HOST=0.0.0.0:11434
export OLLAMA_ORIGINS="http://localhost:*"
# Windows PowerShell
$env:OLLAMA_HOST="0.0.0.0:11434"
$env:OLLAMA_ORIGINS="http://localhost:*"
然后重启 Ollama 服务。这样,后续所有 ollama run 命令都会优先走清华源。
这三者的关系,可以类比为一辆汽车:JDK17 是发动机(提供动力),Spring Boot 3.x 是整车底盘和操作系统(提供运行框架),Ollama 则是油箱和燃油(提供 AI 能力)。少任何一个,车都开不起来。而 Spring AI,就是那个把发动机、底盘、油箱完美耦合在一起的总装厂。
3. 从零开始:一个可运行的 Spring AI + Ollama 对话应用
现在,所有基石都已铺好。我们来亲手搭建第一个真正可用的 Spring AI 应用。目标很明确:一个 Spring Boot Web 服务,接收用户输入的文本问题,调用本地 Ollama 的 Qwen2 模型,返回结构化的 AI 回答,并支持流式输出。整个过程,我们将严格遵循 Spring 的“约定优于配置”原则,尽量减少样板代码。
3.1 创建项目与依赖配置
打开 start.spring.io ,按如下配置生成项目:
- Project:Maven Project
- Spring Boot:3.3.3(或最新稳定版)
- Dependencies:Spring Web、Spring AI Ollama、Lombok(简化 POJO)
下载 ZIP 包,解压后导入 IDE。打开 pom.xml ,确认 spring-ai-ollama-spring-boot-starter 依赖已存在。接着,打开 src/main/resources/application.yml ,进行核心配置:
spring:
ai:
ollama:
# Ollama 服务地址,如果在本机运行,保持默认即可
base-url: http://localhost:11434
# 指定默认使用的聊天模型
chat:
model: qwen2:7b
# 可选:配置嵌入模型,用于后续向量检索
embedding:
model: nomic-embed-text
# 启用 Spring AI 的调试日志,方便排查问题
logging:
level:
org.springframework.ai: DEBUG
# 为 WebFlux 配置响应式超时,避免流式请求挂起
server:
reactive:
max-http-header-size: 65536
这个配置文件,就是 Spring AI 的“魔法开关”。 spring.ai.ollama.base-url 告诉 Spring AI 去哪里找 Ollama; spring.ai.ollama.chat.model 指定了默认模型;而 logging.level.org.springframework.ai: DEBUG 则会在控制台打印出每一次请求的完整 Request/Response,是调试阶段的救命稻草。
3.2 编写核心业务逻辑:ChatService
在 src/main/java/com/example/demo 下,创建 service/ChatService.java :
package com.example.demo.service;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.util.List;
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 同步调用:发送单条消息,获取完整响应
*/
public String ask(String userQuery) {
// 构建用户消息
UserMessage userMessage = new UserMessage(userQuery);
// 构建 Prompt(可选:添加系统指令,让模型更“听话”)
Prompt prompt = new Prompt(List.of(userMessage));
// 调用模型
ChatResponse response = chatClient.call(prompt);
// 提取并返回内容
return response.getResult().getOutput().getContent();
}
/**
* 流式调用:发送单条消息,逐 token 返回响应
*/
public Flux<String> askStream(String userQuery) {
UserMessage userMessage = new UserMessage(userQuery);
Prompt prompt = new Prompt(List.of(userMessage));
// 返回 Flux<String>,每个元素是一个 token 的文本片段
return chatClient.stream(prompt)
.map(chatResponse -> chatResponse.getResult().getOutput().getContent());
}
}
这段代码展示了 Spring AI 最核心的两种调用模式。 ask() 方法返回一个 String ,适用于需要完整回答的场景(如生成报告摘要); askStream() 方法返回一个 Flux<String> ,适用于需要实时反馈的场景(如聊天界面的打字效果)。关键在于,你完全不用关心底层是 HTTP 还是 WebSocket,是 JSON 解析还是 SSE 解码—— ChatClient 已经为你封装好了所有细节。
3.3 构建 RESTful API 接口
创建 controller/ChatController.java :
package com.example.demo.controller;
import com.example.demo.service.ChatService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
/**
* 同步问答接口
* POST /api/chat/sync
* Body: {"query": "你好,今天天气怎么样?"}
*/
@PostMapping(value = "/sync", produces = MediaType.TEXT_PLAIN_VALUE)
public String syncAsk(@RequestBody SyncRequest request) {
return chatService.ask(request.getQuery());
}
/**
* 流式问答接口
* GET /api/chat/stream?query=你好
* 响应类型:text/event-stream
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamAsk(@RequestParam String query) {
return chatService.askStream(query);
}
// 内部 DTO,用于接收 JSON 请求体
public static class SyncRequest {
private String query;
public String getQuery() { return query; }
public void setQuery(String query) { this.query = query; }
}
}
这里有两个关键点:
-
/sync接口使用@PostMapping,返回MediaType.TEXT_PLAIN_VALUE,即纯文本。这是最简单的交互方式,适合 Postman 测试。 -
/stream接口使用@GetMapping,返回MediaType.TEXT_EVENT_STREAM_VALUE(SSE)。浏览器或前端框架(如 Vue 的EventSource)可以监听这个流,实时接收每一个data: xxx的事件。
3.4 启动与验证:见证第一个 AI 响应
一切就绪,启动应用:
mvn spring-boot:run
确保此时 Ollama 已在后台运行: ollama run qwen2:7b (首次运行会自动下载模型,耐心等待几分钟)。
测试同步接口 :
curl -X POST http://localhost:8080/api/chat/sync \
-H "Content-Type: application/json" \
-d '{"query":"用一句话解释什么是 Spring AI?"}'
预期返回:
Spring AI 是一个由 Spring 官方推出的 Java 框架,旨在为 Spring Boot 应用提供统一、可扩展、生产就绪的大模型集成能力。
测试流式接口 :
curl -N http://localhost:8080/api/chat/stream?query=请用三个词形容Java语言的特点
你会看到类似这样的实时输出:
data: 稳定
data: 面向对象
data: 生态丰富
每行 data: 后面的内容,就是一个独立的 token。这就是流式响应的魅力——无需等待,所见即所得。
实操心得:如果
curl -N没有返回任何内容,请首先检查 Ollama 是否真的在运行(ollama list查看模型状态),其次检查application.yml中的base-url是否正确(特别是 Windows 用户,有时localhost会被解析为 IPv6 地址,可尝试改为127.0.0.1)。另外,-N参数是curl的关键,它禁用缓冲,确保你能实时看到流式输出。
4. 深度解构:Spring AI 的核心组件与工作原理
仅仅会“跑起来”是远远不够的。一个资深 Java 开发者,必须理解其内部是如何运转的,才能在复杂场景下驾驭它。Spring AI 的架构并非黑盒,它由几个清晰、正交的核心组件构成,每一层都解决了特定的工程问题。
4.1 ChatModel 接口:模型能力的抽象契约
ChatModel 是 Spring AI 的核心接口,定义了所有聊天模型必须实现的最小行为集:
public interface ChatModel {
ChatResponse call(Prompt prompt);
Flux<ChatResponse> stream(Prompt prompt);
}
这个接口的设计,体现了典型的“面向接口编程”思想。 ChatResponse 是一个标准化的响应容器,它内部封装了:
-
output:ChatResponse.Output,包含content(文本内容)、role(assistant/user/system)、tokenUsage(本次调用消耗的 token 数)、finishReason(停止原因,如stop或length)。 -
metadata:Map<String, Object>,存放模型特有的元信息,如 Ollama 的model字段、OpenAI 的system_fingerprint。
当你在 application.yml 中配置 spring.ai.ollama.chat.model: qwen2:7b 时,Spring AI 的自动配置类 OllamaAutoConfiguration 就会创建一个 OllamaChatModel 的 Bean,并将其注入到所有需要 ChatModel 的地方。这个过程,和你配置 spring.datasource.url 后,Spring 自动创建 DataSource Bean 完全一致。
为什么需要这个抽象? 想象一下,你的项目初期用 Ollama 做 PoC,上线后要切换到阿里云百炼。如果代码里到处都是 new OllamaChatModel(...) ,那将是灾难性的。而有了 ChatModel 接口,你只需:
- 引入
spring-ai-alibaba-spring-boot-starter依赖; - 在
application.yml中添加spring.ai.alibaba.chat.endpoint: https://dashscope.aliyuncs.com/...; - 修改
spring.ai.alibaba.chat.model: qwen-max。
其余所有业务代码,包括 ChatService ,完全无需改动。这就是抽象的价值——它隔离了变化。
4.2 Prompt 与 Message :提示工程的 Java 化表达
在 Spring AI 中,你永远不会直接拼接字符串来构造请求体。所有的输入,都必须通过 Prompt 和 Message 对象来表达。这是一个革命性的转变,它将提示工程(Prompt Engineering)从“字符串艺术”提升为“类型安全的编程实践”。
Message 是一个接口,有三个实现类:
-
UserMessage: 用户输入,对应role: user。 -
AiMessage: AI 的回复,对应role: assistant。 -
SystemMessage: 系统指令,对应role: system,用于设定模型的行为准则(如“你是一个严谨的法律助手”)。
Prompt 则是一个容器,持有一个 List<Message> 。它还有一个重要的子类 ChatPrompt ,专门用于多轮对话场景。
例如,要实现一个“角色扮演”对话,你可以这样写:
// 构建系统指令
SystemMessage systemMessage = new SystemMessage("你是一位精通 Spring Boot 的资深架构师,回答要简洁、准确、有代码示例。");
// 构建用户历史消息(模拟多轮)
UserMessage history1 = new UserMessage("Spring Boot 如何配置多环境?");
AiMessage answer1 = new AiMessage("使用 application-{profile}.yml,通过 spring.profiles.active 激活。");
UserMessage currentQuery = new UserMessage("那如何在 Docker 中指定 profile?");
// 构建完整 Prompt
Prompt prompt = new Prompt(List.of(systemMessage, history1, answer1, currentQuery));
String response = chatClient.call(prompt).getResult().getOutput().getContent();
这种写法的好处是: 可读性、可测试性、可复用性 。你可以把 systemMessage 提取为常量,把历史对话保存在数据库中,然后动态组装 Prompt 。这比在 Controller 里写 String.format("system: %s\nuser: %s\n...", system, user) 要健壮得多。
4.3 ChatClient :面向开发者的“终极 API”
如果说 ChatModel 是面向框架的,那么 ChatClient 就是面向开发者的。它是 Spring AI 提供给业务代码的“门面(Facade)”,隐藏了所有底层复杂性。
ChatClient 的核心方法是:
ChatResponse call(Prompt prompt);
Flux<ChatResponse> stream(Prompt prompt);
但它背后,却整合了:
- 重试机制 :当网络抖动导致请求失败时,自动重试(可配置次数和间隔)。
- 熔断器(Circuit Breaker) :当模型服务连续失败,自动进入“熔断”状态,快速失败,避免雪崩。
- 指标监控(Micrometer) :自动上报
spring.ai.chat.calls.count、spring.ai.chat.calls.duration等指标,可接入 Prometheus/Grafana。 - 请求/响应日志 :在 DEBUG 级别下,自动打印完整的 HTTP 请求头、请求体、响应头、响应体。
- Token 统计 :自动计算并记录每次调用的
inputTokens和outputTokens,为成本核算提供依据。
这一切,都无需你写一行额外代码。你只需要在 application.yml 中开启:
spring:
ai:
client:
retry:
max-attempts: 3
circuit-breaker:
enabled: true
failure-threshold: 0.5
ChatClient 的设计理念,就是让开发者能像调用一个本地方法一样,去调用一个远程的、可能不稳定的、昂贵的 AI 服务。它把分布式系统的经典难题(容错、可观测、限流),变成了一个 YAML 配置项。
4.4 自动配置(Auto-Configuration):Spring Boot 的魔法之源
Spring AI 的所有“开箱即用”体验,都源于其精妙的自动配置机制。以 OllamaAutoConfiguration 为例,它的源码逻辑大致如下:
@Configuration
@ConditionalOnClass({OllamaChatModel.class, OllamaEmbeddingModel.class})
@ConditionalOnProperty(prefix = "spring.ai.ollama", name = "base-url")
public class OllamaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ChatModel ollamaChatModel(OllamaOptions options) {
return new OllamaChatModel(options);
}
@Bean
@ConditionalOnMissingBean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.build();
}
}
这段代码的意思是:
- 如果类路径下有
OllamaChatModel类(即spring-ai-ollama-spring-boot-starter依赖已引入); - 并且
application.yml中配置了spring.ai.ollama.base-url; - 那么,就自动创建一个
ChatModelBean 和一个ChatClientBean。
@ConditionalOnMissingBean 是关键,它保证了你随时可以自己定义一个 ChatModel Bean 来覆盖默认实现,实现了完美的可扩展性。
理解了自动配置,你就明白了 Spring AI 的“灵魂”:它不是一个孤立的框架,而是 Spring Boot 生态的有机组成部分。它的所有能力,都建立在 Spring Boot 的约定之上。你学到的,不仅是如何调用 AI,更是如何在一个现代化的 Java 工程中,优雅地集成任何一种外部服务。
5. 从入门到进阶:常见问题排查与性能调优实战
跑通 Demo 只是万里长征第一步。在真实项目中,你会立刻撞上一系列“意料之外,情理之中”的问题。这些问题,往往不会出现在官方文档的 Hello World 里,但却是每个 Spring AI 使用者必经的“成人礼”。下面,我将结合自己踩过的坑,为你梳理一套完整的排错与调优指南。
5.1 问题排查链路:从“404 Not Found”到“模型未响应”
现象 :启动应用后,调用 /api/chat/sync 接口,返回 404 Not Found 或 500 Internal Server Error 。
排查链路 (严格按照此顺序):
- 确认 Ollama 服务状态 :在终端执行
ollama list。如果没有任何输出,说明 Ollama 进程未启动。执行ollama serve启动服务守护进程。 - 确认模型已下载 :
ollama list的输出中,qwen2:7b的STATUS列必须是ok。如果是pulling,说明还在下载中,需等待;如果是error,则可能是网络问题,需检查镜像源配置。 - 确认 Spring Boot 日志 :查看控制台启动日志,搜索关键词
OllamaAutoConfiguration。如果看到Skipping auto-configuration,说明spring.ai.ollama.base-url配置有误或缺失。 - 验证网络连通性 :在 Spring Boot 应用所在机器上,执行
curl -v http://localhost:11434/api/tags。如果返回Connection refused,说明 Ollama 没有监听localhost:11434,检查 Ollama 的启动日志,或尝试ollama serve --host 0.0.0.0:11434。 - 启用 DEBUG 日志 :在
application.yml中添加logging.level.org.springframework.ai: DEBUG,然后再次调用接口。日志中会清晰打印出 Spring AI 发出的 HTTP 请求 URL、Headers 和 Body,以及收到的响应。这是定位问题的“黄金日志”。
注意:Windows 用户常遇到
localhost解析问题。如果curl http://localhost:11434失败,但curl http://127.0.0.1:11434成功,请将application.yml中的base-url改为http://127.0.0.1:11434。
5.2 性能瓶颈分析:为什么响应慢?Token 消耗高?
现象 :AI 响应时间长达 10 秒以上,或 inputTokens 消耗远超预期。
根因分析与优化 :
- 模型本身性能 :Qwen2:7b 在 CPU 上推理速度较慢。优化方案:
ollama run qwen2:7b-q4_k_m(量化版,速度提升 2-3 倍);或ollama run qwen2:7b-cuda(如果你有 NVIDIA GPU,需先安装 CUDA 驱动)。 - Prompt 过长 :
inputTokens消耗高,通常是因为你把大量无关的上下文(如整个 HTML 页面、冗长的日志)塞进了UserMessage。优化方案:在ChatService中,对userQuery进行预处理,用正则或 NLP 库提取核心问题,丢弃噪声。 - 网络延迟 :Spring Boot 应用与 Ollama 不在同一台机器。优化方案:将两者部署在同一宿主机,或使用 Docker Compose 统一编排,通过
host.docker.internal访问。
实测对比数据(Mac M1 Pro) :
| 模型 | 首 Token 延迟 | 完整响应时间 | inputTokens (100字查询) |
|---|---|---|---|
qwen2:7b | 2.1s | 8.7s | 142 |
qwen2:7b-q4_k_m | 0.8s | 3.2s | 142 |
llama3:8b-q4_k_m | 1.2s | 4.5s | 138 |
可以看到,量化对性能提升巨大,且几乎不损失精度。这是生产环境的必备选项。
5.3 流式响应失效:为什么前端收不到 data: 事件?
现象 : curl -N http://localhost:8080/api/chat/stream?query=xxx 没有输出,或只有最后一行。
根本原因与修复 :
- Spring WebFlux 配置缺失 :
application.yml中必须有server.reactive.max-http-header-size: 65536。否则,Ollama 的 SSE 响应头(如Content-Type: text/event-stream)可能被截断。 - 前端未正确处理 SSE :浏览器原生
EventSource对跨域敏感。如果你的前端在http://localhost:3000,而后端在http://localhost:8080,需要在ChatController上添加@CrossOrigin注解:@CrossOrigin(origins = "http://localhost:3000") @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamAsk(@RequestParam String query) { return chatService.askStream(query); } - IDEA/VSCode 内置终端缓冲 :某些 IDE 的终端会缓冲流式输出。务必使用系统原生终端(Terminal.app, PowerShell, bash)进行测试。
5.4 生产就绪 Checklist:从 Demo 到上线
一个能跑通的 Demo,离生产环境还有十万八千里。以下是我在多个项目中总结的上线前必检清单:
- [ ] 模型版本固化 :
application.yml中的spring.ai.ollama.chat.model必须指定完整标签,如qwen2:7b-q4_k_m,而非qwen2:latest。后者会导致不同环境拉取不同模型,结果不可控。 - [ ] 错误处理兜底 :在
ChatService的ask()方法中,必须用try-catch捕获RuntimeException(Spring AI 的所有异常都继承于此),并返回友好的错误信息,而非让整个服务崩溃。 - [ ] Token 用量监控告警 :通过 Micrometer 将
spring.ai.chat.calls.input-tokens.total指标接入 Grafana,并设置阈值告警(如单次调用 > 5000 tokens)。 - [ ] 请求限流 :使用 Spring Cloud Gateway 或 Resilience4j,对
/api/chat/*接口添加 QPS 限流,防止恶意刷量耗尽模型资源。 - [ ] 敏感信息脱敏 :在
DEBUG日志中,UserMessage的内容会被完整打印。上线前,必须在日志配置中,对UserMessage.getContent()进行脱敏(如***替换中间字符)。
最后一个经验:永远不要相信“它应该能工作”。在交付给 QA 之前,自己用
curl、Postman、浏览器三种方式,分别测试同步、流式、错误场景(如空 query、超长 query、非法字符),并记录下每一步的响应时间和返回内容。这份《冒烟测试报告》,是你对自己代码最大的尊重。
6. 超越 Hello World:Spring AI 的真实应用场景与演进路径
当你的第一个 qwen2:7b 对话应用稳定运行一周后,真正的挑战才刚刚开始。Spring AI 的价值,绝不仅限于“让 Java 程序员也能调用大模型”。它的设计初衷,是成为企业级 AI 应用的“操作系统内核”。下面,我将基于真实项目经验,为你勾勒出一条从入门到专家的演进路径。
6.1 场景一:智能客服知识库(RAG)
这是 Spring AI 最成熟、落地最快的场景。传统客服系统,知识库更新滞后,FAQ 覆盖率低。而 RAG(Retrieval-Augmented Generation)能让你的客服机器人,实时“阅读”最新的产品文档、工单记录、会议纪要,然后给出精准回答。
核心技术栈 :
-
EmbeddingModel:
更多推荐
所有评论(0)