mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-20 01:18:10 +01:00
🎭 feat: Override Custom Endpoint Schema with Specified Params Endpoint (#11788)
* 🔧 refactor: Simplify payload parsing and enhance getSaveOptions logic - Removed unused bedrockInputSchema from payloadParser, streamlining the function. - Updated payloadParser to handle optional chaining for model parameters. - Enhanced getSaveOptions to ensure runOptions defaults to an empty object if parsing fails, improving robustness. - Adjusted the assignment of maxContextTokens to use the instance variable for consistency. * 🔧 fix: Update maxContextTokens assignment logic in initializeAgent function - Enhanced the maxContextTokens assignment to allow for user-defined values, ensuring it defaults to a calculated value only when not provided or invalid. This change improves flexibility in agent initialization. * 🧪 test: Add unit tests for initializeAgent function - Introduced comprehensive unit tests for the initializeAgent function, focusing on maxContextTokens behavior. - Tests cover scenarios for user-defined values, fallback calculations, and edge cases such as zero and negative values, enhancing overall test coverage and reliability of agent initialization logic. * refactor: default params Endpoint Configuration Handling - Integrated `getEndpointsConfig` to fetch endpoint configurations, allowing for dynamic handling of `defaultParamsEndpoint`. - Updated `buildEndpointOption` to pass `defaultParamsEndpoint` to `parseCompactConvo`, ensuring correct parameter handling based on endpoint type. - Added comprehensive unit tests for `buildDefaultConvo` and `cleanupPreset` to validate behavior with `defaultParamsEndpoint`, covering various scenarios and edge cases. - Refactored related hooks and utility functions to support the new configuration structure, improving overall flexibility and maintainability. * refactor: Centralize defaultParamsEndpoint retrieval - Introduced `getDefaultParamsEndpoint` function to streamline the retrieval of `defaultParamsEndpoint` across various hooks and middleware. - Updated multiple files to utilize the new function, enhancing code consistency and maintainability. - Removed redundant logic for fetching `defaultParamsEndpoint`, simplifying the codebase.
This commit is contained in:
parent
6cc6ee3207
commit
467df0f07a
19 changed files with 1234 additions and 45 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { replaceSpecialVars, parseCompactConvo, parseTextParts } from '../src/parsers';
|
||||
import { replaceSpecialVars, parseConvo, parseCompactConvo, parseTextParts } from '../src/parsers';
|
||||
import { specialVariables } from '../src/config';
|
||||
import { EModelEndpoint } from '../src/schemas';
|
||||
import { ContentTypes } from '../src/types/runs';
|
||||
|
|
@ -262,6 +262,257 @@ describe('parseCompactConvo', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('parseConvo - defaultParamsEndpoint', () => {
|
||||
test('should strip maxOutputTokens for custom endpoint without defaultParamsEndpoint', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'anthropic/claude-opus-4.5',
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 8192,
|
||||
maxContextTokens: 50000,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.temperature).toBe(0.7);
|
||||
expect(result?.maxContextTokens).toBe(50000);
|
||||
expect(result?.maxOutputTokens).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should preserve maxOutputTokens when defaultParamsEndpoint is anthropic', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'anthropic/claude-opus-4.5',
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 8192,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
maxContextTokens: 50000,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.model).toBe('anthropic/claude-opus-4.5');
|
||||
expect(result?.temperature).toBe(0.7);
|
||||
expect(result?.maxOutputTokens).toBe(8192);
|
||||
expect(result?.topP).toBe(0.9);
|
||||
expect(result?.topK).toBe(40);
|
||||
expect(result?.maxContextTokens).toBe(50000);
|
||||
});
|
||||
|
||||
test('should strip OpenAI-specific fields when defaultParamsEndpoint is anthropic', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'anthropic/claude-opus-4.5',
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
top_p: 0.9,
|
||||
presence_penalty: 0.5,
|
||||
frequency_penalty: 0.3,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.temperature).toBe(0.7);
|
||||
expect(result?.max_tokens).toBeUndefined();
|
||||
expect(result?.top_p).toBeUndefined();
|
||||
expect(result?.presence_penalty).toBeUndefined();
|
||||
expect(result?.frequency_penalty).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should preserve max_tokens when defaultParamsEndpoint is not set (OpenAI default)', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'gpt-4o',
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
top_p: 0.9,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.max_tokens).toBe(4096);
|
||||
expect(result?.top_p).toBe(0.9);
|
||||
});
|
||||
|
||||
test('should preserve Google-specific fields when defaultParamsEndpoint is google', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'gemini-pro',
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 8192,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: EModelEndpoint.google,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.maxOutputTokens).toBe(8192);
|
||||
expect(result?.topP).toBe(0.9);
|
||||
expect(result?.topK).toBe(40);
|
||||
});
|
||||
|
||||
test('should not strip fields from non-custom endpoints that already have a schema', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'gpt-4o',
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
top_p: 0.9,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: EModelEndpoint.openAI,
|
||||
conversation,
|
||||
defaultParamsEndpoint: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.max_tokens).toBe(4096);
|
||||
expect(result?.top_p).toBe(0.9);
|
||||
});
|
||||
|
||||
test('should not carry bedrock region to custom endpoint without defaultParamsEndpoint', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'gpt-4o',
|
||||
temperature: 0.7,
|
||||
region: 'us-east-1',
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.temperature).toBe(0.7);
|
||||
expect(result?.region).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should fall back to endpointType schema when defaultParamsEndpoint is invalid', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'gpt-4o',
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
maxOutputTokens: 8192,
|
||||
};
|
||||
|
||||
const result = parseConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: 'nonexistent_endpoint',
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.max_tokens).toBe(4096);
|
||||
expect(result?.maxOutputTokens).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseCompactConvo - defaultParamsEndpoint', () => {
|
||||
test('should strip maxOutputTokens for custom endpoint without defaultParamsEndpoint', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'anthropic/claude-opus-4.5',
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 8192,
|
||||
};
|
||||
|
||||
const result = parseCompactConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.temperature).toBe(0.7);
|
||||
expect(result?.maxOutputTokens).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should preserve maxOutputTokens when defaultParamsEndpoint is anthropic', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'anthropic/claude-opus-4.5',
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 8192,
|
||||
topP: 0.9,
|
||||
maxContextTokens: 50000,
|
||||
};
|
||||
|
||||
const result = parseCompactConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.maxOutputTokens).toBe(8192);
|
||||
expect(result?.topP).toBe(0.9);
|
||||
expect(result?.maxContextTokens).toBe(50000);
|
||||
});
|
||||
|
||||
test('should strip iconURL even when defaultParamsEndpoint is set', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'anthropic/claude-opus-4.5',
|
||||
iconURL: 'https://malicious.com/track.png',
|
||||
maxOutputTokens: 8192,
|
||||
};
|
||||
|
||||
const result = parseCompactConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.['iconURL']).toBeUndefined();
|
||||
expect(result?.maxOutputTokens).toBe(8192);
|
||||
});
|
||||
|
||||
test('should fall back to endpointType when defaultParamsEndpoint is null', () => {
|
||||
const conversation: Partial<TConversation> = {
|
||||
model: 'gpt-4o',
|
||||
max_tokens: 4096,
|
||||
maxOutputTokens: 8192,
|
||||
};
|
||||
|
||||
const result = parseCompactConvo({
|
||||
endpoint: 'MyCustomEndpoint' as EModelEndpoint,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
conversation,
|
||||
defaultParamsEndpoint: null,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.max_tokens).toBe(4096);
|
||||
expect(result?.maxOutputTokens).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTextParts', () => {
|
||||
test('should concatenate text parts', () => {
|
||||
const parts: TMessageContentParts[] = [
|
||||
|
|
|
|||
|
|
@ -1908,3 +1908,14 @@ export function getEndpointField<
|
|||
}
|
||||
return config[property];
|
||||
}
|
||||
|
||||
/** Resolves the `defaultParamsEndpoint` for a given endpoint from its custom params config */
|
||||
export function getDefaultParamsEndpoint(
|
||||
endpointsConfig: TEndpointsConfig | undefined | null,
|
||||
endpoint: string | null | undefined,
|
||||
): string | undefined {
|
||||
if (!endpointsConfig || !endpoint) {
|
||||
return undefined;
|
||||
}
|
||||
return endpointsConfig[endpoint]?.customParams?.defaultParamsEndpoint;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,26 +144,25 @@ export const parseConvo = ({
|
|||
endpointType,
|
||||
conversation,
|
||||
possibleValues,
|
||||
defaultParamsEndpoint,
|
||||
}: {
|
||||
endpoint: EndpointSchemaKey;
|
||||
endpointType?: EndpointSchemaKey | null;
|
||||
conversation: Partial<s.TConversation | s.TPreset> | null;
|
||||
possibleValues?: TPossibleValues;
|
||||
// TODO: POC for default schema
|
||||
// defaultSchema?: Partial<EndpointSchema>,
|
||||
defaultParamsEndpoint?: string | null;
|
||||
}) => {
|
||||
let schema = endpointSchemas[endpoint] as EndpointSchema | undefined;
|
||||
|
||||
if (!schema && !endpointType) {
|
||||
throw new Error(`Unknown endpoint: ${endpoint}`);
|
||||
} else if (!schema && endpointType) {
|
||||
schema = endpointSchemas[endpointType];
|
||||
} else if (!schema) {
|
||||
const overrideSchema = defaultParamsEndpoint
|
||||
? endpointSchemas[defaultParamsEndpoint as EndpointSchemaKey]
|
||||
: undefined;
|
||||
schema = overrideSchema ?? (endpointType ? endpointSchemas[endpointType] : undefined);
|
||||
}
|
||||
|
||||
// if (defaultSchema && schemaCreators[endpoint]) {
|
||||
// schema = schemaCreators[endpoint](defaultSchema);
|
||||
// }
|
||||
|
||||
const convo = schema?.parse(conversation) as s.TConversation | undefined;
|
||||
const { models } = possibleValues ?? {};
|
||||
|
||||
|
|
@ -310,13 +309,13 @@ export const parseCompactConvo = ({
|
|||
endpointType,
|
||||
conversation,
|
||||
possibleValues,
|
||||
defaultParamsEndpoint,
|
||||
}: {
|
||||
endpoint?: EndpointSchemaKey;
|
||||
endpointType?: EndpointSchemaKey | null;
|
||||
conversation: Partial<s.TConversation | s.TPreset>;
|
||||
possibleValues?: TPossibleValues;
|
||||
// TODO: POC for default schema
|
||||
// defaultSchema?: Partial<EndpointSchema>,
|
||||
defaultParamsEndpoint?: string | null;
|
||||
}): Omit<s.TConversation, 'iconURL'> | null => {
|
||||
if (!endpoint) {
|
||||
throw new Error(`undefined endpoint: ${endpoint}`);
|
||||
|
|
@ -326,8 +325,11 @@ export const parseCompactConvo = ({
|
|||
|
||||
if (!schema && !endpointType) {
|
||||
throw new Error(`Unknown endpoint: ${endpoint}`);
|
||||
} else if (!schema && endpointType) {
|
||||
schema = compactEndpointSchemas[endpointType];
|
||||
} else if (!schema) {
|
||||
const overrideSchema = defaultParamsEndpoint
|
||||
? compactEndpointSchemas[defaultParamsEndpoint as EndpointSchemaKey]
|
||||
: undefined;
|
||||
schema = overrideSchema ?? (endpointType ? compactEndpointSchemas[endpointType] : undefined);
|
||||
}
|
||||
|
||||
if (!schema) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue