mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-02 14:20:18 +01:00
🧠 feat: Add reasoning_effort configuration for Bedrock models (#11991)
* 🧠 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:
parent
cde5079886
commit
e6b324b259
6 changed files with 326 additions and 9 deletions
|
|
@ -722,4 +722,66 @@ describe('initializeBedrock', () => {
|
|||
expect(amrf.effort).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bedrock reasoning_effort for Moonshot/ZAI models', () => {
|
||||
it('should map reasoning_effort to reasoning_config for Moonshot Kimi K2.5', async () => {
|
||||
const params = createMockParams({
|
||||
model_parameters: {
|
||||
model: 'moonshotai.kimi-k2.5',
|
||||
reasoning_effort: 'high',
|
||||
},
|
||||
});
|
||||
|
||||
const result = (await initializeBedrock(params)) as BedrockLLMConfigResult;
|
||||
const amrf = result.llmConfig.additionalModelRequestFields as Record<string, unknown>;
|
||||
|
||||
expect(amrf.reasoning_config).toBe('high');
|
||||
expect(amrf.reasoning_effort).toBeUndefined();
|
||||
expect(amrf.thinking).toBeUndefined();
|
||||
expect(amrf.anthropic_beta).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should map reasoning_effort to reasoning_config for ZAI GLM', async () => {
|
||||
const params = createMockParams({
|
||||
model_parameters: {
|
||||
model: 'zai.glm-4.7',
|
||||
reasoning_effort: 'medium',
|
||||
},
|
||||
});
|
||||
|
||||
const result = (await initializeBedrock(params)) as BedrockLLMConfigResult;
|
||||
const amrf = result.llmConfig.additionalModelRequestFields as Record<string, unknown>;
|
||||
|
||||
expect(amrf.reasoning_config).toBe('medium');
|
||||
expect(amrf.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not include reasoning_config when reasoning_effort is unset', async () => {
|
||||
const params = createMockParams({
|
||||
model_parameters: {
|
||||
model: 'moonshotai.kimi-k2.5',
|
||||
reasoning_effort: '',
|
||||
},
|
||||
});
|
||||
|
||||
const result = (await initializeBedrock(params)) as BedrockLLMConfigResult;
|
||||
|
||||
expect(result.llmConfig.additionalModelRequestFields).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not map reasoning_effort to reasoning_config for Anthropic models', async () => {
|
||||
const params = createMockParams({
|
||||
model_parameters: {
|
||||
model: 'anthropic.claude-opus-4-6-v1',
|
||||
reasoning_effort: 'high',
|
||||
},
|
||||
});
|
||||
|
||||
const result = (await initializeBedrock(params)) as BedrockLLMConfigResult;
|
||||
const amrf = result.llmConfig.additionalModelRequestFields as Record<string, unknown>;
|
||||
|
||||
expect(amrf.reasoning_config).toBeUndefined();
|
||||
expect(amrf.thinking).toEqual({ type: 'adaptive' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -185,6 +185,12 @@ export enum AnthropicEffort {
|
|||
max = 'max',
|
||||
}
|
||||
|
||||
export enum BedrockReasoningConfig {
|
||||
low = 'low',
|
||||
medium = 'medium',
|
||||
high = 'high',
|
||||
}
|
||||
|
||||
export enum ReasoningSummary {
|
||||
none = '',
|
||||
auto = 'auto',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue