AI模型配置
AI模型配置
业务用途
平台的 AI 能力(、)都依赖一个「已启用且设为默认」的大模型配置。本模块提供多模型管理:
- 多模型并存:可同时配置 OpenAI、DeepSeek、Qwen 等多家模型,只要兼容 OpenAI Chat Completion 协议即可。
- 多租户隔离:
AiModelConfig继承BaseEntity,带tenant_id,每个租户独立维护自己的模型配置与 API Key。 - 设默认模型:同一租户内只能有一个默认模型(
defaultModel=1),AI 功能取默认模型调用。切换默认即切换全家桶的 AI 后端。 - 连通性测试:保存前可调
/test发一条「请只回复:连接成功」探活,避免配错地址。
兼容协议
AiProviderService 走标准 OpenAI Chat Completion 协议(POST {baseUrl}/chat/completions,messages + model + max_tokens)。DeepSeek、Qwen(DashScope)、硅基流动、本地 Ollama 等均兼容此协议,填对 apiBaseUrl + apiKey + modelName 即可。
涉及文件
| 角色 | 文件路径 |
|---|---|
| 模型配置控制器 | backend/src/main/java/com/lowcode/controller/system/AiModelConfigController.java |
| 模型配置服务接口 | backend/src/main/java/com/lowcode/service/AiModelConfigService.java |
| 模型配置服务实现 | backend/src/main/java/com/lowcode/service/impl/AiModelConfigServiceImpl.java |
| AI 调用服务 | backend/src/main/java/com/lowcode/service/AiProviderService.java |
| 实体 | backend/src/main/java/com/lowcode/entity/AiModelConfig.java |
| Mapper | backend/src/main/java/com/lowcode/mapper/AiModelConfigMapper.java |
| 前端 API | frontend/src/api/ai.ts |
| 前端页面 | frontend/src/views/system/settings/ai-model.vue |
后端实现(关键代码)
实体与表
AiModelConfig 映射表 sys_ai_model_config,继承 BaseEntity(含 id / tenantId / createTime / updateTime / createBy / updateBy / deleted):
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_ai_model_config")
public class AiModelConfig extends BaseEntity {
private String providerCode; // 供应商标识,如 openai / deepseek / qwen
private String providerName; // 供应商显示名
private String modelName; // 模型名,如 deepseek-chat、qwen-plus
private String apiBaseUrl; // 接口基础地址(不含 /chat/completions)
private String apiKey; // API Key
private String apiSecret; // API Secret(部分供应商需要)
private String enabled; // "1" 启用 / "0" 禁用
private String defaultModel; // "1" 默认 / "0" 非默认
private String extraConfigJson; // 扩展配置:自定义 header、authHeader、温度等
private String remark;
}extraConfigJson 能放什么
AiProviderService 解析 extraConfigJson(JSON 对象):
authHeader:自定义鉴权头名(默认Authorization)authPrefix:鉴权前缀(默认Bearer)headers:额外请求头对象- 其它键值会作为 body 字段合并进 Chat Completion 请求体(如
temperature、top_p,但headers除外)
服务层
AiModelConfigService 只是 IService<AiModelConfig> 的空继承,CRUD 由 MyBatis-Plus 的 ServiceImpl<AiModelConfigMapper, AiModelConfig> 提供默认实现:
public interface AiModelConfigService extends IService<AiModelConfig> {}
@Service
public class AiModelConfigServiceImpl
extends ServiceImpl<AiModelConfigMapper, AiModelConfig>
implements AiModelConfigService {}端点一览
AiModelConfigController 挂在 /api/system/ai-model 下,8 个端点:
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/system/ai-model/list | 列表(按 defaultModel desc、createTime desc 排序,apiKey/apiSecret 脱敏为 ******) |
GET | /api/system/ai-model/{id} | 详情(脱敏) |
POST | /api/system/ai-model | 新增(若 defaultModel=1 先清其它默认) |
PUT | /api/system/ai-model | 修改(apiKey/apiSecret 为空或 ****** 时保留原值) |
DELETE | /api/system/ai-model/{id} | 删除 |
PUT | /api/system/ai-model/changeStatus | 启用/禁用(body: {id, enabled}) |
PUT | /api/system/ai-model/setDefault/{id} | 设为默认(同时 enabled=1,清其它默认) |
POST | /api/system/ai-model/test | 连通性测试(调用 aiProviderService.generateText) |
关键 controller 逻辑
设默认模型:setDefault 先 clearDefaultModel(id) 把其它 defaultModel=1 的记录置 0,再把目标记录置 enabled=1 + defaultModel=1:
@PutMapping("/setDefault/{id}")
public Result<Void> setDefault(@PathVariable Long id) {
AiModelConfig existing = modelConfigService.getById(id);
if (existing == null) return Result.error(404, "AI模型配置不存在");
clearDefaultModel(id);
AiModelConfig config = new AiModelConfig();
config.setId(id);
config.setEnabled("1");
config.setDefaultModel("1");
modelConfigService.updateById(config);
return Result.success("设置成功");
}密钥脱敏与保留:list / getById 调 maskSecrets 把 apiKey / apiSecret 替换为 ******;update 时若提交的密钥为空或 ******,回填数据库原值,避免前端回显脱敏值覆盖真实 Key:
private static final String MASK = "******";
private void maskSecrets(AiModelConfig config) {
if (StringUtils.hasText(config.getApiKey())) config.setApiKey(MASK);
if (StringUtils.hasText(config.getApiSecret())) config.setApiSecret(MASK);
}
// update 时
if (!StringUtils.hasText(config.getApiKey()) || MASK.equals(config.getApiKey())) {
config.setApiKey(existing.getApiKey());
}连通性测试:test 复用 aiProviderService.generateText,发一句「请只回复:连接成功」并返回模型回答;若 config 带 id 且密钥是脱敏值,自动从库取真实 Key:
@PostMapping("/test")
public Result<Map<String, Object>> test(@RequestBody AiModelConfig config) {
try {
if (config.getId() != null) {
AiModelConfig existing = modelConfigService.getById(config.getId());
if (existing != null) {
if (!StringUtils.hasText(config.getApiKey()) || MASK.equals(config.getApiKey()))
config.setApiKey(existing.getApiKey());
if (!StringUtils.hasText(config.getApiSecret()) || MASK.equals(config.getApiSecret()))
config.setApiSecret(existing.getApiSecret());
}
}
String answer = aiProviderService.generateText(config, "请只回复:连接成功");
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("answer", answer);
return Result.success(result);
} catch (Exception e) {
return Result.error("AI模型连接测试失败: " + e.getMessage());
}
}AiProviderService 调用细节
generateText(config, prompt, maxTokens) 构建标准 OpenAI 请求:
validateConfig:校验apiBaseUrl/modelName/apiKey非空;拦截控制台地址(含/console的 URL 报错,提示填服务端 API 地址)。normalizeChatUrl:若 URL 已以/chat/completions结尾直接用,否则补上。- 构造 body:
model+messages=[{role:user, content:prompt}]+temperature=0.2+max_tokens,再applyExtraConfig合并extraConfigJson里的非headers字段。 applyAuthHeaders:默认Authorization: Bearer <apiKey>,可被extraConfigJson.authHeader/authPrefix/headers覆盖。RestTemplate(connectTimeout=10000ms、readTimeout=60000ms)POST。extractContent:依次尝试choices[0].message.content->message.reasoning_content(DeepSeek-R1 思考链)->output.text->data.content,兼容不同供应商返回结构。
ObjectNode body = objectMapper.createObjectNode();
body.put("model", config.getModelName());
ArrayNode messages = body.putArray("messages");
ObjectNode message = objectMapper.createObjectNode();
message.put("role", "user");
message.put("content", prompt);
messages.add(message);
body.put("temperature", 0.2);
body.put("max_tokens", maxTokens);
applyExtraConfig(body, config.getExtraConfigJson());
// ...
return extractContent(response.getBody());超时与重试
RestTemplate 读超时 60 秒。生成长表字段(max_tokens=8192)时若模型较慢可能逼近超时。当前实现没有自动重试,失败直接抛 IllegalStateException 给上层。
前端实现
frontend/src/api/ai.ts 封装全部模型配置接口:
export const getAiModelConfigs = () => request.get<AiModelConfig[]>('/system/ai-model/list')
export const saveAiModelConfig = (data: AiModelConfig) =>
data.id ? request.put('/system/ai-model', data) : request.post('/system/ai-model', data)
export const deleteAiModelConfig = (id: number) => request.delete(`/system/ai-model/${id}`)
export const changeAiModelStatus = (id: number, enabled: string) =>
request.put('/system/ai-model/changeStatus', { id, enabled })
export const setDefaultAiModel = (id: number) => request.put(`/system/ai-model/setDefault/${id}`)
export const testAiModelConfig = (data: AiModelConfig) => request.post('/system/ai-model/test', data)管理页 frontend/src/views/system/settings/ai-model.vue 提供列表表格(带启用状态开关、默认标记、测试按钮)、新增/编辑弹窗、测试连通弹窗。

操作步骤
- 登录平台(前端
:3000),进入「系统管理 -> AI 模型配置」。 - 点「新增」,填写:
providerCode/providerName:如deepseek/DeepSeekmodelName:如deepseek-chatapiBaseUrl:服务端 API 地址,如https://api.deepseek.com/v1(不要带/console,不要带/chat/completions,服务会自动补)apiKey:你的 API Key- 勾选「设为默认」
- 点「测试」连通性,看到模型回复「连接成功」即配置正确。
- 保存。该条记录
enabled=1+defaultModel=1, 与 立即可用。 - 切换模型:在列表点另一条的「设默认」即可,原默认自动取消。

常见问题
测试报「当前填写的是控制台页面地址」
validateConfig 检测到 apiBaseUrl 里含 /console 会直接拒绝。这是常见坑:DeepSeek/小米等平台控制台地址(platform.deepseek.com/...)和模型服务 API 地址(api.deepseek.com/...)不同。务必填文档里的「API Base URL」。
测试报「无法连接模型接口,请检查接口地址是否为服务端API地址」
ResourceAccessException 触发,通常是网络不通或 URL 写错(如多了空格、协议不对)。确认 apiBaseUrl 以 http:// 或 https:// 开头,且本机能 curl 通。
设了默认但 AI 功能仍报「请先在系统设置中配置并启用默认AI模型」
AI 功能取默认模型的方式是 enabled='1' AND defaultModel='1' LIMIT 1。若记录被禁用(enabled=0)或租户不匹配(tenant_id 不一致)都会查不到。setDefault 会自动把 enabled 置 1,但若之后手动禁用了就会失效。重新点「设默认」即可。
apiKey 列表里显示 ****** 是正常的
maskSecrets 故意脱敏,防止泄露。修改时若不改 Key,提交带 ****** 或空值,后端会保留原 Key,不会覆盖成空。只有显式填入新值才会更新。
多租户下每个租户要单独配
AiModelConfig 继承 BaseEntity,受租户拦截器隔离。normalize 在新增时若 tenantId 为空默认设为 1。切换租户登录后会看到各自租户的模型列表。AI 功能只查当前租户的默认模型。
