mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 06:52:47 +00:00
🧭 feat: Add OpenRouter Prompt Cache Setting (#13029)
* feat: add OpenRouter prompt cache setting * fix: type OpenRouter schema lookup * fix: honor proxied OpenRouter prompt cache * refactor: flatten endpoint schema fallback * chore: Bump `@librechat/agents` to version 3.1.82 * fix: Default OpenRouter prompt cache params * test: Align OpenRouter config expectations * test: Update OpenRouter default cache expectation * fix: Align OpenRouter Detection * chore: Bump `@librechat/agents` to version 3.1.83 * docs: Remove OpenRouter prompt cache setup note * refactor: Use provider enum for OpenRouter defaults * style: Format OpenRouter defaults guard
This commit is contained in:
parent
0d5c2b339a
commit
8a654dc8b1
17 changed files with 487 additions and 38 deletions
|
|
@ -3,6 +3,7 @@ const axios = require('axios');
|
|||
const yaml = require('js-yaml');
|
||||
const keyBy = require('lodash/keyBy');
|
||||
const { loadYaml } = require('@librechat/api');
|
||||
const { Providers } = require('@librechat/agents');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const {
|
||||
configSchema,
|
||||
|
|
@ -17,6 +18,48 @@ const defaultConfigPath = path.resolve(projectRoot, 'librechat.yaml');
|
|||
|
||||
let i = 0;
|
||||
|
||||
const OPENROUTER_PROMPT_CACHE_DEFAULT = {
|
||||
key: 'promptCache',
|
||||
default: true,
|
||||
};
|
||||
|
||||
function includesOpenRouter(value) {
|
||||
return typeof value === 'string' && value.toLowerCase().includes(Providers.OPENROUTER);
|
||||
}
|
||||
|
||||
function isOpenRouterEndpoint(endpoint) {
|
||||
return includesOpenRouter(endpoint.name) || includesOpenRouter(endpoint.baseURL);
|
||||
}
|
||||
|
||||
function shouldPreserveCustomParams(customParams) {
|
||||
const defaultEndpoint = customParams?.defaultParamsEndpoint;
|
||||
return (
|
||||
defaultEndpoint && defaultEndpoint !== 'custom' && defaultEndpoint !== Providers.OPENROUTER
|
||||
);
|
||||
}
|
||||
|
||||
function addOpenRouterDefaults(endpoint) {
|
||||
if (!isOpenRouterEndpoint(endpoint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldPreserveCustomParams(endpoint.customParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const customParams = endpoint.customParams ?? {};
|
||||
const paramDefinitions = customParams.paramDefinitions ?? [];
|
||||
const hasPromptCache = paramDefinitions.some((param) => param.key === 'promptCache');
|
||||
|
||||
endpoint.customParams = {
|
||||
...customParams,
|
||||
defaultParamsEndpoint: Providers.OPENROUTER,
|
||||
paramDefinitions: hasPromptCache
|
||||
? paramDefinitions
|
||||
: [...paramDefinitions, OPENROUTER_PROMPT_CACHE_DEFAULT],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom configuration files and caches the object if the `cache` field at root is true.
|
||||
* Validation via parsing the config file with the config schema.
|
||||
|
|
@ -119,6 +162,8 @@ https://www.librechat.ai/docs/configuration/stt_tts`);
|
|||
}
|
||||
}
|
||||
|
||||
(customConfig.endpoints?.custom ?? []).forEach(addOpenRouterDefaults);
|
||||
|
||||
(customConfig.endpoints?.custom ?? [])
|
||||
.filter((endpoint) => endpoint.customParams)
|
||||
.forEach((endpoint) => parseCustomParams(endpoint.name, endpoint.customParams));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,19 @@ jest.mock('librechat-data-provider', () => {
|
|||
const actual = jest.requireActual('librechat-data-provider');
|
||||
return {
|
||||
...actual,
|
||||
paramSettings: { foo: {}, bar: {}, custom: {} },
|
||||
paramSettings: {
|
||||
foo: {},
|
||||
bar: {},
|
||||
custom: {},
|
||||
openrouter: [
|
||||
{
|
||||
key: 'promptCache',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
agentParamSettings: {
|
||||
custom: [],
|
||||
google: [
|
||||
|
|
@ -195,7 +207,8 @@ describe('loadCustomConfig', () => {
|
|||
};
|
||||
process.env.CONFIG_PATH = 'validConfig.yaml';
|
||||
loadYaml.mockReturnValueOnce(mockConfig);
|
||||
await loadCustomConfig();
|
||||
const result = await loadCustomConfig();
|
||||
expect(result).toEqual(mockConfig);
|
||||
});
|
||||
|
||||
it('should log the loaded custom config', async () => {
|
||||
|
|
@ -297,7 +310,7 @@ describe('loadCustomConfig', () => {
|
|||
it('throws an error when defaultParamsEndpoint is not provided', async () => {
|
||||
const malformedCustomParams = { defaultParamsEndpoint: undefined };
|
||||
await expect(loadCustomParams(malformedCustomParams)).rejects.toThrow(
|
||||
'defaultParamsEndpoint of "Google" endpoint is invalid. Valid options are foo, bar, custom, google',
|
||||
'defaultParamsEndpoint of "Google" endpoint is invalid. Valid options are foo, bar, custom, openrouter, google',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -340,5 +353,109 @@ describe('loadCustomConfig', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds OpenRouter promptCache defaults when custom endpoint name is OpenRouter', async () => {
|
||||
const openRouterConfig = {
|
||||
version: '1.0',
|
||||
cache: false,
|
||||
endpoints: {
|
||||
custom: [
|
||||
{
|
||||
name: 'OpenRouter',
|
||||
apiKey: 'user_provided',
|
||||
baseURL: 'https://proxy.example.com/v1',
|
||||
models: {
|
||||
default: ['anthropic/claude-sonnet-4.6'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
loadYaml.mockReturnValue(openRouterConfig);
|
||||
|
||||
const parsedConfig = await loadCustomConfig();
|
||||
expect(parsedConfig.endpoints.custom[0].customParams).toEqual({
|
||||
defaultParamsEndpoint: 'openrouter',
|
||||
paramDefinitions: [
|
||||
{
|
||||
columnSpan: 1,
|
||||
component: 'switch',
|
||||
default: true,
|
||||
key: 'promptCache',
|
||||
label: 'promptCache',
|
||||
optionType: 'custom',
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('adds OpenRouter promptCache defaults when custom endpoint URL is OpenRouter', async () => {
|
||||
const openRouterConfig = {
|
||||
version: '1.0',
|
||||
cache: false,
|
||||
endpoints: {
|
||||
custom: [
|
||||
{
|
||||
name: 'Company Gateway',
|
||||
apiKey: 'user_provided',
|
||||
baseURL: 'https://openrouter.ai/api/v1',
|
||||
models: {
|
||||
default: ['anthropic/claude-sonnet-4.6'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
loadYaml.mockReturnValue(openRouterConfig);
|
||||
|
||||
const parsedConfig = await loadCustomConfig();
|
||||
expect(parsedConfig.endpoints.custom[0].customParams).toMatchObject({
|
||||
defaultParamsEndpoint: 'openrouter',
|
||||
paramDefinitions: [
|
||||
{
|
||||
default: true,
|
||||
key: 'promptCache',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves explicit OpenRouter promptCache defaults', async () => {
|
||||
const openRouterConfig = {
|
||||
version: '1.0',
|
||||
cache: false,
|
||||
endpoints: {
|
||||
custom: [
|
||||
{
|
||||
name: 'OpenRouter',
|
||||
apiKey: 'user_provided',
|
||||
baseURL: 'https://openrouter.ai/api/v1',
|
||||
models: {
|
||||
default: ['anthropic/claude-sonnet-4.6'],
|
||||
},
|
||||
customParams: {
|
||||
defaultParamsEndpoint: 'openrouter',
|
||||
paramDefinitions: [{ key: 'promptCache', default: false }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
loadYaml.mockReturnValue(openRouterConfig);
|
||||
|
||||
const parsedConfig = await loadCustomConfig();
|
||||
expect(parsedConfig.endpoints.custom[0].customParams.paramDefinitions).toEqual([
|
||||
{
|
||||
columnSpan: 1,
|
||||
component: 'switch',
|
||||
default: false,
|
||||
key: 'promptCache',
|
||||
label: 'promptCache',
|
||||
optionType: 'custom',
|
||||
type: 'boolean',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue