1. 项目概述
最近在探索如何将大语言模型(LLM)集成到Java应用中,发现Spring AI这个框架非常有意思。它让我们能够用熟悉的Spring Boot方式对接各种大模型,包括本地部署的开源模型。本文将分享我如何通过Ollama在本地部署Qwen3:4B模型,并用Spring Boot 3.x实现完整对接的全过程。
这个方案有几个显著优势:
- 完全本地运行:数据不出本地,适合对隐私和安全性要求高的场景
- 轻量级部署:Qwen3:4B模型对硬件要求相对友好,消费级显卡就能跑
- Java生态集成:Spring开发者可以用熟悉的工具链进行开发
- 功能全面:支持同步/异步调用、对话记忆、工具调用等高级功能
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备
2.1 硬件要求
要顺利运行Qwen3:4B模型,建议满足以下硬件配置:
| 组件 | 最低要求 | 推荐配置 |
|---|---|---|
| CPU | 4核 | 8核及以上 |
| 内存 | 16GB | 32GB |
| GPU | 无(纯CPU推理) | NVIDIA显卡(8GB显存+) |
| 存储 | 10GB可用空间 | 20GB SSD |
实测发现:在MacBook Pro M1 Pro(16GB内存)上可以流畅运行4B模型,响应速度约3-5秒/请求。如果有NVIDIA显卡,建议使用CUDA加速。
2.2 软件依赖
需要提前安装以下软件:
- Ollama:模型运行环境
- Java 17+:Spring Boot 3.x要求
- Maven 3.6+:项目构建工具
- IDE:IntelliJ IDEA或VS Code
3. 本地部署Qwen3:4B模型
3.1 安装Ollama
Ollama是一个开源的本地大模型运行环境,支持macOS、Windows和Linux。安装步骤如下:
- 访问Ollama官网下载对应系统的安装包
- 运行安装程序(Windows下是.exe,macOS是.dmg)
- 安装完成后,终端输入
ollama --version验证是否安装成功
3.2 下载模型
Qwen3是阿里巴巴开源的千问大模型第三代,4B参数版本在效果和性能间取得了不错平衡。下载命令:
bash复制ollama pull qwen3:4b
下载过程会显示进度条,模型大小约2.5GB。国内用户如果下载慢,可以尝试配置镜像源:
bash复制export OLLAMA_HOST=mirror.ghproxy.com
ollama pull qwen3:4b
3.3 运行测试
启动模型交互界面:
bash复制ollama run qwen3:4b
输入简单问题测试,如"你好",应该能看到类似输出:
code复制>>> 你好!
你好!我是Qwen3,一个由阿里巴巴开发的人工智能助手。有什么我可以帮你的吗?
按Ctrl+D退出交互模式。
4. Spring Boot项目搭建
4.1 初始化项目
使用Spring Initializr创建项目,关键配置:
- Project:Maven
- Language:Java
- Spring Boot:3.2.5
- Packaging:Jar
- Java:17
依赖选择:
- Spring Web
- Spring AI Ollama Starter
或者直接使用curl命令创建:
bash复制curl https://start.spring.io/starter.tgz \
-d type=maven-project \
-d language=java \
-d bootVersion=3.2.5 \
-d baseDir=spring-ai-demo \
-d groupId=com.example \
-d artifactId=ai-demo \
-d name=ai-demo \
-d packageName=com.example.ai \
-d packaging=jar \
-d javaVersion=17 \
-d dependencies=web,ai-ollama \
| tar -xzvf -
4.2 配置POM文件
确保pom.xml包含以下关键依赖:
xml复制<properties>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<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>
4.3 基础配置
application.yml配置:
yaml复制spring:
ai:
ollama:
base-url: http://localhost:11434 # Ollama默认端口
chat:
model: qwen3:4b # 使用的模型名称
5. 基础对话功能实现
5.1 创建ChatClient
配置类中定义ChatClient Bean:
java复制@Configuration
public class OllamaConfig {
@Bean
public ChatClient chatClient(OllamaChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个专业的AI助手,回答要简洁准确")
.build();
}
}
5.2 实现REST接口
创建控制器提供对话接口:
java复制@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private ChatClient chatClient;
// 同步响应
@GetMapping("/sync")
public String chatSync(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
// 流式响应
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
}
5.3 接口测试
使用curl测试同步接口:
bash复制curl "http://localhost:8080/api/chat/sync?message=你好"
测试流式接口:
bash复制curl "http://localhost:8080/api/chat/stream?message=介绍一下Spring框架"
流式响应会逐步返回结果,适合长文本生成场景。
6. 高级功能实现
6.1 对话记忆功能
默认情况下,每次请求都是独立的。要实现多轮对话,需要配置ChatMemory:
java复制@Configuration
public class OllamaConfig {
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(OllamaChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个专业的AI助手")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory)
)
.build();
}
}
现在对话会保持上下文:
code复制用户:我叫张三
AI:你好张三!
用户:你知道我叫什么吗?
AI:你刚才告诉我你叫张三。
6.2 工具调用功能
让大模型可以调用外部工具,比如查询天气:
- 定义工具类:
java复制@Component
public class WeatherTools {
@Tool(name = "getWeather", description = "获取指定城市的天气情况")
public String getWeather(
@ToolParam(description = "城市名称,如'北京'") String city) {
// 实际应该调用天气API,这里模拟返回
return city + ":晴天,25℃";
}
}
- 配置ChatClient使用工具:
java复制@Bean
public ChatClient chatClient(OllamaChatModel chatModel,
ChatMemory chatMemory,
WeatherTools weatherTools) {
return ChatClient.builder(chatModel)
.defaultSystem("""
你可以使用以下工具:
- getWeather: 查询城市天气
当用户询问天气时,请使用这个工具""")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory)
)
.defaultTools(weatherTools)
.build();
}
- 测试效果:
code复制用户:上海天气怎么样?
AI:正在查询上海天气...
上海:晴天,25℃
6.3 基于PDF的问答
实现从PDF提取内容并回答问题的功能:
- 添加PDF依赖:
xml复制<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
- 配置向量存储:
java复制@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel)
.build();
}
- 实现PDF处理逻辑:
java复制@Service
public class PdfService {
@Autowired
private VectorStore vectorStore;
public void loadPdf(String filePath) {
Resource resource = new FileSystemResource(filePath);
PdfDocumentReader reader = new PdfDocumentReader(resource);
vectorStore.add(reader.read());
}
public String queryPdf(String question) {
List<Document> docs = vectorStore.similaritySearch(question);
if(docs.isEmpty()) {
return "未找到相关信息";
}
String context = docs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
return chatClient.prompt()
.system("根据以下内容回答问题:" + context)
.user(question)
.call()
.content();
}
}
7. 性能优化建议
7.1 模型层面
-
量化模型:使用GGUF量化版模型减少内存占用
bash复制
ollama pull qwen3:4b-q4_0 -
调整参数:在application.yml中配置生成参数
yaml复制spring: ai: ollama: chat: options: temperature: 0.7 # 控制随机性 num_ctx: 2048 # 上下文长度
7.2 应用层面
- 缓存机制:对常见问题答案进行缓存
- 异步处理:耗时操作使用@Async
- 连接池:配置Ollama客户端连接池
7.3 部署层面
-
使用GPU加速:确保Ollama使用CUDA
bash复制
OLLAMA_NO_CUDA=0 ollama serve -
多实例负载均衡:当并发量高时考虑
8. 常见问题排查
8.1 模型相关问题
问题:模型下载失败
- 检查网络连接
- 尝试更换镜像源
- 手动下载模型文件
问题:响应速度慢
- 确认是否使用了GPU
- 降低num_ctx参数值
- 尝试更小的模型版本
8.2 Spring AI相关问题
问题:无法连接到Ollama
- 确认Ollama服务已启动
- 检查base-url配置
- 测试端口连通性:
curl http://localhost:11434
问题:流式响应不工作
- 确保produces = MediaType.TEXT_EVENT_STREAM_VALUE
- 前端需要使用EventSource接收
- 检查是否有拦截器修改了响应
8.3 功能相关问题
问题:对话记忆失效
- 确保每次请求使用相同session
- 检查ChatMemory是否配置正确
- 避免服务重启(内存存储会丢失)
问题:PDF内容提取乱码
- 确认PDF不是扫描件
- 尝试不同PDF解析库
- 检查文件编码格式
9. 生产环境建议
- 持久化存储:使用Redis或数据库存储对话历史
- 监控指标:添加Prometheus监控
- 限流措施:防止API被滥用
- 日志记录:记录所有AI交互
- 备份策略:定期备份向量存储
Redis配置示例:
yaml复制spring:
ai:
vectorstore:
redis:
index-name: doc-vector
prefix: vec:
distance-metric: COSINE
对应的Java配置:
java复制@Bean
public VectorStore vectorStore(RedisConnectionFactory connectionFactory,
EmbeddingModel embeddingModel) {
return RedisVectorStore.builder()
.withConnectionFactory(connectionFactory)
.withEmbeddingModel(embeddingModel)
.withIndexName("doc-vector")
.withPrefix("vec:")
.build();
}
10. 扩展思路
- 多模态支持:接入图片、语音处理
- 领域微调:使用LoRA对模型进行微调
- 混合模型:根据场景切换不同模型
- 知识图谱:结合结构化数据
- 自动化测试:构建AI测试用例
一个微调配置示例:
java复制@Bean
public ChatClient specializedChatClient(OllamaChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("""
你是一个法律专业助手,回答要严谨准确。
如果不知道答案,请明确表示不清楚,不要编造信息。""")
.build();
}
通过这个项目,我深刻体会到Spring AI极大地降低了Java开发者使用大语言模型的门槛。相比直接调用HTTP API,Spring AI提供了更符合Java习惯的抽象层,让开发者能专注于业务逻辑而非底层通信细节。特别是在企业环境中,与Spring Security、Spring Data等组件的无缝集成更是显著优势。
