fix: separate Anthropic output and context limits

Direct Anthropic connections reused the configured context window as max_tokens, which could impose an unintended output value or send a provider-invalid request. Anthropic connections now expose an independent per-response maximum output setting, defaulting to 32K for the protocol-required field, while OpenAI direct requests remain without an added output token cap and direct tool rounds remain unmetered.

Release note: Anthropic 直连模型现在可单独配置单次最大输出,不再复用上下文窗口;OpenAI 直连请求和工具调用轮次不增加额外配额。
This commit is contained in:
mesalogo
2026-08-26 22:53:49 +08:00
parent 49700bc7cf
commit b20c10f820
13 changed files with 96 additions and 12 deletions
+6 -1
View File
@@ -1112,6 +1112,10 @@ describe('SettingsPanel runtime files', () => {
expect(contextWindow).toHaveValue(null)
expect(contextWindow).toHaveAttribute('min', '32')
fireEvent.change(contextWindow, { target: { value: '256' } })
const maximumOutput = screen.getByLabelText('Anthropic 最大输出')
expect(maximumOutput).toHaveValue(32)
expect(maximumOutput).toHaveAttribute('min', '1')
fireEvent.change(maximumOutput, { target: { value: '48' } })
fireEvent.click(screen.getByRole('button', { name: '保存设置' }))
await waitFor(() =>
@@ -1120,7 +1124,8 @@ describe('SettingsPanel runtime files', () => {
modelProfiles: expect.arrayContaining([
expect.objectContaining({
id: modelProfileId,
contextWindowTokens: 256_000
contextWindowTokens: 256_000,
maximumOutputTokens: 48_000
})
])
})
+34
View File
@@ -34,6 +34,7 @@ import type {
} from '../../shared/contracts'
import {
defaultContextCompressionSettings,
defaultAnthropicMaximumOutputTokens,
builtinEmbeddingConnectionId,
defaultModelProfileId as builtInDefaultModelProfileId,
defaultRuntimeSettings,
@@ -257,6 +258,9 @@ function toModelProfileDrafts(
return settings.modelProfiles.map((profile) => ({
...profile,
supportsImageInput: profile.supportsImageInput ?? false,
maximumOutputTokens:
profile.maximumOutputTokens ??
defaultAnthropicMaximumOutputTokens,
apiKey: '',
clearApiKey: false
}))
@@ -1285,6 +1289,7 @@ export function SettingsPanel({
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality: profile.imageGenerationQuality,
apiKey: profile.clearApiKey
? ({ action: 'clear' } as const)
@@ -1670,6 +1675,7 @@ export function SettingsPanel({
protocol: 'openai-chat-completions',
authentication: defaultRuntimeSettings.modelAuthentication,
supportsImageInput: defaultRuntimeSettings.supportsImageInput,
maximumOutputTokens: defaultAnthropicMaximumOutputTokens,
imageGenerationQuality:
defaultRuntimeSettings.imageGenerationQuality,
apiKeyConfigured: false,
@@ -3136,6 +3142,34 @@ export function SettingsPanel({
{t('model.profile.contextWindowDescription')}
</small>
</label>
{profile.protocol === 'anthropic-messages' && (
<label className="field">
<span>{t('model.profile.maximumOutput')}</span>
<input
aria-label={t('model.profile.maximumOutput')}
inputMode="numeric"
max={10_000}
min={1}
onChange={(event) => {
const value = event.target.valueAsNumber
updateModelProfile(profile.id, {
maximumOutputTokens: Number.isFinite(value)
? Math.round(value * 1_000)
: defaultAnthropicMaximumOutputTokens
})
}}
placeholder="32"
type="number"
value={
(profile.maximumOutputTokens ??
defaultAnthropicMaximumOutputTokens) / 1_000
}
/>
<small>
{t('model.profile.maximumOutputDescription')}
</small>
</label>
)}
</>
)}
{profile.protocol ===
@@ -721,6 +721,9 @@ export const settings = {
contextWindow: 'Context window (optional)',
contextWindowDescription:
'Enter 32K–10000K tokens. Leave blank when unknown. This value is used only for GoodBuddy local budget calculations.',
maximumOutput: 'Anthropic maximum output',
maximumOutputDescription:
'The Anthropic Messages protocol requires this value on every request. Enter 1K–10000K tokens. It limits only one model response, not tool rounds or cumulative usage.',
imageQuality: 'Image quality',
imageQualityAriaLabel: 'Image quality for {{name}}',
quality: {
@@ -662,6 +662,9 @@ export const settings = {
contextWindow: '上下文上限(可选)',
contextWindowDescription:
'以 K tokens 填写,范围为 32K–10000K。留空表示未知;此值仅用于 GoodBuddy 本地预算计算。',
maximumOutput: 'Anthropic 最大输出',
maximumOutputDescription:
'Anthropic Messages 协议要求每次请求提供该值。填写 1K–10000K tokens;它只限制单次模型响应,不限制工具调用轮次或累计用量。',
imageQuality: '图片质量',
imageQualityAriaLabel: '图片质量 {{name}}',
quality: {