@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.
437 lines (401 loc) • 14 kB
text/typescript
import {
LobeAnthropicAI,
LobeAzureOpenAI,
LobeBedrockAI,
LobeDeepSeekAI,
LobeGoogleAI,
LobeGroq,
LobeMistralAI,
LobeMoonshotAI,
LobeOllamaAI,
LobeOpenAI,
LobeOpenAICompatibleRuntime,
LobeOpenRouterAI,
LobePerplexityAI,
LobeQwenAI,
LobeTogetherAI,
LobeZeroOneAI,
LobeZhipuAI,
ModelRuntime,
} from '@lobechat/model-runtime';
import { merge } from 'lodash-es';
import { ModelProvider } from 'model-bank';
import OpenAI from 'openai';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { UserStore } from '@/store/user';
import { UserSettingsState, initialSettingsState } from '@/store/user/slices/settings/initialState';
import { initializeWithClientStore } from './clientModelRuntime';
// Mocking external dependencies
vi.mock('i18next', () => ({
t: vi.fn((key) => `translated_${key}`),
}));
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve(new Response(JSON.stringify({ some: 'data' })))),
);
vi.mock('@lobechat/fetch-sse', async (importOriginal) => {
const module = await importOriginal();
return { ...(module as any), getMessageError: vi.fn() };
});
// Mock image processing utilities
vi.mock('@/utils/url', () => ({
isDesktopLocalStaticServerUrl: vi.fn(),
}));
vi.mock('@/utils/imageToBase64', () => ({
imageUrlToBase64: vi.fn(),
}));
vi.mock('@lobechat/model-runtime', async (importOriginal) => {
const actual = await importOriginal();
return {
...(actual as any),
parseDataUri: vi.fn(),
};
});
afterEach(() => {
vi.restoreAllMocks();
});
beforeEach(async () => {
// 清除所有模块的缓存
vi.resetModules();
// 默认设置 isServerMode 为 false
vi.mock('@/const/version', () => ({
isServerMode: false,
isDeprecatedEdition: true,
isDesktop: false,
}));
// Reset all mocks
vi.clearAllMocks();
// Set default mock return values for image processing utilities
const { isDesktopLocalStaticServerUrl } = await import('@/utils/url');
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
const { parseDataUri } = await import('@lobechat/model-runtime');
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
vi.mocked(isDesktopLocalStaticServerUrl).mockReturnValue(false);
vi.mocked(imageUrlToBase64).mockResolvedValue({
base64: 'mock-base64',
mimeType: 'image/jpeg',
});
});
describe('ModelRuntimeOnClient', () => {
describe('initializeWithClientStore', () => {
describe('should initialize with options correctly', () => {
it('OpenAI provider: with apikey and endpoint', async () => {
// Mock the global store to return the user's OpenAI API key and endpoint
merge(initialSettingsState, {
settings: {
keyVaults: {
openai: {
apiKey: 'user-openai-key',
baseURL: 'user-openai-endpoint',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.OpenAI,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);
expect(runtime['_runtime'].baseURL).toBe('user-openai-endpoint');
});
it('Azure provider: with apiKey, apiVersion, endpoint', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
azure: {
apiKey: 'user-azure-key',
endpoint: 'user-azure-endpoint',
apiVersion: '2024-06-01',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Azure,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeAzureOpenAI);
});
it('Google provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
google: {
apiKey: 'user-google-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Google,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeGoogleAI);
});
it('Moonshot AI provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
moonshot: {
apiKey: 'user-moonshot-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Moonshot,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeMoonshotAI);
});
it('Bedrock provider: with accessKeyId, region, secretAccessKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
bedrock: {
accessKeyId: 'user-bedrock-access-key',
region: 'user-bedrock-region',
secretAccessKey: 'user-bedrock-secret',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Bedrock,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeBedrockAI);
});
it('Ollama provider: with endpoint', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
ollama: {
baseURL: 'http://127.0.0.1:1234',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Ollama,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeOllamaAI);
});
it('Perplexity provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
perplexity: {
apiKey: 'user-perplexity-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Perplexity,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobePerplexityAI);
});
it('Anthropic provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
anthropic: {
apiKey: 'user-anthropic-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Anthropic,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeAnthropicAI);
});
it('Mistral provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
mistral: {
apiKey: 'user-mistral-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Mistral,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeMistralAI);
});
it('OpenRouter provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
openrouter: {
apiKey: 'user-openrouter-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.OpenRouter,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeOpenRouterAI);
});
it('TogetherAI provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
togetherai: {
apiKey: 'user-togetherai-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.TogetherAI,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeTogetherAI);
});
it('ZeroOneAI provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
zeroone: {
apiKey: 'user-zeroone-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.ZeroOne,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeZeroOneAI);
});
it('Groq provider: with apiKey,endpoint', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
groq: {
apiKey: 'user-groq-key',
baseURL: 'user-groq-endpoint',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Groq,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
const lobeOpenAICompatibleInstance = runtime['_runtime'] as LobeOpenAICompatibleRuntime;
expect(lobeOpenAICompatibleInstance).toBeInstanceOf(LobeGroq);
expect(lobeOpenAICompatibleInstance.baseURL).toBe('user-groq-endpoint');
expect(lobeOpenAICompatibleInstance.client).toBeInstanceOf(OpenAI);
expect(lobeOpenAICompatibleInstance.client.apiKey).toBe('user-groq-key');
});
it('DeepSeek provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
deepseek: {
apiKey: 'user-deepseek-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.DeepSeek,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeDeepSeekAI);
});
it('Qwen provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
qwen: {
apiKey: 'user-qwen-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.Qwen,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);
});
/**
* Should not have a unknown provider in client, but has
* similar cases in server side
*/
it('Unknown provider: with apiKey', async () => {
merge(initialSettingsState, {
settings: {
keyVaults: {
unknown: {
apiKey: 'user-unknown-key',
endpoint: 'user-unknown-endpoint',
},
},
},
} as any as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: 'unknown' as ModelProvider,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);
});
/**
* The following test cases need to be enforce
*/
it('ZhiPu AI provider: with apiKey', async () => {
// Mock the generateApiToken function
vi.mock('@/libs/model-runtime/zhipu/authToken', () => ({
generateApiToken: vi
.fn()
.mockResolvedValue(
'eyJhbGciOiJIUzI1NiIsInNpZ25fdHlwZSI6IlNJR04iLCJ0eXAiOiJKV1QifQ.eyJhcGlfa2V5IjoiemhpcHUiLCJleHAiOjE3MTU5MTc2NzMsImlhdCI6MTcxMzMyNTY3M30.gt8o-hUDvJFPJLYcH4EhrT1LAmTXI8YnybHeQjpD9oM',
),
}));
merge(initialSettingsState, {
settings: {
keyVaults: {
zhipu: {
apiKey: 'zhipu.user-key',
},
},
},
} as UserSettingsState) as unknown as UserStore;
const runtime = await initializeWithClientStore({
payload: {},
provider: ModelProvider.ZhiPu,
});
expect(runtime).toBeInstanceOf(ModelRuntime);
expect(runtime['_runtime']).toBeInstanceOf(LobeZhipuAI);
});
});
});
});