Add quick LLM provider setup

This commit is contained in:
2026-07-07 13:12:27 +08:00
parent bcdf91c85d
commit dada4aafcd
5 changed files with 208 additions and 88 deletions

View File

@@ -43,7 +43,7 @@ const knowledgeForm = reactive({
});
const modelForm = reactive({
provider: "openai",
provider: "openai-compatible",
displayName: "",
apiType: "openai_compatible",
modelName: "",
@@ -66,58 +66,65 @@ const modelForm = reactive({
timeoutSecond: 30,
});
const modelPresets = [
const quickModelForm = reactive({
provider: "deepseek",
modelName: "deepseek-v4-pro",
apiKey: "",
});
const quickModelProviders = [
{
label: "OpenAI",
provider: "openai",
label: "DeepSeek",
provider: "deepseek",
apiType: "openai_compatible",
baseUrl: "https://api.deepseek.com",
authType: "bearer",
apiVersion: "",
temperature: 0.2,
topP: null,
maxToken: 1024,
contextWindow: 128000,
models: [
{ label: "deepseek-v4-pro", value: "deepseek-v4-pro" },
{ label: "deepseek-v4-flash", value: "deepseek-v4-flash" },
],
},
{
label: "MiniMax",
provider: "minimax",
apiType: "anthropic_messages",
baseUrl: "https://api.minimax.io/anthropic",
authType: "bearer",
apiVersion: "",
temperature: null,
topP: null,
maxToken: 1024,
contextWindow: 1000000,
models: [
{ label: "MiniMax-M3", value: "MiniMax-M3" },
{ label: "MiniMax-M2.7", value: "MiniMax-M2.7" },
{ label: "MiniMax-M2.7-highspeed", value: "MiniMax-M2.7-highspeed" },
{ label: "MiniMax-M2.5", value: "MiniMax-M2.5" },
{ label: "MiniMax-M2.5-highspeed", value: "MiniMax-M2.5-highspeed" },
{ label: "MiniMax-M2.1", value: "MiniMax-M2.1" },
{ label: "MiniMax-M2.1-highspeed", value: "MiniMax-M2.1-highspeed" },
{ label: "MiniMax-M2", value: "MiniMax-M2" },
],
},
] as const;
const protocolPresets = [
{
label: "OpenAI 兼容协议",
provider: "openai-compatible",
apiType: "openai_compatible",
baseUrl: "https://api.openai.com/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "DeepSeek",
provider: "deepseek",
apiType: "openai_compatible",
baseUrl: "https://api.deepseek.com/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "通义千问 DashScope",
provider: "qwen",
apiType: "openai_compatible",
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "Kimi / Moonshot",
provider: "moonshot",
apiType: "openai_compatible",
baseUrl: "https://api.moonshot.cn/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "硅基流动 SiliconFlow",
provider: "siliconflow",
apiType: "openai_compatible",
baseUrl: "https://api.siliconflow.cn/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "火山方舟 Ark",
provider: "volcengine-ark",
apiType: "openai_compatible",
baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
apiUrl: "",
authType: "bearer",
},
{
label: "Anthropic Claude",
provider: "anthropic",
label: "Anthropic Messages 协议",
provider: "anthropic-compatible",
apiType: "anthropic_messages",
baseUrl: "https://api.anthropic.com",
apiUrl: "",
@@ -127,17 +134,6 @@ const modelPresets = [
topP: null,
topK: null,
},
{
label: "Google Gemini",
provider: "gemini",
apiType: "gemini_generate_content",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
apiUrl: "",
authType: "api_key",
temperature: null,
topP: null,
topK: null,
},
];
const nullableModelFields = [
@@ -267,6 +263,52 @@ function buildModelPayload() {
return payload;
}
async function quickAddModel() {
const provider = selectedQuickProvider();
if (!provider) return;
if (!quickModelForm.apiKey) {
ElMessage.error("请填写 API Key");
return;
}
await api.createModel({
provider: provider.provider,
displayName: `${provider.label} ${quickModelForm.modelName}`,
apiType: provider.apiType,
modelName: quickModelForm.modelName,
baseUrl: provider.baseUrl,
apiUrl: "",
apiKey: quickModelForm.apiKey,
authType: provider.authType,
apiVersion: provider.apiVersion,
temperature: provider.temperature,
topP: provider.topP,
topK: null,
presencePenalty: null,
frequencyPenalty: null,
maxToken: provider.maxToken,
contextWindow: provider.contextWindow,
streamEnabled: 1,
responseFormat: null,
extraParams: null,
remark: "快速添加",
timeoutSecond: 30,
});
ElMessage.success(`${provider.label} 模型已新增`);
quickModelForm.apiKey = "";
await loadCurrentMenu();
}
function selectedQuickProvider() {
return quickModelProviders.find((item) => item.provider === quickModelForm.provider) ?? quickModelProviders[0];
}
function applyQuickProvider(provider: string) {
const target = quickModelProviders.find((item) => item.provider === provider);
if (!target) return;
quickModelForm.modelName = target.models[0]?.value ?? "";
}
async function enableModel(id: number) {
await api.enableModel(id);
ElMessage.success("模型已启用");
@@ -283,7 +325,7 @@ async function testModel(id: number) {
}
function applyModelPreset(label: string) {
const preset = modelPresets.find((item) => item.label === label);
const preset = protocolPresets.find((item) => item.label === label);
if (!preset) return;
Object.assign(
modelForm,
@@ -332,7 +374,7 @@ function editModel(row: ModelItem) {
function resetModelForm() {
editingModelId.value = null;
Object.assign(modelForm, {
provider: "openai",
provider: "openai-compatible",
displayName: "",
apiType: "openai_compatible",
modelName: "",
@@ -516,27 +558,64 @@ function buildChatQuery() {
<template v-if="activeMenu === 'models'">
<div class="page-head inline">
<div><h2>模型管理</h2><p>支持 OpenAI 兼容Anthropic MessagesGemini generateContent 和常见第三方 LLM</p></div>
<div><h2>模型管理</h2><p>DeepSeekMiniMax 可快速添加其他供应商按 OpenAI 兼容 Anthropic Messages 协议添加</p></div>
<el-button @click="resetModelForm">清空表单</el-button>
</div>
<section class="quick-model-panel">
<div class="quick-model-head">
<div>
<h3>快速添加</h3>
<p>适用于 DeepSeek MiniMax只需要填写 API Key 并选择模型</p>
</div>
</div>
<div class="quick-model-grid">
<el-form-item label="厂商">
<el-select v-model="quickModelForm.provider" @change="applyQuickProvider">
<el-option
v-for="provider in quickModelProviders"
:key="provider.provider"
:label="provider.label"
:value="provider.provider"
/>
</el-select>
</el-form-item>
<el-form-item label="模型">
<el-select v-model="quickModelForm.modelName">
<el-option
v-for="model in selectedQuickProvider()?.models"
:key="model.value"
:label="model.label"
:value="model.value"
/>
</el-select>
</el-form-item>
<el-form-item label="API Key">
<el-input v-model="quickModelForm.apiKey" placeholder="填写后保存,列表中只展示掩码" show-password />
</el-form-item>
<el-form-item label=" ">
<el-button type="primary" class="full-btn" @click="quickAddModel">快速新增</el-button>
</el-form-item>
</div>
</section>
<el-form class="model-config-panel" label-position="top" :model="modelForm">
<div class="model-config-grid">
<el-form-item label="供应商预设">
<el-select placeholder="选择预设" clearable @change="applyModelPreset">
<el-option v-for="preset in modelPresets" :key="preset.label" :label="preset.label" :value="preset.label" />
<el-form-item label="协议模板">
<el-select placeholder="选择协议模板" clearable @change="applyModelPreset">
<el-option v-for="preset in protocolPresets" :key="preset.label" :label="preset.label" :value="preset.label" />
</el-select>
</el-form-item>
<el-form-item label="供应商标识">
<el-input v-model="modelForm.provider" placeholder="openai / deepseek / qwen" />
<el-input v-model="modelForm.provider" placeholder="custom-openai / custom-anthropic" />
</el-form-item>
<el-form-item label="显示名称">
<el-input v-model="modelForm.displayName" placeholder="例如:DeepSeek V3 生产" />
<el-input v-model="modelForm.displayName" placeholder="例如:自建网关生产模型" />
</el-form-item>
<el-form-item label="协议类型">
<el-select v-model="modelForm.apiType">
<el-option label="OpenAI 兼容" value="openai_compatible" />
<el-option label="Anthropic Messages" value="anthropic_messages" />
<el-option label="Gemini generateContent" value="gemini_generate_content" />
</el-select>
</el-form-item>
<el-form-item label="模型名">

View File

@@ -193,16 +193,48 @@ textarea {
background: #ffffff;
}
.quick-model-panel {
margin-bottom: 18px;
padding: 18px;
border: 1px solid #cde7df;
border-radius: 8px;
background: #f8fcfb;
}
.quick-model-head {
margin-bottom: 12px;
}
.quick-model-head h3 {
margin: 0;
font-size: 17px;
}
.quick-model-head p {
margin: 6px 0 0;
color: #667a73;
}
.quick-model-grid {
display: grid;
grid-template-columns: 180px 240px minmax(280px, 1fr) 130px;
gap: 12px;
align-items: end;
}
.model-config-grid {
display: grid;
grid-template-columns: repeat(4, minmax(180px, 1fr));
gap: 12px 14px;
}
.quick-model-panel .el-form-item,
.model-config-panel .el-form-item {
margin-bottom: 12px;
}
.quick-model-panel .el-select,
.quick-model-panel .el-input,
.model-config-panel .el-select,
.model-config-panel .el-input-number,
.model-config-panel .el-input {

View File

@@ -138,11 +138,7 @@ def _call_anthropic_messages(model: ModelConfig, rag_result: RagResult) -> str:
_put_if_not_none(payload, "top_k", model.top_k)
payload.update(_load_extra_params(model.extra_params))
headers = {
"x-api-key": model.api_key,
"anthropic-version": model.api_version or "2023-06-01",
"Content-Type": "application/json",
}
headers = _anthropic_headers(model)
try:
response = httpx.post(
_resolve_anthropic_endpoint(model),
@@ -263,6 +259,19 @@ def _auth_headers(model: ModelConfig) -> dict[str, str]:
return headers
def _anthropic_headers(model: ModelConfig) -> dict[str, str]:
headers = {"Content-Type": "application/json"}
if model.auth_type == "bearer":
headers["Authorization"] = f"Bearer {model.api_key}"
else:
headers["x-api-key"] = model.api_key
if model.api_version:
headers["anthropic-version"] = model.api_version
elif model.provider == "anthropic":
headers["anthropic-version"] = "2023-06-01"
return headers
def _decimal_to_float(value: Any) -> float | None:
return float(value) if value is not None else None

View File

@@ -35,11 +35,12 @@
| D-012 | 大模型真实调用先按 OpenAI 兼容 `chat/completions` 非流式响应接入。 | 默认采用 | 先打通配置化真实模型调用和失败日志,后续再升级为模型原生流式透传。 |
| D-013 | 管理后台开发阶段支持首次登录自动创建默认管理员。 | 默认采用 | 降低本地初始化成本;生产环境必须修改默认账号密码。 |
| D-014 | 管理后台 Element Plus 先整体引入,后续再做按需导入优化。 | 已调整 | 阶段五优先保证后台功能闭环和交付速度;已在 D-019 调整为按需注册。 |
| D-015 | 模型管理按“OpenAI 兼容协议 + 原生协议”两类补全。 | 默认采用 | DeepSeek、通义千问兼容模式、Kimi、硅基流动、火山方舟优先走 OpenAI 兼容Anthropic Messages、Gemini generateContent 保留原生协议适配。 |
| D-015 | 模型管理按“快速厂商 + 协议通用配置”补全。 | 已调整 | DeepSeek 和 MiniMax 提供快速添加;其他供应商按 OpenAI 兼容协议或 Anthropic Messages 协议添加。 |
| D-016 | 模型配置支持 `baseUrl` 自动拼接接口,同时保留 `apiUrl` 覆盖能力。 | 默认采用 | 降低普通配置成本,同时给特殊代理网关、私有化模型和供应商非标准路径保留出口。 |
| D-017 | 编辑模型时 API Key 留空表示保留旧密钥。 | 默认采用 | 减少密钥重复暴露,避免后台人员只改参数时误覆盖密钥。 |
| D-018 | 模型高级参数用 JSON 对象透传。 | 默认采用 | 用较低成本兼容供应商差异,避免数据库字段无限膨胀。 |
| D-019 | 管理后台 Element Plus 改为按需注册,并拆分表格和基础 vendor chunk。 | 默认采用 | 解决后台构建单 chunk 过大的提示,降低首屏主入口体积,同时不引入额外构建插件。 |
| D-020 | MiniMax 快速添加默认使用 Anthropic Messages 兼容协议。 | 默认采用 | MiniMax 文本生成指南推荐 Anthropic-compatible 路径;后端按 `auth_type` 兼容 Bearer 和 x-api-key 两种鉴权。 |
## 待确认决策

View File

@@ -4,16 +4,16 @@
## 本次目标
把管理后台的模型管理从“只保存一个 OpenAI 兼容地址”补全为可维护的第三方 LLM 配置能力,后续可以支持 OpenAI、DeepSeek、通义千问兼容模式、Kimi、硅基流动、火山方舟、Anthropic Claude、Google Gemini 等供应商
把管理后台的模型管理从“只保存一个 OpenAI 兼容地址”补全为可维护的第三方 LLM 配置能力。当前交互收敛为两个常用厂商快速添加,以及两种协议级通用添加
## 参考口径
本次以各供应商公开接口文档的共同配置项为基础,不绑定某一个供应商 SDK
- OpenAI 兼容类:统一使用 `chat/completions` 风格,支持 `model``messages``temperature``top_p``max_tokens``stream``response_format` 等常见参数。
- DeepSeek、硅基流动、通义千问兼容模式、Kimi、火山方舟优先作为 OpenAI 兼容协议接入,通过不同 `Base URL``modelName` 区分
- Anthropic Claude使用 Messages API 原生协议,鉴权使用 `x-api-key`,并支持 `anthropic-version`
- Google Gemini使用 `generateContent` 原生协议,生成参数放在 `generationConfig`
- Anthropic Messages 类:统一使用 `/v1/messages` 风格,支持 `model``messages``system``max_tokens` 等常见参数
- DeepSeek作为快速添加厂商默认走 OpenAI 兼容协议
- MiniMax作为快速添加厂商默认走 Anthropic Messages 兼容协议
## 已完成
@@ -50,16 +50,13 @@
### 管理后台
- 模型管理表单改为完整配置面板
- 新增供应商预设
- OpenAI
- DeepSeek
-义千问 DashScope
- Kimi / Moonshot
- 硅基流动 SiliconFlow
- 火山方舟 Ark
- Anthropic Claude
- Google Gemini
- 模型管理表单改为“快速添加 + 按协议添加”两段
- 新增快速添加
- DeepSeek填写 API Key选择 `deepseek-v4-pro``deepseek-v4-flash`
- MiniMax填写 API Key选择 MiniMax-M3、MiniMax-M2.7、MiniMax-M2.5 等模型。
-用添加只保留协议模板:
- OpenAI 兼容协议。
- Anthropic Messages 协议。
- 新增参数配置:
- 协议类型
- Base URL
@@ -88,12 +85,13 @@
| 编号 | 决策 | 原因 |
| --- | --- | --- |
| M-001 | 第三方 LLM 分为 OpenAI 兼容协议和原生协议两类。 | 国内多数供应商提供 OpenAI 兼容模式用统一协议可以减少重复代码Anthropic、Gemini 保留原生协议以避免参数结构不匹配。 |
| M-001 | 第三方 LLM 分为 OpenAI 兼容协议和 Anthropic Messages 协议两类。 | 这两类是当前 Agent/LLM 配置中最常见的接入形态,后台不再为每个供应商堆大量预设。 |
| M-002 | `apiUrl` 可以为空,优先由 `baseUrl + apiType` 自动拼接。 | 后台人员更容易配置,只要填供应商 Base URL 和模型名即可;高级场景仍可用 API URL 覆盖。 |
| M-003 | 编辑模型时 API Key 留空表示不修改。 | 避免密钥频繁暴露和重复粘贴,降低误改风险。 |
| M-004 | 高级参数使用 JSON 对象透传。 | 保留不同供应商的扩展能力,同时避免每个小参数都单独改数据库结构。 |
| M-005 | Anthropic、Gemini 预设默认不强行填采样参数。 | 部分模型对非默认采样参数较敏感,预设应先保证能通,再由后台人员按需调整。 |
| M-005 | Anthropic Messages 协议模板默认不强行填采样参数。 | 部分模型对非默认采样参数较敏感,协议模板应先保证能通,再由后台人员按需调整。 |
| M-006 | 模型测试接口直接调用真实供应商。 | 这是后台配置验收动作,应该真实发现 Key、URL、模型名和参数错误。 |
| M-007 | DeepSeek 和 MiniMax 作为快速添加厂商。 | 一期高频模型供应商不需要后台人员理解协议细节,只填 API Key 和模型即可。 |
## 当前边界
@@ -101,6 +99,7 @@
- 流式输出字段已保存,但当前真实模型调用仍以非流式方式完成,用户端 SSE 分块由后端模拟输出。
- 不同供应商的费用、限流、区域和模型上下文窗口仍需要运营配置时自行确认。
- `extra_params` 由后台人员自行维护,系统只校验 JSON 对象格式,不校验供应商语义。
- 后端仍保留 Gemini generateContent 适配代码,但当前后台入口不再暴露 Gemini 协议模板;如后续确认为高频供应商,再单独设计快速入口。
## 本次验证