mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-15 12:16:33 +01:00
🔀 feat: update OpenRouter with new Reasoning config (#11993)
* fix: Update OpenRouter reasoning handling in LLM configuration - Modified the OpenRouter configuration to use a unified `reasoning` object instead of separate `reasoning_effort` and `include_reasoning` properties. - Updated tests to ensure that `reasoning_summary` is excluded from the reasoning object and that the configuration behaves correctly based on the presence of reasoning parameters. - Enhanced test coverage for OpenRouter-specific configurations, ensuring proper handling of various reasoning effort levels. * refactor: Improve OpenRouter reasoning handling in LLM configuration - Updated the handling of the `reasoning` object in the OpenRouter configuration to clarify the relationship between `reasoning_effort` and `include_reasoning`. - Enhanced comments to explain the behavior of the `reasoning` object and its compatibility with legacy parameters. - Ensured that the configuration correctly falls back to legacy behavior when no explicit reasoning effort is provided. * test: Enhance OpenRouter LLM configuration tests - Added a new test to verify the combination of web search plugins and reasoning object for OpenRouter configurations. - Updated existing tests to ensure proper handling of reasoning effort levels and fallback behavior when reasoning_effort is unset. - Improved test coverage for OpenRouter-specific configurations, ensuring accurate validation of reasoning parameters. * chore: Update @librechat/agents dependency to version 3.1.53 - Bumped the version of @librechat/agents in package-lock.json and related package.json files to ensure compatibility with the latest features and fixes. - Updated integrity hashes to reflect the new version.
This commit is contained in:
parent
e6b324b259
commit
826b494578
6 changed files with 110 additions and 17 deletions
|
|
@ -44,7 +44,7 @@
|
||||||
"@google/genai": "^1.19.0",
|
"@google/genai": "^1.19.0",
|
||||||
"@keyv/redis": "^4.3.3",
|
"@keyv/redis": "^4.3.3",
|
||||||
"@langchain/core": "^0.3.80",
|
"@langchain/core": "^0.3.80",
|
||||||
"@librechat/agents": "^3.1.52",
|
"@librechat/agents": "^3.1.53",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
|
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -59,7 +59,7 @@
|
||||||
"@google/genai": "^1.19.0",
|
"@google/genai": "^1.19.0",
|
||||||
"@keyv/redis": "^4.3.3",
|
"@keyv/redis": "^4.3.3",
|
||||||
"@langchain/core": "^0.3.80",
|
"@langchain/core": "^0.3.80",
|
||||||
"@librechat/agents": "^3.1.52",
|
"@librechat/agents": "^3.1.53",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
|
|
@ -11836,9 +11836,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@librechat/agents": {
|
"node_modules/@librechat/agents": {
|
||||||
"version": "3.1.52",
|
"version": "3.1.53",
|
||||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.1.52.tgz",
|
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.1.53.tgz",
|
||||||
"integrity": "sha512-Bg35zp+vEDZ0AEJQPZ+ukWb/UqBrsLcr3YQWRQpuvpftEgfQz0fHM5Wrxn6l5P7PvaD1ViolxoG44nggjCt7Hw==",
|
"integrity": "sha512-jK9JHIhQYgr+Ha2FhknEYQmS6Ft3/TGdYIlL6L6EtIq20SIA59r1DvQx/x9sd3wHoHkk6AZumMgqAUTTCaWBIA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.73.0",
|
"@anthropic-ai/sdk": "^0.73.0",
|
||||||
|
|
@ -43797,7 +43797,7 @@
|
||||||
"@google/genai": "^1.19.0",
|
"@google/genai": "^1.19.0",
|
||||||
"@keyv/redis": "^4.3.3",
|
"@keyv/redis": "^4.3.3",
|
||||||
"@langchain/core": "^0.3.80",
|
"@langchain/core": "^0.3.80",
|
||||||
"@librechat/agents": "^3.1.52",
|
"@librechat/agents": "^3.1.53",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||||
"@smithy/node-http-handler": "^4.4.5",
|
"@smithy/node-http-handler": "^4.4.5",
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
"@google/genai": "^1.19.0",
|
"@google/genai": "^1.19.0",
|
||||||
"@keyv/redis": "^4.3.3",
|
"@keyv/redis": "^4.3.3",
|
||||||
"@langchain/core": "^0.3.80",
|
"@langchain/core": "^0.3.80",
|
||||||
"@librechat/agents": "^3.1.52",
|
"@librechat/agents": "^3.1.53",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||||
"@smithy/node-http-handler": "^4.4.5",
|
"@smithy/node-http-handler": "^4.4.5",
|
||||||
|
|
|
||||||
|
|
@ -861,7 +861,7 @@ describe('getOpenAIConfig', () => {
|
||||||
expect(result.provider).toBe('openrouter');
|
expect(result.provider).toBe('openrouter');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle OpenRouter with reasoning params', () => {
|
it('should handle OpenRouter with reasoning params (no summary)', () => {
|
||||||
const modelOptions = {
|
const modelOptions = {
|
||||||
reasoning_effort: ReasoningEffort.high,
|
reasoning_effort: ReasoningEffort.high,
|
||||||
reasoning_summary: ReasoningSummary.detailed,
|
reasoning_summary: ReasoningSummary.detailed,
|
||||||
|
|
@ -872,10 +872,11 @@ describe('getOpenAIConfig', () => {
|
||||||
modelOptions,
|
modelOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// OpenRouter reasoning object should only include effort, not summary
|
||||||
expect(result.llmConfig.reasoning).toEqual({
|
expect(result.llmConfig.reasoning).toEqual({
|
||||||
effort: ReasoningEffort.high,
|
effort: ReasoningEffort.high,
|
||||||
summary: ReasoningSummary.detailed,
|
|
||||||
});
|
});
|
||||||
|
expect(result.llmConfig.include_reasoning).toBeUndefined();
|
||||||
expect(result.provider).toBe('openrouter');
|
expect(result.provider).toBe('openrouter');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1205,8 +1206,9 @@ describe('getOpenAIConfig', () => {
|
||||||
model: 'gpt-4-turbo',
|
model: 'gpt-4-turbo',
|
||||||
temperature: 0.8,
|
temperature: 0.8,
|
||||||
streaming: false,
|
streaming: false,
|
||||||
include_reasoning: true, // OpenRouter specific
|
reasoning: { effort: ReasoningEffort.high }, // OpenRouter reasoning object
|
||||||
});
|
});
|
||||||
|
expect(result.llmConfig.include_reasoning).toBeUndefined();
|
||||||
// Should NOT have useResponsesApi for OpenRouter
|
// Should NOT have useResponsesApi for OpenRouter
|
||||||
expect(result.llmConfig.useResponsesApi).toBeUndefined();
|
expect(result.llmConfig.useResponsesApi).toBeUndefined();
|
||||||
expect(result.llmConfig.maxTokens).toBe(2000);
|
expect(result.llmConfig.maxTokens).toBe(2000);
|
||||||
|
|
@ -1480,13 +1482,12 @@ describe('getOpenAIConfig', () => {
|
||||||
user: 'openrouter-user',
|
user: 'openrouter-user',
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
maxTokens: 4000,
|
maxTokens: 4000,
|
||||||
include_reasoning: true, // OpenRouter specific
|
|
||||||
reasoning: {
|
reasoning: {
|
||||||
effort: ReasoningEffort.high,
|
effort: ReasoningEffort.high,
|
||||||
summary: ReasoningSummary.detailed,
|
|
||||||
},
|
},
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
});
|
});
|
||||||
|
expect(result.llmConfig.include_reasoning).toBeUndefined();
|
||||||
expect(result.llmConfig.modelKwargs).toMatchObject({
|
expect(result.llmConfig.modelKwargs).toMatchObject({
|
||||||
top_k: 50,
|
top_k: 50,
|
||||||
repetition_penalty: 1.1,
|
repetition_penalty: 1.1,
|
||||||
|
|
|
||||||
|
|
@ -381,6 +381,23 @@ describe('getOpenAILLMConfig', () => {
|
||||||
expect(result.llmConfig).toHaveProperty('include_reasoning', true);
|
expect(result.llmConfig).toHaveProperty('include_reasoning', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should combine web search plugins and reasoning object for OpenRouter', () => {
|
||||||
|
const result = getOpenAILLMConfig({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
streaming: true,
|
||||||
|
useOpenRouter: true,
|
||||||
|
modelOptions: {
|
||||||
|
model: 'anthropic/claude-3-sonnet',
|
||||||
|
reasoning_effort: ReasoningEffort.high,
|
||||||
|
web_search: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.llmConfig).toHaveProperty('reasoning', { effort: ReasoningEffort.high });
|
||||||
|
expect(result.llmConfig).not.toHaveProperty('include_reasoning');
|
||||||
|
expect(result.llmConfig.modelKwargs).toHaveProperty('plugins', [{ id: 'web' }]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should disable web search via dropParams', () => {
|
it('should disable web search via dropParams', () => {
|
||||||
const result = getOpenAILLMConfig({
|
const result = getOpenAILLMConfig({
|
||||||
apiKey: 'test-api-key',
|
apiKey: 'test-api-key',
|
||||||
|
|
@ -575,7 +592,7 @@ describe('getOpenAILLMConfig', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('OpenRouter Configuration', () => {
|
describe('OpenRouter Configuration', () => {
|
||||||
it('should include include_reasoning for OpenRouter', () => {
|
it('should include include_reasoning for OpenRouter when no reasoning_effort set', () => {
|
||||||
const result = getOpenAILLMConfig({
|
const result = getOpenAILLMConfig({
|
||||||
apiKey: 'test-api-key',
|
apiKey: 'test-api-key',
|
||||||
streaming: true,
|
streaming: true,
|
||||||
|
|
@ -586,6 +603,71 @@ describe('getOpenAILLMConfig', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.llmConfig).toHaveProperty('include_reasoning', true);
|
expect(result.llmConfig).toHaveProperty('include_reasoning', true);
|
||||||
|
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use reasoning object for OpenRouter when reasoning_effort is set', () => {
|
||||||
|
const result = getOpenAILLMConfig({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
streaming: true,
|
||||||
|
useOpenRouter: true,
|
||||||
|
modelOptions: {
|
||||||
|
model: 'anthropic/claude-3-sonnet',
|
||||||
|
reasoning_effort: ReasoningEffort.high,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.llmConfig).toHaveProperty('reasoning', { effort: ReasoningEffort.high });
|
||||||
|
expect(result.llmConfig).not.toHaveProperty('include_reasoning');
|
||||||
|
expect(result.llmConfig).not.toHaveProperty('reasoning_effort');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exclude reasoning_summary from OpenRouter reasoning object', () => {
|
||||||
|
const result = getOpenAILLMConfig({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
streaming: true,
|
||||||
|
useOpenRouter: true,
|
||||||
|
modelOptions: {
|
||||||
|
model: 'anthropic/claude-3-sonnet',
|
||||||
|
reasoning_effort: ReasoningEffort.high,
|
||||||
|
reasoning_summary: ReasoningSummary.detailed,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.llmConfig).toHaveProperty('reasoning', { effort: ReasoningEffort.high });
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([ReasoningEffort.xhigh, ReasoningEffort.minimal, ReasoningEffort.none])(
|
||||||
|
'should support OpenRouter effort level: %s',
|
||||||
|
(effort) => {
|
||||||
|
const result = getOpenAILLMConfig({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
streaming: true,
|
||||||
|
useOpenRouter: true,
|
||||||
|
modelOptions: {
|
||||||
|
model: 'openai/o3-mini',
|
||||||
|
reasoning_effort: effort,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.llmConfig).toHaveProperty('reasoning', { effort });
|
||||||
|
expect(result.llmConfig).not.toHaveProperty('include_reasoning');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should fall back to include_reasoning when reasoning_effort is unset (empty string)', () => {
|
||||||
|
const result = getOpenAILLMConfig({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
streaming: true,
|
||||||
|
useOpenRouter: true,
|
||||||
|
modelOptions: {
|
||||||
|
model: 'anthropic/claude-3-sonnet',
|
||||||
|
reasoning_effort: ReasoningEffort.unset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.llmConfig).toHaveProperty('include_reasoning', true);
|
||||||
|
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { EModelEndpoint, removeNullishValues } from 'librechat-data-provider';
|
import { EModelEndpoint, removeNullishValues } from 'librechat-data-provider';
|
||||||
import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
|
import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
|
||||||
import type { SettingDefinition } from 'librechat-data-provider';
|
import type { SettingDefinition } from 'librechat-data-provider';
|
||||||
|
import type { OpenRouterReasoning } from '@librechat/agents';
|
||||||
import type { AzureOpenAIInput } from '@langchain/openai';
|
import type { AzureOpenAIInput } from '@langchain/openai';
|
||||||
import type { OpenAI } from 'openai';
|
import type { OpenAI } from 'openai';
|
||||||
import type * as t from '~/types';
|
import type * as t from '~/types';
|
||||||
|
|
@ -223,10 +224,19 @@ export function getOpenAILLMConfig({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useOpenRouter) {
|
if (useOpenRouter) {
|
||||||
llmConfig.include_reasoning = true;
|
if (hasReasoningParams({ reasoning_effort })) {
|
||||||
}
|
/**
|
||||||
|
* OpenRouter uses a `reasoning` object — `summary` is not supported.
|
||||||
if (
|
* ChatOpenRouter treats `reasoning` and `include_reasoning` as mutually exclusive:
|
||||||
|
* `include_reasoning` is legacy compat that maps to `{ enabled: true }` only when
|
||||||
|
* no `reasoning` object is present, so we intentionally omit it here.
|
||||||
|
*/
|
||||||
|
llmConfig.reasoning = { effort: reasoning_effort } as OpenRouterReasoning;
|
||||||
|
} else {
|
||||||
|
/** No explicit effort; fall back to legacy `include_reasoning` for reasoning token inclusion */
|
||||||
|
llmConfig.include_reasoning = true;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
hasReasoningParams({ reasoning_effort, reasoning_summary }) &&
|
hasReasoningParams({ reasoning_effort, reasoning_summary }) &&
|
||||||
(llmConfig.useResponsesApi === true ||
|
(llmConfig.useResponsesApi === true ||
|
||||||
(endpoint !== EModelEndpoint.openAI && endpoint !== EModelEndpoint.azureOpenAI))
|
(endpoint !== EModelEndpoint.openAI && endpoint !== EModelEndpoint.azureOpenAI))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue