From 3d7e4ebb71a2a0880e32a64e8ac20ae633bb94b5 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 19 Feb 2025 15:54:34 -0800 Subject: [PATCH] Alibaba Cloud support, incl Qwen Max, Plus, Turbo. Fixes #759 --- README.md | 16 +-- docs/README.md | 1 + docs/environment-variables.md | 4 + docs/k8s/env-secret.yaml | 2 + .../icons/vendors/AlibabaCloudIcon.tsx | 11 ++ src/common/util/dMessageUtils.tsx | 10 ++ src/modules/aix/AIX.README.md | 1 + .../chatGenerate/chatGenerate.dispatch.ts | 1 + src/modules/backend/backend.router.ts | 1 + .../backend/store-backend-capabilities.ts | 2 + .../server/openai/models/alibaba.models.ts | 127 ++++++++++++++++++ .../llms/server/openai/openai.router.ts | 27 +++- .../vendors/alibaba/AlibabaServiceSetup.tsx | 90 +++++++++++++ .../llms/vendors/alibaba/alibaba.vendor.ts | 46 +++++++ src/modules/llms/vendors/vendors.registry.ts | 3 + src/server/env.mjs | 4 + 16 files changed, 336 insertions(+), 10 deletions(-) create mode 100644 src/common/components/icons/vendors/AlibabaCloudIcon.tsx create mode 100644 src/modules/llms/server/openai/models/alibaba.models.ts create mode 100644 src/modules/llms/vendors/alibaba/AlibabaServiceSetup.tsx create mode 100644 src/modules/llms/vendors/alibaba/alibaba.vendor.ts diff --git a/README.md b/README.md index 3c8a2a357..5c6e7ed21 100644 --- a/README.md +++ b/README.md @@ -174,14 +174,14 @@ For full details and former releases, check out the [changelog](docs/changelog.m You can easily configure 100s of AI models in big-AGI: -| **AI models** | _supported vendors_ | -|:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Opensource Servers | [LocalAI](https://localai.io/) (multimodal) · [Ollama](https://ollama.com/) | -| Local Servers | [LM Studio](https://lmstudio.ai/) | -| Multimodal services | [Azure](https://azure.microsoft.com/en-us/products/ai-services/openai-service) · [Google Gemini](https://ai.google.dev/) · [OpenAI](https://platform.openai.com/docs/overview) | -| Language services | [Anthropic](https://anthropic.com) · [Groq](https://wow.groq.com/) · [Mistral](https://mistral.ai/) · [OpenRouter](https://openrouter.ai/) · [Perplexity](https://www.perplexity.ai/) · [Together AI](https://www.together.ai/) | -| Image services | [Prodia](https://prodia.com/) (SDXL) | -| Speech services | [ElevenLabs](https://elevenlabs.io) (Voice synthesis / cloning) | +| **AI models** | _supported vendors_ | +|:--------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Opensource Servers | [LocalAI](https://localai.io/) (multimodal) · [Ollama](https://ollama.com/) | +| Local Servers | [LM Studio](https://lmstudio.ai/) | +| Multimodal services | [Azure](https://azure.microsoft.com/en-us/products/ai-services/openai-service) · [Anthropic](https://anthropic.com) · [Google Gemini](https://ai.google.dev/) · [OpenAI](https://platform.openai.com/docs/overview) | +| Language services | [Alibaba](https://www.alibabacloud.com/en/product/modelstudio) · [DeepSeek](https://deepseek.com) · [Groq](https://wow.groq.com/) · [Mistral](https://mistral.ai/) · [OpenRouter](https://openrouter.ai/) · [Perplexity](https://www.perplexity.ai/) · [Together AI](https://www.together.ai/) | +| Image services | [Prodia](https://prodia.com/) (SDXL) | +| Speech services | [ElevenLabs](https://elevenlabs.io) (Voice synthesis / cloning) | Add extra functionality with these integrations: diff --git a/docs/README.md b/docs/README.md index 366c4f947..0e4f27ca0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,6 +17,7 @@ How to set up AI models and features in big-AGI. - **Cloud AI Services**: - Easy API key configuration: + [Alibaba](https://bailian.console.alibabacloud.com/?apiKey=1#/api-key), [Anthropic](https://console.anthropic.com/settings/keys), [Deepseek](https://platform.deepseek.com/api_keys), [Google Gemini](https://aistudio.google.com/app/apikey), diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 688c70547..8b7669634 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -23,6 +23,8 @@ MDB_URI= OPENAI_API_KEY= OPENAI_API_HOST= OPENAI_API_ORG_ID= +ALIBABA_API_HOST= +ALIBABA_API_KEY= AZURE_OPENAI_API_ENDPOINT= AZURE_OPENAI_API_KEY= ANTHROPIC_API_KEY= @@ -88,6 +90,8 @@ requiring the user to enter an API key | `OPENAI_API_KEY` | API key for OpenAI | Recommended | | `OPENAI_API_HOST` | Changes the backend host for the OpenAI vendor, to enable platforms such as Helicone and CloudFlare AI Gateway | Optional | | `OPENAI_API_ORG_ID` | Sets the "OpenAI-Organization" header field to support organization users | Optional | +| `ALIBABA_API_HOST` | The Alibaba AI OpenAI-compatible endpoint | Optional | +| `ALIBABA_API_KEY` | The API key for Alibaba AI | Optional | | `AZURE_OPENAI_API_ENDPOINT` | Azure OpenAI endpoint - host only, without the path | Optional, but if set `AZURE_OPENAI_API_KEY` must also be set | | `AZURE_OPENAI_API_KEY` | Azure OpenAI API key, see [config-azure-openai.md](config-azure-openai.md) | Optional, but if set `AZURE_OPENAI_API_ENDPOINT` must also be set | | `ANTHROPIC_API_KEY` | The API key for Anthropic | Optional | diff --git a/docs/k8s/env-secret.yaml b/docs/k8s/env-secret.yaml index 7b4caabd9..8d113a143 100644 --- a/docs/k8s/env-secret.yaml +++ b/docs/k8s/env-secret.yaml @@ -16,6 +16,8 @@ stringData: OPENAI_API_KEY: "" OPENAI_API_HOST: "" OPENAI_API_ORG_ID: "" + ALIBABA_API_HOST: "" + ALIBABA_API_KEY: "" AZURE_OPENAI_API_ENDPOINT: "" AZURE_OPENAI_API_KEY: "" ANTHROPIC_API_KEY: "" diff --git a/src/common/components/icons/vendors/AlibabaCloudIcon.tsx b/src/common/components/icons/vendors/AlibabaCloudIcon.tsx new file mode 100644 index 000000000..c6f2f1999 --- /dev/null +++ b/src/common/components/icons/vendors/AlibabaCloudIcon.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +import { SvgIcon, SvgIconProps } from '@mui/joy'; + +// This icon has been converted from the official SVG of the Alibaba Cloud logo +export function AlibabaCloudIcon(props: SvgIconProps) { + return + + + ; +} \ No newline at end of file diff --git a/src/common/util/dMessageUtils.tsx b/src/common/util/dMessageUtils.tsx index 4e8c04e41..1b6c11ad0 100644 --- a/src/common/util/dMessageUtils.tsx +++ b/src/common/util/dMessageUtils.tsx @@ -345,6 +345,16 @@ export function prettyShortChatModelName(model: string | undefined): string { } // [LocalAI?] if (model.endsWith('.bin')) return model.slice(0, -4); + // [Alibaba] + if (model.startsWith('alibaba-qwen-') || model.startsWith('qwen-')) { + return model + .replace('alibaba-', ' ') + .replace('qwen', 'Qwen') + .replace('max', 'Max') + .replace('plus', 'Plus') + .replace('turbo', 'Turbo') + .replaceAll('-', ' '); + } // [Anthropic] const prettyAnthropic = _prettyAnthropicModelName(model); if (prettyAnthropic) return prettyAnthropic; diff --git a/src/modules/aix/AIX.README.md b/src/modules/aix/AIX.README.md index 56367632d..6ff36735e 100644 --- a/src/modules/aix/AIX.README.md +++ b/src/modules/aix/AIX.README.md @@ -22,6 +22,7 @@ Built with tRPC, it manages the lifecycle of AI-generated content from request t | Service | Chat | Function Calling | Multi-Modal Input | Cont. (1) | Streaming | Idiosyncratic | |------------|------------|------------------|-------------------|-----------|-----------|---------------| +| Alibaba | ✅ | ✅ | | ✅ | Yes + 📦 | | | Anthropic | ✅ | ✅ + Parallel | Img: ✅ | ✅ | Yes + 📦 | | | Azure | ✅ | ✅ | | ✅ | Yes + 📦 | | | Deepseek | ✅ | ❌ (rejected) | | ✅ | Yes + 📦 | | diff --git a/src/modules/aix/server/dispatch/chatGenerate/chatGenerate.dispatch.ts b/src/modules/aix/server/dispatch/chatGenerate/chatGenerate.dispatch.ts index 63d658b2e..03e64635f 100644 --- a/src/modules/aix/server/dispatch/chatGenerate/chatGenerate.dispatch.ts +++ b/src/modules/aix/server/dispatch/chatGenerate/chatGenerate.dispatch.ts @@ -74,6 +74,7 @@ export function createChatGenerateDispatch(access: AixAPI_Access, model: AixAPI_ chatGenerateParse: streaming ? createOpenAIChatCompletionsChunkParser() : createOpenAIChatCompletionsParserNS(), }; + case 'alibaba': case 'azure': case 'deepseek': case 'groq': diff --git a/src/modules/backend/backend.router.ts b/src/modules/backend/backend.router.ts index f41cbd4d7..1582092f2 100644 --- a/src/modules/backend/backend.router.ts +++ b/src/modules/backend/backend.router.ts @@ -49,6 +49,7 @@ export const backendRouter = createTRPCRouter({ .query(async ({ ctx: _unused }): Promise => { return { // llms + hasLlmAlibaba: !!env.ALIBABA_API_KEY || !!env.ALIBABA_API_HOST, hasLlmAnthropic: !!env.ANTHROPIC_API_KEY, hasLlmAzureOpenAI: !!env.AZURE_OPENAI_API_KEY && !!env.AZURE_OPENAI_API_ENDPOINT, hasLlmDeepseek: !!env.DEEPSEEK_API_KEY, diff --git a/src/modules/backend/store-backend-capabilities.ts b/src/modules/backend/store-backend-capabilities.ts index 4e11ed648..d454ea66c 100644 --- a/src/modules/backend/store-backend-capabilities.ts +++ b/src/modules/backend/store-backend-capabilities.ts @@ -8,6 +8,7 @@ import { useShallow } from 'zustand/react/shallow'; export interface BackendCapabilities { // llms + hasLlmAlibaba: boolean; hasLlmAnthropic: boolean; hasLlmAzureOpenAI: boolean; hasLlmDeepseek: boolean; @@ -48,6 +49,7 @@ const useBackendCapabilitiesStore = create()( (set) => ({ // initial values + hasLlmAlibaba: false, hasLlmAnthropic: false, hasLlmAzureOpenAI: false, hasLlmDeepseek: false, diff --git a/src/modules/llms/server/openai/models/alibaba.models.ts b/src/modules/llms/server/openai/models/alibaba.models.ts new file mode 100644 index 000000000..d8e899172 --- /dev/null +++ b/src/modules/llms/server/openai/models/alibaba.models.ts @@ -0,0 +1,127 @@ +import { LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision } from '~/common/stores/llms/llms.types'; + +import type { ModelDescriptionSchema } from '../../llm.server.types'; + +import { fromManualMapping, ManualMappings } from './models.data'; + +// - Models: https://www.alibabacloud.com/help/en/model-studio/getting-started/models +// - Pricing: https://www.alibabacloud.com/en/product/modelstudio?_p_lc=1&spm=a3c0i.11852017.6791778070.50.46f07ac9erixlG#J_9325669630 + +const _knownAlibabaChatModels: ManualMappings = [ + // Commercial Models + { + idPrefix: 'qwen-max', + label: 'Qwen-Max', + description: 'Best inference performance among Qwen models, especially for complex tasks. 32K context.', + contextWindow: 32768, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 8192, + chatPrice: { input: 0.0016, output: 0.0064 }, + benchmark: { cbaElo: 1332 }, + }, + { + idPrefix: 'qwen-plus', + label: 'Qwen-Plus', + description: 'Balanced performance, speed, and cost. 131K context.', + contextWindow: 131072, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 8192, + chatPrice: { input: 0.0004, output: 0.0012 }, + benchmark: { cbaElo: 1282 }, + }, + { + idPrefix: 'qwen-turbo', + label: 'Qwen-Turbo', + description: 'Fast speed and low cost, suitable for simple tasks. 1M context.', + contextWindow: 1000000, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 8192, + chatPrice: { input: 0.00005, output: 0.0002 }, + }, + + // Vision Models + { + idPrefix: 'qwen-vl-max', + label: 'Qwen-VL Max', + description: 'Enhanced visual reasoning and instruction-following capabilities.', + contextWindow: 7500, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Vision], + maxCompletionTokens: 1500, + chatPrice: { input: 'free', output: 'free' }, // Time-limited free trial + }, + { + idPrefix: 'qwen-vl-plus', + label: 'Qwen-VL Plus', + description: 'Enhanced detail and text recognition for visual tasks.', + contextWindow: 7500, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Vision], + maxCompletionTokens: 1500, + chatPrice: { input: 'free', output: 'free' }, // Time-limited free trial + }, + + // Open Source Models - Qwen2.5 + { + idPrefix: 'qwen2.5-72b-instruct', + label: 'Qwen 2.5 72B', + description: 'Latest Qwen series, 131K context.', + contextWindow: 131072, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 8192, + chatPrice: { input: 'free', output: 'free' }, // Time-limited free trial + }, + { + idPrefix: 'qwen2.5-14b-instruct-1m', + label: 'Qwen 2.5 14B (1M)', + description: 'Latest Qwen series with 1M context.', + contextWindow: 1000000, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 8192, + chatPrice: { input: 'free', output: 'free' }, // Time-limited free trial + }, + { + idPrefix: 'qwen2.5-7b-instruct-1m', + label: 'Qwen 2.5 7B (1M)', + description: 'Latest Qwen series with 1M context.', + contextWindow: 1000000, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 8192, + chatPrice: { input: 'free', output: 'free' }, // Time-limited free trial + }, + + // Open Source Models - Qwen2 + { + idPrefix: 'qwen2-7b-instruct', + label: 'Qwen 2 7B', + description: 'Open source Qwen2 model.', + contextWindow: 131072, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn], + maxCompletionTokens: 6144, + chatPrice: { input: 'free', output: 'free' }, // Time-limited free trial + }, +] as const; + + +export function alibabaModelToModelDescription(alibabaModelId: string, created?: number): ModelDescriptionSchema { + // create is a number like '1728632029' - convert to Month/Year + // const createdDate = created ? new Date(created * 1000) : undefined; + // const createdStr = createdDate?.toLocaleString('en-US', { month: 'short', year: 'numeric' }); + // NOTE: as of Feb 2025, reports that the 4 Qwen models were created in Oct 2024. + // So we're not using the created date for now, as to not confuse Users. + return fromManualMapping(_knownAlibabaChatModels, alibabaModelId, created, undefined, { + idPrefix: alibabaModelId, + label: alibabaModelId.replaceAll(/[_-]/g, ' '), + description: 'New Alibaba Model', + contextWindow: 128000, + maxCompletionTokens: 4096, + interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision], // assume.. + }); +} + +export function alibabaModelSort(a: ModelDescriptionSchema, b: ModelDescriptionSchema) { + // sort by the order in the known models list + const aIndex = _knownAlibabaChatModels.findIndex(m => a.id.startsWith(m.idPrefix)); + const bIndex = _knownAlibabaChatModels.findIndex(m => b.id.startsWith(m.idPrefix)); + if (aIndex !== -1 && bIndex !== -1) + return aIndex - bIndex; + return a.id.localeCompare(b.id); +} \ No newline at end of file diff --git a/src/modules/llms/server/openai/openai.router.ts b/src/modules/llms/server/openai/openai.router.ts index 2e0dcfd12..4689eab39 100644 --- a/src/modules/llms/server/openai/openai.router.ts +++ b/src/modules/llms/server/openai/openai.router.ts @@ -14,11 +14,12 @@ import { fixupHost } from '~/common/util/urlUtils'; import { OpenAIWire_API_Images_Generations, OpenAIWire_API_Models_List, OpenAIWire_API_Moderations_Create } from '~/modules/aix/server/dispatch/wiretypes/openai.wiretypes'; import { ListModelsResponse_schema, ModelDescriptionSchema } from '../llm.server.types'; +import { alibabaModelSort, alibabaModelToModelDescription } from './models/alibaba.models'; import { azureModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openAISortModels } from './models/openai.models'; import { deepseekModelFilter, deepseekModelSort, deepseekModelToModelDescription } from './models/deepseek.models'; import { fireworksAIHeuristic, fireworksAIModelsToModelDescriptions } from './models/fireworksai.models'; import { groqModelFilter, groqModelSortFn, groqModelToModelDescription } from './models/groq.models'; -import { lmStudioModelToModelDescription, localAIModelToModelDescription, localAIModelSortFn } from './models/models.data'; +import { lmStudioModelToModelDescription, localAIModelSortFn, localAIModelToModelDescription } from './models/models.data'; import { mistralModelsSort, mistralModelToModelDescription } from './models/mistral.models'; import { openPipeModelDescriptions, openPipeModelSort, openPipeModelToModelDescriptions } from './models/openpipe.models'; import { openRouterModelFamilySortFn, openRouterModelToModelDescription } from './models/openrouter.models'; @@ -29,7 +30,7 @@ import { xaiModelDescriptions, xaiModelSort } from './models/xai.models'; const openAIDialects = z.enum([ - 'azure', 'deepseek', 'groq', 'lmstudio', 'localai', 'mistral', 'openai', 'openpipe', 'openrouter', 'perplexity', 'togetherai', 'xai', + 'alibaba', 'azure', 'deepseek', 'groq', 'lmstudio', 'localai', 'mistral', 'openai', 'openpipe', 'openrouter', 'perplexity', 'togetherai', 'xai', ]); export type OpenAIDialects = z.infer; @@ -157,6 +158,12 @@ export const llmOpenAIRouter = createTRPCRouter({ // every dialect has a different way to enumerate models - we execute the mapping on the server side switch (access.dialect) { + case 'alibaba': + models = openAIModels + .map(({ id, created }) => alibabaModelToModelDescription(id, created)) + .sort(alibabaModelSort); + break; + case 'deepseek': models = openAIModels .filter(({ id }) => deepseekModelFilter(id)) @@ -343,6 +350,7 @@ export const llmOpenAIRouter = createTRPCRouter({ }); +const DEFAULT_ALIBABA_HOST = 'https://dashscope-intl.aliyuncs.com/compatible-mode'; const DEFAULT_HELICONE_OPENAI_HOST = 'oai.hconeai.com'; const DEFAULT_DEEPSEEK_HOST = 'https://api.deepseek.com'; const DEFAULT_GROQ_HOST = 'https://api.groq.com/openai'; @@ -358,6 +366,21 @@ const DEFAULT_XAI_HOST = 'https://api.x.ai'; export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | null, apiPath: string): { headers: HeadersInit, url: string } { switch (access.dialect) { + case 'alibaba': + const alibabaOaiKey = access.oaiKey || env.ALIBABA_API_KEY || ''; + const alibabaOaiHost = fixupHost(access.oaiHost || env.ALIBABA_API_HOST || DEFAULT_ALIBABA_HOST, apiPath); + if (!alibabaOaiKey || !alibabaOaiHost) + throw new Error('Missing Alibaba API Key. Add it on the UI or server side (your deployment).'); + + return { + headers: { + 'Authorization': `Bearer ${alibabaOaiKey}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + url: alibabaOaiHost + apiPath, + }; + case 'azure': const azureKey = access.oaiKey || env.AZURE_OPENAI_API_KEY || ''; const azureHost = fixupHost(access.oaiHost || env.AZURE_OPENAI_API_ENDPOINT || '', apiPath); diff --git a/src/modules/llms/vendors/alibaba/AlibabaServiceSetup.tsx b/src/modules/llms/vendors/alibaba/AlibabaServiceSetup.tsx new file mode 100644 index 000000000..e0b66c4a8 --- /dev/null +++ b/src/modules/llms/vendors/alibaba/AlibabaServiceSetup.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; + +import { Typography } from '@mui/joy'; + +import type { DModelsServiceId } from '~/common/stores/llms/llms.service.types'; +import { AlreadySet } from '~/common/components/AlreadySet'; +import { ExternalLink } from '~/common/components/ExternalLink'; +import { FormInputKey } from '~/common/components/forms/FormInputKey'; +import { FormTextField } from '~/common/components/forms/FormTextField'; +import { InlineError } from '~/common/components/InlineError'; +import { Link } from '~/common/components/Link'; +import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton'; +import { useToggleableBoolean } from '~/common/util/hooks/useToggleableBoolean'; + +import { ApproximateCosts } from '../ApproximateCosts'; +import { useLlmUpdateModels } from '../../llm.client.hooks'; +import { useServiceSetup } from '../useServiceSetup'; + +import { ModelVendorAlibaba } from './alibaba.vendor'; + + +const CLIENT_ALIBABA_DEFAULT_HOST = 'https://dashscope-intl.aliyuncs.com/compatible-mode'; +const ALIBABA_REG_LINK = 'https://bailian.console.alibabacloud.com/?apiKey=1#/api-key'; +const ALIBABA_MODELS = 'https://www.alibabacloud.com/help/en/model-studio/getting-started/models'; + + +export function AlibabaServiceSetup(props: { serviceId: DModelsServiceId }) { + + // state + const advanced = useToggleableBoolean(); + + // external state + const { + service, serviceAccess, serviceHasBackendCap, serviceHasLLMs, + serviceSetupValid, updateSettings, + } = useServiceSetup(props.serviceId, ModelVendorAlibaba); + + // derived state + const { oaiKey: alibabaOaiKey, oaiHost: alibabaOaiHost } = serviceAccess; + const needsUserKey = !serviceHasBackendCap; + const shallFetchSucceed = !needsUserKey || (!!alibabaOaiKey && serviceSetupValid); + const showKeyError = !!alibabaOaiKey && !serviceSetupValid; + + // fetch models + const { isFetching, refetch, isError, error } = + useLlmUpdateModels(!serviceHasLLMs && shallFetchSucceed, service); + + return <> + + +
+ + Alibaba Cloud supports the following models via + the OpenAI-compatible endpoint. + +
+
+ + get API key + : + } + value={alibabaOaiKey} + onChange={value => updateSettings({ alibabaOaiKey: value })} + required={needsUserKey} isError={showKeyError} + placeholder={needsUserKey ? 'sk-...' : ''} + /> + + {/**/} + {/* Alibaba Cloud Qwen models provide high-quality language model capabilities.*/} + {/* See the Alibaba Cloud Model Studio for more information.*/} + {/**/} + + {advanced.on && updateSettings({ alibabaOaiHost: text })} + />} + + + + {isError && } + + ; +} diff --git a/src/modules/llms/vendors/alibaba/alibaba.vendor.ts b/src/modules/llms/vendors/alibaba/alibaba.vendor.ts new file mode 100644 index 000000000..f6ae3e82a --- /dev/null +++ b/src/modules/llms/vendors/alibaba/alibaba.vendor.ts @@ -0,0 +1,46 @@ +import { AlibabaCloudIcon } from '~/common/components/icons/vendors/AlibabaCloudIcon'; + +import type { IModelVendor } from '../IModelVendor'; +import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; +import { ModelVendorOpenAI } from '../openai/openai.vendor'; + +import { AlibabaServiceSetup } from './AlibabaServiceSetup'; + + +interface DAlibabaServiceSettings { + alibabaOaiKey: string; + alibabaOaiHost: string; +} + +export const ModelVendorAlibaba: IModelVendor = { + id: 'alibaba', + name: 'Alibaba Cloud', + displayRank: 35, + location: 'cloud', + instanceLimit: 1, + hasBackendCapKey: 'hasLlmAlibaba', + + // components + Icon: AlibabaCloudIcon, + ServiceSetupComponent: AlibabaServiceSetup, + + // functions + initializeSetup: () => ({ + alibabaOaiKey: '', + alibabaOaiHost: '', + }), + validateSetup: (setup) => { + return setup.alibabaOaiKey?.length >= 32; + }, + getTransportAccess: (partialSetup) => ({ + dialect: 'alibaba', + oaiKey: partialSetup?.alibabaOaiKey || '', + oaiOrg: '', + oaiHost: partialSetup?.alibabaOaiHost || '', + heliKey: '', + moderationCheck: false, + }), + + // OpenAI transport ('alibaba' dialect in 'access') + rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, +}; diff --git a/src/modules/llms/vendors/vendors.registry.ts b/src/modules/llms/vendors/vendors.registry.ts index 2b1c9f13c..8189f3079 100644 --- a/src/modules/llms/vendors/vendors.registry.ts +++ b/src/modules/llms/vendors/vendors.registry.ts @@ -1,3 +1,4 @@ +import { ModelVendorAlibaba } from './alibaba/alibaba.vendor'; import { ModelVendorAnthropic } from './anthropic/anthropic.vendor'; import { ModelVendorAzure } from './azure/azure.vendor'; import { ModelVendorDeepseek } from './deepseek/deepseekai.vendor'; @@ -18,6 +19,7 @@ import type { IModelVendor } from './IModelVendor'; export type ModelVendorId = + | 'alibaba' | 'anthropic' | 'azure' | 'deepseek' @@ -37,6 +39,7 @@ export type ModelVendorId = /** Global: Vendor Instances Registry **/ const MODEL_VENDOR_REGISTRY: Record = { + alibaba: ModelVendorAlibaba, anthropic: ModelVendorAnthropic, azure: ModelVendorAzure, deepseek: ModelVendorDeepseek, diff --git a/src/server/env.mjs b/src/server/env.mjs index 50dae2912..e90654255 100644 --- a/src/server/env.mjs +++ b/src/server/env.mjs @@ -21,6 +21,10 @@ export const env = createEnv({ OPENAI_API_HOST: z.string().url().optional(), OPENAI_API_ORG_ID: z.string().optional(), + // LLM: Alibaba (OpenAI) + ALIBABA_API_HOST: z.string().url().optional(), + ALIBABA_API_KEY: z.string().optional(), + // LLM: Azure OpenAI AZURE_OPENAI_API_ENDPOINT: z.string().url().optional(), AZURE_OPENAI_API_KEY: z.string().optional(),