mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-07 02:58:50 +01:00
✨ feat: Add OpenAI Verbosity Parameter (#8929)
* WIP: Verbosity OpenAI Parameter * 🔧 chore: remove unused import of extractEnvVariable from parsers.ts * ✨ feat: add comprehensive tests for getOpenAIConfig and enhance verbosity handling * fix: Handling for maxTokens in GPT-5+ models and add corresponding tests * feat: Implement GPT-5+ model handling in processMemory function
This commit is contained in:
parent
486fe34a2b
commit
7147bce3c3
14 changed files with 989 additions and 6 deletions
|
|
@ -1,5 +1,8 @@
|
|||
import { Tools, type MemoryArtifact } from 'librechat-data-provider';
|
||||
import { createMemoryTool } from '../memory';
|
||||
import { Response } from 'express';
|
||||
import { Providers } from '@librechat/agents';
|
||||
import { Tools } from 'librechat-data-provider';
|
||||
import type { MemoryArtifact } from 'librechat-data-provider';
|
||||
import { createMemoryTool, processMemory } from '../memory';
|
||||
|
||||
// Mock the logger
|
||||
jest.mock('winston', () => ({
|
||||
|
|
@ -25,6 +28,22 @@ jest.mock('~/utils', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// Mock the Run module
|
||||
jest.mock('@librechat/agents', () => ({
|
||||
...jest.requireActual('@librechat/agents'),
|
||||
Run: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
Providers: {
|
||||
OPENAI: 'openai',
|
||||
ANTHROPIC: 'anthropic',
|
||||
AZURE: 'azure',
|
||||
},
|
||||
GraphEvents: {
|
||||
TOOL_END: 'tool_end',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('createMemoryTool', () => {
|
||||
let mockSetMemory: jest.Mock;
|
||||
|
||||
|
|
@ -163,3 +182,220 @@ describe('createMemoryTool', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processMemory - GPT-5+ handling', () => {
|
||||
let mockSetMemory: jest.Mock;
|
||||
let mockDeleteMemory: jest.Mock;
|
||||
let mockRes: Partial<Response>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSetMemory = jest.fn().mockResolvedValue({ ok: true });
|
||||
mockDeleteMemory = jest.fn().mockResolvedValue({ ok: true });
|
||||
mockRes = {
|
||||
headersSent: false,
|
||||
write: jest.fn(),
|
||||
};
|
||||
|
||||
// Setup the Run.create mock
|
||||
const { Run } = jest.requireMock('@librechat/agents');
|
||||
(Run.create as jest.Mock).mockResolvedValue({
|
||||
processStream: jest.fn().mockResolvedValue('Memory processed'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove temperature for GPT-5 models', async () => {
|
||||
await processMemory({
|
||||
res: mockRes as Response,
|
||||
userId: 'test-user',
|
||||
setMemory: mockSetMemory,
|
||||
deleteMemory: mockDeleteMemory,
|
||||
messages: [],
|
||||
memory: 'Test memory',
|
||||
messageId: 'msg-123',
|
||||
conversationId: 'conv-123',
|
||||
instructions: 'Test instructions',
|
||||
llmConfig: {
|
||||
provider: Providers.OPENAI,
|
||||
model: 'gpt-5',
|
||||
temperature: 0.7, // This should be removed
|
||||
maxTokens: 1000, // This should be moved to modelKwargs
|
||||
},
|
||||
});
|
||||
|
||||
const { Run } = jest.requireMock('@librechat/agents');
|
||||
expect(Run.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
graphConfig: expect.objectContaining({
|
||||
llmConfig: expect.objectContaining({
|
||||
model: 'gpt-5',
|
||||
modelKwargs: {
|
||||
max_completion_tokens: 1000,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify temperature was removed
|
||||
const callArgs = (Run.create as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.graphConfig.llmConfig.temperature).toBeUndefined();
|
||||
expect(callArgs.graphConfig.llmConfig.maxTokens).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle GPT-5+ models with existing modelKwargs', async () => {
|
||||
await processMemory({
|
||||
res: mockRes as Response,
|
||||
userId: 'test-user',
|
||||
setMemory: mockSetMemory,
|
||||
deleteMemory: mockDeleteMemory,
|
||||
messages: [],
|
||||
memory: 'Test memory',
|
||||
messageId: 'msg-123',
|
||||
conversationId: 'conv-123',
|
||||
instructions: 'Test instructions',
|
||||
llmConfig: {
|
||||
provider: Providers.OPENAI,
|
||||
model: 'gpt-6',
|
||||
temperature: 0.8,
|
||||
maxTokens: 2000,
|
||||
modelKwargs: {
|
||||
customParam: 'value',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { Run } = jest.requireMock('@librechat/agents');
|
||||
expect(Run.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
graphConfig: expect.objectContaining({
|
||||
llmConfig: expect.objectContaining({
|
||||
model: 'gpt-6',
|
||||
modelKwargs: {
|
||||
customParam: 'value',
|
||||
max_completion_tokens: 2000,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const callArgs = (Run.create as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.graphConfig.llmConfig.temperature).toBeUndefined();
|
||||
expect(callArgs.graphConfig.llmConfig.maxTokens).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not modify non-GPT-5+ models', async () => {
|
||||
await processMemory({
|
||||
res: mockRes as Response,
|
||||
userId: 'test-user',
|
||||
setMemory: mockSetMemory,
|
||||
deleteMemory: mockDeleteMemory,
|
||||
messages: [],
|
||||
memory: 'Test memory',
|
||||
messageId: 'msg-123',
|
||||
conversationId: 'conv-123',
|
||||
instructions: 'Test instructions',
|
||||
llmConfig: {
|
||||
provider: Providers.OPENAI,
|
||||
model: 'gpt-4',
|
||||
temperature: 0.7,
|
||||
maxTokens: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
const { Run } = jest.requireMock('@librechat/agents');
|
||||
expect(Run.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
graphConfig: expect.objectContaining({
|
||||
llmConfig: expect.objectContaining({
|
||||
model: 'gpt-4',
|
||||
temperature: 0.7,
|
||||
maxTokens: 1000,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify nothing was moved to modelKwargs for GPT-4
|
||||
const callArgs = (Run.create as jest.Mock).mock.calls[0][0];
|
||||
expect(callArgs.graphConfig.llmConfig.modelKwargs).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle various GPT-5+ model formats', async () => {
|
||||
const testCases = [
|
||||
{ model: 'gpt-5', shouldTransform: true },
|
||||
{ model: 'gpt-5-turbo', shouldTransform: true },
|
||||
{ model: 'gpt-7-preview', shouldTransform: true },
|
||||
{ model: 'gpt-9', shouldTransform: true },
|
||||
{ model: 'gpt-4o', shouldTransform: false },
|
||||
{ model: 'gpt-3.5-turbo', shouldTransform: false },
|
||||
];
|
||||
|
||||
for (const { model, shouldTransform } of testCases) {
|
||||
jest.clearAllMocks();
|
||||
const { Run } = jest.requireMock('@librechat/agents');
|
||||
(Run.create as jest.Mock).mockResolvedValue({
|
||||
processStream: jest.fn().mockResolvedValue('Memory processed'),
|
||||
});
|
||||
|
||||
await processMemory({
|
||||
res: mockRes as Response,
|
||||
userId: 'test-user',
|
||||
setMemory: mockSetMemory,
|
||||
deleteMemory: mockDeleteMemory,
|
||||
messages: [],
|
||||
memory: 'Test memory',
|
||||
messageId: 'msg-123',
|
||||
conversationId: 'conv-123',
|
||||
instructions: 'Test instructions',
|
||||
llmConfig: {
|
||||
provider: Providers.OPENAI,
|
||||
model,
|
||||
temperature: 0.5,
|
||||
maxTokens: 1500,
|
||||
},
|
||||
});
|
||||
|
||||
const callArgs = (Run.create as jest.Mock).mock.calls[0][0];
|
||||
const llmConfig = callArgs.graphConfig.llmConfig;
|
||||
|
||||
if (shouldTransform) {
|
||||
expect(llmConfig.temperature).toBeUndefined();
|
||||
expect(llmConfig.maxTokens).toBeUndefined();
|
||||
expect(llmConfig.modelKwargs?.max_completion_tokens).toBe(1500);
|
||||
} else {
|
||||
expect(llmConfig.temperature).toBe(0.5);
|
||||
expect(llmConfig.maxTokens).toBe(1500);
|
||||
expect(llmConfig.modelKwargs).toBeUndefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should use default model (gpt-4.1-mini) without temperature removal when no llmConfig provided', async () => {
|
||||
await processMemory({
|
||||
res: mockRes as Response,
|
||||
userId: 'test-user',
|
||||
setMemory: mockSetMemory,
|
||||
deleteMemory: mockDeleteMemory,
|
||||
messages: [],
|
||||
memory: 'Test memory',
|
||||
messageId: 'msg-123',
|
||||
conversationId: 'conv-123',
|
||||
instructions: 'Test instructions',
|
||||
// No llmConfig provided
|
||||
});
|
||||
|
||||
const { Run } = jest.requireMock('@librechat/agents');
|
||||
expect(Run.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
graphConfig: expect.objectContaining({
|
||||
llmConfig: expect.objectContaining({
|
||||
model: 'gpt-4.1-mini',
|
||||
temperature: 0.4, // Default temperature should remain
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import { Tools } from 'librechat-data-provider';
|
|||
import { logger } from '@librechat/data-schemas';
|
||||
import { Run, Providers, GraphEvents } from '@librechat/agents';
|
||||
import type {
|
||||
OpenAIClientOptions,
|
||||
StreamEventData,
|
||||
ToolEndCallback,
|
||||
ClientOptions,
|
||||
EventHandler,
|
||||
ToolEndData,
|
||||
LLMConfig,
|
||||
|
|
@ -332,7 +334,7 @@ ${memory ?? 'No existing memories'}`;
|
|||
disableStreaming: true,
|
||||
};
|
||||
|
||||
const finalLLMConfig = {
|
||||
const finalLLMConfig: ClientOptions = {
|
||||
...defaultLLMConfig,
|
||||
...llmConfig,
|
||||
/**
|
||||
|
|
@ -342,6 +344,20 @@ ${memory ?? 'No existing memories'}`;
|
|||
disableStreaming: true,
|
||||
};
|
||||
|
||||
// Handle GPT-5+ models
|
||||
if ('model' in finalLLMConfig && /\bgpt-[5-9]\b/i.test(finalLLMConfig.model ?? '')) {
|
||||
// Remove temperature for GPT-5+ models
|
||||
delete finalLLMConfig.temperature;
|
||||
|
||||
// Move maxTokens to modelKwargs for GPT-5+ models
|
||||
if ('maxTokens' in finalLLMConfig && finalLLMConfig.maxTokens != null) {
|
||||
const modelKwargs = (finalLLMConfig as OpenAIClientOptions).modelKwargs ?? {};
|
||||
modelKwargs.max_completion_tokens = finalLLMConfig.maxTokens;
|
||||
delete finalLLMConfig.maxTokens;
|
||||
(finalLLMConfig as OpenAIClientOptions).modelKwargs = modelKwargs;
|
||||
}
|
||||
}
|
||||
|
||||
const artifactPromises: Promise<TAttachment | null>[] = [];
|
||||
const memoryCallback = createMemoryCallback({ res, artifactPromises });
|
||||
const customHandlers = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue