Spring Boot 接入大模型:从配置到实战的完整指南
我们的电商 SaaS 平台已经配置了 DeepSeek、智谱、豆包、通义千问等 10+ AI 平台的 API Key,但 AI 模块尚未接入。本文从零开始,记录如何在 Spring Boot 项目中搭建 AI 能力------从依赖引入、多模型路由、对话功能到知识库 RAG 的完整流程。
一、现状与选型
1.1 我们已有的配置
项目 application.yaml 中已经配好了各平台的密钥:
| 平台 | 模型 | 用途 |
|---|---|---|
| DeepSeek | deepseek-chat | 通用对话、代码生成 |
| 字节豆包 | doubao-1-5-lite-32k | 长文本处理 |
| 腾讯混元 | hunyuan-turbo | 中文理解 |
| 硅基流动 | DeepSeek-R1-Distill-Qwen-7B | 推理任务 |
| 讯飞星火 | generalv3.5 | 教育场景 |
| 通义千问 | dashscope | 多模态 |
| Midjourney | mj-relax | 图片生成 |
1.2 技术选型

选型理由:
- Spring AI:Spring 官方出品,统一了 ChatCompletion、ImageGeneration、VectorStore 的 API,后续换模型不需要改业务代码
- 多模型路由:不同场景用不同模型,比如对话用 DeepSeek,图片用 Midjourney,长文本用豆包
二、第一步:引入依赖
2.1 BOM 版本管理
在 ymh-dependencies/pom.xml 中添加:
xml
<properties>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring AI BOM -->
<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>
2.2 Server 模块引入
在 ymh-server/pom.xml 中添加 AI 相关依赖:
xml
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- 通义千问适配 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-dashscope-spring-boot-starter</artifactId>
</dependency>
<!-- 智谱适配 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
</dependency>
<!-- Redis 向量存储(知识库用) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-vector-store-redis-spring-boot-starter</artifactId>
</dependency>
<!-- Spring AI 测试 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
三、第二步:多模型配置
3.1 application.yaml 配置
yaml
spring:
ai:
# ========== 通用配置 ==========
openai:
api-key: ${AI_OPENAI_API_KEY:sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17}
base-url: https://api.gptsapi.net
chat:
options:
model: gpt-4o-mini
temperature: 0.7
max-tokens: 2048
# 通义千问
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY:sk-71800982914041848008480000000000}
chat:
options:
model: qwen-turbo
temperature: 0.7
# 智谱 AI
zhipuai:
api-key: ${AI_ZHIPUAI_API_KEY:32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs}
chat:
options:
model: glm-4-flash
temperature: 0.7
# 向量存储(Redis)
vectorstore:
redis:
initialize-schema: true
index: knowledge_index
prefix: "knowledge_segment:"
3.2 自定义模型配置(DeepSeek/豆包等非标准 OpenAI 兼容接口)
DeepSeek、豆包等平台有自己的 SDK,不走 Spring AI 的标准适配。我们在项目原有的 yudao.ai 命名空间下封装:
yaml
yudao:
ai:
deep-seek:
enable: true
api-key: ${AI_DEEPSEEK_API_KEY}
model: deepseek-chat
base-url: https://api.deepseek.com/v1
doubao:
enable: true
api-key: ${AI_DOUBAO_API_KEY}
model: doubao-1-5-lite-32k-250115
base-url: https://ark.cn-beijing.volcesapis.com
四、第三步:实现模型路由
这是最关键的一环------不同场景自动选择最合适的模型。
4.1 模型路由接口定义
less
/**
* AI 模型路由策略枚举
*/
@Getter
@AllArgsConstructor
public enum AiModelRoute {
/** 通用对话 - 速度快、成本低 */
CHAT_FAST("deepseek", "deepseek-chat"),
/** 复杂推理 - R1 推理能力强 */
CHAT_REASONING("siliconflow", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"),
/** 长文本 - 支持 32K token */
LONG_TEXT("doubao", "doubao-1-5-lite-32k-250115"),
/** 代码生成 - DeepSeek Coder 擅长 */
CODE_GENERATION("deepseek", "deepseek-coder"),
/** 中文理解 - 混元对中文优化好 */
CHINESE_UNDERSTANDING("hunyuan", "hunyuan-turbo"),
/** 图片生成 - Midjourney */
IMAGE_GENERATION("midjourney", "mj-relax"),
/** 默认 - 通用对话 */
DEFAULT("openai", "gpt-4o-mini");
private final String provider;
private final String modelName;
}
4.2 智能路由服务

scss
@Service
@Slf4j
public class AiModelRouter {
@Autowired
private ChatClient chatClientRegistry; // Spring AI 提供的 ChatClient 注册表
/**
* 根据任务类型自动选择模型并执行对话
*/
public String chat(AiModelRoute route, String userMessage) {
ChatModel chatModel = resolveChatModel(route);
if (chatModel == null) {
log.warn("未找到匹配的模型路由: {}", route);
return "暂时无法处理,请稍后重试";
}
ChatResponse response = chatModel.call(userMessage);
String result = response.getResult().getOutput().getContent();
log.info("路由 {} 完成,token 消耗: {}", route, response.getMetadata().getUsage());
return result;
}
/**
* 批量对话(用于知识库问答)
*/
public List<String> batchChat(AiModelRoute route, List<String> messages) {
ChatModel chatModel = resolveChatModel(route);
return messages.stream()
.map(chatModel::call)
.map(r -> r.getResult().getOutput().getContent())
.collect(Collectors.toList());
}
private ChatModel resolveChatModel(AiModelRoute route) {
// 从 Spring AI 的 ChatModel Bean 中按 provider 查找
Map<String, ChatModel> chatModels = chatClientRegistry.getChatModels();
return chatModels.getOrDefault(route.getProvider(), chatModels.get("default"));
}
}
4.3 对话 Controller
less
@RestController
@RequestMapping("/admin-api/ai/chat")
@Tag(name = "AI 对话管理")
public class AiChatController {
@Autowired
private AiModelRouter aiModelRouter;
@PostMapping("/completion")
public CommonResult<AiChatRespDTO> completion(@RequestBody @Valid AiChatReqDTO req) {
// 1. 校验用户是否有权限调用 AI
Long userId = SecurityFrameworkUtils.getLoginUserId();
checkAiQuota(userId);
// 2. 根据场景选择模型
AiModelRoute route = resolveRoute(req.getScene());
// 3. 构建 Prompt(带系统提示词)
String prompt = buildPrompt(req);
// 4. 调用 AI
String reply = aiModelRouter.chat(route, prompt);
// 5. 记录对话日志
saveChatLog(userId, req, reply);
// 6. 扣减配额
deductAiQuota(userId);
return CommonResult.success(new AiChatRespDTO(reply));
}
private AiModelRoute resolveRoute(String scene) {
return switch (scene) {
case "code" -> AiModelRoute.CODE_GENERATION;
case "reasoning" -> AiModelRoute.CHAT_REASONING;
case "long-text" -> AiModelRoute.LONG_TEXT;
case "chinese" -> AiModelRoute.CHINESE_UNDERSTANDING;
default -> AiModelRoute.CHAT_FAST;
};
}
}
五、第四步:图片生成功能
5.1 ImageGeneration 配置
typescript
@Configuration
public class AiImageConfig {
@Bean
public ImageModel midjourneyImageModel() {
// Midjourney 通过自定义 HTTP 客户端实现
return new MidjourneyImageModel(
"https://api.holdai.top/mj",
System.getProperty("ai.midjourney.api-key")
);
}
@Bean
public ImageModel stableDiffusionImageModel() {
// Spring AI 原生支持 Stable Diffusion
return new OpenAiImageModel(openAiApi());
}
}
5.2 图片生成 Controller
less
@RestController
@RequestMapping("/admin-api/ai/image")
@Tag(name = "AI 图片生成")
public class AiImageController {
@Autowired
private ImageModel imageModel;
@PostMapping("/generate")
public CommonResult<AiImageRespDTO> generate(@RequestBody @Valid AiImageReqDTO req) {
// 构建生成请求
GenerationRequest request = GenerationRequest.builder()
.prompt(buildPrompt(req)) // 提示词
.model(req.getModel() ?? "dall-e-3")
.numberOfResults(req.getCount() ?? 1)
.quality(req.getQuality() ?? "standard")
.build();
// 执行生成
GenerationResult result = imageModel.call(request);
// 返回图片 URL 列表
List<String> urls = result.getResults().stream()
.map(GenerationResponseItem::getUrl)
.toList();
return CommonResult.success(new AiImageRespDTO(urls));
}
}
六、第五步:知识库 RAG(检索增强生成)
这是 AI 模块最有价值的场景------让大模型基于你的业务数据回答问题。
6.1 RAG 架构

markdown
用户提问
↓
┌──────────────────────────┐
│ 1. 问题向量化 │ ← 用 Embedding 模型把问题转成向量
│ ↓ │
│ 2. 向量相似度搜索 │ ← 在 Redis 向量库中找最相似的文档片段
│ ↓ │
│ 3. 拼接上下文 + 原问题 │
│ ↓ │
│ 4. 发送给 LLM 生成回答 │
└──────────────────────────┘
↓
返回答案
6.2 文档分片与向量化
scss
@Service
@Slf4j
public class AiKnowledgeService {
@Autowired
private VectorStore vectorStore; // Spring AI 的 VectorStore 接口
@Autowired
private EmbeddingModel embeddingModel;
/**
* 上传文档到知识库
*/
public void uploadDocument(Long documentId, MultipartFile file, String category) {
try {
// 1. 读取文档内容(支持 PDF/Word/TXT)
String content = readDocumentContent(file);
// 2. 文档分片(每片 500 字,重叠 50 字)
List<TextSegment> segments = TextSplitter.builder()
.chunkSize(500)
.chunkOverlap(50)
.build()
.split(content);
// 3. 为每个片段添加元数据
List<Document> documents = segments.stream()
.map(segment -> new Document(
segment.getText(),
new HashMap<String, Object>() {{
put("documentId", documentId);
put("category", category);
put("segmentIndex", segment.getIndex());
put("createdAt", Instant.now());
}}
))
.toList();
// 4. 存入向量库(自动调用 Embedding 模型生成向量)
vectorStore.add(documents);
log.info("文档上传成功: id={}, 分片数={}", documentId, documents.size());
} catch (Exception e) {
log.error("文档上传失败", e);
throw new BusinessException("文档上传失败: " + e.getMessage());
}
}
/**
* 知识库问答(RAG)
*/
public String qa(String question, String category) {
// 1. 将问题转为向量
float[] questionVector = embeddingModel.embed(question);
// 2. 在向量库中搜索最相似的 5 个片段
List<VectorStore.QueryResponse> responses = vectorStore.similaritySearch(
SimilarityQuery.builder()
.query(new Document(question))
.topK(5)
.similarityThreshold(0.7)
.filterExpression("category == '" + category + "'")
.build()
);
// 3. 拼接上下文
String context = responses.stream()
.map(r -> r.getDocument().getText())
.collect(Collectors.joining("\n\n"));
if (context.isEmpty()) {
return "知识库中没有相关内容,请尝试其他问题";
}
// 4. 构建 Prompt(带上下文)
String prompt = """
你是一个智能客服助手。请根据以下参考资料回答问题。
如果参考资料中没有相关内容,请如实告知"抱歉,知识库中没有相关信息"。
=== 参考资料 ===
%s
=== 问题 ===
%s
=== 回答 ===
""".formatted(context, question);
// 5. 调用 LLM 生成回答
return aiModelRouter.chat(AiModelRoute.CHINESE_UNDERSTANDING, prompt);
}
}
6.3 知识管理 API
less
@RestController
@RequestMapping("/admin-api/ai/knowledge")
@Tag(name = "AI 知识库管理")
public class AiKnowledgeController {
@Autowired
private AiKnowledgeService knowledgeService;
/** 上传文档 */
@PostMapping("/document/upload")
public CommonResult<Long> uploadDocument(
@RequestParam("file") MultipartFile file,
@RequestParam("categoryId") Long categoryId) {
Long docId = knowledgeService.uploadDocument(file, categoryId);
return CommonResult.success(docId);
}
/** 知识库问答 */
@PostMapping("/qa")
public CommonResult<AiQaRespDTO> qa(@RequestBody @Valid AiQaReqDTO req) {
String answer = knowledgeService.qa(req.getQuestion(), req.getCategory());
return CommonResult.success(new AiQaRespDTO(answer));
}
/** 删除文档 */
@DeleteMapping("/document/{id}")
public CommonResult<Boolean> deleteDocument(@PathVariable Long id) {
knowledgeService.deleteDocument(id);
return CommonResult.success(true);
}
}
七、踩过的坑
坑 1:流式响应乱码
Spring AI 的流式输出默认使用 UTF-16,在 WebFlux 环境下会乱码。
解决方案 :在 application.yaml 中强制设置字符集:
yaml
spring:
mvc:
charset: UTF-8 # 必须设置,否则 WebFlux 流式返回会乱码
坑 2:并发控制
多个用户同时调用 AI,API Key 的 QPS 限制很容易触达。
解决方案:用 Redisson 分布式锁做限流:
typescript
@RateLimiter(key = "ai:quota:{userId}", permitsPerSecond = 5)
public String chat(String message) {
// 每个用户每秒最多 5 次调用
}
坑 3:Token 计数不准
不同模型的 Token 计算方式不同,GPT-4 和 DeepSeek 的 tokenizer 不一样。
解决方案:统一使用 Spring AI 内置的 Token 计数器:
ini
ChatResponse response = chatModel.call(prompt);
TokenUsage usage = response.getMetadata().getTokenUsage();
log.info("输入 token: {}, 输出 token: {}, 总计: {}",
usage.getInputTokens(), usage.getOutputTokens(), usage.getTotalTokens());
坑 4:Embedding 模型选型
Spring AI 默认的 Embedding 模型是 OpenAI 的 text-embedding-ada-002,中文效果一般。
推荐替换为:
-
通义千问 embedding:
text-embedding-v3,中文效果好,免费额度大 -
智谱 embedding:
embedding-2,免费spring: ai: zhipuai: embedding: options: model: embedding-2
坑 5:超时时间太短
默认 60 秒超时,遇到复杂推理或图片生成直接超时。
解决方案:
yaml
spring:
ai:
openai:
chat:
options:
timeout: PT120S # 120 秒
dashscope:
chat:
options:
timeout: PT180S # 图片生成可能需要更久
八、AI 配额管理
多租户 SaaS 场景下,每个租户的 AI 调用次数需要管控。
8.1 配额表设计
sql
CREATE TABLE `ai_quota` (
`id` bigint NOT NULL AUTO_INCREMENT,
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户 ID',
`user_id` bigint NOT NULL COMMENT '用户 ID',
`month` varchar(7) NOT NULL COMMENT '月份,如 2026-07',
`total_quota` int NOT NULL DEFAULT 1000 COMMENT '总配额(次)',
`used_quota` int NOT NULL DEFAULT 0 COMMENT '已用配额',
`primary_key` (`tenant_id`, `month`)
);
8.2 配额拦截器
java
@Component
public class AiQuotaInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 只拦截 AI 相关的接口
if (!request.getRequestURI().startsWith("/admin-api/ai/")) {
return true;
}
Long tenantId = TenantContextHolder.getTenantId();
String month = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
// 查询配额
AiQuotaDO quota = quotaMapper.selectByTenantAndMonth(tenantId, month);
if (quota.getUsedQuota() >= quota.getTotalQuota()) {
response.setStatus(429);
response.getWriter().write("本月 AI 调用次数已达上限");
return false;
}
return true;
}
}
九、总结
- Spring AI 是最佳选择:统一了 ChatCompletion、ImageGeneration、VectorStore 的 API,换模型只需改配置
- 多模型路由是关键:不同场景用不同模型,成本和效果兼顾
- RAG 是最有价值的场景:知识库问答能让 AI 真正服务于业务,而不是泛泛而谈
- 配额管理不能少:多租户 SaaS 必须管控每个租户的用量,防止被滥用
- 流式响应要处理编码:WebFlux 环境下 UTF-8 是必须的