Skip to content

feat(provider): 为模型的temperature/top_p/max_tokens 增加参数开关#8422

Open
kawayiYokami wants to merge 2 commits into
AstrBotDevs:masterfrom
kawayiYokami:feat/disable-custom-params-injection
Open

feat(provider): 为模型的temperature/top_p/max_tokens 增加参数开关#8422
kawayiYokami wants to merge 2 commits into
AstrBotDevs:masterfrom
kawayiYokami:feat/disable-custom-params-injection

Conversation

@kawayiYokami
Copy link
Copy Markdown
Contributor

@kawayiYokami kawayiYokami commented May 29, 2026

Motivation / 动机

当前 custom_extra_body 中的 temperature/top_p/max_tokens 默认注入到所有供应商请求中,存在以下问题:

  1. 默认 temperature=0.6 会导致 Kimi 等模型报错
  2. top_p=1.0 对绝大多数模型来说不是最佳值(最佳是0.95),且几乎所有现代模型推荐直接使用 temperature=1
  3. 多数模型 API 自带经过调优的默认参数,强行注入反而破坏模型最佳行为
  4. 原有 "X" 按钮语义不清,用户无法区分"删除"、"还原"还是"不注入"

结论:设置不如不设置,新供应商应默认不注入这些参数。

Modifications / 改动点

  • 新供应商默认不注入 temperature/top_p/max_tokens(通过 _disabled_keys 机制)
  • UI 中将原 "X" 按钮替换为"还原默认值"按钮 + checkbox 启用/禁用注入
  • Anthropic 供应商的 max_tokens checkbox 锁定不可禁用(API 强制要求)
  • Gemini 供应商补充了 custom_extra_body 支持(此前缺失)
  • 默认值调整:temperature 0.6→1.0,top_p 1.0→0.95(用户主动启用时的预设值)
  • 已有供应商配置不受影响,向后兼容

核心文件:

  • astrbot/core/provider/provider.py — 新增 get_effective_custom_extra_body() 方法

  • astrbot/core/provider/sources/openai_source.py — 使用新方法

  • astrbot/core/provider/sources/anthropic_source.py — 使用新方法 + max_tokens 强制注入

  • astrbot/core/provider/sources/gemini_source.py — 新增 custom_extra_body 支持

  • astrbot/core/config/default.py — 默认值和 metadata 调整

  • dashboard/src/components/shared/ObjectEditor.vue — UI 改造

  • dashboard/src/composables/useProviderModelConfigDialog.ts — Anthropic max_tokens 锁定

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

image

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 I have ensured that no new dependencies are introduced.
  • 😮 My changes do not introduce malicious code.

Summary by Sourcery

Introduce configurable disabling of custom parameter injection for model providers and update defaults to better align with provider-recommended behavior.

New Features:

  • Add support for marking specific custom_extra_body keys as disabled via a _disabled_keys list and filtering them in provider requests.
  • Expose UI controls in the object editor to reset template values to defaults and enable or disable individual preset parameters, with non-disableable keys support.
  • Extend Gemini provider to honor custom_extra_body overrides in both streaming and non-streaming requests.

Enhancements:

  • Adjust default temperature and top_p values and mark temperature/top_p/max_tokens as disabled by default for new provider configurations.
  • Ensure Anthropic providers always send max_tokens even if the parameter is disabled in the UI, and prevent disabling it in the configuration schema.
  • Hide the internal _disabled_keys field from display while preserving it in saved configurations.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. area:webui The bug / feature is about webui(dashboard) of astrbot. labels May 29, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The isTemplateValueModified helper always compares via Number(...), which may misbehave for non-numeric template types (e.g., booleans or string defaults); consider doing type-aware comparison based on template.type instead of forcing numeric casting.
  • The magic key '_disabled_keys' is now referenced in multiple places (Vue editor, provider base, and sources); consider centralizing it in a shared constant or helper to avoid typos and keep the semantics in sync across UI and backend.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `isTemplateValueModified` helper always compares via `Number(...)`, which may misbehave for non-numeric template types (e.g., booleans or string defaults); consider doing type-aware comparison based on `template.type` instead of forcing numeric casting.
- The magic key `'_disabled_keys'` is now referenced in multiple places (Vue editor, provider base, and sources); consider centralizing it in a shared constant or helper to avoid typos and keep the semantics in sync across UI and backend.

## Individual Comments

### Comment 1
<location path="dashboard/src/components/shared/ObjectEditor.vue" line_range="490-495" />
<code_context>
+  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
+  return Number(pair.value) !== Number(template.default)
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Numeric comparison in isTemplateValueModified can misbehave for non-numeric defaults (NaN comparisons will always look "modified").

`isTemplateValueModified` always coerces both values with `Number(...)`. For non-numeric defaults (string/boolean), this often produces `NaN`, and `NaN !== NaN` is always true, so the field will always appear "modified" and the reset button will never disable. Even for numeric templates, `Number('')` or malformed input can behave unexpectedly.

Since `template.type` is available, consider branching on it instead:
- numeric types: numeric comparison with explicit `NaN` handling
- other types: compare as strings (or deep-compare for structured values)

