diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py
index 7e65489368..2770b5f690 100644
--- a/astrbot/core/config/default.py
+++ b/astrbot/core/config/default.py
@@ -2027,7 +2027,7 @@
"description": "温度参数",
"hint": "控制输出的随机性,范围通常为 0-2。值越高越随机。",
"type": "float",
- "default": 0.6,
+ "default": 1.0,
"slider": {"min": 0, "max": 2, "step": 0.1},
},
"top_p": {
@@ -2035,7 +2035,7 @@
"description": "Top-p 采样",
"hint": "核采样参数,范围通常为 0-1。控制模型考虑的概率质量。",
"type": "float",
- "default": 1.0,
+ "default": 0.95,
"slider": {"min": 0, "max": 1, "step": 0.01},
},
"max_tokens": {
@@ -2046,6 +2046,11 @@
"default": 8192,
},
},
+ "default_disabled_keys": [
+ "temperature",
+ "top_p",
+ "max_tokens",
+ ],
},
"provider": {
"type": "string",
diff --git a/astrbot/core/provider/provider.py b/astrbot/core/provider/provider.py
index efe9e2e47e..227e95614e 100644
--- a/astrbot/core/provider/provider.py
+++ b/astrbot/core/provider/provider.py
@@ -74,6 +74,35 @@ def __init__(
super().__init__(provider_config)
self.provider_settings = provider_settings
+ def get_effective_custom_extra_body(self) -> dict:
+ """Get custom_extra_body with disabled keys filtered out.
+
+ Returns the custom_extra_body dict excluding any keys that are
+ listed in the _disabled_keys metadata field within the dict.
+ """
+ custom_extra_body = self.provider_config.get("custom_extra_body", {})
+ if not isinstance(custom_extra_body, dict):
+ return {}
+ disabled_keys = custom_extra_body.get("_disabled_keys", [])
+ if not isinstance(disabled_keys, list):
+ disabled_keys = []
+ return {
+ k: v
+ for k, v in custom_extra_body.items()
+ if k != "_disabled_keys" and k not in disabled_keys
+ }
+
+ def _merge_custom_extra_body(self, payloads: dict) -> dict:
+ """Merge effective custom_extra_body into payloads without overwriting existing keys.
+
+ Returns a shallow copy of payloads with custom_extra_body values merged in.
+ """
+ merged = dict(payloads)
+ for k, v in self.get_effective_custom_extra_body().items():
+ if k not in merged:
+ merged[k] = v
+ return merged
+
@abc.abstractmethod
def get_current_key(self) -> str:
raise NotImplementedError
diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py
index 5caef0b263..57378ca351 100644
--- a/astrbot/core/provider/sources/anthropic_source.py
+++ b/astrbot/core/provider/sources/anthropic_source.py
@@ -328,7 +328,14 @@ async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
else "auto"
}
- extra_body = self.provider_config.get("custom_extra_body", {})
+ extra_body = self.get_effective_custom_extra_body()
+ # Anthropic requires max_tokens - always include it even if disabled
+ full_custom_extra_body = self.provider_config.get("custom_extra_body", {})
+ if (
+ isinstance(full_custom_extra_body, dict)
+ and "max_tokens" in full_custom_extra_body
+ ):
+ extra_body["max_tokens"] = full_custom_extra_body["max_tokens"]
if "max_tokens" not in payloads:
payloads["max_tokens"] = 65536
@@ -422,7 +429,14 @@ async def _query_stream(
final_tool_calls = []
id = None
usage = TokenUsage()
- extra_body = self.provider_config.get("custom_extra_body", {})
+ extra_body = self.get_effective_custom_extra_body()
+ # Anthropic requires max_tokens - always include it even if disabled
+ full_custom_extra_body = self.provider_config.get("custom_extra_body", {})
+ if (
+ isinstance(full_custom_extra_body, dict)
+ and "max_tokens" in full_custom_extra_body
+ ):
+ extra_body["max_tokens"] = full_custom_extra_body["max_tokens"]
reasoning_content = ""
reasoning_signature = ""
diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py
index f38fcfc359..bad05de93e 100644
--- a/astrbot/core/provider/sources/gemini_source.py
+++ b/astrbot/core/provider/sources/gemini_source.py
@@ -605,6 +605,9 @@ def _process_content_parts(
async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
"""非流式请求 Gemini API"""
+ # Merge effective custom_extra_body into payloads
+ payloads = self._merge_custom_extra_body(payloads)
+
system_instruction = next(
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
None,
@@ -694,6 +697,9 @@ async def _query_stream(
tools: ToolSet | None,
) -> AsyncGenerator[LLMResponse, None]:
"""流式请求 Gemini API"""
+ # Merge effective custom_extra_body into payloads
+ payloads = self._merge_custom_extra_body(payloads)
+
system_instruction = next(
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
None,
diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py
index 1585846987..ba57b8c588 100644
--- a/astrbot/core/provider/sources/openai_source.py
+++ b/astrbot/core/provider/sources/openai_source.py
@@ -585,9 +585,8 @@ async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
del payloads[key]
# 读取并合并 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)
+ custom_extra_body = self.get_effective_custom_extra_body()
+ extra_body.update(custom_extra_body)
self._apply_provider_specific_extra_body_overrides(extra_body)
model = payloads.get("model", "").lower()
@@ -631,9 +630,7 @@ async def _query_stream(
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)
+ extra_body.update(self.get_effective_custom_extra_body())
to_del = []
for key in payloads:
diff --git a/dashboard/src/components/shared/ObjectEditor.vue b/dashboard/src/components/shared/ObjectEditor.vue
index a57a606a56..3b0432112d 100644
--- a/dashboard/src/components/shared/ObjectEditor.vue
+++ b/dashboard/src/components/shared/ObjectEditor.vue
@@ -110,7 +110,7 @@
{{ t('core.common.objectEditor.presets') }}
-
+
@@ -118,7 +118,7 @@
{{ resolveTemplateText(templateKey, 'hint', template.hint) }}
-
+
@@ -139,6 +140,7 @@
color="primary"
density="compact"
hide-details
+ :disabled="isTemplateKeyDisabled(templateKey)"
class="flex-grow-1"
>
@@ -158,20 +161,39 @@
@update:model-value="updateTemplateValue(templateKey, $event)"
density="compact"
hide-details
+ :disabled="isTemplateKeyDisabled(templateKey)"
color="primary"
>
-
-
- mdi-close
-
+
+
+
+
+ mdi-restore
+
+
+
+
+
+
+
+
@@ -277,6 +299,10 @@ const newKey = ref('')
const newValueType = ref('string')
const nextPairId = ref(0)
+// Disabled keys tracking
+const localDisabledKeys = ref([])
+const originalDisabledKeys = ref([])
+
// Template schema support
const templateSchema = computed(() => {
return props.itemMeta?.template_schema || {}
@@ -286,9 +312,20 @@ const hasTemplateSchema = computed(() => {
return Object.keys(templateSchema.value).length > 0
})
-// 计算要显示的键名
+// Default disabled keys from metadata
+const defaultDisabledKeys = computed(() => {
+ return props.itemMeta?.default_disabled_keys || []
+})
+
+// Keys that cannot be disabled
+const nonDisableableKeys = computed(() => {
+ return props.itemMeta?.non_disableable_keys || []
+})
+
+// 计算要显示的键名 (exclude _disabled_keys from display)
const displayKeys = computed(() => {
- return Object.keys(props.modelValue).slice(0, props.maxDisplayItems)
+ if (!props.modelValue) return []
+ return Object.keys(props.modelValue).filter(k => k !== '_disabled_keys').slice(0, props.maxDisplayItems)
})
// 分离模板字段和普通字段
@@ -318,7 +355,23 @@ function createPair({ key, value, type, slider, template, jsonError = '', _origi
function initializeLocalKeyValuePairs() {
localKeyValuePairs.value = []
nextPairId.value = 0
- for (const [key, value] of Object.entries(props.modelValue)) {
+
+ // Initialize disabled keys from modelValue or defaults
+ const existingDisabled = props.modelValue?._disabled_keys
+ if (Array.isArray(existingDisabled)) {
+ localDisabledKeys.value = existingDisabled.filter(k => !nonDisableableKeys.value.includes(k))
+ } else if (Object.keys(props.modelValue || {}).filter(k => k !== '_disabled_keys').length === 0 && defaultDisabledKeys.value.length > 0) {
+ // New/empty config: use default disabled keys
+ localDisabledKeys.value = defaultDisabledKeys.value.filter(k => !nonDisableableKeys.value.includes(k))
+ } else {
+ localDisabledKeys.value = []
+ }
+ originalDisabledKeys.value = [...localDisabledKeys.value]
+
+ for (const [key, value] of Object.entries(props.modelValue || {})) {
+ // Skip the internal _disabled_keys field
+ if (key === '_disabled_keys') continue
+
let _type = (typeof value) === 'object' ? 'json':(typeof value)
let _value = _type === 'json' ? JSON.stringify(value) : value
@@ -431,6 +484,43 @@ function isTemplateKeyAdded(templateKey) {
return localKeyValuePairs.value.some(pair => pair.key === templateKey)
}
+function isTemplateKeyDisabled(templateKey) {
+ return localDisabledKeys.value.includes(templateKey)
+}
+
+function isTemplateValueModified(templateKey) {
+ const template = templateSchema.value[templateKey]
+ if (!template || template.default === undefined) return false
+ const pair = localKeyValuePairs.value.find(p => p.key === templateKey)
+ if (!pair) return false
+ const type = template.type || 'string'
+ if (type === 'number' || type === 'float' || type === 'int') {
+ const pairNum = Number(pair.value)
+ const defaultNum = Number(template.default)
+ if (isNaN(pairNum) && isNaN(defaultNum)) return false
+ return pairNum !== defaultNum
+ }
+ return String(pair.value) !== String(template.default)
+}
+
+function toggleTemplateKeyDisabled(templateKey) {
+ const index = localDisabledKeys.value.indexOf(templateKey)
+ if (index >= 0) {
+ // Enable: remove from disabled list
+ localDisabledKeys.value.splice(index, 1)
+ } else {
+ // Disable: add to disabled list
+ localDisabledKeys.value.push(templateKey)
+ }
+}
+
+function resetTemplateKey(templateKey) {
+ const template = templateSchema.value[templateKey]
+ if (template && template.default !== undefined) {
+ updateTemplateValue(templateKey, template.default)
+ }
+}
+
function getTemplateValue(templateKey) {
const pair = localKeyValuePairs.value.find(pair => pair.key === templateKey)
if (pair) {
@@ -496,29 +586,25 @@ function confirmDialog() {
break
case 'float':
case 'number':
- // 尝试转换为数字,如果失败则保持原值(或设为默认值0)
convertedValue = Number(pair.value)
- // 可选:检查是否为有效数字,无效则设为0或报错
- // if (isNaN(convertedValue)) convertedValue = 0;
break
case 'bool':
case 'boolean':
- // 布尔值通常由 v-switch 正确处理,但为保险起见可以显式转换
- // 注意:在 JavaScript 中,只有严格的 false, 0, '', null, undefined, NaN 会被转换为 false
- // 这里直接赋值 pair.value 应该是安全的,因为 v-model 绑定的就是布尔值
- // convertedValue = Boolean(pair.value)
break
case 'json':
convertedValue = JSON.parse(pair.value)
break
case 'string':
default:
- // 默认转换为字符串
convertedValue = String(pair.value)
break
}
updatedValue[pair.key] = convertedValue
}
+ // Store disabled keys in the value if there are any
+ if (localDisabledKeys.value.length > 0) {
+ updatedValue['_disabled_keys'] = [...localDisabledKeys.value]
+ }
emit('update:modelValue', updatedValue)
dialog.value = false
}
@@ -526,6 +612,7 @@ function confirmDialog() {
function cancelDialog() {
// Reset to original state
localKeyValuePairs.value = originalKeyValuePairs.value.map(pair => ({ ...pair }))
+ localDisabledKeys.value = [...originalDisabledKeys.value]
dialog.value = false
}
@@ -550,7 +637,7 @@ function resolveTemplateText(templateKey, attr, fallback) {
transition: opacity 0.2s;
}
-.template-field-inactive {
- opacity: 0.8;
+.template-field-disabled {
+ opacity: 0.5;
}
diff --git a/dashboard/src/composables/useProviderModelConfigDialog.ts b/dashboard/src/composables/useProviderModelConfigDialog.ts
index 4d041423ed..bc29ed64ad 100644
--- a/dashboard/src/composables/useProviderModelConfigDialog.ts
+++ b/dashboard/src/composables/useProviderModelConfigDialog.ts
@@ -46,6 +46,12 @@ export function useProviderModelConfigDialog(options: UseProviderModelConfigDial
schema.provider.items[key].invisible = true
}
}
+
+ // Anthropic requires max_tokens - mark it as non-disableable
+ if (sourceType === 'anthropic_chat_completion' && schema.provider.items.custom_extra_body) {
+ schema.provider.items.custom_extra_body.non_disableable_keys = ['max_tokens']
+ }
+
return schema
})
diff --git a/dashboard/src/i18n/locales/en-US/core/common.json b/dashboard/src/i18n/locales/en-US/core/common.json
index 973feea542..cbdff33da9 100644
--- a/dashboard/src/i18n/locales/en-US/core/common.json
+++ b/dashboard/src/i18n/locales/en-US/core/common.json
@@ -115,6 +115,9 @@
"valueTypeLabel": "Value type",
"keyExists": "Key already exists",
"invalidJson": "Invalid JSON format",
+ "resetToDefault": "Reset to default",
+ "disableParam": "Disable injection",
+ "enableParam": "Enable injection",
"placeholders": {
"keyName": "Key",
"stringValue": "String value",
diff --git a/dashboard/src/i18n/locales/ru-RU/core/common.json b/dashboard/src/i18n/locales/ru-RU/core/common.json
index e9f0d4a112..59ea0b73a3 100644
--- a/dashboard/src/i18n/locales/ru-RU/core/common.json
+++ b/dashboard/src/i18n/locales/ru-RU/core/common.json
@@ -115,6 +115,9 @@
"valueTypeLabel": "Тип значения",
"keyExists": "Ключ уже существует",
"invalidJson": "Некорректный формат JSON",
+ "resetToDefault": "Сбросить по умолчанию",
+ "disableParam": "Отключить инъекцию",
+ "enableParam": "Включить инъекцию",
"placeholders": {
"keyName": "Ключ",
"stringValue": "Строка",
diff --git a/dashboard/src/i18n/locales/zh-CN/core/common.json b/dashboard/src/i18n/locales/zh-CN/core/common.json
index fa1a8bf8b6..fc389a7294 100644
--- a/dashboard/src/i18n/locales/zh-CN/core/common.json
+++ b/dashboard/src/i18n/locales/zh-CN/core/common.json
@@ -115,6 +115,9 @@
"valueTypeLabel": "值类型",
"keyExists": "键名已存在",
"invalidJson": "JSON 格式错误",
+ "resetToDefault": "还原默认值",
+ "disableParam": "禁用注入",
+ "enableParam": "启用注入",
"placeholders": {
"keyName": "键名",
"stringValue": "字符串值",