feat(channels): 添加默认参数功能以增强模型配置灵活性

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Jeff GuandCopilot committed 2026-04-27 16:20:43 +08:00
1 parent fa5fed56d9
commit 315e995656
9 files changed
+469 -179

No files matched your search

+11 -2
View File
@@ -157,6 +157,13 @@
"modelConfigDesc": "External calls use the model name; the system maps it to the channel model ID.", "modelConfigDesc": "External calls use the model name; the system maps it to the channel model ID.",
"modelId": "Model ID", "modelId": "Model ID",
"modelName": "Model Name", "modelName": "Model Name",
"defaultParams": "Params",
"defaultParamsEmpty": "Set Params",
"defaultParamsConfigured": "{{count}} set",
"defaultParamsDialogTitle": "Edit Default Parameters",
"defaultParamsDialogDesc": "Enter a JSON object. Parameters explicitly sent by callers override matching defaults here.",
"defaultParamsPlaceholder": "{\n \"temperature\": 0.7,\n \"top_p\": 0.9,\n \"reasoning\": { \"effort\": \"medium\" }\n}",
"formatJson": "Format",
"modelStatusDelete": "Status / Delete", "modelStatusDelete": "Status / Delete",
"modelIdPlaceholder": "Model ID, e.g. gpt-4.1-mini", "modelIdPlaceholder": "Model ID, e.g. gpt-4.1-mini",
"modelNamePlaceholder": "Model name, defaults to model ID", "modelNamePlaceholder": "Model name, defaults to model ID",
@@ -173,7 +180,7 @@
"noMatchingModels": "No matching models", "noMatchingModels": "No matching models",
"saveChannel": "Save Channel", "saveChannel": "Save Channel",
"jsonConfig": "JSON Config", "jsonConfig": "JSON Config",
"modelJsonStructure": "JSON array with `id`, `name`, `enabled` fields per item.", "modelJsonStructure": "JSON array with `id`, `name`, `enabled`, and optional `default_params` object per item.",
"fillRequired": "Please fill in all required fields", "fillRequired": "Please fill in all required fields",
"fillChannelKey": "Please enter a channel key", "fillChannelKey": "Please enter a channel key",
"fillTypeEndpointKey": "Please fill in channel type, API endpoint, and API key first", "fillTypeEndpointKey": "Please fill in channel type, API endpoint, and API key first",
@@ -186,7 +193,9 @@
"modelJsonInvalid": "Invalid model JSON format", "modelJsonInvalid": "Invalid model JSON format",
"modelJsonMustBeArray": "Model JSON must be an array", "modelJsonMustBeArray": "Model JSON must be an array",
"modelJsonItemMustBeObject": "Model JSON items must be objects", "modelJsonItemMustBeObject": "Model JSON items must be objects",
"modelJsonIdEmpty": "Model ID in JSON cannot be empty" "modelJsonIdEmpty": "Model ID in JSON cannot be empty",
"defaultParamsJsonInvalid": "Invalid default parameters JSON",
"defaultParamsJsonMustBeObject": "Default parameters must be a JSON object"
} }
}, },
"tokens": { "tokens": {
+11 -2
View File
@@ -157,6 +157,13 @@
"modelConfigDesc": "外部使用模型名称调用,程序会自动映射到渠道模型 ID。", "modelConfigDesc": "外部使用模型名称调用,程序会自动映射到渠道模型 ID。",
"modelId": "模型 ID", "modelId": "模型 ID",
"modelName": "模型名称", "modelName": "模型名称",
"defaultParams": "参数",
"defaultParamsEmpty": "设置参数",
"defaultParamsConfigured": "已设置 {{count}} 项",
"defaultParamsDialogTitle": "编辑默认参数",
"defaultParamsDialogDesc": "输入 JSON 对象;调用接口时显式传入的同名参数会覆盖这里的默认值。",
"defaultParamsPlaceholder": "{\n \"temperature\": 0.7,\n \"top_p\": 0.9,\n \"reasoning\": { \"effort\": \"medium\" }\n}",
"formatJson": "格式化",
"modelStatusDelete": "状态 / 删除", "modelStatusDelete": "状态 / 删除",
"modelIdPlaceholder": "模型ID,例如:gpt-4.1-mini", "modelIdPlaceholder": "模型ID,例如:gpt-4.1-mini",
"modelNamePlaceholder": "模型名称,默认等于模型ID", "modelNamePlaceholder": "模型名称,默认等于模型ID",
@@ -173,7 +180,7 @@
"noMatchingModels": "没有匹配的模型", "noMatchingModels": "没有匹配的模型",
"saveChannel": "保存渠道", "saveChannel": "保存渠道",
"jsonConfig": "JSON 配置", "jsonConfig": "JSON 配置",
"modelJsonStructure": "JSON 结构为数组,每项包含 `id`、`name`、`enabled`。", "modelJsonStructure": "JSON 结构为数组,每项包含 `id`、`name`、`enabled`,可选 `default_params` 对象。",
"fillRequired": "请填写所有必填字段", "fillRequired": "请填写所有必填字段",
"fillChannelKey": "请填写渠道标识", "fillChannelKey": "请填写渠道标识",
"fillTypeEndpointKey": "请先填写渠道类型、API 端点和 API 密钥", "fillTypeEndpointKey": "请先填写渠道类型、API 端点和 API 密钥",
@@ -186,7 +193,9 @@
"modelJsonInvalid": "模型 JSON 格式错误", "modelJsonInvalid": "模型 JSON 格式错误",
"modelJsonMustBeArray": "模型 JSON 必须是数组", "modelJsonMustBeArray": "模型 JSON 必须是数组",
"modelJsonItemMustBeObject": "模型 JSON 项必须是对象", "modelJsonItemMustBeObject": "模型 JSON 项必须是对象",
"modelJsonIdEmpty": "模型 JSON 中的 id 不能为空" "modelJsonIdEmpty": "模型 JSON 中的 id 不能为空",
"defaultParamsJsonInvalid": "默认参数 JSON 格式错误",
"defaultParamsJsonMustBeObject": "默认参数必须是 JSON 对象"
} }
}, },
"tokens": { "tokens": {
+11 -2
View File
@@ -157,6 +157,13 @@
"modelConfigDesc": "外部使用模型名稱呼叫,程式會自動對應到渠道模型 ID。", "modelConfigDesc": "外部使用模型名稱呼叫,程式會自動對應到渠道模型 ID。",
"modelId": "模型 ID", "modelId": "模型 ID",
"modelName": "模型名稱", "modelName": "模型名稱",
"defaultParams": "參數",
"defaultParamsEmpty": "設定參數",
"defaultParamsConfigured": "已設定 {{count}} 項",
"defaultParamsDialogTitle": "編輯預設參數",
"defaultParamsDialogDesc": "輸入 JSON 物件;呼叫介面時明確傳入的同名參數會覆蓋這裡的預設值。",
"defaultParamsPlaceholder": "{\n \"temperature\": 0.7,\n \"top_p\": 0.9,\n \"reasoning\": { \"effort\": \"medium\" }\n}",
"formatJson": "格式化",
"modelStatusDelete": "狀態 / 刪除", "modelStatusDelete": "狀態 / 刪除",
"modelIdPlaceholder": "模型ID,例如:gpt-4.1-mini", "modelIdPlaceholder": "模型ID,例如:gpt-4.1-mini",
"modelNamePlaceholder": "模型名稱,預設等於模型ID", "modelNamePlaceholder": "模型名稱,預設等於模型ID",
@@ -173,7 +180,7 @@
"noMatchingModels": "沒有匹配的模型", "noMatchingModels": "沒有匹配的模型",
"saveChannel": "儲存渠道", "saveChannel": "儲存渠道",
"jsonConfig": "JSON 設定", "jsonConfig": "JSON 設定",
"modelJsonStructure": "JSON 結構為陣列,每項包含 `id`、`name`、`enabled`。", "modelJsonStructure": "JSON 結構為陣列,每項包含 `id`、`name`、`enabled`,可選 `default_params` 物件。",
"fillRequired": "請填寫所有必填欄位", "fillRequired": "請填寫所有必填欄位",
"fillChannelKey": "請填寫渠道標識", "fillChannelKey": "請填寫渠道標識",
"fillTypeEndpointKey": "請先填寫渠道類型、API 端點和 API 金鑰", "fillTypeEndpointKey": "請先填寫渠道類型、API 端點和 API 金鑰",
@@ -186,7 +193,9 @@
"modelJsonInvalid": "模型 JSON 格式錯誤", "modelJsonInvalid": "模型 JSON 格式錯誤",
"modelJsonMustBeArray": "模型 JSON 必須是陣列", "modelJsonMustBeArray": "模型 JSON 必須是陣列",
"modelJsonItemMustBeObject": "模型 JSON 項必須是物件", "modelJsonItemMustBeObject": "模型 JSON 項必須是物件",
"modelJsonIdEmpty": "模型 JSON 中的 id 不能為空" "modelJsonIdEmpty": "模型 JSON 中的 id 不能為空",
"defaultParamsJsonInvalid": "預設參數 JSON 格式錯誤",
"defaultParamsJsonMustBeObject": "預設參數必須是 JSON 物件"
} }
}, },
"tokens": { "tokens": {
File diff suppressed because it is too large. Load diff
+1
View File
@@ -8,6 +8,7 @@ export interface ChannelModelMapping {
id: string id: string
name: string name: string
enabled?: boolean enabled?: boolean
default_params?: Record<string, unknown>
} }
export interface ChannelConfig { export interface ChannelConfig {
+1
View File
@@ -16,6 +16,7 @@ const ChannelModelSchema = z.object({
id: z.string().describe('Upstream model ID'), id: z.string().describe('Upstream model ID'),
name: z.string().describe('External model name exposed by this proxy'), name: z.string().describe('External model name exposed by this proxy'),
enabled: z.boolean().optional().describe('Whether this model is available for routing'), enabled: z.boolean().optional().describe('Whether this model is available for routing'),
default_params: z.record(z.string(), z.unknown()).optional().describe('Default request parameters applied when callers do not provide them'),
}); });
const parseFetchedModels = (payload: any): ChannelModelMapping[] => { const parseFetchedModels = (payload: any): ChannelModelMapping[] => {
+26 -3
View File
@@ -52,11 +52,28 @@ const normalizeApiKeys = (config: Partial<ChannelConfig>): string[] => {
return normalizedKeys; return normalizedKeys;
}; };
const normalizeDefaultParams = (defaultParams: unknown): Record<string, unknown> | undefined => {
if (!defaultParams || typeof defaultParams !== "object" || Array.isArray(defaultParams)) {
return undefined;
}
if (Object.keys(defaultParams).length === 0) {
return undefined;
}
return defaultParams as Record<string, unknown>;
};
const normalizeLegacyModels = (config: Partial<ChannelConfig>): ChannelModelMapping[] => { const normalizeLegacyModels = (config: Partial<ChannelConfig>): ChannelModelMapping[] => {
const normalizedModels: ChannelModelMapping[] = []; const normalizedModels: ChannelModelMapping[] = [];
const seenNames = new Set<string>(); const seenNames = new Set<string>();
const pushModel = (modelId: string, modelName?: string, enabled = DEFAULT_CHANNEL_MODEL_ENABLED) => { const pushModel = (
modelId: string,
modelName?: string,
enabled = DEFAULT_CHANNEL_MODEL_ENABLED,
defaultParams?: unknown
) => {
const id = modelId.trim(); const id = modelId.trim();
const name = (modelName || modelId).trim(); const name = (modelName || modelId).trim();
@@ -64,8 +81,14 @@ const normalizeLegacyModels = (config: Partial<ChannelConfig>): ChannelModelMapp
return; return;
} }
const normalizedDefaultParams = normalizeDefaultParams(defaultParams);
const normalizedModel: ChannelModelMapping = { id, name, enabled };
if (normalizedDefaultParams) {
normalizedModel.default_params = normalizedDefaultParams;
}
seenNames.add(name); seenNames.add(name);
normalizedModels.push({ id, name, enabled }); normalizedModels.push(normalizedModel);
}; };
if (Array.isArray(config.models)) { if (Array.isArray(config.models)) {
@@ -77,7 +100,7 @@ const normalizeLegacyModels = (config: Partial<ChannelConfig>): ChannelModelMapp
const id = typeof model.id === "string" ? model.id : ""; const id = typeof model.id === "string" ? model.id : "";
const name = typeof model.name === "string" ? model.name : id; const name = typeof model.name === "string" ? model.name : id;
const enabled = model.enabled !== false; const enabled = model.enabled !== false;
pushModel(id, name, enabled); pushModel(id, name, enabled, model.default_params);
} }
} }
+21 -1
View File
@@ -50,13 +50,33 @@ const cloneRequestBody = <T>(requestBody: T): T => {
return JSON.parse(JSON.stringify(requestBody)) as T; return JSON.parse(JSON.stringify(requestBody)) as T;
}; };
const getModelDefaultParams = (
mapping: ChannelModelMapping
): Record<string, unknown> | null => {
const defaultParams = mapping.default_params;
if (!defaultParams || typeof defaultParams !== "object" || Array.isArray(defaultParams)) {
return null;
}
return defaultParams;
};
const buildChannelRequestBody = ( const buildChannelRequestBody = (
requestBody: any, requestBody: any,
channel: ResolvedChannelCandidate channel: ResolvedChannelCandidate
) => { ) => {
const runtimeRequestBody = cloneRequestBody(requestBody); const runtimeRequestBody = cloneRequestBody(requestBody);
if (runtimeRequestBody && typeof runtimeRequestBody === "object") { if (runtimeRequestBody && typeof runtimeRequestBody === "object") {
runtimeRequestBody.model = channel.mapping.id; const defaultParams = getModelDefaultParams(channel.mapping);
const mergedRequestBody = defaultParams
? {
...cloneRequestBody(defaultParams),
...runtimeRequestBody,
}
: runtimeRequestBody;
mergedRequestBody.model = channel.mapping.id;
return mergedRequestBody;
} }
return runtimeRequestBody; return runtimeRequestBody;
}; };
Vendored
+1
View File
@@ -65,6 +65,7 @@ type ChannelModelMapping = {
id: string; id: string;
name: string; name: string;
enabled?: boolean; enabled?: boolean;
default_params?: Record<string, unknown>;
} }
type ChannelConfig = { type ChannelConfig = {