跳转到内容

JeecgBoot 多模型统一管理与路由

JeecgBoot 多模型统一管理与路由

学习目标

通过本章学习,你将能够:

  • 理解 JeecgBoot 如何统一管理 10+ 个 AI 模型提供商
  • 掌握模型参数合并的优先级策略(应用级 > 模型级 > 系统默认)
  • 实现模型未激活时的自动降级到默认模型
  • 了解 DeepSeek 推理模型的特殊适配方案

前置知识

本章涉及多模型适配的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 JeecgBoot 的具体实现。


项目实践

模型注册与实体设计

JeecgBoot 通过 AiragModel 实体统一管理所有 AI 模型:

字段说明
provider模型提供商(openai、anthropic、dashscope、qianfan、zhipu、ollama、gemini 等)
modelName模型名称(gpt-4o、claude-sonnet-4、deepseek-v4-flash 等)
baseUrlAPI 基础地址(支持私有部署)
credentialJSON 存储凭证(apiKey、secretKey、httpVersionOne)
modelParamsJSON 存储模型参数(temperature、topP、maxTokens、timeout 等)
activateFlag激活状态(0=未激活,1=已激活)
modelType模型类型(LLM、EMBED、IMAGE)

设计亮点:凭证和参数全部以 JSON 字符串存储,而非独立数据库字段。这样新增模型时无需修改表结构,只需在前端配置界面扩展即可。

统一调用层:AIChatHandler

AIChatHandler 是平台的核心聊天入口,它实现了 IAIChatHandler 接口,提供:

  • 非流式调用completions(modelId, messages) — 一次性返回完整响应
  • 流式调用chat(modelId, messages) — 返回 TokenStream,支持逐 token 推送
  • 默认模型调用chatByDefaultModel(messages) — 不指定模型时使用系统默认

调用链路:

Controller → AIChatHandler → LLMHandler → langchain4j → 具体模型 API

LLMHandler 是基于 langchain4j 的统一封装层,负责根据 AIChatParams.providerAIChatParams.modelName 选择对应的 langchain4j 模型实现。

参数合并策略:mergeParams

模型参数的合并遵循明确的优先级:

最终参数 = 应用级参数 > 模型级参数 > 系统默认值
// 伪代码:mergeParams 逻辑
private AIChatParams mergeParams(AiragModel airagModel, AIChatParams params) {
// 1. 模型级参数作为基础
if (airagModel 有 modelParams) {
params.temperature = params.temperature ?? airagModel.temperature;
params.topP = params.topP ?? airagModel.topP;
params.maxTokens = params.maxTokens ?? airagModel.maxTokens;
// ... 其他参数
}
// 2. 凭证透传
params.apiKey = airagModel.credential.apiKey;
params.secretKey = airagModel.credential.secretKey;
// 3. RAG 配置(如果应用绑定了知识库)
if (params.knowIds 非空) {
params.queryRouter = embeddingHandler.getQueryRouter(
params.knowIds, params.topNumber, params.similarity
);
}
// 4. MCP/插件配置(非推理模型)
if (!是 DeepSeek 推理模型) {
buildPlugins(params);
}
// 5. DeepSeek 推理模型特殊处理
if (是 DeepSeek 推理模型) {
params.returnThinking = true;
params.sendThinking = true;
}
return params;
}

模型降级策略

当用户选择的模型未激活(activateFlag = 0)时,系统不会报错,而是自动降级:

// 流式聊天降级逻辑
AiragModel airagModel = airagModelMapper.getByIdIgnoreTenant(modelId);
if (airagModel == null || airagModel.getActivateFlag() == 0) {
log.warn("模型未激活,采用默认模型");
return chatByDefaultModel(messages, params);
}

默认模型通过 AiChatConfig YAML 配置:

jeecg:
ai:
chat:
ai-model-draw:
provider: openai
model: dall-e-3
api-key: ${OPENAI_API_KEY}
api-host: https://api.openai.com

适用场景:多租户环境中,管理员配置了多种模型供用户选择。当某个模型的 API Key 过期或余额不足时,用户不会立即感知到中断,系统自动切换到默认模型继续服务。

DeepSeek 推理模型适配

这是 JeecgBoot 源码中发现的一个经典实战案例。

问题:DeepSeek 推理模型(deepseek-v4-flash 等)要求多轮工具调用时,每次请求必须回传上一次的 reasoning_content 字段。但历史消息持久化层不保存该字段,导致 API 校验失败。

解决方案:在发送前,为历史 AI 消息注入占位 thinking:

// 为 DeepSeek 推理模型的历史 AI 消息注入占位 thinking
private static List<ChatMessage> injectThinkingPlaceholderIfNeeded(messages, modelName) {
if (!isDeepSeekThinkingModel(modelName)) return messages;
for msg in messages:
if msg is AiMessage and msg.thinking() 为空:
rebuilt = AiMessage.builder()
.text(msg.text())
.thinking("...") // 占位
.toolExecutionRequests(msg.toolExecutionRequests())
.build()
result.add(rebuilt)
return result
}

模型识别采用精确匹配 + 关键字包含的双重策略:

DEEPSEEK_THINKING_MODELS = {"deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro"}
isDeepSeekThinkingModel(name):
1. DEEPSEEK_THINKING_MODELS 精确匹配
2. 关键字包含:reasoner / v4-flash / v4-pro
3. 兼容版本后缀(如 deepseek-v4-flash-0428)

问题与规避

陷阱表现对策
模型未配置默认降级AiChatConfig 未配置默认模型 → 图像生成直接报错启动时检查默认模型配置,缺失时给出友好提示
通义千问 null 消息内容Qwen API 不接受 null 消息内容重建 ToolExecutionResultMessage 时确保 text 不为 null(PR#9539 修复)
工具调用消息序列不完整历史消息数量设置过小 → 工具调用上下文丢失友好提示用户增加历史消息条数
DeepSeek 联网搜索后异常联网搜索工具的响应格式与预期不符强制 returnThinking=true 确保深度思考模式(issues/9607)

设计取舍

JSON 存储 vs 独立字段

JeecgBoot 选择credentialmodelParams 使用 JSON 字符串存储。

方案优势代价
JSON 字符串新增模型无需改表结构,前端配置灵活无法在数据库层面做索引和查询
独立字段可索引、可查询每增加一种凭证类型都要改表结构

在模型管理场景中,管理员更关注的是”有哪些模型”而非”按 apiKey 搜索模型”,JSON 存储是合理选择。

降级到默认 vs 报错提示

JeecgBoot 选择:模型未激活时静默降级到默认模型。

方案体验风险
静默降级用户无感知,服务不中断用户可能不知道自己的响应来自不同的模型
报错提示用户明确知道问题服务中断,用户体验差

生产系统中,服务连续性优先于透明度。JeecgBoot 通过 log.warn 记录降级事件,管理员可在后台日志中追踪。


参考来源