@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.
278 lines (224 loc) • 8.25 kB
text/typescript
// @vitest-environment node
import { ModelProvider } from 'model-bank';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { testProvider } from '../../providerTestUtils';
import { FireworksAIModelCard, LobeFireworksAI, params } from './index';
const provider = ModelProvider.FireworksAI;
const defaultBaseURL = 'https://api.fireworks.ai/inference/v1';
// Basic provider tests
testProvider({
Runtime: LobeFireworksAI,
bizErrorType: 'ProviderBizError',
chatDebugEnv: 'DEBUG_FIREWORKSAI_CHAT_COMPLETION',
chatModel: 'deepseek-r1',
defaultBaseURL,
invalidErrorType: 'InvalidProviderAPIKey',
provider,
test: {
skipAPICall: true,
skipErrorHandle: true,
},
});
// Custom feature tests
describe('LobeFireworksAI - custom features', () => {
describe('params export', () => {
it('should export params object', () => {
expect(params).toBeDefined();
expect(params.baseURL).toBe(defaultBaseURL);
expect(params.provider).toBe(provider);
});
it('should have correct debug configuration', () => {
expect(params.debug?.chatCompletion).toBeDefined();
// Test debug = false
delete process.env.DEBUG_FIREWORKSAI_CHAT_COMPLETION;
expect(params.debug?.chatCompletion?.()).toBe(false);
// Test debug = true
process.env.DEBUG_FIREWORKSAI_CHAT_COMPLETION = '1';
expect(params.debug?.chatCompletion?.()).toBe(true);
// Cleanup
delete process.env.DEBUG_FIREWORKSAI_CHAT_COMPLETION;
});
});
describe('models function', () => {
let mockClient: any;
beforeEach(() => {
mockClient = {
models: {
list: vi.fn(),
},
};
});
it('should transform FireworksAI models to ChatModelCard format', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'accounts/fireworks/models/deepseek-r1',
supports_image_input: false,
supports_tools: true,
},
{
context_length: 4096,
id: 'accounts/fireworks/models/qwq-32b',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result).toHaveLength(2);
expect(result[0]).toMatchObject({
contextWindowTokens: 8192,
functionCall: true,
id: 'accounts/fireworks/models/deepseek-r1',
reasoning: true, // Contains 'deepseek-r1' keyword
vision: false,
});
expect(result[1]).toMatchObject({
contextWindowTokens: 4096,
functionCall: false,
id: 'accounts/fireworks/models/qwq-32b',
reasoning: true, // Contains 'qwq' keyword
vision: false,
});
});
it('should detect reasoning models by keyword - deepseek-r1', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'deepseek-r1-distill-qwen-32b',
supports_image_input: false,
supports_tools: true,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].reasoning).toBe(true);
});
it('should detect reasoning models by keyword - qwq', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 4096,
id: 'qwq-32b-preview',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].reasoning).toBe(true);
});
it('should detect function call from supports_tools', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'test-model',
supports_image_input: false,
supports_tools: true,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].functionCall).toBe(true);
});
it('should detect function call from model id containing "function"', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'test-function-calling-model',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].functionCall).toBe(true);
});
it('should set functionCall to false when neither supports_tools nor id contains "function"', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'test-model',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].functionCall).toBe(false);
});
it('should detect vision support from supports_image_input', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'vision-model',
supports_image_input: true,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].vision).toBe(true);
});
it('should handle models not in LOBE_DEFAULT_MODEL_LIST', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 4096,
id: 'unknown-custom-model',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0]).toMatchObject({
contextWindowTokens: 4096,
displayName: undefined,
enabled: false,
functionCall: false,
id: 'unknown-custom-model',
reasoning: false,
vision: false,
});
});
it('should handle empty model list', async () => {
mockClient.models.list.mockResolvedValue({ data: [] });
const result = await params.models!({ client: mockClient });
expect(result).toEqual([]);
});
it('should use case-insensitive matching for model detection', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'DEEPSEEK-R1-UPPER',
supports_image_input: false,
supports_tools: false,
},
{
context_length: 4096,
id: 'TEST-FUNCTION-MODEL',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
expect(result[0].reasoning).toBe(true); // 'deepseek-r1' keyword match (case-insensitive)
expect(result[1].functionCall).toBe(true); // 'function' in id (case-insensitive)
});
it('should merge knownModel abilities with detected reasoning', async () => {
const mockModels: FireworksAIModelCard[] = [
{
context_length: 8192,
id: 'regular-model',
supports_image_input: false,
supports_tools: false,
},
];
mockClient.models.list.mockResolvedValue({ data: mockModels });
const result = await params.models!({ client: mockClient });
// Since 'regular-model' doesn't contain reasoning keywords and is not in LOBE_DEFAULT_MODEL_LIST
// with reasoning abilities, it should be false
expect(result[0].reasoning).toBe(false);
});
});
});