mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-21 23:55:23 +00:00
🌊 fix: Preserve Custom Endpoint streamRate When endpoints.all Is Defined (#14645)
* 🌊 fix: Preserve Custom Endpoint `streamRate` When `endpoints.all` Is Defined `buildCustomOptions` assigned `allConfig.streamRate` unconditionally whenever an `endpoints.all` block existed, overwriting the per-endpoint `streamRate` with `undefined` for any `all` block that did not define one of its own. The value is read back later to set `_lc_stream_delay` on the llmConfig, so stream smoothing was silently disabled for every custom endpoint whenever `endpoints.all` was present for unrelated reasons (e.g. `activityLabel`). Guard on `allConfig?.streamRate`, matching the existing OpenAI path. * 🌊 fix: Preserve Explicit `streamRate: 0` Through the Custom Endpoint Chain Codex review: truthy guards dropped zero-valued streamRate at both the endpoints.all override and the llmConfig assignment. With agents 3.4.0 defaulting stream smoothing ON, 0 becomes the explicit disable, so both sites now use nullish guards; endpoints.all.streamRate: 0 overrides an endpoint-level rate and an endpoint-level 0 reaches _lc_stream_delay. Spec extended with both zero cases.
This commit is contained in:
parent
56175af0b5
commit
305107998d
2 changed files with 106 additions and 2 deletions
|
|
@ -115,7 +115,7 @@ function buildCustomOptions(
|
|||
}
|
||||
|
||||
const allConfig = appConfig?.endpoints?.all;
|
||||
if (allConfig) {
|
||||
if (allConfig?.streamRate != null) {
|
||||
customOptions.streamRate = allConfig.streamRate;
|
||||
}
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ export async function initializeCustom({
|
|||
}
|
||||
|
||||
const streamRate = clientOptions.streamRate as number | undefined;
|
||||
if (streamRate) {
|
||||
if (streamRate != null) {
|
||||
(options.llmConfig as Record<string, unknown>)._lc_stream_delay = streamRate;
|
||||
}
|
||||
|
||||
|
|
|
|||
104
packages/api/src/endpoints/custom/streamrate.spec.ts
Normal file
104
packages/api/src/endpoints/custom/streamrate.spec.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import type { BaseInitializeParams } from '~/types';
|
||||
|
||||
jest.mock('~/auth', () => ({
|
||||
validateEndpointURL: jest.fn(),
|
||||
createSSRFSafeUndiciConnect: jest.fn(() => ({ lookup: jest.fn() })),
|
||||
}));
|
||||
|
||||
const mockGetOpenAIConfig = jest.fn((..._args: unknown[]) => ({
|
||||
llmConfig: { model: 'claude-haiku-4-5' } as Record<string, unknown>,
|
||||
configOptions: {},
|
||||
}));
|
||||
jest.mock('~/endpoints/openai/config', () => ({
|
||||
getOpenAIConfig: (...args: unknown[]) => mockGetOpenAIConfig(...args),
|
||||
}));
|
||||
|
||||
jest.mock('~/endpoints/models', () => ({ fetchModels: jest.fn() }));
|
||||
jest.mock('~/cache', () => ({
|
||||
standardCache: jest.fn(() => ({ get: jest.fn().mockResolvedValue(null) })),
|
||||
tokenConfigCache: jest.fn(() => ({ get: jest.fn().mockResolvedValue(null) })),
|
||||
}));
|
||||
jest.mock('~/utils', () => ({
|
||||
isUserProvided: (val: string) => val === 'user_provided',
|
||||
checkUserKeyExpiry: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockGetCustomEndpointConfig = jest.fn();
|
||||
jest.mock('~/app/config', () => ({
|
||||
getCustomEndpointConfig: (...args: unknown[]) => mockGetCustomEndpointConfig(...args),
|
||||
}));
|
||||
|
||||
import { initializeCustom } from './initialize';
|
||||
|
||||
function makeParams({
|
||||
allBlock,
|
||||
endpointStreamRate,
|
||||
}: {
|
||||
allBlock?: Record<string, unknown>;
|
||||
endpointStreamRate?: number;
|
||||
} = {}): BaseInitializeParams {
|
||||
mockGetCustomEndpointConfig.mockReturnValue({
|
||||
apiKey: 'test-key',
|
||||
baseURL: 'https://gateway.example.com/v1',
|
||||
models: { default: ['claude-haiku-4-5'], fetch: false },
|
||||
streamRate: endpointStreamRate,
|
||||
});
|
||||
|
||||
return {
|
||||
req: {
|
||||
user: { id: 'user-1' },
|
||||
body: {},
|
||||
config: allBlock ? { endpoints: { all: allBlock } } : {},
|
||||
} as unknown as BaseInitializeParams['req'],
|
||||
endpoint: 'ClickHouse',
|
||||
model_parameters: { model: 'claude-haiku-4-5' },
|
||||
db: { getUserKeyValues: jest.fn() } as unknown as BaseInitializeParams['db'],
|
||||
};
|
||||
}
|
||||
|
||||
function streamDelayOf(options: { llmConfig: unknown }): unknown {
|
||||
return (options.llmConfig as Record<string, unknown>)._lc_stream_delay;
|
||||
}
|
||||
|
||||
describe('custom endpoint streamRate resolution', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('applies the endpoint streamRate when no `endpoints.all` block is present', async () => {
|
||||
const options = await initializeCustom(makeParams({ endpointStreamRate: 25 }));
|
||||
expect(streamDelayOf(options)).toBe(25);
|
||||
});
|
||||
|
||||
it('preserves the endpoint streamRate when `endpoints.all` exists without its own streamRate', async () => {
|
||||
const options = await initializeCustom(
|
||||
makeParams({
|
||||
endpointStreamRate: 25,
|
||||
allBlock: { activityLabel: true, activityModel: 'gpt-5.6-luna' },
|
||||
}),
|
||||
);
|
||||
expect(streamDelayOf(options)).toBe(25);
|
||||
});
|
||||
|
||||
it('lets `endpoints.all.streamRate` override the endpoint streamRate', async () => {
|
||||
const options = await initializeCustom(
|
||||
makeParams({ endpointStreamRate: 25, allBlock: { streamRate: 10 } }),
|
||||
);
|
||||
expect(streamDelayOf(options)).toBe(10);
|
||||
});
|
||||
|
||||
it('leaves the stream delay unset when neither level configures a streamRate', async () => {
|
||||
const options = await initializeCustom(makeParams({ allBlock: { activityLabel: true } }));
|
||||
expect(streamDelayOf(options)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('lets `endpoints.all.streamRate: 0` override an endpoint streamRate (explicit disable)', async () => {
|
||||
const options = await initializeCustom(
|
||||
makeParams({ endpointStreamRate: 25, allBlock: { streamRate: 0 } }),
|
||||
);
|
||||
expect(streamDelayOf(options)).toBe(0);
|
||||
});
|
||||
|
||||
it('passes an explicit endpoint `streamRate: 0` through to the llmConfig', async () => {
|
||||
const options = await initializeCustom(makeParams({ endpointStreamRate: 0 }));
|
||||
expect(streamDelayOf(options)).toBe(0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue