Alibaba Cloud support, incl Qwen Max, Plus, Turbo. Fixes #759

This commit is contained in:
Enrico Ros
2025-02-19 15:54:34 -08:00
parent c9457f7610
commit 3d7e4ebb71
16 changed files with 336 additions and 10 deletions
+8 -8
View File
@@ -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: You can easily configure 100s of AI models in big-AGI:
| **AI models** | _supported vendors_ | | **AI models** | _supported vendors_ |
|:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |:--------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Opensource Servers | [LocalAI](https://localai.io/) (multimodal) · [Ollama](https://ollama.com/) | | Opensource Servers | [LocalAI](https://localai.io/) (multimodal) · [Ollama](https://ollama.com/) |
| Local Servers | [LM Studio](https://lmstudio.ai/) | | 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) | | 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 | [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/) | | 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) | | Image services | [Prodia](https://prodia.com/) (SDXL) |
| Speech services | [ElevenLabs](https://elevenlabs.io) (Voice synthesis / cloning) | | Speech services | [ElevenLabs](https://elevenlabs.io) (Voice synthesis / cloning) |
Add extra functionality with these integrations: Add extra functionality with these integrations:
+1
View File
@@ -17,6 +17,7 @@ How to set up AI models and features in big-AGI.
- **Cloud AI Services**: - **Cloud AI Services**:
- Easy API key configuration: - Easy API key configuration:
[Alibaba](https://bailian.console.alibabacloud.com/?apiKey=1#/api-key),
[Anthropic](https://console.anthropic.com/settings/keys), [Anthropic](https://console.anthropic.com/settings/keys),
[Deepseek](https://platform.deepseek.com/api_keys), [Deepseek](https://platform.deepseek.com/api_keys),
[Google Gemini](https://aistudio.google.com/app/apikey), [Google Gemini](https://aistudio.google.com/app/apikey),
+4
View File
@@ -23,6 +23,8 @@ MDB_URI=
OPENAI_API_KEY= OPENAI_API_KEY=
OPENAI_API_HOST= OPENAI_API_HOST=
OPENAI_API_ORG_ID= OPENAI_API_ORG_ID=
ALIBABA_API_HOST=
ALIBABA_API_KEY=
AZURE_OPENAI_API_ENDPOINT= AZURE_OPENAI_API_ENDPOINT=
AZURE_OPENAI_API_KEY= AZURE_OPENAI_API_KEY=
ANTHROPIC_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_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_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 | | `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_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 | | `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 | | `ANTHROPIC_API_KEY` | The API key for Anthropic | Optional |
+2
View File
@@ -16,6 +16,8 @@ stringData:
OPENAI_API_KEY: "" OPENAI_API_KEY: ""
OPENAI_API_HOST: "" OPENAI_API_HOST: ""
OPENAI_API_ORG_ID: "" OPENAI_API_ORG_ID: ""
ALIBABA_API_HOST: ""
ALIBABA_API_KEY: ""
AZURE_OPENAI_API_ENDPOINT: "" AZURE_OPENAI_API_ENDPOINT: ""
AZURE_OPENAI_API_KEY: "" AZURE_OPENAI_API_KEY: ""
ANTHROPIC_API_KEY: "" ANTHROPIC_API_KEY: ""
@@ -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 <SvgIcon viewBox='0 0 24 24' width='24' height='24' {...props}>
<path d='m 8.025,12.9 h 7.95 v -1.8 h -7.95 z' />
<path d='M 19.9875,4.5 H 14.7 l 1.275,1.8 3.8625,1.2 C 20.55,7.725 21,8.3625 21,9.1125 v 5.775 c 0,0.75 -0.45,1.3875 -1.1625,1.6125 L 15.975,17.7 14.7,19.5 h 5.2875 C 22.2375,19.5 24,17.7 24,15.4875 V 8.5125 C 24,6.2625 22.2,4.5 19.9875,4.5 m -15.975,0 H 9.3 L 8.025,6.3 4.1625,7.5 A 1.6875,1.6875 0 0 0 3,9.1125 v 5.775 c 0,0.75 0.45,1.3875 1.1625,1.6125 L 8.025,17.7 9.3,19.5 H 4.0125 C 1.7625,19.5 0,17.7 0,15.4875 V 8.5125 C 0,6.2625 1.8,4.5 4.0125,4.5' />
</SvgIcon>;
}
+10
View File
@@ -345,6 +345,16 @@ export function prettyShortChatModelName(model: string | undefined): string {
} }
// [LocalAI?] // [LocalAI?]
if (model.endsWith('.bin')) return model.slice(0, -4); 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] // [Anthropic]
const prettyAnthropic = _prettyAnthropicModelName(model); const prettyAnthropic = _prettyAnthropicModelName(model);
if (prettyAnthropic) return prettyAnthropic; if (prettyAnthropic) return prettyAnthropic;
+1
View File
@@ -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 | | Service | Chat | Function Calling | Multi-Modal Input | Cont. (1) | Streaming | Idiosyncratic |
|------------|------------|------------------|-------------------|-----------|-----------|---------------| |------------|------------|------------------|-------------------|-----------|-----------|---------------|
| Alibaba | ✅ | ✅ | | ✅ | Yes + 📦 | |
| Anthropic | ✅ | ✅ + Parallel | Img: ✅ | ✅ | Yes + 📦 | | | Anthropic | ✅ | ✅ + Parallel | Img: ✅ | ✅ | Yes + 📦 | |
| Azure | ✅ | ✅ | | ✅ | Yes + 📦 | | | Azure | ✅ | ✅ | | ✅ | Yes + 📦 | |
| Deepseek | ✅ | ❌ (rejected) | | ✅ | Yes + 📦 | | | Deepseek | ✅ | ❌ (rejected) | | ✅ | Yes + 📦 | |
@@ -74,6 +74,7 @@ export function createChatGenerateDispatch(access: AixAPI_Access, model: AixAPI_
chatGenerateParse: streaming ? createOpenAIChatCompletionsChunkParser() : createOpenAIChatCompletionsParserNS(), chatGenerateParse: streaming ? createOpenAIChatCompletionsChunkParser() : createOpenAIChatCompletionsParserNS(),
}; };
case 'alibaba':
case 'azure': case 'azure':
case 'deepseek': case 'deepseek':
case 'groq': case 'groq':
+1
View File
@@ -49,6 +49,7 @@ export const backendRouter = createTRPCRouter({
.query(async ({ ctx: _unused }): Promise<BackendCapabilities> => { .query(async ({ ctx: _unused }): Promise<BackendCapabilities> => {
return { return {
// llms // llms
hasLlmAlibaba: !!env.ALIBABA_API_KEY || !!env.ALIBABA_API_HOST,
hasLlmAnthropic: !!env.ANTHROPIC_API_KEY, hasLlmAnthropic: !!env.ANTHROPIC_API_KEY,
hasLlmAzureOpenAI: !!env.AZURE_OPENAI_API_KEY && !!env.AZURE_OPENAI_API_ENDPOINT, hasLlmAzureOpenAI: !!env.AZURE_OPENAI_API_KEY && !!env.AZURE_OPENAI_API_ENDPOINT,
hasLlmDeepseek: !!env.DEEPSEEK_API_KEY, hasLlmDeepseek: !!env.DEEPSEEK_API_KEY,
@@ -8,6 +8,7 @@ import { useShallow } from 'zustand/react/shallow';
export interface BackendCapabilities { export interface BackendCapabilities {
// llms // llms
hasLlmAlibaba: boolean;
hasLlmAnthropic: boolean; hasLlmAnthropic: boolean;
hasLlmAzureOpenAI: boolean; hasLlmAzureOpenAI: boolean;
hasLlmDeepseek: boolean; hasLlmDeepseek: boolean;
@@ -48,6 +49,7 @@ const useBackendCapabilitiesStore = create<BackendStore>()(
(set) => ({ (set) => ({
// initial values // initial values
hasLlmAlibaba: false,
hasLlmAnthropic: false, hasLlmAnthropic: false,
hasLlmAzureOpenAI: false, hasLlmAzureOpenAI: false,
hasLlmDeepseek: false, hasLlmDeepseek: false,
@@ -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);
}
@@ -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 { 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 { ListModelsResponse_schema, ModelDescriptionSchema } from '../llm.server.types';
import { alibabaModelSort, alibabaModelToModelDescription } from './models/alibaba.models';
import { azureModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openAISortModels } from './models/openai.models'; import { azureModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openAISortModels } from './models/openai.models';
import { deepseekModelFilter, deepseekModelSort, deepseekModelToModelDescription } from './models/deepseek.models'; import { deepseekModelFilter, deepseekModelSort, deepseekModelToModelDescription } from './models/deepseek.models';
import { fireworksAIHeuristic, fireworksAIModelsToModelDescriptions } from './models/fireworksai.models'; import { fireworksAIHeuristic, fireworksAIModelsToModelDescriptions } from './models/fireworksai.models';
import { groqModelFilter, groqModelSortFn, groqModelToModelDescription } from './models/groq.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 { mistralModelsSort, mistralModelToModelDescription } from './models/mistral.models';
import { openPipeModelDescriptions, openPipeModelSort, openPipeModelToModelDescriptions } from './models/openpipe.models'; import { openPipeModelDescriptions, openPipeModelSort, openPipeModelToModelDescriptions } from './models/openpipe.models';
import { openRouterModelFamilySortFn, openRouterModelToModelDescription } from './models/openrouter.models'; import { openRouterModelFamilySortFn, openRouterModelToModelDescription } from './models/openrouter.models';
@@ -29,7 +30,7 @@ import { xaiModelDescriptions, xaiModelSort } from './models/xai.models';
const openAIDialects = z.enum([ 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<typeof openAIDialects>; export type OpenAIDialects = z.infer<typeof openAIDialects>;
@@ -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 // every dialect has a different way to enumerate models - we execute the mapping on the server side
switch (access.dialect) { switch (access.dialect) {
case 'alibaba':
models = openAIModels
.map(({ id, created }) => alibabaModelToModelDescription(id, created))
.sort(alibabaModelSort);
break;
case 'deepseek': case 'deepseek':
models = openAIModels models = openAIModels
.filter(({ id }) => deepseekModelFilter(id)) .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_HELICONE_OPENAI_HOST = 'oai.hconeai.com';
const DEFAULT_DEEPSEEK_HOST = 'https://api.deepseek.com'; const DEFAULT_DEEPSEEK_HOST = 'https://api.deepseek.com';
const DEFAULT_GROQ_HOST = 'https://api.groq.com/openai'; 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 } { export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | null, apiPath: string): { headers: HeadersInit, url: string } {
switch (access.dialect) { 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': case 'azure':
const azureKey = access.oaiKey || env.AZURE_OPENAI_API_KEY || ''; const azureKey = access.oaiKey || env.AZURE_OPENAI_API_KEY || '';
const azureHost = fixupHost(access.oaiHost || env.AZURE_OPENAI_API_ENDPOINT || '', apiPath); const azureHost = fixupHost(access.oaiHost || env.AZURE_OPENAI_API_ENDPOINT || '', apiPath);
@@ -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 <>
<ApproximateCosts serviceId={service?.id}>
<div>
<Typography level='body-sm'>
Alibaba Cloud supports the <ExternalLink href={ALIBABA_MODELS}>following models</ExternalLink> via
the OpenAI-compatible endpoint.
</Typography>
</div>
</ApproximateCosts>
<FormInputKey
autoCompleteId='alibaba-key' label='Alibaba Cloud API Key'
rightLabel={needsUserKey
? alibabaOaiKey && <Link level='body-sm' href={ALIBABA_REG_LINK} target='_blank'>get API key</Link>
: <AlreadySet />
}
value={alibabaOaiKey}
onChange={value => updateSettings({ alibabaOaiKey: value })}
required={needsUserKey} isError={showKeyError}
placeholder={needsUserKey ? 'sk-...' : ''}
/>
{/*<Typography level='body-sm'>*/}
{/* Alibaba Cloud Qwen models provide high-quality language model capabilities.*/}
{/* See the <ExternalLink href={ALIBABA_REG_LINK}>Alibaba Cloud Model Studio</ExternalLink> for more information.*/}
{/*</Typography>*/}
{advanced.on && <FormTextField
autoCompleteId='alibaba-host'
title='API Endpoint'
tooltip={`The API endpoint for the Alibaba Cloud OpenAI service, to be used instead of the default endpoint.`}
placeholder={`e.g., ${CLIENT_ALIBABA_DEFAULT_HOST}`}
value={alibabaOaiHost}
onChange={text => updateSettings({ alibabaOaiHost: text })}
/>}
<SetupFormRefetchButton refetch={refetch} disabled={/*!shallFetchSucceed ||*/ isFetching} loading={isFetching} error={isError} advanced={advanced} />
{isError && <InlineError error={error} />}
</>;
}
+46
View File
@@ -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<DAlibabaServiceSettings, OpenAIAccessSchema> = {
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,
};
+3
View File
@@ -1,3 +1,4 @@
import { ModelVendorAlibaba } from './alibaba/alibaba.vendor';
import { ModelVendorAnthropic } from './anthropic/anthropic.vendor'; import { ModelVendorAnthropic } from './anthropic/anthropic.vendor';
import { ModelVendorAzure } from './azure/azure.vendor'; import { ModelVendorAzure } from './azure/azure.vendor';
import { ModelVendorDeepseek } from './deepseek/deepseekai.vendor'; import { ModelVendorDeepseek } from './deepseek/deepseekai.vendor';
@@ -18,6 +19,7 @@ import type { IModelVendor } from './IModelVendor';
export type ModelVendorId = export type ModelVendorId =
| 'alibaba'
| 'anthropic' | 'anthropic'
| 'azure' | 'azure'
| 'deepseek' | 'deepseek'
@@ -37,6 +39,7 @@ export type ModelVendorId =
/** Global: Vendor Instances Registry **/ /** Global: Vendor Instances Registry **/
const MODEL_VENDOR_REGISTRY: Record<ModelVendorId, IModelVendor> = { const MODEL_VENDOR_REGISTRY: Record<ModelVendorId, IModelVendor> = {
alibaba: ModelVendorAlibaba,
anthropic: ModelVendorAnthropic, anthropic: ModelVendorAnthropic,
azure: ModelVendorAzure, azure: ModelVendorAzure,
deepseek: ModelVendorDeepseek, deepseek: ModelVendorDeepseek,
+4
View File
@@ -21,6 +21,10 @@ export const env = createEnv({
OPENAI_API_HOST: z.string().url().optional(), OPENAI_API_HOST: z.string().url().optional(),
OPENAI_API_ORG_ID: z.string().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 // LLM: Azure OpenAI
AZURE_OPENAI_API_ENDPOINT: z.string().url().optional(), AZURE_OPENAI_API_ENDPOINT: z.string().url().optional(),
AZURE_OPENAI_API_KEY: z.string().optional(), AZURE_OPENAI_API_KEY: z.string().optional(),