@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.
336 lines (283 loc) • 10.5 kB
text/typescript
// @vitest-environment node
import { ModelProvider } from 'model-bank';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { testProvider } from '../../providerTestUtils';
import { LobeStepfunAI, params } from './index';
const provider = ModelProvider.Stepfun;
const defaultBaseURL = 'https://api.stepfun.com/v1';
testProvider({
Runtime: LobeStepfunAI,
chatDebugEnv: 'DEBUG_STEPFUN_CHAT_COMPLETION',
chatModel: 'stepfun',
defaultBaseURL,
provider,
test: {
skipAPICall: true,
},
});
describe('LobeStepfunAI - custom features', () => {
let instance: InstanceType<typeof LobeStepfunAI>;
beforeEach(() => {
instance = new LobeStepfunAI({ apiKey: 'test_api_key' });
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
new ReadableStream() as any,
);
});
describe('params export', () => {
it('should export params with correct structure', () => {
expect(params).toBeDefined();
expect(params.provider).toBe(ModelProvider.Stepfun);
expect(params.baseURL).toBe('https://api.stepfun.com/v1');
expect(params.debug).toBeDefined();
expect(params.chatCompletion).toBeDefined();
expect(params.models).toBeDefined();
});
it('should have debug.chatCompletion function', () => {
expect(typeof params.debug?.chatCompletion).toBe('function');
});
it('should return false when DEBUG_STEPFUN_CHAT_COMPLETION is not set', () => {
delete process.env.DEBUG_STEPFUN_CHAT_COMPLETION;
expect(params.debug?.chatCompletion()).toBe(false);
});
it('should return true when DEBUG_STEPFUN_CHAT_COMPLETION is set to 1', () => {
process.env.DEBUG_STEPFUN_CHAT_COMPLETION = '1';
expect(params.debug?.chatCompletion()).toBe(true);
delete process.env.DEBUG_STEPFUN_CHAT_COMPLETION;
});
});
describe('handlePayload', () => {
it('should add web_search tool when enabledSearch is true', async () => {
await instance.chat({
enabledSearch: true,
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toContainEqual({
function: {
description: 'use web_search to search information on the internet',
},
type: 'web_search',
});
});
it('should merge web_search with existing tools', async () => {
await instance.chat({
enabledSearch: true,
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
tools: [{ function: { name: 'test' }, type: 'function' }],
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toHaveLength(2);
expect(calledPayload.tools[0]).toEqual({ function: { name: 'test' }, type: 'function' });
expect(calledPayload.tools[1]).toEqual({
function: {
description: 'use web_search to search information on the internet',
},
type: 'web_search',
});
});
it('should not add web_search tool when enabledSearch is false', async () => {
await instance.chat({
enabledSearch: false,
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toBeUndefined();
});
it('should not add web_search tool when enabledSearch is undefined', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toBeUndefined();
});
it('should preserve existing tools when enabledSearch is false', async () => {
await instance.chat({
enabledSearch: false,
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
tools: [{ function: { name: 'test' }, type: 'function' }],
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.tools).toHaveLength(1);
expect(calledPayload.tools[0]).toEqual({ function: { name: 'test' }, type: 'function' });
});
it('should set stream to false when tools are present', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
tools: [{ function: { name: 'test' }, type: 'function' }],
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.stream).toBe(false);
});
it('should set stream to false when web_search is enabled', async () => {
await instance.chat({
enabledSearch: true,
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.stream).toBe(false);
});
it('should set stream to true when no tools are present', async () => {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.stream).toBe(true);
});
it('should preserve other payload properties', async () => {
await instance.chat({
max_tokens: 100,
messages: [{ content: 'Hello', role: 'user' }],
model: 'step-1-8k',
temperature: 0.7,
});
const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
expect(calledPayload.temperature).toBe(0.7);
expect(calledPayload.max_tokens).toBe(100);
});
});
describe('models function - keyword detection', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should detect function call from step-1- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-1-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect function call from step-1o- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-1o-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect function call from step-1v- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-1v-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect function call from step-2- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-2-16k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect vision from step-1o- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-1o-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect vision from step-r1-v- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-r1-v-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect vision from step-1v- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-1v-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should detect reasoning from step-r1- keyword', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-r1-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should handle case-insensitive keyword matching', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'STEP-1-8K' }, { id: 'Step-R1-Turbo' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should handle models without matching keywords', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'other-model' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
it('should merge abilities from known models', async () => {
const mockClient = {
models: {
list: vi.fn().mockResolvedValue({
data: [{ id: 'step-1-8k' }],
}),
},
};
const models = await params.models!({ client: mockClient as any });
expect(models).toBeDefined();
});
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([]);
});
});
});