【Spring AI 实战】Spring AI 接入指南:Ollama 本地模型、API 代理与混合路由
Spring AI 接入指南:Ollama 本地模型、API 代理与混合路由
本篇为补充专题 02b,深入讲解三类本地/低成本接入方案的完整配置与代码实战。
1. 为什么需要本地模型接入?
在生产环境中,调用 OpenAI / Claude API 存在三大痛点:
| 痛点 | 影响 |
|---|---|
| 数据隐私 | 用户数据不能离开企业内网,合规审计严格 |
| 网络延迟 | 国内访问海外 API 往返 >300ms,体验差 |
| 成本压力 | GPT-4o 约 $2.5/1M tokens,中小项目难以承受 |
Spring AI 提供了三大接入路径:本地 Ollama、DashScope API(国内直连低价)、API 代理(如 OneAPI)统一管理多模型,本篇逐一拆解。
2. Ollama:本地大模型零成本运行
2.1 安装与模型下载
macOS / Linux 一行安装:
# 一键安装
curl -fsSL https://ollama.com/install.sh | sh
# Windows 直接下载安装包:https://ollama.com/download
拉取常用模型(根据显存选型):
# 通义千问 7B(推荐入门,int4 量化约 4GB 显存)
ollama pull qwen2.5:7b
# Llama 3.1 8B(通用能力强)
ollama pull llama3.1:8b
# Mistral 7B(指令遵循优秀)
ollama pull mistral
# Phi-3.5(微软小模型,CPU 可跑)
ollama pull phi3.5:latest
# bge-m3 嵌入模型(知识库必备)
ollama pull nomic-embed-text
显存参考表:
| 模型 | 参数量级 | FP16 显存 | int4 量化显存 | CPU 可跑 |
|---|---|---|---|---|
| Phi-3.5 | 3.8B | ~8GB | ~2GB | ✅ |
| Qwen2.5 7B | 7B | ~14GB | ~4GB | ⚠️ 慢 |
| Llama 3.1 8B | 8B | ~16GB | ~5GB | ❌ |
| Qwen2.5 14B | 14B | ~28GB | ~8GB | ❌ |
| Qwen2.5 32B | 32B | ~64GB | ~18GB | ❌ |
验证安装:
ollama list
# NAME ID SIZE MODIFIED
# qwen2.5:7b a3........ 4.7GB 5 minutes ago
# nomic-embed-text 0a........ 274MB 2 hours ago
ollama run qwen2.5:7b "用一句话解释什么是 RAG"
# RAG(检索增强生成)是通过检索外部知识库来增强大语言模型生成能力的技术
2.2 Modelfile:自定义模型行为
Ollama 支持用 Modelfile 配置文件深度定制模型行为,无需重新训练。
场景一:固定系统提示词,打造专属客服人格
# 创建文件:./modelfiles/rag-assistant
FROM qwen2.5:7b
PARAMETER temperature 0.3
PARAMETER top_p 0.85
PARAMETER num_ctx 8192
TEMPLATE """
{% for message in messages %}
{{ '[BOT]' if message['role'] == 'assistant' else '[USER]' }}
{{ message['content'] }}
{% endfor %}
[BOT]
"""
SYSTEM """
你是一个专业的企业知识库助手。回答必须:
1. 基于提供的上下文信息回答,不要编造
2. 如果上下文没有相关信息,诚实告知用户
3. 回答简洁有条理,优先使用列表格式
"""
场景二:流式输出配置(工程化必须)
# ./modelfiles/fast-response
FROM qwen2.5:7b
PARAMETER temperature 0.7
PARAMETER num_predict 512 # 最大生成长度
PARAMETER stop "[DONE]" # 停止词
PARAMETER num_ctx 4096 # 上下文窗口
创建并测试自定义模型:
ollama create rag-assistant -f ./modelfiles/rag-assistant
ollama run rag-assistant "Spring Boot 如何集成 Redis?"
2.3 Spring AI + Ollama 集成
Maven 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
application.yml 配置:
spring:
application:
name: spring-ai-ollama-demo
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen2.5:7b
temperature: 0.7
numCtx: 8192
keepAlive: 5m
# 嵌入模型单独配置(知识库用)
rag:
embedding:
model: nomic-embed-text
基础对话代码:
@RestController
@RequestMapping("/ai")
@RequiredArgsConstructor
public class OllamaChatController {
private final ChatClient chatClient;
@GetMapping("/chat")
public String chat(@RequestParam(defaultValue = "你好,介绍一下你自己") String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
// 流式响应:适合前端 SSE 推送
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.stream()
.content();
}
}
知识库嵌入配置:
@Configuration
public class OllamaEmbeddingConfig {
@Bean
public OllamaEmbeddingModel embeddingModel(OllamaApi api) {
return new OllamaEmbeddingModel(api);
}
@Bean
public VectorStore vectorStore(OllamaEmbeddingModel embeddingModel) {
// 使用 SimpleVectorStore(轻量级,生产推荐 PGVector/Chroma)
return new SimpleVectorStore(embeddingModel);
}
}
导入文档到向量库:
@Service
@RequiredArgsConstructor
public class DocumentIngestionService {
private final OllamaEmbeddingModel embeddingModel;
private final VectorStore vectorStore;
public void ingestDocument(Path pdfPath) throws Exception {
// PDF 解析
PdfDocumentReader pdfReader = new PdfDocumentReader(pdfPath.toUri().toString());
var document = pdfReader.get();
// 文本分割(控制每段 token 数量)
var splitter = new TokenTextSplitter(800, 200, 10, 5000, true);
var chunks = splitter.apply(List.of(document));
// 写入向量库
vectorStore.add(chunks);
System.out.println("✅ 已导入 " + chunks.size() + " 个文本块到向量库");
}
public List<Document> search(String query, int topK) {
return vectorStore.similaritySearch(
SearchRequest.query(query).withTopK(topK)
);
}
}
3. DashScope API:国内低价直连通义千问
3.1 为什么选 DashScope?
| 对比项 | OpenAI API | DashScope |
|---|---|---|
| 网络 | 需代理,延迟高 | 国内直连 <50ms |
| 价格 | GPT-4o $2.5/1M | Qwen-Turbo ¥0.002/1K tokens |
| 政策 | 存在封号风险 | 阿里云合规保障 |
| 开源模型 | ❌ | ✅ 支持开源模型 API |
获取 API Key:
- 阿里云百炼平台 → 开通服务 → 创建 API Key
- 或阿里云 DashScope 控制台
3.2 Spring AI DashScope 配置
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-alibaba-spring-boot-starter</artifactId>
</dependency>
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY} # 推荐从环境变量读取
base-url: https://dashscope.aliyuncs.com
chat:
options:
model: qwen-turbo
# qwen-plus / qwen-max 也可选
temperature: 0.8
max-tokens: 2000
@RestController
@RequestMapping("/ai")
public class DashScopeController {
private final ChatClient chatClient;
public DashScopeController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个 Java 技术专家,用简洁专业的语言回答问题")
.build();
}
@PostMapping("/ask")
public Map<String, Object> ask(@RequestBody Map<String, String> request) {
String question = request.get("question");
String answer = chatClient.prompt()
.user(question)
.call()
.content();
return Map.of("question", question, "answer", answer);
}
}
3.3 DashScope 嵌入模型(知识库必备)
spring:
ai:
dashscope:
embedding:
options:
model: text-embedding-v3 # 阿里自研嵌入模型,1536 维
// 配合 RAG 使用
@Service
public class RAGService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public String ragQuery(String question) {
// Step 1: 检索相关上下文
List<Document> contextDocs = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(5)
);
String context = contextDocs.stream()
.map(Document::getText)
.collect(Collectors.joining("\n\n"));
// Step 2: 构造有上下文的 Prompt
String prompt = String.format("""
基于以下参考资料回答用户问题。如果资料中没有相关信息,请如实说明。
参考资料:
%s
用户问题:%s
""", context, question);
// Step 3: 调用通义千问
return chatClient.prompt()
.user(prompt)
.call()
.content();
}
}
4. API 代理:OneAPI 统一管理多模型
4.1 为什么需要 API 代理层?
在企业场景中,通常需要同时接入多个 AI 供应商:
业务代码
↓ 统一调用
OneAPI(本地代理)
├── OpenAI(主力)
├── Claude(备选)
├── DashScope(国内备选)
└── Ollama(内网补充)
核心价值:
- 统一接口:业务代码只对接一个端点,换模型改配置不碰代码
- 负载均衡:多渠道 token 轮询,降低单渠道限流风险
- 用量统计:一个面板看所有模型的调用量和费用
- 密钥管理:API Key 集中存储,不分散在各个服务中
4.2 OneAPI 快速部署
# Docker 一键部署
docker run -d \
--name oneapi \
-p 3000:3000 \
-v ~/oneapi/data:/data \
--restart always \
ghcr.io/songquanpeng/one-api:latest
# 访问 http://localhost:3000
# 默认账号:root 默认密码:123456
配置渠道(以 OpenAI 为例):
- 登录管理后台 → 渠道管理 → 添加渠道
- 类型选
OpenAI,填入 API Key 和 base-url(国内需代理) - 模型列表填入:
gpt-4o,gpt-4o-mini,gpt-3.5-turbo,gpt-4-turbo - 额度设置合理阈值
配置渠道(以 DashScope 为例):
- 类型选
自定义,base-url 填:https://dashscope.aliyuncs.com/compatible-mode/v1 - API Key 填阿里云 DashScope Key
- 模型列表:
qwen-turbo,qwen-plus,qwen-max,text-embedding-v3
4.3 Spring AI 通过 OneAPI 接入
spring:
ai:
openai:
base-url: http://localhost:3000/v1 # OneAPI 代理地址
api-key: ${ONEAPI_API_KEY} # OneAPI 的 API Key(管理后台→个人API密钥)
chat:
options:
model: gpt-4o # 可动态切换:gpt-4o / qwen-turbo / claude-3-5-sonnet
效果:业务代码完全不用改,只需改配置中的模型名称!
// 同一个 ChatClient,换模型只需改 yml
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
// 配置统一从 yml 读取,这里零改动
this.chatClient = builder.build();
}
}
4.4 模型切换策略实战
@Service
public class SmartChatService {
private final OpenAiApi openAiApi;
// 简单路由:根据输入长度选择模型
public String chatWithRouting(String input) {
String model;
if (input.length() < 200) {
// 简单问题用小模型,省钱
model = "gpt-4o-mini";
} else if (input.length() < 2000) {
// 中等复杂度用主力模型
model = "gpt-4o";
} else {
// 复杂任务用最强模型
model = "gpt-4-turbo";
}
return ChatClient.builder(new OpenAiChatModel(openAiApi, OpenAiChatOptions.builder()
.withModel(model)
.withTemperature(0.7)
.build()))
.build()
.prompt()
.user(input)
.call()
.content();
}
// 熔断降级:主模型失败自动切换备选
public String chatWithFallback(String input) {
try {
return primaryChat(input);
} catch (Exception e) {
System.err.println("主模型调用失败,切换备选:" + e.getMessage());
return fallbackChat(input);
}
}
private String primaryChat(String input) {
return ChatClient.builder(new DashScopeChatModel(DashScopeApi.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build(),
DashScopeChatOptions.builder()
.withModel("qwen-plus")
.build()))
.build()
.prompt()
.user(input)
.call()
.content();
}
private String fallbackChat(String input) {
return ChatClient.builder(new OpenAiApi("http://localhost:3000/v1",
System.getenv("ONEAPI_API_KEY")))
.build()
.prompt()
.user(input)
.call()
.content();
}
}
5. 混合路由:本地 + 云端智能调度
在实际生产中,最优方案是分层使用:
用户请求
│
▼
┌─────────────────────────────┐
│ 路由层(Spring AI Router) │
└─────────────────────────────┘
│
├── 简单/隐私数据 ──→ Ollama 本地(零成本,极速)
│
├── 复杂推理 ──→ DashScope(国内低价,高性能)
│
└── 最高质量要求 ──→ OpenAI / Claude(最强能力)
5.1 路由策略设计
@Component
public class ModelRouter {
private final OllamaChatModel localModel;
private final DashScopeChatModel dashscopeModel;
private final OpenAiApi openaiApi;
/**
* 根据请求特征选择最优模型
* @param input 用户输入
* @param mode 业务场景:SIMPLE / COMPLEX / PREMIUM
*/
public String route(String input, String mode) {
return switch (mode) {
case "SIMPLE" -> callLocal(input);
case "COMPLEX" -> callDashScope(input);
case "PREMIUM" -> callOpenAI(input);
default -> callDashScope(input); // 默认用 DashScope
};
}
private String callLocal(String input) {
return ChatClient.builder(localModel)
.build()
.prompt().user(input).call().content();
}
private String callDashScope(String input) {
return ChatClient.builder(dashscopeModel)
.build()
.prompt().user(input).call().content();
}
private String callOpenAI(String input) {
return ChatClient.builder(new OpenAiChatModel(openaiApi,
OpenAiChatOptions.builder().withModel("gpt-4o").build()))
.build()
.prompt().user(input).call().content();
}
}
5.2 根据隐私程度自动路由
public String privacyAwareRoute(String input, boolean containsUserData) {
if (containsUserData) {
// 敏感数据强制走本地,避免合规风险
return "🚫 包含用户隐私数据,已路由至本地模型处理:" + callLocal(input);
} else {
// 非敏感数据走云端,享受最强能力
return "📤 已路由至云端高性能模型:" + callDashScope(input);
}
}
6. 性能对比实测
测试环境:MacBook M2 Pro(32GB),网络为上海家宽
| 方案 | 模型 | 首次响应 | 成本 | 适合场景 |
|---|---|---|---|---|
| Ollama | Qwen2.5 7B int4 | ~1.2s | ¥0 | 内网/隐私/开发 |
| Ollama | Llama 3.1 8B int4 | ~1.8s | ¥0 | 通用对话 |
| DashScope | Qwen-Turbo | ~0.8s | ¥0.002/1K | 国内生产主力 |
| DashScope | Qwen-Plus | ~1.2s | ¥0.02/1K | 高质量需求 |
| OneAPI→OpenAI | GPT-4o-mini | ~1.5s | ~$0.15/1M | 复杂推理 |
7. 选型决策树
输入类型是什么?
│
├─ 包含敏感/隐私数据?
│ └─ ✅ 是 ──→ Ollama 本地(qwen2.5:7b)
│
├─ 国内生产环境,低成本优先?
│ └─ ✅ 是 ──→ DashScope Qwen-Turbo/Plus
│
├─ 需要最强推理能力(代码/复杂分析)?
│ └─ ✅ 是 ──→ OpenAI GPT-4o / Claude 3.5
│
└─ 多模型混合使用?
└─ ✅ 是 ──→ OneAPI + 智能路由层
8. 常见问题
Q1: Ollama 模型更新后需要重启吗?
不需要。ollama pull 会自动更新。正在运行的模型不受影响,下次调用时自动使用新版本。
Q2: OneAPI 和直接调 API 的性能差异?
OneAPI 多一跳 HTTP 转发,延迟增加约 5-20ms。对于 LLM 的 token 生成时间(通常数百毫秒~数秒),这个开销可以忽略。
Q3: Ollama 显存不够怎么办?
- 换用更小的量化版本(如 qwen2.5:3b)
- 使用 Phi-3.5(3.8B,CPU 可跑)
- 调低 num_ctx 参数减少 KV Cache 显存占用
Q4: DashScope 和 OpenAI API 语法兼容吗?
不完全兼容。Spring AI 的 spring-ai-alibaba 提供了独立适配器,需要单独引入依赖并配置,不能和 spring-ai-openai 混用同一个 ChatClient。
Q5: 如何监控各模型的调用量和费用?
OneAPI 自带统计面板,可以按渠道、模型查看调用次数和预估费用。也可接入 Prometheus + Grafana 展示自定义指标。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)