Skip to content

Commit

Permalink
πŸš€ feat: GPT-4.5, Anthropic Tool Header, and OpenAPI Ref Resolution (d…
Browse files Browse the repository at this point in the history
…anny-avila#6118)

* πŸ”§ refactor: Update settings to use 'as const' for improved type safety and make gpt-4o-mini default model (cheapest)

* πŸ“– docs: Update README to reflect support for GPT-4.5 in image analysis feature

* πŸ”§ refactor: Update model handling to use default settings and improve encoding logic

* πŸ”§ refactor: Enhance model version extraction logic for improved compatibility with future GPT and omni models

* feat: GPT-4.5 tx/token update, vision support

* fix: $ref resolution logic in OpenAPI handling

* feat: add new 'anthropic-beta' header for Claude 3.7 to include token-efficient tools; ref: https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
  • Loading branch information
danny-avila authored Feb 28, 2025
1 parent 9802629 commit 2293cd6
Show file tree
Hide file tree
Showing 15 changed files with 335 additions and 146 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ GOOGLE_KEY=user_provided
#============#

OPENAI_API_KEY=user_provided
# OPENAI_MODELS=o1,o1-mini,o1-preview,gpt-4o,chatgpt-4o-latest,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k
# OPENAI_MODELS=o1,o1-mini,o1-preview,gpt-4o,gpt-4.5-preview,chatgpt-4o-latest,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k

DEBUG_OPENAI=false

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
- [Fork Messages & Conversations](https://www.librechat.ai/docs/features/fork) for Advanced Context control

- πŸ’¬ **Multimodal & File Interactions**:
- Upload and analyze images with Claude 3, GPT-4o, o1, Llama-Vision, and Gemini πŸ“Έ
- Upload and analyze images with Claude 3, GPT-4.5, GPT-4o, o1, Llama-Vision, and Gemini πŸ“Έ
- Chat with Files using Custom Endpoints, OpenAI, Azure, Anthropic, AWS Bedrock, & Google πŸ—ƒοΈ

- 🌎 **Multilingual UI**:
Expand Down
10 changes: 6 additions & 4 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@ class OpenAIClient extends BaseClient {
}

getEncoding() {
return this.model?.includes('gpt-4o') ? 'o200k_base' : 'cl100k_base';
return this.modelOptions?.model && /gpt-4[^-\s]/.test(this.modelOptions.model)
? 'o200k_base'
: 'cl100k_base';
}

/**
Expand Down Expand Up @@ -605,7 +607,7 @@ class OpenAIClient extends BaseClient {
}

initializeLLM({
model = 'gpt-4o-mini',
model = openAISettings.model.default,
modelName,
temperature = 0.2,
max_tokens,
Expand Down Expand Up @@ -706,7 +708,7 @@ class OpenAIClient extends BaseClient {

const { OPENAI_TITLE_MODEL } = process.env ?? {};

let model = this.options.titleModel ?? OPENAI_TITLE_MODEL ?? 'gpt-4o-mini';
let model = this.options.titleModel ?? OPENAI_TITLE_MODEL ?? openAISettings.model.default;
if (model === Constants.CURRENT_MODEL) {
model = this.modelOptions.model;
}
Expand Down Expand Up @@ -899,7 +901,7 @@ ${convo}
let prompt;

// TODO: remove the gpt fallback and make it specific to endpoint
const { OPENAI_SUMMARY_MODEL = 'gpt-4o-mini' } = process.env ?? {};
const { OPENAI_SUMMARY_MODEL = openAISettings.model.default } = process.env ?? {};
let model = this.options.summaryModel ?? OPENAI_SUMMARY_MODEL;
if (model === Constants.CURRENT_MODEL) {
model = this.modelOptions.model;
Expand Down
3 changes: 3 additions & 0 deletions api/models/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const tokenValues = Object.assign(
'o1-mini': { prompt: 1.1, completion: 4.4 },
'o1-preview': { prompt: 15, completion: 60 },
o1: { prompt: 15, completion: 60 },
'gpt-4.5': { prompt: 75, completion: 150 },
'gpt-4o-mini': { prompt: 0.15, completion: 0.6 },
'gpt-4o': { prompt: 2.5, completion: 10 },
'gpt-4o-2024-05-13': { prompt: 5, completion: 15 },
Expand Down Expand Up @@ -167,6 +168,8 @@ const getValueKey = (model, endpoint) => {
return 'o1-mini';
} else if (modelName.includes('o1')) {
return 'o1';
} else if (modelName.includes('gpt-4.5')) {
return 'gpt-4.5';
} else if (modelName.includes('gpt-4o-2024-05-13')) {
return 'gpt-4o-2024-05-13';
} else if (modelName.includes('gpt-4o-mini')) {
Expand Down
10 changes: 10 additions & 0 deletions api/models/tx.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ describe('getValueKey', () => {
expect(getValueKey('gpt-4-0125')).toBe('gpt-4-1106');
});

it('should return "gpt-4.5" for model type of "gpt-4.5"', () => {
expect(getValueKey('gpt-4.5-preview')).toBe('gpt-4.5');
expect(getValueKey('gpt-4.5-2024-08-06')).toBe('gpt-4.5');
expect(getValueKey('gpt-4.5-2024-08-06-0718')).toBe('gpt-4.5');
expect(getValueKey('openai/gpt-4.5')).toBe('gpt-4.5');
expect(getValueKey('openai/gpt-4.5-2024-08-06')).toBe('gpt-4.5');
expect(getValueKey('gpt-4.5-turbo')).toBe('gpt-4.5');
expect(getValueKey('gpt-4.5-0125')).toBe('gpt-4.5');
});

it('should return "gpt-4o" for model type of "gpt-4o"', () => {
expect(getValueKey('gpt-4o-2024-08-06')).toBe('gpt-4o');
expect(getValueKey('gpt-4o-2024-08-06-0718')).toBe('gpt-4o');
Expand Down
3 changes: 2 additions & 1 deletion api/server/services/Endpoints/anthropic/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ function getClaudeHeaders(model, supportsCacheControl) {
};
} else if (/claude-3[-.]7/.test(model)) {
return {
'anthropic-beta': 'output-128k-2025-02-19,prompt-caching-2024-07-31',
'anthropic-beta':
'token-efficient-tools-2025-02-19,output-128k-2025-02-19,prompt-caching-2024-07-31',
};
} else {
return {
Expand Down
1 change: 1 addition & 0 deletions api/utils/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const openAIModels = {
'gpt-4-32k-0613': 32758, // -10 from max
'gpt-4-1106': 127500, // -500 from max
'gpt-4-0125': 127500, // -500 from max
'gpt-4.5': 127500, // -500 from max
'gpt-4o': 127500, // -500 from max
'gpt-4o-mini': 127500, // -500 from max
'gpt-4o-2024-05-13': 127500, // -500 from max
Expand Down
10 changes: 10 additions & 0 deletions api/utils/tokens.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ describe('getModelMaxTokens', () => {
);
});

test('should return correct tokens for gpt-4.5 matches', () => {
expect(getModelMaxTokens('gpt-4.5')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-4.5']);
expect(getModelMaxTokens('gpt-4.5-preview')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4.5'],
);
expect(getModelMaxTokens('openai/gpt-4.5-preview')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-4.5'],
);
});

test('should return correct tokens for Anthropic models', () => {
const models = [
'claude-2.1',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/data-provider/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
"version": "0.7.6993",
"version": "0.7.6994",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",
Expand Down
131 changes: 124 additions & 7 deletions packages/data-provider/specs/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,21 +585,99 @@ describe('resolveRef', () => {
openapiSpec.paths['/ai.chatgpt.render-flowchart']?.post
?.requestBody as OpenAPIV3.RequestBodyObject
).content['application/json'].schema;

expect(flowchartRequestRef).toBeDefined();
const resolvedFlowchartRequest = resolveRef(
flowchartRequestRef as OpenAPIV3.RequestBodyObject,

const resolvedSchemaObject = resolveRef(
flowchartRequestRef as OpenAPIV3.ReferenceObject,
openapiSpec.components,
);
) as OpenAPIV3.SchemaObject;

expect(resolvedFlowchartRequest).toBeDefined();
expect(resolvedFlowchartRequest.type).toBe('object');
const properties = resolvedFlowchartRequest.properties as FlowchartSchema;
expect(properties).toBeDefined();
expect(resolvedSchemaObject).toBeDefined();
expect(resolvedSchemaObject.type).toBe('object');
expect(resolvedSchemaObject.properties).toBeDefined();

const properties = resolvedSchemaObject.properties as FlowchartSchema;
expect(properties.mermaid).toBeDefined();
expect(properties.mermaid.type).toBe('string');
});
});

describe('resolveRef general cases', () => {
const spec = {
openapi: '3.0.0',
info: { title: 'TestSpec', version: '1.0.0' },
paths: {},
components: {
schemas: {
TestSchema: { type: 'string' },
},
parameters: {
TestParam: {
name: 'myParam',
in: 'query',
required: false,
schema: { $ref: '#/components/schemas/TestSchema' },
},
},
requestBodies: {
TestRequestBody: {
content: {
'application/json': {
schema: { $ref: '#/components/schemas/TestSchema' },
},
},
},
},
},
} satisfies OpenAPIV3.Document;

it('resolves schema refs correctly', () => {
const schemaRef: OpenAPIV3.ReferenceObject = { $ref: '#/components/schemas/TestSchema' };
const resolvedSchema = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
schemaRef,
spec.components,
);
expect(resolvedSchema.type).toEqual('string');
});

it('resolves parameter refs correctly, then schema within parameter', () => {
const paramRef: OpenAPIV3.ReferenceObject = { $ref: '#/components/parameters/TestParam' };
const resolvedParam = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject>(
paramRef,
spec.components,
);
expect(resolvedParam.name).toEqual('myParam');
expect(resolvedParam.in).toEqual('query');
expect(resolvedParam.required).toBe(false);

const paramSchema = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
resolvedParam.schema as OpenAPIV3.ReferenceObject,
spec.components,
);
expect(paramSchema.type).toEqual('string');
});

it('resolves requestBody refs correctly, then schema within requestBody', () => {
const requestBodyRef: OpenAPIV3.ReferenceObject = {
$ref: '#/components/requestBodies/TestRequestBody',
};
const resolvedRequestBody = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject>(
requestBodyRef,
spec.components,
);

expect(resolvedRequestBody.content['application/json']).toBeDefined();

const schemaInRequestBody = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
resolvedRequestBody.content['application/json'].schema as OpenAPIV3.ReferenceObject,
spec.components,
);

expect(schemaInRequestBody.type).toEqual('string');
});
});

describe('openapiToFunction', () => {
it('converts OpenAPI spec to function signatures and request builders', () => {
const { functionSignatures, requestBuilders } = openapiToFunction(getWeatherOpenapiSpec);
Expand Down Expand Up @@ -1095,4 +1173,43 @@ describe('createURL', () => {
});
});
});

describe('openapiToFunction parameter refs resolution', () => {
const weatherSpec = {
openapi: '3.0.0',
info: { title: 'Weather', version: '1.0.0' },
servers: [{ url: 'https://api.weather.gov' }],
paths: {
'/points/{point}': {
get: {
operationId: 'getPoint',
parameters: [{ $ref: '#/components/parameters/PathPoint' }],
responses: { '200': { description: 'ok' } },
},
},
},
components: {
parameters: {
PathPoint: {
name: 'point',
in: 'path',
required: true,
schema: { type: 'string', pattern: '^(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$' },
},
},
},
} satisfies OpenAPIV3.Document;

it('correctly resolves $ref for parameters', () => {
const { functionSignatures } = openapiToFunction(weatherSpec, true);
const func = functionSignatures.find((sig) => sig.name === 'getPoint');
expect(func).toBeDefined();
expect(func?.parameters.properties).toHaveProperty('point');
expect(func?.parameters.required).toContain('point');

const paramSchema = func?.parameters.properties['point'] as OpenAPIV3.SchemaObject;
expect(paramSchema.type).toEqual('string');
expect(paramSchema.pattern).toEqual('^(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$');
});
});
});
Loading

0 comments on commit 2293cd6

Please sign in to comment.