feat: add support for DouBao, Ernie, Kimi, Qwen, ZhiPu LLM providers

Introduces new provider modules for DouBao, Ernie, Kimi, Qwen, and ZhiPu, and registers them in the LLM registry. Updates documentation and .env.example to include configuration instructions for these providers. Refactors OpenAI provider to support OpenAI-compatible endpoints. Adds @ai-sdk/openai-compatible and node-fetch dependencies.
This commit is contained in:
LIlGG
2025-09-29 16:28:56 +08:00
parent ed0f9a81e1
commit c31e366af9
13 changed files with 407 additions and 13 deletions

View File

@@ -22,7 +22,7 @@ let currentLevel: DebugLevel =
(process.env.LOG_LEVEL as DebugLevel | undefined) || (import.meta.env.DEV ? 'debug' : 'info');
// 文件日志配置
const enableFileLogging = process.env.USAGE_LOG_FILE === 'true' || import.meta.env.DEV;
const enableFileLogging = process.env.USAGE_LOG_FILE !== 'false';
const logDir = path.join(process.cwd(), 'logs');
// 确保日志目录存在

View File

@@ -22,7 +22,7 @@ let currentLevel: DebugLevel =
(process.env.LOG_LEVEL as DebugLevel | undefined) || (import.meta.env.DEV ? 'debug' : 'info');
// 文件日志配置
const enableFileLogging = process.env.USAGE_LOG_FILE === 'true' || import.meta.env.DEV;
const enableFileLogging = process.env.USAGE_LOG_FILE !== 'false';
const logDir = path.join(process.cwd(), 'logs');
// 确保日志目录存在

View File

@@ -0,0 +1,57 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai';
import { BaseProvider } from '~/lib/modules/llm/base-provider';
import type { ModelInfo } from '~/lib/modules/llm/types';
import type { IProviderSetting } from '~/types/model';
export default class DouBaoProvider extends BaseProvider {
name = 'DouBao';
getApiKeyLink = undefined;
staticModels: ModelInfo[] = [];
async getDynamicModels(settings?: IProviderSetting): Promise<ModelInfo[]> {
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings);
const baseUrl = fetchBaseUrl || 'https://ark.cn-beijing.volces.com/api/v3';
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const response = await fetch(`${baseUrl}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
const res = (await response.json()) as any;
const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat);
return data.map((m: any) => ({
name: m.id,
label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`,
provider: this.name,
maxTokenAllowed: m.context_length || 8000,
}));
}
getModelInstance(options: { model: string; providerSettings?: Record<string, IProviderSetting> }): LanguageModel {
const { model, providerSettings } = options;
const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]);
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const provider = createOpenAICompatible({
name: this.name,
baseURL: 'https://ark.cn-beijing.volces.com/api/v3',
apiKey,
includeUsage: true,
});
return provider(model);
}
}

View File

@@ -0,0 +1,57 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai';
import { BaseProvider } from '~/lib/modules/llm/base-provider';
import type { ModelInfo } from '~/lib/modules/llm/types';
import type { IProviderSetting } from '~/types/model';
export default class ErnieProvider extends BaseProvider {
name = 'Ernie';
getApiKeyLink = undefined;
staticModels: ModelInfo[] = [];
async getDynamicModels(settings?: IProviderSetting): Promise<ModelInfo[]> {
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings);
const baseUrl = fetchBaseUrl || 'https://qianfan.baidubce.com/v2';
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const response = await fetch(`${baseUrl}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
const res = (await response.json()) as any;
const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat);
return data.map((m: any) => ({
name: m.id,
label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`,
provider: this.name,
maxTokenAllowed: m.context_length || 8000,
}));
}
getModelInstance(options: { model: string; providerSettings?: Record<string, IProviderSetting> }): LanguageModel {
const { model, providerSettings } = options;
const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]);
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const provider = createOpenAICompatible({
name: this.name,
baseURL: 'https://qianfan.baidubce.com/v2',
apiKey,
includeUsage: true,
});
return provider(model);
}
}

View File

@@ -0,0 +1,57 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai';
import { BaseProvider } from '~/lib/modules/llm/base-provider';
import type { ModelInfo } from '~/lib/modules/llm/types';
import type { IProviderSetting } from '~/types/model';
export default class KimiProvider extends BaseProvider {
name = 'Kimi';
getApiKeyLink = undefined;
staticModels: ModelInfo[] = [];
async getDynamicModels(settings?: IProviderSetting): Promise<ModelInfo[]> {
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings);
const baseUrl = fetchBaseUrl || 'https://api.moonshot.cn/v1';
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const response = await fetch(`${baseUrl}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
const res = (await response.json()) as any;
const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat);
return data.map((m: any) => ({
name: m.id,
label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`,
provider: this.name,
maxTokenAllowed: m.context_length || 8000,
}));
}
getModelInstance(options: { model: string; providerSettings?: Record<string, IProviderSetting> }): LanguageModel {
const { model, providerSettings } = options;
const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]);
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const provider = createOpenAICompatible({
name: this.name,
baseURL: 'https://api.moonshot.cn/v1',
apiKey,
includeUsage: true,
});
return provider(model);
}
}

