摘要:在企业级 AI 开发中,单一模型往往难以适应多变的业务需求。本文将介绍如何基于 Spring AI 框架,构建一个支持动态切换底层模型(如 OpenAI、DeepSeek)、可配置参数(如温度、TopK)以及自定义角色人设的通用 AI 服务架构。我们将从数据库设计出发,深入讲解策略模式与工厂模式在 AI 中台建设中的实际应用。
0. 技术栈说明
本文基于以下核心技术栈:
- JDK: 17+
- Spring Boot: 3.3.x
- Spring AI: 1.1.x
0.1 引入 Maven 依赖
在使用 Spring AI 之前,我们需要在 pom.xml 中引入 spring-ai-bom 来管理版本,并添加相应的 Starter 依赖。
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- WebFlux 支持流式响应 (SSE) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- OpenAI 模型支持 (Spring AI 核心) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- DeepSeek 模型支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-deepseek</artifactId>
</dependency>
<!-- 智谱 AI 模型支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-zhipuai</artifactId>
</dependency>
<!-- RAG 向量检索支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
</dependencies>
1. 核心痛点与设计目标
在 Spring AI 的默认配置中,ChatModel 通常是单例的。但在实际业务场景中,我们经常面临以下挑战:
- 动态切换:管理员在后台配置不同的 API Key 和 Endpoint,系统需即时生效,无需重启。
- 个性化人设:不同的业务场景需要不同的"角色(System Prompt)",例如"客服助手"、"代码专家"等。
- 参数热配 :Temperature (温度) 、Top-P 等推理参数需支持数据库化配置,以便根据效果灵活调整。
为此,我们采用了 "数据库配置表 + 动态工厂模式" 的架构方案。
2. 数据库设计
为了实现上述目标,我们需要两张核心表:一张存储模型连接信息 ,另一张存储角色与 Prompt 配置。
2.1 模型配置表 (ai_model_config)
这张表用于存储 LLM 的连接参数和推理参数。
关键字段解析:
api_endpoint: 标准的 OpenAI 接口地址是api.openai.com,但国内许多大模型(如 DeepSeek, Moonshot)都兼容 OpenAI 协议。我们只需要将 Endpoint 修改为厂商提供的地址,即可复用同一套代码。temperature: 值通常在 0 到 1(或 2)之间。数值越低 ,模型越保守、严谨,适合客服问答;数值越高,模型越发散、有创造力,适合写诗或头脑风暴。top_p: 核采样概率。它与 Temperature 类似,也是控制生成多样性的参数。通常建议这两个参数只调整其中一个。
sql
CREATE TABLE `ai_model_config` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`config_name` varchar(100) NOT NULL COMMENT '配置名称',
`api_provider` varchar(50) NOT NULL COMMENT 'API 提供商 (核心字段,决定调用哪个策略,如 openai, deepseek)',
`model_id` varchar(100) NOT NULL COMMENT '模型标识 (如 gpt-4o, deepseek-chat)',
`is_enabled` tinyint(1) DEFAULT '1' COMMENT '是否启用(1-启用 0-禁止)',
`is_default` tinyint(1) DEFAULT '0' COMMENT '是否默认(1-是 0-否)',
`role_id` bigint DEFAULT NULL COMMENT '绑定角色与提示词ID(可为空,用于设置 System Prompt)',
`api_key` varchar(255) DEFAULT NULL COMMENT 'API Key (敏感信息)',
`api_endpoint` varchar(255) DEFAULT NULL COMMENT 'API 端点 (用于适配兼容 OpenAI 协议的国内模型)',
`temperature` double DEFAULT NULL COMMENT '温度 (控制回答的随机性)',
`top_p` double DEFAULT NULL COMMENT 'Top P',
`max_tokens` int DEFAULT NULL COMMENT '最大 Token 数',
PRIMARY KEY (`id`)
) COMMENT='AI 模型配置表';
2.2 角色与 RAG 配置表 (ai_role_prompt)
这张表定义了 AI 的"人设"以及 RAG(检索增强生成)的相关规则。
关键字段解析:
system_prompt: 系统提示词。这是发给 AI 的第一句话,规定了 AI 的身份和行为准则(例如:"你是一个资深的 Java 架构师,只回答技术问题")。similarity_threshold: 相似度阈值(0.0 - 1.0)。在进行向量检索时,如果文档与问题的相似度低于这个值,就会被过滤掉。这能有效防止 AI 被不相关的文档误导。
sql
CREATE TABLE `ai_role_prompt` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_name` varchar(100) NOT NULL COMMENT '角色名称',
`description` varchar(255) DEFAULT NULL COMMENT '角色描述',
`is_enabled` tinyint(1) DEFAULT '1' COMMENT '是否启用',
`system_prompt` text COMMENT '系统提示词(System Prompt)',\
`is_rag_enabled` tinyint(1) DEFAULT '0' COMMENT '是否启用 RAG',
`rag_template` text COMMENT 'RAG 提示词模板',
`top_k` int DEFAULT '5' COMMENT '检索文档数量 topK',
`similarity_threshold` double DEFAULT '0.7' COMMENT '相似度阈值(0-1)',
`custom_filter` varchar(255) DEFAULT NULL COMMENT '自定义过滤表达式',
PRIMARY KEY (`id`)
) COMMENT='AI 角色与提示词配置表';
3. 动态工厂核心实现拆解
DynamicChatClientFactory 是本系统的核心。我们通过分步解析,来看看它是如何工作的。
3.1 步骤一:加载默认配置
首先,我们需要从数据库中查询当前生效的配置。这里使用了 MyBatis-Plus 的 LambdaQuery,只查询 is_default=1 且 is_enabled=1 的记录。
arduino
// Service 层方法调用
AiModelConfig config = aiModelConfigService.getDefaultConfig();
if (config == null) {
throw new BizException(ResultCode.NOT_FOUND, "未找到默认启用的模型配置,请先在模型配置中设置默认模型");
}
3.2 步骤二:策略路由与模型构建
不同的厂商(Provider)初始化方式不同。我们使用策略模式 ,根据 api_provider 字段(如 "openai", "deepseek")找到对应的策略实现类,构建出底层的 ChatModel。
ChatModel vs ChatClient:
- ChatModel: 是 Spring AI 的底层接口,负责与具体的大模型 API 进行 HTTP 通信(类似 JDBC Driver)。
- ChatClient: 是 Spring AI 的高层接口,提供了 fluent API(链式调用),负责处理 Prompt 封装、Advisor 拦截等逻辑(类似 JdbcTemplate)。
ini
// 获取策略并构建底层的 ChatModel
ModelChatStrategy strategy = modelChatStrategyFactory.getStrategy(config.getApiProvider());
ChatModel chatModel = strategy.buildChatModel(config);
3.3 步骤三:System Prompt 组装
这里处理 AI 的"人设"。我们在代码中内置了一个默认的通用人设(植入品牌信息),同时支持从 ai_role_prompt 表中加载动态配置来覆盖默认值。
ini
// 设置默认的 System Prompt(内置兜底)
String systemPrompt = """
你是lanjii(岚迹)的客服助手,在用户第一次问你的时候,你要表明自己的身份,并且要说明 lanjii 是什么。
针对用户的问题总是能以友好愉悦的方式去进行交流,希望带给客户很好的体验。
你总是会用中文去回答问题。
""";
// 如果配置了角色,覆盖默认 System Prompt
if (config.getRoleId() != null) {
AiRolePrompt rolePrompt = aiRolePromptService.getById(config.getRoleId());
if (rolePrompt != null && rolePrompt.getSystemPrompt() != null && !rolePrompt.getSystemPrompt().isEmpty()) {
systemPrompt = rolePrompt.getSystemPrompt();
}
}
3.4 步骤四:注入增强器 (Advisors)
Advisor 是 Spring AI 1.x 引入的重要概念,类似于 Spring AOP 的切面。它允许我们在不修改核心业务逻辑的情况下,动态地为 ChatClient 增加能力。
MessageChatMemoryAdvisor: 负责自动管理对话历史。它会在发请求前把历史记录拼接到 Prompt 中,在收到响应后把新对话存入 Memory。VectorStoreChatMemoryAdvisor: 负责 RAG 检索。
scss
// 组装 ChatClient,注入记忆和向量检索能力
return ChatClient.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
VectorStoreChatMemoryAdvisor.builder(vectorStore).build()
)
.build();
4. 工厂类完整代码清单
将上述步骤整合,我们得到了完整的工厂类代码:
ini
package com.lanjii.biz.admin.ai.service;
import com.lanjii.biz.admin.ai.model.entity.AiModelConfig;
import com.lanjii.biz.admin.ai.model.entity.AiRolePrompt;
import com.lanjii.biz.admin.ai.strategy.ModelChatStrategy;
import com.lanjii.biz.admin.ai.strategy.ModelChatStrategyFactory;
import com.lanjii.common.exception.BizException;
import com.lanjii.core.resp.ResultCode;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;
/**
* 根据数据库中的模型配置动态构建 {@link ChatClient}
*
* @author lanjii
*/
@Component
@RequiredArgsConstructor
public class DynamicChatClientFactory {
private final AiModelConfigService aiModelConfigService;
private final AiRolePromptService aiRolePromptService;
private final ModelChatStrategyFactory modelChatStrategyFactory;
private final ChatMemory chatMemory;
private final VectorStore vectorStore;
/**
* 构建默认模型
*/
public ChatClient buildDefaultClient() {
// 加载默认配置
AiModelConfig config = aiModelConfigService.getDefaultConfig();
if (config == null) {
throw new BizException(ResultCode.NOT_FOUND, "未找到默认启用的模型配置,请先在模型配置中设置默认模型");
}
// 策略路由构建模型
ModelChatStrategy strategy = modelChatStrategyFactory.getStrategy(config.getApiProvider());
ChatModel chatModel = strategy.buildChatModel(config);
// 初始化默认提示词
String systemPrompt = """
你是lanjii(岚迹)的客服助手,在用户第一次问你的时候,你要表明自己的身份,并且要说明 lanjii 是什么。
针对用户的问题总是能以友好愉悦的方式去进行交流,希望带给客户很好的体验。
你总是会用中文去回答问题。
""";
// 动态覆盖提示词
if (config.getRoleId() != null) {
AiRolePrompt rolePrompt = aiRolePromptService.getById(config.getRoleId());
if (rolePrompt != null && rolePrompt.getSystemPrompt() != null && !rolePrompt.getSystemPrompt().isEmpty()) {
systemPrompt = rolePrompt.getSystemPrompt();
}
}
// 构建增强版客户端
return ChatClient.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
VectorStoreChatMemoryAdvisor.builder(vectorStore).build()
)
.build();
}
}
5. 架构思考:性能与灵活性的权衡
在上述实现中,我们每次调用 buildDefaultClient() 时,都会执行一次"查库 + 策略构建 + Client 组装"的全流程。
- 优势 :实现了 100% 的热更新。管理员在后台修改了 API Key 或 Temperature,下一个请求立即生效,没有任何延迟。
- 劣势 :在高并发场景下,频繁创建
ChatModel对象可能会带来一定的 GC 压力。
进阶优化方向 :如果您的系统 QPS 很高,可以引入 本地缓存 (如 Caffeine)。
- 将
config.getId()作为缓存 Key。 - 将构建好的
ChatClient作为缓存 Value。 - 设置较短的过期时间(如 1 分钟),或者利用 Spring Cache 的
@CacheEvict在配置更新时主动清除缓存。
这样既保留了动态性,又兼顾了性能。
6. 项目资源
- Gitee 源码仓库 :gitee.com/leven2018/l...这里提供了项目的完整源代码、SQL 脚本以及后续的功能更新。
- 在线体验地址 :http://106.54.167.194/admin/login无需本地部署,您可以直接访问此系统体验文中的动态模型配置与流式对话功能。
7. 小结
在本文中,我们完成了最底层的"基础设施"建设。通过 ai_model_config 和 ai_role_prompt 两张表,配合 DynamicChatClientFactory,我们实现了一个灵活的 AI 核心。
在下篇中,我们将进入业务层,深入拆解如何在 Service 中利用这个工厂,实现手动控制的 RAG 检索与 SSE 流式响应。