Add quick LLM provider setup
This commit is contained in:
@@ -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 Messages、Gemini generateContent 和常见第三方 LLM。</p></div>
|
||||
<div><h2>模型管理</h2><p>DeepSeek、MiniMax 可快速添加;其他供应商按 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="模型名">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 两种鉴权。 |
|
||||
|
||||
## 待确认决策
|
||||
|
||||
|
||||
@@ -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 协议模板;如后续确认为高频供应商,再单独设计快速入口。
|
||||
|
||||
## 本次验证
|
||||
|
||||
|
||||
Reference in New Issue
Block a user