View File

@@ -1,4 +1,5 @@
import { createOpenAI } from '@ai-sdk/openai';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai';
import { BaseProvider } from '~/lib/modules/llm/base-provider';
import type { ModelInfo } from '~/lib/modules/llm/types';
@@ -42,13 +43,19 @@ export default class OpenAILikeProvider extends BaseProvider {
throw new Error(`Missing configuration for ${this.name} provider`);
}
let openaiBaseUrl = baseUrl;
if (!baseUrl) {
openaiBaseUrl = undefined;
if (!!baseUrl) {
const provider = createOpenAICompatible({
name: this.name,
baseURL: baseUrl,
apiKey,
includeUsage: true,
});
return provider(model);
}
const openai = createOpenAI({
baseURL: openaiBaseUrl,
baseURL: baseUrl,
apiKey,
});

View File

@@ -0,0 +1,57 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai';
import { BaseProvider } from '~/lib/modules/llm/base-provider';
import type { ModelInfo } from '~/lib/modules/llm/types';
import type { IProviderSetting } from '~/types/model';
export default class QwenProvider extends BaseProvider {
name = 'Qwen';
getApiKeyLink = undefined;
staticModels: ModelInfo[] = [];
async getDynamicModels(settings?: IProviderSetting): Promise<ModelInfo[]> {
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings);
const baseUrl = fetchBaseUrl || 'https://dashscope.aliyuncs.com/compatible-mode/v1';
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const response = await fetch(`${baseUrl}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
const res = (await response.json()) as any;
const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat);
return data.map((m: any) => ({
name: m.id,
label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`,
provider: this.name,
maxTokenAllowed: m.context_length || 8000,
}));
}
getModelInstance(options: { model: string; providerSettings?: Record<string, IProviderSetting> }): LanguageModel {
const { model, providerSettings } = options;
const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]);
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const provider = createOpenAICompatible({
name: this.name,
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
apiKey,
includeUsage: true,
});
return provider(model);
}
}

View File

@@ -0,0 +1,57 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { LanguageModel } from 'ai';
import { BaseProvider } from '~/lib/modules/llm/base-provider';
import type { ModelInfo } from '~/lib/modules/llm/types';
import type { IProviderSetting } from '~/types/model';
export default class ZhiPuProvider extends BaseProvider {
name = 'ZhiPu';
getApiKeyLink = undefined;
staticModels: ModelInfo[] = [];
async getDynamicModels(settings?: IProviderSetting): Promise<ModelInfo[]> {
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings);
const baseUrl = fetchBaseUrl || 'https://open.bigmodel.cn/api/paas/v4';
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const response = await fetch(`${baseUrl}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
const res = (await response.json()) as any;
const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat);
return data.map((m: any) => ({
name: m.id,
label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`,
provider: this.name,
maxTokenAllowed: m.context_length || 8000,
}));
}
getModelInstance(options: { model: string; providerSettings?: Record<string, IProviderSetting> }): LanguageModel {
const { model, providerSettings } = options;
const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]);
if (!apiKey) {
throw `Missing Api Key configuration for ${this.name} provider`;
}
const openai = createOpenAICompatible({
name: this.name,
baseURL: 'https://open.bigmodel.cn/api/paas/v4',
apiKey,
includeUsage: true,
});
return openai(model);
}
}

View File

@@ -2,24 +2,34 @@ import AmazonBedrockProvider from './providers/amazon-bedrock';
import AnthropicProvider from './providers/anthropic';
import CohereProvider from './providers/cohere';
import DeepseekProvider from './providers/deepseek';
import DouBaoProvider from './providers/doubao';
import ErnieProvider from './providers/ernie';
import GithubProvider from './providers/github';
import GoogleProvider from './providers/google';
import GroqProvider from './providers/groq';
import HuggingFaceProvider from './providers/huggingface';
import HyperbolicProvider from './providers/hyperbolic';
import KimiProvider from './providers/kimi';
import LMStudioProvider from './providers/lmstudio';
import MistralProvider from './providers/mistral';
import OllamaProvider from './providers/ollama';
import OpenRouterProvider from './providers/open-router';
import OpenAIProvider from './providers/openai';
import PerplexityProvider from './providers/perplexity';
import QwenProvider from './providers/qwen';
import TogetherProvider from './providers/together';
import XAIProvider from './providers/xai';
import ZhiPuProvider from './providers/zhipu';
export {
AnthropicProvider,
CohereProvider,
DeepseekProvider,
DouBaoProvider,
ErnieProvider,
KimiProvider,
QwenProvider,
ZhiPuProvider,
GoogleProvider,
GroqProvider,
HuggingFaceProvider,