mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00

* refactor: move model definitions and database-related methods to packages/data-schemas * ci: update tests due to new DB structure fix: disable mocking `librechat-data-provider` feat: Add schema exports to data-schemas package - Introduced a new schema module that exports various schemas including action, agent, and user schemas. - Updated index.ts to include the new schema exports for better modularity and organization. ci: fix appleStrategy tests fix: Agent.spec.js ci: refactor handleTools tests to use MongoMemoryServer for in-memory database fix: getLogStores imports ci: update banViolation tests to use MongoMemoryServer and improve session mocking test: refactor samlStrategy tests to improve mock configurations and user handling ci: fix crypto mock in handleText tests for improved accuracy ci: refactor spendTokens tests to improve model imports and setup ci: refactor Message model tests to use MongoMemoryServer and improve database interactions * refactor: streamline IMessage interface and move feedback properties to types/message.ts * refactor: use exported initializeRoles from `data-schemas`, remove api workspace version (this serves as an example of future migrations that still need to happen) * refactor: update model imports to use destructuring from `~/db/models` for consistency and clarity * refactor: remove unused mongoose imports from model files for cleaner code * refactor: remove unused mongoose imports from Share, Prompt, and Transaction model files for cleaner code * refactor: remove unused import in Transaction model for cleaner code * ci: update deploy workflow to reference new Docker Dev Branch Images Build and add new workflow for building Docker images on dev branch * chore: cleanup imports
314 lines
11 KiB
JavaScript
314 lines
11 KiB
JavaScript
const crypto = require('crypto');
|
|
const { Constants } = require('librechat-data-provider');
|
|
const { HumanMessage, AIMessage } = require('@langchain/core/messages');
|
|
const PluginsClient = require('../PluginsClient');
|
|
|
|
jest.mock('~/db/connect');
|
|
jest.mock('~/models/Conversation', () => {
|
|
return function () {
|
|
return {
|
|
save: jest.fn(),
|
|
deleteConvos: jest.fn(),
|
|
};
|
|
};
|
|
});
|
|
|
|
const defaultAzureOptions = {
|
|
azureOpenAIApiInstanceName: 'your-instance-name',
|
|
azureOpenAIApiDeploymentName: 'your-deployment-name',
|
|
azureOpenAIApiVersion: '2020-07-01-preview',
|
|
};
|
|
|
|
describe('PluginsClient', () => {
|
|
let TestAgent;
|
|
let options = {
|
|
tools: [],
|
|
modelOptions: {
|
|
model: 'gpt-3.5-turbo',
|
|
temperature: 0,
|
|
max_tokens: 2,
|
|
},
|
|
agentOptions: {
|
|
model: 'gpt-3.5-turbo',
|
|
},
|
|
};
|
|
let parentMessageId;
|
|
let conversationId;
|
|
const fakeMessages = [];
|
|
const userMessage = 'Hello, ChatGPT!';
|
|
const apiKey = 'fake-api-key';
|
|
|
|
beforeEach(() => {
|
|
TestAgent = new PluginsClient(apiKey, options);
|
|
TestAgent.loadHistory = jest
|
|
.fn()
|
|
.mockImplementation((conversationId, parentMessageId = null) => {
|
|
if (!conversationId) {
|
|
TestAgent.currentMessages = [];
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
const orderedMessages = TestAgent.constructor.getMessagesForConversation({
|
|
messages: fakeMessages,
|
|
parentMessageId,
|
|
});
|
|
|
|
const chatMessages = orderedMessages.map((msg) =>
|
|
msg?.isCreatedByUser || msg?.role?.toLowerCase() === 'user'
|
|
? new HumanMessage(msg.text)
|
|
: new AIMessage(msg.text),
|
|
);
|
|
|
|
TestAgent.currentMessages = orderedMessages;
|
|
return Promise.resolve(chatMessages);
|
|
});
|
|
TestAgent.sendMessage = jest.fn().mockImplementation(async (message, opts = {}) => {
|
|
if (opts && typeof opts === 'object') {
|
|
TestAgent.setOptions(opts);
|
|
}
|
|
const conversationId = opts.conversationId || crypto.randomUUID();
|
|
const parentMessageId = opts.parentMessageId || Constants.NO_PARENT;
|
|
const userMessageId = opts.overrideParentMessageId || crypto.randomUUID();
|
|
this.pastMessages = await TestAgent.loadHistory(
|
|
conversationId,
|
|
TestAgent.options?.parentMessageId,
|
|
);
|
|
|
|
const userMessage = {
|
|
text: message,
|
|
sender: 'ChatGPT',
|
|
isCreatedByUser: true,
|
|
messageId: userMessageId,
|
|
parentMessageId,
|
|
conversationId,
|
|
};
|
|
|
|
const response = {
|
|
sender: 'ChatGPT',
|
|
text: 'Hello, User!',
|
|
isCreatedByUser: false,
|
|
messageId: crypto.randomUUID(),
|
|
parentMessageId: userMessage.messageId,
|
|
conversationId,
|
|
};
|
|
|
|
fakeMessages.push(userMessage);
|
|
fakeMessages.push(response);
|
|
return response;
|
|
});
|
|
});
|
|
|
|
test('initializes PluginsClient without crashing', () => {
|
|
expect(TestAgent).toBeInstanceOf(PluginsClient);
|
|
});
|
|
|
|
test('check setOptions function', () => {
|
|
expect(TestAgent.agentIsGpt3).toBe(true);
|
|
});
|
|
|
|
describe('sendMessage', () => {
|
|
test('sendMessage should return a response message', async () => {
|
|
const expectedResult = expect.objectContaining({
|
|
sender: 'ChatGPT',
|
|
text: expect.any(String),
|
|
isCreatedByUser: false,
|
|
messageId: expect.any(String),
|
|
parentMessageId: expect.any(String),
|
|
conversationId: expect.any(String),
|
|
});
|
|
|
|
const response = await TestAgent.sendMessage(userMessage);
|
|
parentMessageId = response.messageId;
|
|
conversationId = response.conversationId;
|
|
expect(response).toEqual(expectedResult);
|
|
});
|
|
|
|
test('sendMessage should work with provided conversationId and parentMessageId', async () => {
|
|
const userMessage = 'Second message in the conversation';
|
|
const opts = {
|
|
conversationId,
|
|
parentMessageId,
|
|
};
|
|
|
|
const expectedResult = expect.objectContaining({
|
|
sender: 'ChatGPT',
|
|
text: expect.any(String),
|
|
isCreatedByUser: false,
|
|
messageId: expect.any(String),
|
|
parentMessageId: expect.any(String),
|
|
conversationId: opts.conversationId,
|
|
});
|
|
|
|
const response = await TestAgent.sendMessage(userMessage, opts);
|
|
parentMessageId = response.messageId;
|
|
expect(response.conversationId).toEqual(conversationId);
|
|
expect(response).toEqual(expectedResult);
|
|
});
|
|
|
|
test('should return chat history', async () => {
|
|
const chatMessages = await TestAgent.loadHistory(conversationId, parentMessageId);
|
|
expect(TestAgent.currentMessages).toHaveLength(4);
|
|
expect(chatMessages[0].text).toEqual(userMessage);
|
|
});
|
|
});
|
|
|
|
describe('getFunctionModelName', () => {
|
|
let client;
|
|
|
|
beforeEach(() => {
|
|
client = new PluginsClient('dummy_api_key');
|
|
});
|
|
|
|
test('should return the input when it includes a dash followed by four digits', () => {
|
|
expect(client.getFunctionModelName('-1234')).toBe('-1234');
|
|
expect(client.getFunctionModelName('gpt-4-5678-preview')).toBe('gpt-4-5678-preview');
|
|
});
|
|
|
|
test('should return the input for all function-capable models (`0613` models and above)', () => {
|
|
expect(client.getFunctionModelName('gpt-4-0613')).toBe('gpt-4-0613');
|
|
expect(client.getFunctionModelName('gpt-4-32k-0613')).toBe('gpt-4-32k-0613');
|
|
expect(client.getFunctionModelName('gpt-3.5-turbo-0613')).toBe('gpt-3.5-turbo-0613');
|
|
expect(client.getFunctionModelName('gpt-3.5-turbo-16k-0613')).toBe('gpt-3.5-turbo-16k-0613');
|
|
expect(client.getFunctionModelName('gpt-3.5-turbo-1106')).toBe('gpt-3.5-turbo-1106');
|
|
expect(client.getFunctionModelName('gpt-4-1106-preview')).toBe('gpt-4-1106-preview');
|
|
expect(client.getFunctionModelName('gpt-4-1106')).toBe('gpt-4-1106');
|
|
});
|
|
|
|
test('should return the corresponding model if input is non-function capable (`0314` models)', () => {
|
|
expect(client.getFunctionModelName('gpt-4-0314')).toBe('gpt-4');
|
|
expect(client.getFunctionModelName('gpt-4-32k-0314')).toBe('gpt-4');
|
|
expect(client.getFunctionModelName('gpt-3.5-turbo-0314')).toBe('gpt-3.5-turbo');
|
|
expect(client.getFunctionModelName('gpt-3.5-turbo-16k-0314')).toBe('gpt-3.5-turbo');
|
|
});
|
|
|
|
test('should return "gpt-3.5-turbo" when the input includes "gpt-3.5-turbo"', () => {
|
|
expect(client.getFunctionModelName('test gpt-3.5-turbo model')).toBe('gpt-3.5-turbo');
|
|
});
|
|
|
|
test('should return "gpt-4" when the input includes "gpt-4"', () => {
|
|
expect(client.getFunctionModelName('testing gpt-4')).toBe('gpt-4');
|
|
});
|
|
|
|
test('should return "gpt-3.5-turbo" for input that does not meet any specific condition', () => {
|
|
expect(client.getFunctionModelName('random string')).toBe('gpt-3.5-turbo');
|
|
expect(client.getFunctionModelName('')).toBe('gpt-3.5-turbo');
|
|
});
|
|
});
|
|
|
|
describe('Azure OpenAI tests specific to Plugins', () => {
|
|
// TODO: add more tests for Azure OpenAI integration with Plugins
|
|
// let client;
|
|
// beforeEach(() => {
|
|
// client = new PluginsClient('dummy_api_key');
|
|
// });
|
|
|
|
test('should not call getFunctionModelName when azure options are set', () => {
|
|
const spy = jest.spyOn(PluginsClient.prototype, 'getFunctionModelName');
|
|
const model = 'gpt-4-turbo';
|
|
|
|
// note, without the azure change in PR #1766, `getFunctionModelName` is called twice
|
|
const testClient = new PluginsClient('dummy_api_key', {
|
|
agentOptions: {
|
|
model,
|
|
agent: 'functions',
|
|
},
|
|
azure: defaultAzureOptions,
|
|
});
|
|
|
|
expect(spy).not.toHaveBeenCalled();
|
|
expect(testClient.agentOptions.model).toBe(model);
|
|
|
|
spy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('sendMessage with filtered tools', () => {
|
|
let TestAgent;
|
|
const apiKey = 'fake-api-key';
|
|
const mockTools = [{ name: 'tool1' }, { name: 'tool2' }, { name: 'tool3' }, { name: 'tool4' }];
|
|
|
|
beforeEach(() => {
|
|
TestAgent = new PluginsClient(apiKey, {
|
|
tools: mockTools,
|
|
modelOptions: {
|
|
model: 'gpt-3.5-turbo',
|
|
temperature: 0,
|
|
max_tokens: 2,
|
|
},
|
|
agentOptions: {
|
|
model: 'gpt-3.5-turbo',
|
|
},
|
|
});
|
|
|
|
TestAgent.options.req = {
|
|
app: {
|
|
locals: {},
|
|
},
|
|
};
|
|
|
|
TestAgent.sendMessage = jest.fn().mockImplementation(async () => {
|
|
const { filteredTools = [], includedTools = [] } = TestAgent.options.req.app.locals;
|
|
|
|
if (includedTools.length > 0) {
|
|
const tools = TestAgent.options.tools.filter((plugin) =>
|
|
includedTools.includes(plugin.name),
|
|
);
|
|
TestAgent.options.tools = tools;
|
|
} else {
|
|
const tools = TestAgent.options.tools.filter(
|
|
(plugin) => !filteredTools.includes(plugin.name),
|
|
);
|
|
TestAgent.options.tools = tools;
|
|
}
|
|
|
|
return {
|
|
text: 'Mocked response',
|
|
tools: TestAgent.options.tools,
|
|
};
|
|
});
|
|
});
|
|
|
|
test('should filter out tools when filteredTools is provided', async () => {
|
|
TestAgent.options.req.app.locals.filteredTools = ['tool1', 'tool3'];
|
|
const response = await TestAgent.sendMessage('Test message');
|
|
expect(response.tools).toHaveLength(2);
|
|
expect(response.tools).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ name: 'tool2' }),
|
|
expect.objectContaining({ name: 'tool4' }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('should only include specified tools when includedTools is provided', async () => {
|
|
TestAgent.options.req.app.locals.includedTools = ['tool2', 'tool4'];
|
|
const response = await TestAgent.sendMessage('Test message');
|
|
expect(response.tools).toHaveLength(2);
|
|
expect(response.tools).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ name: 'tool2' }),
|
|
expect.objectContaining({ name: 'tool4' }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('should prioritize includedTools over filteredTools', async () => {
|
|
TestAgent.options.req.app.locals.filteredTools = ['tool1', 'tool3'];
|
|
TestAgent.options.req.app.locals.includedTools = ['tool1', 'tool2'];
|
|
const response = await TestAgent.sendMessage('Test message');
|
|
expect(response.tools).toHaveLength(2);
|
|
expect(response.tools).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ name: 'tool1' }),
|
|
expect.objectContaining({ name: 'tool2' }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test('should not modify tools when no filters are provided', async () => {
|
|
const response = await TestAgent.sendMessage('Test message');
|
|
expect(response.tools).toHaveLength(4);
|
|
expect(response.tools).toEqual(expect.arrayContaining(mockTools));
|
|
});
|
|
});
|
|
});
|