From e841b6af882d5a129e381b4a8dd705767929fe2b Mon Sep 17 00:00:00 2001 From: Yokami <95584793+kawayiYokami@users.noreply.github.com> Date: Sat, 13 Sep 2025 13:23:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9C=A8=20WebUI=20?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=20OpenAI=20API=20extra=5Fbody=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0=20(#2719)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 支持OPENAI系 模型的自定义标头,以解决qwen模型无法使用的问题 * fix: 修复AI说的问题 * fix: 布尔开关向右对齐 --- astrbot/core/config/default.py | 20 ++ .../core/provider/sources/openai_source.py | 17 +- .../src/components/shared/AstrBotConfig.vue | 90 +++--- .../src/components/shared/AstrBotConfigV4.vue | 42 +-- .../src/components/shared/ObjectEditor.vue | 282 ++++++++++++++++++ 5 files changed, 388 insertions(+), 63 deletions(-) create mode 100644 dashboard/src/components/shared/ObjectEditor.vue diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 12b06bdcc..3563c04d9 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -599,6 +599,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.openai.com/v1", "timeout": 120, "model_config": {"model": "gpt-4o-mini", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], "hint": "也兼容所有与 OpenAI API 兼容的服务。", }, @@ -613,6 +614,7 @@ CONFIG_METADATA_2 = { "api_base": "", "timeout": 120, "model_config": {"model": "gpt-4o-mini", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "xAI": { @@ -625,6 +627,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.x.ai/v1", "timeout": 120, "model_config": {"model": "grok-2-latest", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "Anthropic": { @@ -654,6 +657,7 @@ CONFIG_METADATA_2 = { "key": ["ollama"], # ollama 的 key 默认是 ollama "api_base": "http://localhost:11434/v1", "model_config": {"model": "llama3.1-8b", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "LM Studio": { @@ -667,6 +671,7 @@ CONFIG_METADATA_2 = { "model_config": { "model": "llama-3.1-8b", }, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "Gemini(OpenAI兼容)": { @@ -682,6 +687,7 @@ CONFIG_METADATA_2 = { "model": "gemini-1.5-flash", "temperature": 0.4, }, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "Gemini": { @@ -722,6 +728,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.deepseek.com/v1", "timeout": 120, "model_config": {"model": "deepseek-chat", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "302.AI": { @@ -734,6 +741,7 @@ CONFIG_METADATA_2 = { "api_base": "https://api.302.ai/v1", "timeout": 120, "model_config": {"model": "gpt-4.1-mini", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "硅基流动": { @@ -749,6 +757,7 @@ CONFIG_METADATA_2 = { "model": "deepseek-ai/DeepSeek-V3", "temperature": 0.4, }, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "PPIO派欧云": { @@ -764,6 +773,7 @@ CONFIG_METADATA_2 = { "model": "deepseek/deepseek-r1", "temperature": 0.4, }, + "custom_extra_body": {}, }, "优云智算": { "id": "compshare", @@ -777,6 +787,7 @@ CONFIG_METADATA_2 = { "model_config": { "model": "moonshotai/Kimi-K2-Instruct", }, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "Kimi": { @@ -789,6 +800,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "api_base": "https://api.moonshot.cn/v1", "model_config": {"model": "moonshot-v1-8k", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "智谱 AI": { @@ -847,6 +859,7 @@ CONFIG_METADATA_2 = { "timeout": 120, "api_base": "https://api-inference.modelscope.cn/v1", "model_config": {"model": "Qwen/Qwen3-32B", "temperature": 0.4}, + "custom_extra_body": {}, "modalities": ["text", "image", "tool_use"], }, "FastGPT": { @@ -858,6 +871,7 @@ CONFIG_METADATA_2 = { "key": [], "api_base": "https://api.fastgpt.in/api/v1", "timeout": 60, + "custom_extra_body": {}, }, "Whisper(API)": { "id": "whisper", @@ -1102,6 +1116,12 @@ CONFIG_METADATA_2 = { "render_type": "checkbox", "hint": "模型支持的模态。如所填写的模型不支持图像,请取消勾选图像。", }, + "custom_extra_body": { + "description": "自定义请求体参数", + "type": "dict", + "items": {}, + "hint": "此处添加的键值对将被合并到发送给 API 的 extra_body 中。值可以是字符串、数字或布尔值。", + }, "provider": { "type": "string", "invisible": True, diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index ff2461ab0..467ae9f5b 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -99,12 +99,13 @@ class ProviderOpenAIOfficial(Provider): for key in to_del: del payloads[key] - model = payloads.get("model", "") - # 针对 qwen3 非 thinking 模型的特殊处理:非流式调用必须设置 enable_thinking=false - if "qwen3" in model.lower() and "thinking" not in model.lower(): - extra_body["enable_thinking"] = False + # 读取并合并 custom_extra_body 配置 + custom_extra_body = self.provider_config.get("custom_extra_body", {}) + if isinstance(custom_extra_body, dict): + extra_body.update(custom_extra_body) + # 针对 deepseek 模型的特殊处理:deepseek-reasoner调用必须移除 tools ,否则将被切换至 deepseek-chat - elif model == "deepseek-reasoner" and "tools" in payloads: + if model == "deepseek-reasoner" and "tools" in payloads: del payloads["tools"] completion = await self.client.chat.completions.create( @@ -137,6 +138,12 @@ class ProviderOpenAIOfficial(Provider): # 不在默认参数中的参数放在 extra_body 中 extra_body = {} + + # 读取并合并 custom_extra_body 配置 + custom_extra_body = self.provider_config.get("custom_extra_body", {}) + if isinstance(custom_extra_body, dict): + extra_body.update(custom_extra_body) + to_del = [] for key in payloads.keys(): if key not in self.default_params: diff --git a/dashboard/src/components/shared/AstrBotConfig.vue b/dashboard/src/components/shared/AstrBotConfig.vue index 4374c99a0..ea8a62b80 100644 --- a/dashboard/src/components/shared/AstrBotConfig.vue +++ b/dashboard/src/components/shared/AstrBotConfig.vue @@ -2,6 +2,7 @@ import { VueMonacoEditor } from '@guolao/vue-monaco-editor' import { ref, computed } from 'vue' import ListConfigItem from './ListConfigItem.vue' +import ObjectEditor from './ObjectEditor.vue' import ProviderSelector from './ProviderSelector.vue' import PersonaSelector from './PersonaSelector.vue' import KnowledgeBaseSelector from './KnowledgeBaseSelector.vue' @@ -80,7 +81,7 @@ function shouldShowItem(itemMeta, itemKey) { function hasVisibleItemsAfter(items, currentIndex) { const itemEntries = Object.entries(items) - + // 检查当前索引之后是否还有可见的配置项 for (let i = currentIndex + 1; i < itemEntries.length; i++) { const [itemKey, itemValue] = itemEntries[i] @@ -89,7 +90,7 @@ function hasVisibleItemsAfter(items, currentIndex) { return true } } - + return false } @@ -130,7 +131,7 @@ function hasVisibleItemsAfter(items, currentIndex) { - + - +
@@ -342,9 +350,9 @@ function hasVisibleItemsAfter(items, currentIndex) { - {{ metadata[metadataKey]?.type }} @@ -364,7 +372,7 @@ function hasVisibleItemsAfter(items, currentIndex) { class="config-field" hide-details > - + - + - + - + - + - - @@ -567,11 +575,11 @@ function hasVisibleItemsAfter(items, currentIndex) { .nested-object { padding-left: 8px; } - + .config-row { padding: 8px 0; } - + .property-info, .type-indicator, .config-input { padding: 4px; } diff --git a/dashboard/src/components/shared/AstrBotConfigV4.vue b/dashboard/src/components/shared/AstrBotConfigV4.vue index 228df2f0b..f0f23e1be 100644 --- a/dashboard/src/components/shared/AstrBotConfigV4.vue +++ b/dashboard/src/components/shared/AstrBotConfigV4.vue @@ -2,6 +2,7 @@ import { VueMonacoEditor } from '@guolao/vue-monaco-editor' import { ref, computed } from 'vue' import ListConfigItem from './ListConfigItem.vue' +import ObjectEditor from './ObjectEditor.vue' import ProviderSelector from './ProviderSelector.vue' import PersonaSelector from './PersonaSelector.vue' import KnowledgeBaseSelector from './KnowledgeBaseSelector.vue' @@ -102,7 +103,7 @@ function shouldShowItem(itemMeta, itemKey) { function hasVisibleItemsAfter(items, currentIndex) { const itemEntries = Object.entries(items) - + // 检查当前索引之后是否还有可见的配置项 for (let i = currentIndex + 1; i < itemEntries.length; i++) { const [itemKey, itemMeta] = itemEntries[i] @@ -110,7 +111,7 @@ function hasVisibleItemsAfter(items, currentIndex) { return true } } - + return false } @@ -188,13 +189,20 @@ function hasVisibleItemsAfter(items, currentIndex) { color="primary" inset density="compact" hide-details style="display: flex; justify-content: end;"> - + + + @@ -202,48 +210,48 @@ function hasVisibleItemsAfter(items, currentIndex) {
-
-
-
-
-
-
-
-
@@ -261,12 +269,12 @@ function hasVisibleItemsAfter(items, currentIndex) { 已选择的插件:
- {{ plugin === '*' ? '所有插件' : plugin }} diff --git a/dashboard/src/components/shared/ObjectEditor.vue b/dashboard/src/components/shared/ObjectEditor.vue new file mode 100644 index 000000000..055bf3310 --- /dev/null +++ b/dashboard/src/components/shared/ObjectEditor.vue @@ -0,0 +1,282 @@ + + + + + \ No newline at end of file