This keeps the reset state consistent with actual user changes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread dashboard/src/components/shared/ObjectEditor.vue Outdated
@kawayiYokami kawayiYokami force-pushed the feat/disable-custom-params-injection branch from 9128797 to 56304a7 Compare May 29, 2026 18:06
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism to disable specific keys in the custom_extra_body configuration for LLM providers, updating the backend provider sources and the frontend ObjectEditor component to support disabling, enabling, and resetting template parameters. Feedback on the changes includes addressing a bug in isTemplateValueModified where non-numeric values are incorrectly compared using Number(), adding null-safety guards for props.modelValue in ObjectEditor.vue to prevent runtime crashes, removing redundant type checks in openai_source.py, and avoiding in-place modification of payloads in gemini_source.py by refactoring the logic into a shared helper function.

Comment on lines +490 to +496
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') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当前 isTemplateValueModified 的实现对所有模板参数都使用了 Number(pair.value) !== Number(template.default)。然而,ObjectEditor 是一个通用组件,可以处理非数值类型(如字符串或 JSON)。对于非数值字符串,Number() 会返回 NaN。由于在 JavaScript 中 NaN !== NaN 总是为 true,这会导致该函数在字符串值未被修改时也错误地返回 true,从而使非数值参数的“还原默认值”按钮一直处于启用状态。我们应该根据模板的类型进行针对性的类型安全比较。

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
  if (template.type === 'int' || template.type === 'float' || template.type === 'number') {
    return Number(pair.value) !== Number(template.default)
  }
  if (template.type === 'bool' || template.type === 'boolean') {
    return Boolean(pair.value) !== Boolean(template.default)
  }
  return pair.value !== template.default
}

Comment on lines 326 to 328
const displayKeys = computed(() => {
return Object.keys(props.modelValue).slice(0, props.maxDisplayItems)
return Object.keys(props.modelValue).filter(k => k !== '_disabled_keys').slice(0, props.maxDisplayItems)
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

如果 props.modelValue 为 null 或 undefined,调用 Object.keys(props.modelValue) 将会抛出 TypeError 并导致组件崩溃。添加空值保护可以确保组件在模型值暂不可用时也能安全渲染。

const displayKeys = computed(() => {
  if (!props.modelValue) return []
  return Object.keys(props.modelValue).filter(k => k !== '_disabled_keys').slice(0, props.maxDisplayItems)
})

}
originalDisabledKeys.value = [...localDisabledKeys.value]

for (const [key, value] of Object.entries(props.modelValue)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

如果 props.modelValue 为 null 或 undefined,调用 Object.entries(props.modelValue) 将会抛出 TypeError。建议在此处添加 props.modelValue || {} 的空值保护,以防止潜在的运行时崩溃。

  for (const [key, value] of Object.entries(props.modelValue || {})) {

Comment on lines 588 to 590
custom_extra_body = self.get_effective_custom_extra_body()
if isinstance(custom_extra_body, dict):
extra_body.update(custom_extra_body)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

由于 get_effective_custom_extra_body() 保证会返回一个 dict(空字典或已过滤的字典),因此这里的 isinstance(custom_extra_body, dict) 类型检查是多余的。我们可以直接使用返回的字典来更新 extra_body,使代码更加简洁。

        # 读取并合并 custom_extra_body 配置
        extra_body.update(self.get_effective_custom_extra_body())

Comment on lines 634 to 636
custom_extra_body = self.get_effective_custom_extra_body()
if isinstance(custom_extra_body, dict):
extra_body.update(custom_extra_body)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

由于 get_effective_custom_extra_body() 保证会返回一个 dict,这里的 isinstance(custom_extra_body, dict) 类型检查是多余的。我们可以直接使用返回的字典来更新 extra_body,使代码更加简洁。

        # 读取并合并 custom_extra_body 配置
        extra_body.update(self.get_effective_custom_extra_body())

Comment on lines +608 to +612
# Merge effective custom_extra_body into payloads
custom_extra_body = self.get_effective_custom_extra_body()
for k, v in custom_extra_body.items():
if k not in payloads:
payloads[k] = v
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

直接在原处修改传入的 payloads 字典可能会对调用者或重试循环产生意料之外的副作用。更安全的做法是在合并自定义额外请求体参数之前,先对 payloads 进行浅拷贝。此外,由于此合并逻辑在多处重复,建议将其重构为一个共享的辅助函数以避免代码重复。

Suggested change
# Merge effective custom_extra_body into payloads
custom_extra_body = self.get_effective_custom_extra_body()
for k, v in custom_extra_body.items():
if k not in payloads:
payloads[k] = v
payloads = self._merge_custom_extra_body(payloads)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

Comment on lines +703 to +707
# Merge effective custom_extra_body into payloads
custom_extra_body = self.get_effective_custom_extra_body()
for k, v in custom_extra_body.items():
if k not in payloads:
payloads[k] = v
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

直接在原处修改传入的 payloads 字典可能会对调用者或重试循环产生意料之外的副作用。更安全的做法是在合并自定义额外请求体参数之前,先对 payloads 进行浅拷贝。此外,由于此合并逻辑在多处重复,建议将其重构为一个共享的辅助函数以避免代码重复。

Suggested change
# Merge effective custom_extra_body into payloads
custom_extra_body = self.get_effective_custom_extra_body()
for k, v in custom_extra_body.items():
if k not in payloads:
payloads[k] = v
payloads = self._merge_custom_extra_body(payloads)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

@kawayiYokami kawayiYokami changed the title feat: add disable injection toggle for custom params (temperature/top… feat(provider): add disable injection toggle for custom params (temperature/top… May 29, 2026
@kawayiYokami kawayiYokami changed the title feat(provider): add disable injection toggle for custom params (temperature/top… feat(provider): 为模型的temperature/top_p/max_tokens 增加参数开关 May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. area:webui The bug / feature is about webui(dashboard) of astrbot. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant