🧠 feat: Add reasoning_effort configuration for Bedrock models (#11991)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run

* 🧠 feat: Add reasoning_effort configuration for Bedrock models

- Introduced a new `reasoning_effort` setting in the Bedrock configuration, allowing users to specify the reasoning level for supported models.
- Updated the input parser to map `reasoning_effort` to `reasoning_config` for Moonshot and ZAI models, ensuring proper handling of reasoning levels.
- Enhanced tests to validate the mapping of `reasoning_effort` to `reasoning_config` and to ensure correct behavior for various model types, including Anthropic models.
- Updated translation files to include descriptions for the new configuration option.

* chore: Update translation keys for Bedrock reasoning configuration

- Renamed translation key from `com_endpoint_bedrock_reasoning_config` to `com_endpoint_bedrock_reasoning_effort` for consistency with the new configuration setting.
- Updated the parameter settings to reflect the change in the description key, ensuring accurate mapping in the application.

* 🧪 test: Enhance bedrockInputParser tests for reasoning_config handling

- Added tests to ensure that stale `reasoning_config` is stripped when switching models from Moonshot to Meta and ZAI to DeepSeek.
- Included additional tests to verify that `reasoning_effort` values of "none", "minimal", and "xhigh" do not forward to `reasoning_config` for Moonshot and ZAI models.
- Improved coverage for the bedrockInputParser functionality to ensure correct behavior across various model configurations.

* feat: Introduce Bedrock reasoning configuration and update input parser

- Added a new `BedrockReasoningConfig` enum to define reasoning levels: low, medium, and high.
- Updated the `bedrockInputParser` to utilize the new reasoning configuration, ensuring proper handling of `reasoning_effort` values.
- Enhanced logic to validate `reasoning_effort` against the defined configuration values before assigning to `reasoning_config`.
- Improved code clarity with additional comments and refactored conditions for better readability.
This commit is contained in:
Danny Avila 2026-02-28 15:02:09 -05:00 committed by GitHub
parent cde5079886
commit e6b324b259
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 326 additions and 9 deletions

View file

@ -688,5 +688,175 @@ describe('bedrockInputParser', () => {
expect(amrf.anthropic_beta).toBeDefined();
expect(Array.isArray(amrf.anthropic_beta)).toBe(true);
});
test('should strip stale reasoning_config when switching to Anthropic model', () => {
const staleConversationData = {
model: 'anthropic.claude-sonnet-4-6',
additionalModelRequestFields: {
reasoning_config: 'high',
},
};
const result = bedrockInputParser.parse(staleConversationData) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBeUndefined();
});
test('should strip stale reasoning_config when switching from Moonshot to Meta model', () => {
const staleData = {
model: 'meta.llama-3-1-70b',
additionalModelRequestFields: {
reasoning_config: 'high',
},
};
const result = bedrockInputParser.parse(staleData) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
test('should strip stale reasoning_config when switching from ZAI to DeepSeek model', () => {
const staleData = {
model: 'deepseek.deepseek-r1',
additionalModelRequestFields: {
reasoning_config: 'medium',
},
};
const result = bedrockInputParser.parse(staleData) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
});
describe('Bedrock reasoning_effort → reasoning_config for Moonshot/ZAI models', () => {
test('should map reasoning_effort to reasoning_config for moonshotai.kimi-k2.5', () => {
const input = {
model: 'moonshotai.kimi-k2.5',
reasoning_effort: 'high',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBe('high');
expect(amrf.reasoning_effort).toBeUndefined();
});
test('should map reasoning_effort to reasoning_config for moonshot.kimi-k2.5', () => {
const input = {
model: 'moonshot.kimi-k2.5',
reasoning_effort: 'medium',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBe('medium');
});
test('should map reasoning_effort to reasoning_config for zai.glm-4.7', () => {
const input = {
model: 'zai.glm-4.7',
reasoning_effort: 'high',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBe('high');
});
test('should map reasoning_effort "low" to reasoning_config for Moonshot model', () => {
const input = {
model: 'moonshotai.kimi-k2.5',
reasoning_effort: 'low',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBe('low');
});
test('should not include reasoning_config when reasoning_effort is unset (empty string)', () => {
const input = {
model: 'moonshotai.kimi-k2.5',
reasoning_effort: '',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
test('should not include reasoning_config when reasoning_effort is not provided', () => {
const input = {
model: 'moonshotai.kimi-k2.5',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
test('should not forward reasoning_effort "none" to reasoning_config', () => {
const result = bedrockInputParser.parse({
model: 'moonshotai.kimi-k2.5',
reasoning_effort: 'none',
}) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
test('should not forward reasoning_effort "minimal" to reasoning_config', () => {
const result = bedrockInputParser.parse({
model: 'moonshotai.kimi-k2.5',
reasoning_effort: 'minimal',
}) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
test('should not forward reasoning_effort "xhigh" to reasoning_config', () => {
const result = bedrockInputParser.parse({
model: 'zai.glm-4.7',
reasoning_effort: 'xhigh',
}) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
expect(amrf?.reasoning_config).toBeUndefined();
});
test('should not add reasoning_config to Anthropic models', () => {
const input = {
model: 'anthropic.claude-sonnet-4-6',
reasoning_effort: 'high',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBeUndefined();
expect(amrf.reasoning_effort).toBeUndefined();
});
test('should not add thinking or anthropic_beta to Moonshot models with reasoning_effort', () => {
const input = {
model: 'moonshotai.kimi-k2.5',
reasoning_effort: 'high',
};
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.thinking).toBeUndefined();
expect(amrf.thinkingBudget).toBeUndefined();
expect(amrf.anthropic_beta).toBeUndefined();
});
test('should pass reasoning_config through bedrockOutputParser', () => {
const parsed = bedrockInputParser.parse({
model: 'moonshotai.kimi-k2.5',
reasoning_effort: 'high',
}) as Record<string, unknown>;
const output = bedrockOutputParser(parsed);
const amrf = output.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBe('high');
});
test('should strip stale reasoning_config from additionalModelRequestFields for Anthropic models', () => {
const staleData = {
model: 'anthropic.claude-opus-4-6-v1',
additionalModelRequestFields: {
reasoning_config: 'high',
},
};
const result = bedrockInputParser.parse(staleData) as Record<string, unknown>;
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
expect(amrf.reasoning_config).toBeUndefined();
});
});
});

View file

@ -4,6 +4,8 @@ import * as s from './schemas';
const DEFAULT_ENABLED_MAX_TOKENS = 8192;
const DEFAULT_THINKING_BUDGET = 2000;
const bedrockReasoningConfigValues = new Set<string>(Object.values(s.BedrockReasoningConfig));
type ThinkingConfig = { type: 'enabled'; budget_tokens: number } | { type: 'adaptive' };
type AnthropicReasoning = {
@ -134,6 +136,7 @@ export const bedrockInputSchema = s.tConversationSchema
thinking: true,
thinkingBudget: true,
effort: true,
reasoning_effort: true,
promptCache: true,
/* Catch-all fields */
topK: true,
@ -178,6 +181,7 @@ export const bedrockInputParser = s.tConversationSchema
thinking: true,
thinkingBudget: true,
effort: true,
reasoning_effort: true,
promptCache: true,
/* Catch-all fields */
topK: true,
@ -256,6 +260,9 @@ export const bedrockInputParser = s.tConversationSchema
delete additionalFields.effort;
}
/** Anthropic uses 'effort' via output_config, not reasoning_config */
delete additionalFields.reasoning_effort;
if ((typedData.model as string).includes('anthropic.')) {
const betaHeaders = getBedrockAnthropicBetaHeaders(typedData.model as string);
if (betaHeaders.length > 0) {
@ -268,23 +275,37 @@ export const bedrockInputParser = s.tConversationSchema
delete additionalFields.effort;
delete additionalFields.output_config;
delete additionalFields.anthropic_beta;
const reasoningEffort = additionalFields.reasoning_effort;
delete additionalFields.reasoning_effort;
if (
typeof reasoningEffort === 'string' &&
bedrockReasoningConfigValues.has(reasoningEffort)
) {
additionalFields.reasoning_config = reasoningEffort;
}
}
const isAnthropicModel =
typeof typedData.model === 'string' && typedData.model.includes('anthropic.');
/** Strip stale anthropic_beta from previously-persisted additionalModelRequestFields */
/** Strip stale fields from previously-persisted additionalModelRequestFields */
if (
!isAnthropicModel &&
typeof typedData.additionalModelRequestFields === 'object' &&
typedData.additionalModelRequestFields != null
) {
const amrf = typedData.additionalModelRequestFields as Record<string, unknown>;
delete amrf.anthropic_beta;
delete amrf.thinking;
delete amrf.thinkingBudget;
delete amrf.effort;
delete amrf.output_config;
if (!isAnthropicModel) {
delete amrf.anthropic_beta;
delete amrf.thinking;
delete amrf.thinkingBudget;
delete amrf.effort;
delete amrf.output_config;
delete amrf.reasoning_config;
} else {
delete amrf.reasoning_config;
delete amrf.reasoning_effort;
}
}
/** Default promptCache for claude and nova models, if not defined */

View file

@ -530,6 +530,30 @@ const bedrock: Record<string, SettingDefinition> = {
showDefault: false,
columnSpan: 2,
},
reasoning_effort: {
key: 'reasoning_effort',
label: 'com_endpoint_reasoning_effort',
labelCode: true,
description: 'com_endpoint_bedrock_reasoning_effort',
descriptionCode: true,
type: 'enum',
default: ReasoningEffort.unset,
component: 'slider',
options: [
ReasoningEffort.unset,
ReasoningEffort.low,
ReasoningEffort.medium,
ReasoningEffort.high,
],
enumMappings: {
[ReasoningEffort.unset]: 'com_ui_off',
[ReasoningEffort.low]: 'com_ui_low',
[ReasoningEffort.medium]: 'com_ui_medium',
[ReasoningEffort.high]: 'com_ui_high',
},
optionType: 'model',
columnSpan: 4,
},
};
const mistral: Record<string, SettingDefinition> = {
@ -905,6 +929,34 @@ const bedrockGeneralCol2: SettingsConfiguration = [
librechat.fileTokenLimit,
];
const bedrockZAI: SettingsConfiguration = [
librechat.modelLabel,
librechat.promptPrefix,
librechat.maxContextTokens,
meta.temperature,
meta.topP,
librechat.resendFiles,
bedrock.region,
bedrock.reasoning_effort,
librechat.fileTokenLimit,
];
const bedrockZAICol1: SettingsConfiguration = [
baseDefinitions.model as SettingDefinition,
librechat.modelLabel,
librechat.promptPrefix,
];
const bedrockZAICol2: SettingsConfiguration = [
librechat.maxContextTokens,
meta.temperature,
meta.topP,
librechat.resendFiles,
bedrock.region,
bedrock.reasoning_effort,
librechat.fileTokenLimit,
];
const bedrockMoonshot: SettingsConfiguration = [
librechat.modelLabel,
bedrock.system,
@ -917,6 +969,7 @@ const bedrockMoonshot: SettingsConfiguration = [
baseDefinitions.stop,
librechat.resendFiles,
bedrock.region,
bedrock.reasoning_effort,
librechat.fileTokenLimit,
];
@ -936,6 +989,7 @@ const bedrockMoonshotCol2: SettingsConfiguration = [
bedrock.topP,
librechat.resendFiles,
bedrock.region,
bedrock.reasoning_effort,
librechat.fileTokenLimit,
];
@ -954,7 +1008,7 @@ export const paramSettings: Record<string, SettingsConfiguration | undefined> =
[`${EModelEndpoint.bedrock}-${BedrockProviders.Moonshot}`]: bedrockMoonshot,
[`${EModelEndpoint.bedrock}-${BedrockProviders.MoonshotAI}`]: bedrockMoonshot,
[`${EModelEndpoint.bedrock}-${BedrockProviders.OpenAI}`]: bedrockGeneral,
[`${EModelEndpoint.bedrock}-${BedrockProviders.ZAI}`]: bedrockGeneral,
[`${EModelEndpoint.bedrock}-${BedrockProviders.ZAI}`]: bedrockZAI,
[EModelEndpoint.google]: googleConfig,
};
@ -1008,7 +1062,10 @@ export const presetSettings: Record<
col2: bedrockMoonshotCol2,
},
[`${EModelEndpoint.bedrock}-${BedrockProviders.OpenAI}`]: bedrockGeneralColumns,
[`${EModelEndpoint.bedrock}-${BedrockProviders.ZAI}`]: bedrockGeneralColumns,
[`${EModelEndpoint.bedrock}-${BedrockProviders.ZAI}`]: {
col1: bedrockZAICol1,
col2: bedrockZAICol2,
},
[EModelEndpoint.google]: {
col1: googleCol1,
col2: googleCol2,

View file

@ -185,6 +185,12 @@ export enum AnthropicEffort {
max = 'max',
}
export enum BedrockReasoningConfig {
low = 'low',
medium = 'medium',
high = 'high',
}
export enum ReasoningSummary {
none = '',
auto = 'auto',