@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
1,226 lines (1,036 loc) • 37.9 kB
text/typescript
// @vitest-environment node
import { ModelProvider } from 'model-bank';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { testProvider } from '../../providerTestUtils';
import { LobeAi360AI, params } from './index';
testProvider({
Runtime: LobeAi360AI,
provider: ModelProvider.Ai360,
defaultBaseURL: 'https://api.360.cn/v1',
chatDebugEnv: 'DEBUG_AI360_CHAT_COMPLETION',
chatModel: 'deepseek-r1',
invalidErrorType: 'InvalidProviderAPIKey',
bizErrorType: 'ProviderBizError',
test: {
skipAPICall: true,
skipErrorHandle: true,
},
});
describe('LobeAi360AI - custom features', () => {
let instance: InstanceType<typeof LobeAi360AI>;
beforeEach(() => {
instance = new LobeAi360AI({ apiKey: 'test_api_key' });
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
new ReadableStream() as any,
);
});
describe('params.debug', () => {
it('should disable debug mode by default', () => {
delete process.env.DEBUG_AI360_CHAT_COMPLETION;
const result = params.debug.chatCompletion();
expect(result).toBe(false);
});
it('should enable debug mode when DEBUG_AI360_CHAT_COMPLETION is set to 1', () => {
process.env.DEBUG_AI360_CHAT_COMPLETION = '1';
const result = params.debug.chatCompletion();
expect(result).toBe(true);
delete process.env.DEBUG_AI360_CHAT_COMPLETION;
});
it('should disable debug mode when DEBUG_AI360_CHAT_COMPLETION is not 1', () => {
process.env.DEBUG_AI360_CHAT_COMPLETION = '0';
const result = params.debug.chatCompletion();
expect(result).toBe(false);
delete process.env.DEBUG_AI360_CHAT_COMPLETION;
});
});
describe('params.chatCompletion.handlePayload', () => {
it('should add web_search tool when enabledSearch is true', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toBeDefined();
expect(result.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
expect(result.tools?.find((tool: any) => tool.type === 'web_search')).toEqual({
type: 'web_search',
web_search: {
search_mode: 'auto',
},
});
});
it('should correctly set web_search properties', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
const webSearchTool = result.tools?.find((tool: any) => tool.type === 'web_search');
expect(webSearchTool).toBeDefined();
expect(webSearchTool.type).toBe('web_search');
expect(webSearchTool.web_search).toBeDefined();
expect(webSearchTool.web_search.search_mode).toBe('auto');
});
it('should not add web_search tool when enabledSearch is false', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: false,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toBeUndefined();
});
it('should not add web_search tool when enabledSearch is not present', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toBeUndefined();
});
it('should disable stream when tools are present', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
tools: [{ type: 'function', function: { name: 'test' } }],
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.stream).toBe(false);
});
it('should disable stream when enabledSearch is true', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.stream).toBe(false);
});
it('should not set stream to false when no tools', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.stream).not.toBe(false);
});
it('should merge tools with web_search when enabledSearch is true', () => {
const existingTool = { type: 'function', function: { name: 'existing_tool' } };
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
tools: [existingTool],
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toHaveLength(2);
expect(result.tools).toContainEqual(existingTool);
expect(result.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
});
it('should remove enabledSearch from payload', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.enabledSearch).toBeUndefined();
});
it('should preserve other payload properties', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0.7,
max_tokens: 1024,
top_p: 0.9,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.messages).toEqual(payload.messages);
expect(result.model).toBe(payload.model);
expect(result.temperature).toBe(payload.temperature);
expect(result.max_tokens).toBe(payload.max_tokens);
expect(result.top_p).toBe(payload.top_p);
});
it('should handle empty tools array when enabledSearch is true', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
tools: [],
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toHaveLength(1);
expect(result.tools?.[0].type).toBe('web_search');
});
it('should handle multiple existing tools when enabledSearch is true', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
tools: [
{ type: 'function', function: { name: 'tool1' } },
{ type: 'function', function: { name: 'tool2' } },
],
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toHaveLength(3);
expect(result.tools?.[0].function.name).toBe('tool1');
expect(result.tools?.[1].function.name).toBe('tool2');
expect(result.tools?.[2].type).toBe('web_search');
});
});
describe('params.models', () => {
it('should fetch and process models successfully', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
{
id: '360gpt2-o1',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toHaveLength(2);
expect(mockClient.models.list).toHaveBeenCalled();
});
it('should correctly map model properties', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.id).toBe('360gpt-pro');
expect(model.contextWindowTokens).toBe(8192);
expect(model.maxOutput).toBe(4096);
});
it('should detect 360gpt-pro as function call capable', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.functionCall).toBe(true);
});
it('should detect reasoning models with 360gpt2-o1 keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt2-o1',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.reasoning).toBe(true);
});
it('should detect reasoning models with 360zhinao2-o1 keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360zhinao2-o1-preview',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.reasoning).toBe(true);
});
it('should handle case-insensitive reasoning keyword matching', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360GPT2-O1',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.reasoning).toBe(true);
});
it('should merge with LOBE_DEFAULT_MODEL_LIST for known models', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
// Known models from LOBE_DEFAULT_MODEL_LIST should have displayName and enabled flag
expect(model.displayName).toBeDefined();
expect(model.enabled).toBeDefined();
});
it('should handle models not in LOBE_DEFAULT_MODEL_LIST', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'unknown-model',
max_tokens: 2048,
total_tokens: 4096,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.id).toBe('unknown-model');
expect(model.displayName).toBeUndefined();
expect(model.enabled).toBe(false);
});
it('should inherit abilities from LOBE_DEFAULT_MODEL_LIST', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
// Check that abilities are inherited from known model
expect(typeof model.functionCall).toBe('boolean');
expect(typeof model.vision).toBe('boolean');
expect(typeof model.reasoning).toBe('boolean');
});
it('should handle models with non-number max_tokens', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'test-model',
max_tokens: 'unlimited' as any,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.maxOutput).toBeUndefined();
});
it('should handle models with null max_tokens', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'test-model',
max_tokens: null as any,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.maxOutput).toBeUndefined();
});
it('should handle empty model list', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toEqual([]);
});
it('should handle model list with valid models only', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'model1',
max_tokens: 4096,
total_tokens: 8192,
},
{
id: 'model2',
max_tokens: 2048,
total_tokens: 4096,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toHaveLength(2);
expect(models.every((m) => m !== null && m.id)).toBe(true);
});
it('should handle API errors gracefully', async () => {
const mockClient = {
models: {
list: vi.fn().mockRejectedValue(new Error('API Error')),
},
};
await expect(params.models!({ client: mockClient as any })).rejects.toThrow('API Error');
});
it('should set reasoning to false for non-reasoning models', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-base',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.reasoning).toBe(false);
});
it('should set functionCall to false for non-360gpt-pro models without functionCall ability', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'test-model',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.functionCall).toBe(false);
});
it('should set vision to false for models without vision ability', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'test-model',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.vision).toBe(false);
});
it('should handle case-insensitive model ID matching with LOBE_DEFAULT_MODEL_LIST', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360GPT-PRO',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
// Should match case-insensitively with LOBE_DEFAULT_MODEL_LIST
expect(model.id).toBe('360GPT-PRO');
expect(model.displayName).toBeDefined();
});
it('should handle models with all properties present', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model).toHaveProperty('id');
expect(model).toHaveProperty('contextWindowTokens');
expect(model).toHaveProperty('maxOutput');
expect(model).toHaveProperty('functionCall');
expect(model).toHaveProperty('vision');
expect(model).toHaveProperty('reasoning');
expect(model).toHaveProperty('enabled');
});
it('should detect all reasoning model variants correctly', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt2-o1',
max_tokens: 8192,
total_tokens: 16384,
},
{
id: '360gpt2-o1-preview',
max_tokens: 8192,
total_tokens: 16384,
},
{
id: '360zhinao2-o1',
max_tokens: 8192,
total_tokens: 16384,
},
{
id: '360zhinao2-o1-mini',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toHaveLength(4);
expect(models.every((m) => m.reasoning === true)).toBe(true);
});
it('should handle models with zero context window', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'test-model',
max_tokens: 0,
total_tokens: 0,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.contextWindowTokens).toBe(0);
expect(model.maxOutput).toBe(0);
});
it('should handle very large context window values', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: 'large-context-model',
max_tokens: 128000,
total_tokens: 1000000,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.contextWindowTokens).toBe(1000000);
expect(model.maxOutput).toBe(128000);
});
it('should handle models with special characters in ID', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro-v2.0-beta',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.id).toBe('360gpt-pro-v2.0-beta');
});
it('should detect 360gpt-pro as functionCall capable', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.id).toBe('360gpt-pro');
expect(model.functionCall).toBe(true);
});
it('should prioritize reasoning from keyword over knownModel', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt2-o1-custom',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
// Should detect reasoning from keyword even if not in LOBE_DEFAULT_MODEL_LIST
expect(model.reasoning).toBe(true);
});
it('should prioritize functionCall from model ID check over knownModel', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt-pro',
max_tokens: 4096,
total_tokens: 8192,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.functionCall).toBe(true);
});
it('should merge multiple capabilities correctly', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [
{
id: '360gpt2-o1',
max_tokens: 8192,
total_tokens: 16384,
},
],
}),
},
};
const models = await params.models!({ client: mockClient as any });
const model = models[0];
expect(model.reasoning).toBe(true);
expect(typeof model.functionCall).toBe('boolean');
expect(typeof model.vision).toBe('boolean');
});
});
describe('LobeAi360AI instance - integration tests', () => {
it('should add web_search tool when enabledSearch is true', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toBeDefined();
expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
});
it('should disable stream when tools are present', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
tools: [{ type: 'function', function: { name: 'test' } }],
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.stream).toBe(false);
});
it('should enable stream when no tools', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.stream).not.toBe(false);
});
it('should merge tools with web_search when enabledSearch is true', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
tools: [{ type: 'function', function: { name: 'existing_tool' } }],
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toHaveLength(2);
expect(calledPayload.tools?.some((tool: any) => tool.type === 'function')).toBe(true);
expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
});
it('should preserve temperature without normalization', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0.7,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(0.7);
});
it('should preserve top_p without modification', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
top_p: 0.9,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.top_p).toBe(0.9);
});
it('should preserve max_tokens without modification', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
max_tokens: 1024,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.max_tokens).toBe(1024);
});
it('should preserve messages as-is', async () => {
const messages = [
{ content: 'Hello', role: 'user' as const },
{ content: 'Hi there!', role: 'assistant' as const },
{ content: 'How are you?', role: 'user' as const },
];
await instance.chat({
messages,
model: '360gpt-pro',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.messages).toEqual(messages);
});
it('should preserve model name', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt2-o1',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.model).toBe('360gpt2-o1');
});
it('should handle combined parameters correctly', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0.8,
top_p: 0.85,
max_tokens: 2048,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.model).toBe('360gpt-pro');
expect(calledPayload.temperature).toBe(0.8);
expect(calledPayload.top_p).toBe(0.85);
expect(calledPayload.max_tokens).toBe(2048);
});
it('should not include undefined parameters in payload', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
});
const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];
expect(callArgs).not.toHaveProperty('temperature');
expect(callArgs).not.toHaveProperty('top_p');
expect(callArgs).not.toHaveProperty('max_tokens');
});
});
describe('exports', () => {
it('should export params object', () => {
expect(params).toBeDefined();
expect(params.provider).toBe(ModelProvider.Ai360);
expect(params.baseURL).toBe('https://api.360.cn/v1');
});
it('should export LobeAi360AI class', () => {
expect(LobeAi360AI).toBeDefined();
expect(typeof LobeAi360AI).toBe('function');
});
it('should export params with all required properties', () => {
expect(params).toHaveProperty('provider');
expect(params).toHaveProperty('baseURL');
expect(params).toHaveProperty('chatCompletion');
expect(params).toHaveProperty('debug');
expect(params).toHaveProperty('models');
});
it('should have chatCompletion.handlePayload function', () => {
expect(params.chatCompletion).toBeDefined();
expect(params.chatCompletion.handlePayload).toBeDefined();
expect(typeof params.chatCompletion.handlePayload).toBe('function');
});
it('should have debug.chatCompletion function', () => {
expect(params.debug).toBeDefined();
expect(params.debug.chatCompletion).toBeDefined();
expect(typeof params.debug.chatCompletion).toBe('function');
});
it('should have models function', () => {
expect(params.models).toBeDefined();
expect(typeof params.models).toBe('function');
});
});
describe('edge cases and comprehensive coverage', () => {
it('should handle payload with undefined tools', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
tools: undefined,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toBeUndefined();
});
it('should handle payload with null tools', () => {
const payload = {
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
tools: null as any,
} as any;
const result = params.chatCompletion.handlePayload!(payload);
expect(result.tools).toBeNull();
});
it('should handle empty messages array', async () => {
await instance.chat({
messages: [],
model: '360gpt-pro',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.messages).toEqual([]);
});
it('should handle very small temperature values', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0.01,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(0.01);
});
it('should handle temperature of 0', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(0);
});
it('should handle temperature of 1', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 1,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(1);
});
it('should handle high temperature values', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 2.0,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(2.0);
});
it('should handle top_p of 0', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
top_p: 0,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.top_p).toBe(0);
});
it('should handle top_p of 1', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
top_p: 1,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.top_p).toBe(1);
});
it('should handle very small top_p values', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
top_p: 0.01,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.top_p).toBe(0.01);
});
it('should handle large max_tokens values', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
max_tokens: 100000,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.max_tokens).toBe(100000);
});
it('should handle complex message history with multiple roles', async () => {
const messages = [
{ content: 'You are a helpful assistant', role: 'system' as const },
{ content: 'Hello', role: 'user' as const },
{ content: 'Hi! How can I help?', role: 'assistant' as const },
{ content: 'What is 2+2?', role: 'user' as const },
{ content: '2+2 equals 4', role: 'assistant' as const },
{ content: 'Thanks', role: 'user' as const },
];
await instance.chat({
messages,
model: '360gpt-pro',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.messages).toEqual(messages);
});
it('should handle combined parameters with web search', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0.8,
top_p: 0.85,
max_tokens: 2048,
enabledSearch: true,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(0.8);
expect(calledPayload.top_p).toBe(0.85);
expect(calledPayload.max_tokens).toBe(2048);
expect(calledPayload.stream).toBe(false);
expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
});
it('should handle combined parameters with tools and no web search', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
temperature: 0.9,
top_p: 0.95,
max_tokens: 4096,
tools: [{ type: 'function', function: { name: 'test_tool' } }],
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(0.9);
expect(calledPayload.top_p).toBe(0.95);
expect(calledPayload.max_tokens).toBe(4096);
expect(calledPayload.stream).toBe(false);
expect(calledPayload.tools).toHaveLength(1);
});
it('should preserve all model parameters', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360zhinao2-o1-preview',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.model).toBe('360zhinao2-o1-preview');
});
it('should handle multiple function tools', async () => {
const tools = [
{ type: 'function' as const, function: { name: 'tool1', description: 'First tool' } },
{ type: 'function' as const, function: { name: 'tool2', description: 'Second tool' } },
{ type: 'function' as const, function: { name: 'tool3', description: 'Third tool' } },
];
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
tools,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toHaveLength(3);
expect(calledPayload.stream).toBe(false);
});
it('should handle enabledSearch with multiple function tools', async () => {
const tools = [
{ type: 'function' as const, function: { name: 'tool1', description: 'First tool' } },
{ type: 'function' as const, function: { name: 'tool2', description: 'Second tool' } },
];
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: '360gpt-pro',
enabledSearch: true,
tools,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toHaveLength(3);
expect(calledPayload.tools?.filter((t: any) => t.type === 'function')).toHaveLength(2);
expect(calledPayload.tools?.filter((t: any) => t.type === 'web_search')).toHaveLength(1);
expect(calledPayload.stream).toBe(false);
});
});
});