import { EModelEndpoint } from 'librechat-data-provider'; import type { TConversation, TPreset } from 'librechat-data-provider'; import createChatSearchParams from './createChatSearchParams'; describe('createChatSearchParams', () => { describe('conversation inputs', () => { it('handles basic conversation properties', () => { const conversation: Partial = { endpoint: EModelEndpoint.openAI, model: 'gpt-4', temperature: 0.7, }; const result = createChatSearchParams(conversation as TConversation); expect(result.get('endpoint')).toBe(EModelEndpoint.openAI); expect(result.get('model')).toBe('gpt-4'); expect(result.get('temperature')).toBe('0.7'); }); it('applies only the endpoint property when other conversation fields are absent', () => { const endpointOnly = createChatSearchParams({ endpoint: EModelEndpoint.openAI, } as TConversation); expect(endpointOnly.get('endpoint')).toBe(EModelEndpoint.openAI); expect(endpointOnly.has('model')).toBe(false); expect(endpointOnly.has('endpoint')).toBe(true); }); it('applies only the model property when other conversation fields are absent', () => { const modelOnly = createChatSearchParams({ model: 'gpt-4' } as TConversation); expect(modelOnly.has('endpoint')).toBe(false); expect(modelOnly.get('model')).toBe('gpt-4'); expect(modelOnly.has('model')).toBe(true); }); it('used to include endpoint and model with assistant_id but now only includes assistant_id', () => { const withAssistantId = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', assistant_id: 'asst_123', temperature: 0.7, } as TConversation); expect(withAssistantId.get('assistant_id')).toBe('asst_123'); expect(withAssistantId.has('endpoint')).toBe(false); expect(withAssistantId.has('model')).toBe(false); expect(withAssistantId.has('temperature')).toBe(false); }); it('used to include endpoint and model with agent_id but now only includes agent_id', () => { const withAgentId = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', agent_id: 'agent_123', temperature: 0.7, } as TConversation); expect(withAgentId.get('agent_id')).toBe('agent_123'); expect(withAgentId.has('endpoint')).toBe(false); expect(withAgentId.has('model')).toBe(false); expect(withAgentId.has('temperature')).toBe(false); }); it('when assistant_id is present, only include that param (excluding endpoint, model, etc)', () => { const withAssistantId = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', assistant_id: 'asst_123', temperature: 0.7, } as TConversation); expect(withAssistantId.get('assistant_id')).toBe('asst_123'); expect(withAssistantId.has('endpoint')).toBe(false); expect(withAssistantId.has('model')).toBe(false); expect(withAssistantId.has('temperature')).toBe(false); expect([...withAssistantId.entries()].length).toBe(1); }); it('when agent_id is present, only include that param (excluding endpoint, model, etc)', () => { const withAgentId = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', agent_id: 'agent_123', temperature: 0.7, } as TConversation); expect(withAgentId.get('agent_id')).toBe('agent_123'); expect(withAgentId.has('endpoint')).toBe(false); expect(withAgentId.has('model')).toBe(false); expect(withAgentId.has('temperature')).toBe(false); expect([...withAgentId.entries()].length).toBe(1); }); it('handles stop arrays correctly by joining with commas', () => { const withStopArray = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', stop: ['stop1', 'stop2'], } as TConversation); expect(withStopArray.get('endpoint')).toBe(EModelEndpoint.openAI); expect(withStopArray.get('model')).toBe('gpt-4'); expect(withStopArray.get('stop')).toBe('stop1,stop2'); }); it('filters out non-supported array properties', () => { const withOtherArray = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', otherArrayProp: ['value1', 'value2'], } as any); expect(withOtherArray.get('endpoint')).toBe(EModelEndpoint.openAI); expect(withOtherArray.get('model')).toBe('gpt-4'); expect(withOtherArray.has('otherArrayProp')).toBe(false); }); it('includes empty arrays in output params', () => { const result = createChatSearchParams({ endpoint: EModelEndpoint.openAI, stop: [], }); expect(result.get('endpoint')).toBe(EModelEndpoint.openAI); expect(result.has('stop')).toBe(true); expect(result.get('stop')).toBe(''); }); it('handles non-stop arrays correctly in paramMap', () => { const conversation: any = { endpoint: EModelEndpoint.openAI, model: 'gpt-4', top_p: ['0.7', '0.8'], }; const result = createChatSearchParams(conversation); const expectedJson = JSON.stringify(['0.7', '0.8']); expect(result.get('top_p')).toBe(expectedJson); expect(result.get('endpoint')).toBe(EModelEndpoint.openAI); expect(result.get('model')).toBe('gpt-4'); }); it('includes empty non-stop arrays as serialized empty arrays', () => { const result = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', temperature: 0.7, top_p: [], } as any); expect(result.get('endpoint')).toBe(EModelEndpoint.openAI); expect(result.get('model')).toBe('gpt-4'); expect(result.get('temperature')).toBe('0.7'); expect(result.has('top_p')).toBe(true); expect(result.get('top_p')).toBe('[]'); }); it('excludes parameters with null or undefined values from the output', () => { const result = createChatSearchParams({ endpoint: EModelEndpoint.openAI, model: 'gpt-4', temperature: 0.7, top_p: undefined, presence_penalty: undefined, frequency_penalty: null, } as any); expect(result.get('endpoint')).toBe(EModelEndpoint.openAI); expect(result.get('model')).toBe('gpt-4'); expect(result.get('temperature')).toBe('0.7'); expect(result.has('top_p')).toBe(false); expect(result.has('presence_penalty')).toBe(false); expect(result.has('frequency_penalty')).toBe(false); expect(result).toBeDefined(); }); it('handles float parameter values correctly', () => { const result = createChatSearchParams({ endpoint: EModelEndpoint.google, model: 'gemini-pro', frequency_penalty: 0.25, temperature: 0.75, }); expect(result.get('endpoint')).toBe(EModelEndpoint.google); expect(result.get('model')).toBe('gemini-pro'); expect(result.get('frequency_penalty')).toBe('0.25'); expect(result.get('temperature')).toBe('0.75'); }); it('handles integer parameter values correctly', () => { const result = createChatSearchParams({ endpoint: EModelEndpoint.google, model: 'gemini-pro', topK: 40, maxOutputTokens: 2048, }); expect(result.get('endpoint')).toBe(EModelEndpoint.google); expect(result.get('model')).toBe('gemini-pro'); expect(result.get('topK')).toBe('40'); expect(result.get('maxOutputTokens')).toBe('2048'); }); }); describe('preset inputs', () => { it('handles preset objects correctly', () => { const preset: Partial = { endpoint: EModelEndpoint.google, model: 'gemini-pro', temperature: 0.5, topP: 0.8, }; const result = createChatSearchParams(preset as TPreset); expect(result.get('endpoint')).toBe(EModelEndpoint.google); expect(result.get('model')).toBe('gemini-pro'); expect(result.get('temperature')).toBe('0.5'); expect(result.get('topP')).toBe('0.8'); }); }); describe('record inputs', () => { it('includes allowed parameters from Record inputs', () => { const record: Record = { endpoint: EModelEndpoint.anthropic, model: 'claude-2', temperature: '0.8', top_p: '0.95', extraParam: 'should-not-be-included', invalidParam1: 'value1', invalidParam2: 'value2', }; const result = createChatSearchParams(record); expect(result.get('endpoint')).toBe(EModelEndpoint.anthropic); expect(result.get('model')).toBe('claude-2'); expect(result.get('temperature')).toBe('0.8'); expect(result.get('top_p')).toBe('0.95'); }); it('excludes disallowed parameters from Record inputs', () => { const record: Record = { endpoint: EModelEndpoint.anthropic, model: 'claude-2', extraParam: 'should-not-be-included', invalidParam1: 'value1', invalidParam2: 'value2', }; const result = createChatSearchParams(record); expect(result.has('extraParam')).toBe(false); expect(result.has('invalidParam1')).toBe(false); expect(result.has('invalidParam2')).toBe(false); expect(result.toString().includes('invalidParam')).toBe(false); expect(result.toString().includes('extraParam')).toBe(false); }); it('includes valid values from Record inputs', () => { const record: Record = { temperature: '0.7', top_p: null, frequency_penalty: undefined, }; const result = createChatSearchParams(record); expect(result.get('temperature')).toBe('0.7'); }); it('excludes null or undefined values from Record inputs', () => { const record: Record = { temperature: '0.7', top_p: null, frequency_penalty: undefined, }; const result = createChatSearchParams(record); expect(result.has('top_p')).toBe(false); expect(result.has('frequency_penalty')).toBe(false); }); it('handles generic object without endpoint or model properties', () => { const customObject = { temperature: '0.5', top_p: '0.7', customProperty: 'value', }; const result = createChatSearchParams(customObject); expect(result.get('temperature')).toBe('0.5'); expect(result.get('top_p')).toBe('0.7'); expect(result.has('customProperty')).toBe(false); }); }); describe('edge cases', () => { it('returns an empty URLSearchParams instance when input is null', () => { const result = createChatSearchParams(null); expect(result.toString()).toBe(''); expect(result instanceof URLSearchParams).toBe(true); }); it('returns an empty URLSearchParams instance for an empty object input', () => { const result = createChatSearchParams({}); expect(result.toString()).toBe(''); expect(result instanceof URLSearchParams).toBe(true); }); }); });