mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
🌐 feat: OpenRouter Web Search (#9853)
* 🌐 feat: OpenRouter Web Search
- Added tests for handling web_search parameter with OpenRouter in various scenarios.
- Implemented logic to manage web_search in modelOptions and addParams/dropParams.
- Ensured correct configuration of llmConfig and modelKwargs for OpenRouter, including handling of plugins.
- Improved overall integration of OpenRouter with OpenAI API, ensuring expected behavior across different configurations.
* chore: bump @librechat/agents to v2.4.81
This commit is contained in:
parent
823015160c
commit
3d7eaf0fcc
5 changed files with 128 additions and 8 deletions
|
|
@ -49,7 +49,7 @@
|
||||||
"@langchain/google-vertexai": "^0.2.13",
|
"@langchain/google-vertexai": "^0.2.13",
|
||||||
"@langchain/openai": "^0.5.18",
|
"@langchain/openai": "^0.5.18",
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@librechat/agents": "^2.4.80",
|
"@librechat/agents": "^2.4.81",
|
||||||
"@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
|
|
@ -65,7 +65,7 @@
|
||||||
"@langchain/google-vertexai": "^0.2.13",
|
"@langchain/google-vertexai": "^0.2.13",
|
||||||
"@langchain/openai": "^0.5.18",
|
"@langchain/openai": "^0.5.18",
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@librechat/agents": "^2.4.80",
|
"@librechat/agents": "^2.4.81",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
|
|
@ -21910,9 +21910,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@librechat/agents": {
|
"node_modules/@librechat/agents": {
|
||||||
"version": "2.4.80",
|
"version": "2.4.81",
|
||||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.80.tgz",
|
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.81.tgz",
|
||||||
"integrity": "sha512-+GlWHMrgiwkptnUip7jJrz9oAQSz4+uJNoa+X6zR9W6x90NnknFwoQsXhQlnXoDGW75uGb+U7KI9mQz5H/YL6Q==",
|
"integrity": "sha512-uPepwOepQS03NJg9jzLvYGonyewy33QDB7iENKHooO8+6eIOv2QC4gm1k/fYKgsIfBfng2caRmn532UTrkE3rQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@langchain/anthropic": "^0.3.26",
|
"@langchain/anthropic": "^0.3.26",
|
||||||
|
|
@ -51641,7 +51641,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@langchain/core": "^0.3.62",
|
"@langchain/core": "^0.3.62",
|
||||||
"@librechat/agents": "^2.4.80",
|
"@librechat/agents": "^2.4.81",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@langchain/core": "^0.3.62",
|
"@langchain/core": "^0.3.62",
|
||||||
"@librechat/agents": "^2.4.80",
|
"@librechat/agents": "^2.4.81",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
|
|
|
||||||
|
|
@ -687,6 +687,82 @@ describe('getOpenAIConfig', () => {
|
||||||
expect(result.provider).toBe('openrouter');
|
expect(result.provider).toBe('openrouter');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle web_search with OpenRouter using plugins format', () => {
|
||||||
|
const modelOptions = {
|
||||||
|
model: 'gpt-4',
|
||||||
|
web_search: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getOpenAIConfig(mockApiKey, {
|
||||||
|
reverseProxyUrl: 'https://openrouter.ai/api/v1',
|
||||||
|
modelOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should use plugins format for OpenRouter, not tools
|
||||||
|
expect(result.llmConfig.modelKwargs).toEqual({
|
||||||
|
plugins: [{ id: 'web' }],
|
||||||
|
});
|
||||||
|
expect(result.tools).toEqual([]);
|
||||||
|
// Should NOT set useResponsesApi for OpenRouter
|
||||||
|
expect(result.llmConfig.useResponsesApi).toBeUndefined();
|
||||||
|
expect(result.provider).toBe('openrouter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle web_search false with OpenRouter', () => {
|
||||||
|
const modelOptions = {
|
||||||
|
model: 'gpt-4',
|
||||||
|
web_search: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getOpenAIConfig(mockApiKey, {
|
||||||
|
reverseProxyUrl: 'https://openrouter.ai/api/v1',
|
||||||
|
modelOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should not have plugins when web_search is false
|
||||||
|
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||||
|
expect(result.tools).toEqual([]);
|
||||||
|
expect(result.provider).toBe('openrouter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle web_search with OpenRouter from addParams', () => {
|
||||||
|
const addParams = {
|
||||||
|
web_search: true,
|
||||||
|
customParam: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getOpenAIConfig(mockApiKey, {
|
||||||
|
reverseProxyUrl: 'https://openrouter.ai/api/v1',
|
||||||
|
addParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should use plugins format and include other params
|
||||||
|
expect(result.llmConfig.modelKwargs).toEqual({
|
||||||
|
plugins: [{ id: 'web' }],
|
||||||
|
customParam: 'value',
|
||||||
|
});
|
||||||
|
expect(result.tools).toEqual([]);
|
||||||
|
expect(result.provider).toBe('openrouter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle web_search with OpenRouter and dropParams', () => {
|
||||||
|
const modelOptions = {
|
||||||
|
model: 'gpt-4',
|
||||||
|
web_search: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getOpenAIConfig(mockApiKey, {
|
||||||
|
reverseProxyUrl: 'https://openrouter.ai/api/v1',
|
||||||
|
modelOptions,
|
||||||
|
dropParams: ['web_search'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// dropParams should disable web_search even for OpenRouter
|
||||||
|
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||||
|
expect(result.tools).toEqual([]);
|
||||||
|
expect(result.provider).toBe('openrouter');
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle OpenRouter with reasoning params', () => {
|
it('should handle OpenRouter with reasoning params', () => {
|
||||||
const modelOptions = {
|
const modelOptions = {
|
||||||
reasoning_effort: ReasoningEffort.high,
|
reasoning_effort: ReasoningEffort.high,
|
||||||
|
|
@ -995,6 +1071,45 @@ describe('getOpenAIConfig', () => {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle all configuration with OpenRouter and web_search', () => {
|
||||||
|
const complexConfig = {
|
||||||
|
modelOptions: {
|
||||||
|
model: 'gpt-4-turbo',
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 2000,
|
||||||
|
verbosity: Verbosity.medium,
|
||||||
|
reasoning_effort: ReasoningEffort.high,
|
||||||
|
web_search: true,
|
||||||
|
},
|
||||||
|
reverseProxyUrl: 'https://openrouter.ai/api/v1',
|
||||||
|
headers: { 'X-Custom': 'value' },
|
||||||
|
streaming: false,
|
||||||
|
addParams: {
|
||||||
|
customParam: 'custom-value',
|
||||||
|
temperature: 0.8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getOpenAIConfig(mockApiKey, complexConfig);
|
||||||
|
|
||||||
|
expect(result.llmConfig).toMatchObject({
|
||||||
|
model: 'gpt-4-turbo',
|
||||||
|
temperature: 0.8,
|
||||||
|
streaming: false,
|
||||||
|
include_reasoning: true, // OpenRouter specific
|
||||||
|
});
|
||||||
|
// Should NOT have useResponsesApi for OpenRouter
|
||||||
|
expect(result.llmConfig.useResponsesApi).toBeUndefined();
|
||||||
|
expect(result.llmConfig.maxTokens).toBe(2000);
|
||||||
|
expect(result.llmConfig.modelKwargs).toEqual({
|
||||||
|
verbosity: Verbosity.medium,
|
||||||
|
customParam: 'custom-value',
|
||||||
|
plugins: [{ id: 'web' }], // OpenRouter web search format
|
||||||
|
});
|
||||||
|
expect(result.tools).toEqual([]); // No tools for OpenRouter web search
|
||||||
|
expect(result.provider).toBe('openrouter');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Real Usage Integration Tests', () => {
|
describe('Real Usage Integration Tests', () => {
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,12 @@ export function getOpenAILLMConfig({
|
||||||
enableWebSearch = false;
|
enableWebSearch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableWebSearch) {
|
if (useOpenRouter && enableWebSearch) {
|
||||||
|
/** OpenRouter expects web search as a plugins parameter */
|
||||||
|
modelKwargs.plugins = [{ id: 'web' }];
|
||||||
|
hasModelKwargs = true;
|
||||||
|
} else if (enableWebSearch) {
|
||||||
|
/** Standard OpenAI web search uses tools API */
|
||||||
llmConfig.useResponsesApi = true;
|
llmConfig.useResponsesApi = true;
|
||||||
tools.push({ type: 'web_search_preview' });
|
tools.push({ type: 'web_search_preview' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue