From 027f7deb3a5179fab542fa096bfa81ccfbe5ac4a Mon Sep 17 00:00:00 2001 From: Sorawit Kongnurat Date: Sun, 7 Jul 2024 16:02:08 +0700 Subject: [PATCH 1/2] feat: implement deepseek vendor --- .../components/icons/vendors/DeepseekIcon.tsx | 9 +++ src/modules/backend/backend.router.ts | 1 + .../backend/store-backend-capabilities.ts | 2 + .../llms/server/llm.server.streaming.ts | 1 + .../llms/server/openai/deepseek.wiretypes.ts | 11 ++++ src/modules/llms/server/openai/models.data.ts | 31 +++++++++ .../llms/server/openai/openai.router.ts | 23 ++++++- .../deepseek/DeepseekAISourceSetup.tsx | 64 +++++++++++++++++++ .../vendors/deepseek/deepseekai.vendor.ts | 49 ++++++++++++++ src/modules/llms/vendors/vendors.registry.ts | 5 +- src/server/env.mjs | 3 + 11 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/common/components/icons/vendors/DeepseekIcon.tsx create mode 100644 src/modules/llms/server/openai/deepseek.wiretypes.ts create mode 100644 src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx create mode 100644 src/modules/llms/vendors/deepseek/deepseekai.vendor.ts diff --git a/src/common/components/icons/vendors/DeepseekIcon.tsx b/src/common/components/icons/vendors/DeepseekIcon.tsx new file mode 100644 index 000000000..811410b75 --- /dev/null +++ b/src/common/components/icons/vendors/DeepseekIcon.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +import { SvgIcon, SvgIconProps } from '@mui/joy'; + +export function DeepseekIcon(props: SvgIconProps) { + return + + ; +} diff --git a/src/modules/backend/backend.router.ts b/src/modules/backend/backend.router.ts index 2d39c1030..5d4f490ad 100644 --- a/src/modules/backend/backend.router.ts +++ b/src/modules/backend/backend.router.ts @@ -59,6 +59,7 @@ export const backendRouter = createTRPCRouter({ hasLlmOpenRouter: !!env.OPENROUTER_API_KEY, hasLlmPerplexity: !!env.PERPLEXITY_API_KEY, hasLlmTogetherAI: !!env.TOGETHERAI_API_KEY, + hasLlmDeepseek: !!env.DEEPSEEK_API_KEY, hasVoiceElevenLabs: !!env.ELEVENLABS_API_KEY, llmConfigHash: generateLlmEnvConfigHash(env), }; diff --git a/src/modules/backend/store-backend-capabilities.ts b/src/modules/backend/store-backend-capabilities.ts index 0f6ee1f3a..7cae52679 100644 --- a/src/modules/backend/store-backend-capabilities.ts +++ b/src/modules/backend/store-backend-capabilities.ts @@ -23,6 +23,7 @@ export interface BackendCapabilities { hasLlmOpenRouter: boolean; hasLlmPerplexity: boolean; hasLlmTogetherAI: boolean; + hasLlmDeepseek: boolean; hasVoiceElevenLabs: boolean; llmConfigHash: string; } @@ -52,6 +53,7 @@ const useBackendCapabilitiesStore = create()( hasLlmOpenRouter: false, hasLlmPerplexity: false, hasLlmTogetherAI: false, + hasLlmDeepseek: false, hasVoiceElevenLabs: false, llmConfigHash: '', diff --git a/src/modules/llms/server/llm.server.streaming.ts b/src/modules/llms/server/llm.server.streaming.ts index a85aa1a6f..07fde012a 100644 --- a/src/modules/llms/server/llm.server.streaming.ts +++ b/src/modules/llms/server/llm.server.streaming.ts @@ -535,6 +535,7 @@ function _prepareRequestData({ access, model, history, context: _context }: Chat case 'openrouter': case 'perplexity': case 'togetherai': + case 'deepseek': return { ...openAIAccess(access, model.id, '/v1/chat/completions'), body: openAIChatCompletionPayload(access.dialect, model, history, null, null, 1, true), diff --git a/src/modules/llms/server/openai/deepseek.wiretypes.ts b/src/modules/llms/server/openai/deepseek.wiretypes.ts new file mode 100644 index 000000000..e035b8471 --- /dev/null +++ b/src/modules/llms/server/openai/deepseek.wiretypes.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + + +// [Deepseek AI] Models List API - Response + +export const wireDeepseekAIListOutputSchema = z.array(z.object({ + id: z.string(), + object: z.literal('model'), + owned_by: z.string(), +})); + diff --git a/src/modules/llms/server/openai/models.data.ts b/src/modules/llms/server/openai/models.data.ts index 0cf234174..0d1250fbb 100644 --- a/src/modules/llms/server/openai/models.data.ts +++ b/src/modules/llms/server/openai/models.data.ts @@ -775,6 +775,37 @@ export function togetherAIModelsToModelDescriptions(wireModels: unknown): ModelD .sort(togetherAIModelsSort); } +const _knownDeepseekModels: ModelDescriptionSchema[] = [ + { + id: 'deepseek-chat', + label: 'Deepseek Chat V2', + description: 'Good at general tasks, 128K context length', + contextWindow: 128000, + interfaces: [LLM_IF_OAI_Chat], + maxCompletionTokens: 4096, + pricing: { + chatIn: 0.14, + chatOut: 0.28, + } + }, + { + id: 'deepseek-code', + label: 'Deepseek Code V2', + description: 'Good at coding and math tasks, 128K context length', + contextWindow: 128000, + interfaces: [LLM_IF_OAI_Chat], + maxCompletionTokens: 4096, + pricing: { + chatIn: 0.14, + chatOut: 0.28, + } + } +] + +export function deepseekModelDescriptions() { + // change this implementation once upstream implements some form of models listing + return _knownDeepseekModels; +} // Perplexity diff --git a/src/modules/llms/server/openai/openai.router.ts b/src/modules/llms/server/openai/openai.router.ts index 84ff7f30c..a486b7313 100644 --- a/src/modules/llms/server/openai/openai.router.ts +++ b/src/modules/llms/server/openai/openai.router.ts @@ -11,7 +11,7 @@ import { Brand } from '~/common/app.config'; import { fixupHost } from '~/common/util/urlUtils'; import { OpenAIWire, WireOpenAICreateImageOutput, wireOpenAICreateImageOutputSchema, WireOpenAICreateImageRequest } from './openai.wiretypes'; -import { azureModelToModelDescription, groqModelSortFn, groqModelToModelDescription, lmStudioModelToModelDescription, localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription, perplexityAIModelDescriptions, perplexityAIModelSort, togetherAIModelsToModelDescriptions } from './models.data'; +import { azureModelToModelDescription, deepseekModelDescriptions, groqModelSortFn, groqModelToModelDescription, lmStudioModelToModelDescription, localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription, perplexityAIModelDescriptions, perplexityAIModelSort, togetherAIModelsToModelDescriptions } from './models.data'; import { llmsChatGenerateWithFunctionsOutputSchema, llmsGenerateContextSchema, llmsListModelsOutputSchema, ModelDescriptionSchema } from '../llm.server.types'; import { wilreLocalAIModelsApplyOutputSchema, wireLocalAIModelsAvailableOutputSchema, wireLocalAIModelsListOutputSchema } from './localai.wiretypes'; @@ -21,7 +21,7 @@ const ABERRATION_FIXUP_SQUASH = '\n\n\n---\n\n\n'; const openAIDialects = z.enum([ - 'azure', 'groq', 'lmstudio', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter', 'perplexity', 'togetherai', + 'azure', 'groq', 'lmstudio', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter', 'perplexity', 'togetherai', 'deepseek' ]); type OpenAIDialects = z.infer; @@ -155,6 +155,10 @@ export const llmOpenAIRouter = createTRPCRouter({ if (access.dialect === 'togetherai') return { models: togetherAIModelsToModelDescriptions(openAIWireModelsResponse) }; + // [Deepseek] missing the .data property + if (access.dialect === 'deepseek') + return { models: deepseekModelDescriptions() }; + let openAIModels: OpenAIWire.Models.ModelDescription[] = openAIWireModelsResponse.data || []; // de-duplicate by ids (can happen for local servers.. upstream bugs) @@ -408,6 +412,7 @@ const DEFAULT_OPENAI_HOST = 'api.openai.com'; const DEFAULT_OPENROUTER_HOST = 'https://openrouter.ai/api'; const DEFAULT_PERPLEXITY_HOST = 'https://api.perplexity.ai'; const DEFAULT_TOGETHERAI_HOST = 'https://api.together.xyz'; +const DEFAULT_DEEPSEEK_HOST = 'https://api.deepseek.com'; export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | null, apiPath: string): { headers: HeadersInit, url: string } { switch (access.dialect) { @@ -582,6 +587,20 @@ export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | nu url: togetherHost + apiPath, }; + case 'deepseek': + const deepseekKey = access.oaiKey || env.DEEPSEEK_API_KEY || ''; + const deepseekHost = fixupHost(access.oaiHost || DEFAULT_DEEPSEEK_HOST, apiPath); + if (!deepseekKey || !deepseekHost) + throw new Error('Missing Deepseek API Key or Host. Add it on the UI (Models Setup) or server side (your deployment).'); + + return { + headers: { + 'Authorization': `Bearer ${deepseekKey}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + url: deepseekHost + apiPath, + }; } } diff --git a/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx b/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx new file mode 100644 index 000000000..754ab679b --- /dev/null +++ b/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; + +import { Alert, Typography } from '@mui/joy'; + +import { AlreadySet } from '~/common/components/AlreadySet'; +import { FormInputKey } from '~/common/components/forms/FormInputKey'; +import { FormSwitchControl } from '~/common/components/forms/FormSwitchControl'; +import { InlineError } from '~/common/components/InlineError'; +import { Link } from '~/common/components/Link'; +import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton'; +import { useToggleableBoolean } from '~/common/util/useToggleableBoolean'; + +import { DModelSourceId } from '../../store-llms'; +import { useLlmUpdateModels } from '../../llm.client.hooks'; +import { useSourceSetup } from '../useSourceSetup'; + +import { ModelVendorDeepseek } from './deepseekai.vendor'; + + +const DEEPSEEK_REG_LINK = 'https://platform.deepseek.com/api_keys'; + + +export function DeepseekAISourceSetup(props: { sourceId: DModelSourceId }) { + + // state + const advanced = useToggleableBoolean(); + + // external state + const { + source, sourceHasLLMs, access, + partialSetup, sourceSetupValid, hasNoBackendCap: needsUserKey, updateSetup, + } = useSourceSetup(props.sourceId, ModelVendorDeepseek); + + // derived state + const { oaiKey: deepseekKey } = access; + + // validate if url is a well formed proper url with zod + const shallFetchSucceed = !needsUserKey || (!!deepseekKey && sourceSetupValid); + const showKeyError = !!deepseekKey && !sourceSetupValid; + + // fetch models + const { isFetching, refetch, isError, error } = + useLlmUpdateModels(!sourceHasLLMs && shallFetchSucceed, source); + + + return <> + + {needsUserKey + ? !deepseekKey && request Key + : } + } + value={deepseekKey} onChange={value => updateSetup({ deepseekKey: value })} + required={needsUserKey} isError={showKeyError} + placeholder='...' + /> + + + + {isError && } + + ; +} diff --git a/src/modules/llms/vendors/deepseek/deepseekai.vendor.ts b/src/modules/llms/vendors/deepseek/deepseekai.vendor.ts new file mode 100644 index 000000000..779e61fce --- /dev/null +++ b/src/modules/llms/vendors/deepseek/deepseekai.vendor.ts @@ -0,0 +1,49 @@ +import { DeepseekIcon } from '~/common/components/icons/vendors/DeepseekIcon'; + +import type { IModelVendor } from '../IModelVendor'; +import type { OpenAIAccessSchema } from '../../server/openai/openai.router'; + +import { LLMOptionsOpenAI, ModelVendorOpenAI } from '../openai/openai.vendor'; +import { OpenAILLMOptions } from '../openai/OpenAILLMOptions'; + +import { DeepseekAISourceSetup } from './DeepseekAISourceSetup'; + + +export interface SourceSetupDeepseek { + deepseekKey: string; +} + +export const ModelVendorDeepseek: IModelVendor = { + id: 'deepseek', + name: 'Deepseek', + rank: 19, + location: 'cloud', + instanceLimit: 1, + hasBackendCapKey: 'hasLlmDeepseek', + + // components + Icon: DeepseekIcon, + SourceSetupComponent: DeepseekAISourceSetup, + LLMOptionsComponent: OpenAILLMOptions, + + // functions + initializeSetup: () => ({ + deepseekKey: '', + }), + validateSetup: (setup) => { + return setup.deepseekKey?.length >= 35; + }, + getTransportAccess: (partialSetup) => ({ + dialect: 'deepseek', + oaiKey: partialSetup?.deepseekKey || '', + oaiOrg: '', + oaiHost: '', + heliKey: '', + moderationCheck: false, + }), + + // OpenAI transport ('Deepseek' dialect in 'access') + rpcUpdateModelsOrThrow: ModelVendorOpenAI.rpcUpdateModelsOrThrow, + rpcChatGenerateOrThrow: ModelVendorOpenAI.rpcChatGenerateOrThrow, + streamingChatGenerateOrThrow: ModelVendorOpenAI.streamingChatGenerateOrThrow, +}; diff --git a/src/modules/llms/vendors/vendors.registry.ts b/src/modules/llms/vendors/vendors.registry.ts index 0193718e4..f4353919f 100644 --- a/src/modules/llms/vendors/vendors.registry.ts +++ b/src/modules/llms/vendors/vendors.registry.ts @@ -14,6 +14,7 @@ import { ModelVendorTogetherAI } from './togetherai/togetherai.vendor'; import type { IModelVendor } from './IModelVendor'; import { DLLMId, DModelSource, DModelSourceId, findLLMOrThrow, findSourceOrThrow } from '../store-llms'; +import { ModelVendorDeepseek } from './deepseek/deepseekai.vendor'; export type ModelVendorId = | 'anthropic' @@ -28,7 +29,8 @@ export type ModelVendorId = | 'openai' | 'openrouter' | 'perplexity' - | 'togetherai'; + | 'togetherai' + | 'deepseek'; /** Global: Vendor Instances Registry **/ const MODEL_VENDOR_REGISTRY: Record = { @@ -45,6 +47,7 @@ const MODEL_VENDOR_REGISTRY: Record = { openrouter: ModelVendorOpenRouter, perplexity: ModelVendorPerplexity, togetherai: ModelVendorTogetherAI, + deepseek: ModelVendorDeepseek, } as Record; const MODEL_VENDOR_DEFAULT: ModelVendorId = 'openai'; diff --git a/src/server/env.mjs b/src/server/env.mjs index 8c75b7ec2..00fd86132 100644 --- a/src/server/env.mjs +++ b/src/server/env.mjs @@ -53,6 +53,9 @@ export const env = createEnv({ // LLM: Toghether AI TOGETHERAI_API_KEY: z.string().optional(), + // LLM: Deepseek AI + DEEPSEEK_API_KEY: z.string().optional(), + // Helicone - works on both OpenAI and Anthropic vendors HELICONE_API_KEY: z.string().optional(), From 144ead8cfe268b6755e47c4347a734f0f7c12526 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Sun, 7 Jul 2024 03:34:45 -0700 Subject: [PATCH 2/2] Deepseek: cleanups Mostly reordered the properties in alphabetical order and made sure that models are listed dynamically (for future changes) --- src/modules/backend/backend.router.ts | 2 +- .../backend/store-backend-capabilities.ts | 4 +- .../llms/server/llm.server.streaming.ts | 2 +- .../llms/server/openai/deepseek.wiretypes.ts | 11 --- src/modules/llms/server/openai/models.data.ts | 74 +++++++++++-------- .../llms/server/openai/openai.router.ts | 45 +++++------ .../deepseek/DeepseekAISourceSetup.tsx | 5 +- 7 files changed, 72 insertions(+), 71 deletions(-) delete mode 100644 src/modules/llms/server/openai/deepseek.wiretypes.ts diff --git a/src/modules/backend/backend.router.ts b/src/modules/backend/backend.router.ts index 5d4f490ad..65759a1cc 100644 --- a/src/modules/backend/backend.router.ts +++ b/src/modules/backend/backend.router.ts @@ -49,6 +49,7 @@ export const backendRouter = createTRPCRouter({ hasImagingProdia: !!env.PRODIA_API_KEY, hasLlmAnthropic: !!env.ANTHROPIC_API_KEY, hasLlmAzureOpenAI: !!env.AZURE_OPENAI_API_KEY && !!env.AZURE_OPENAI_API_ENDPOINT, + hasLlmDeepseek: !!env.DEEPSEEK_API_KEY, hasLlmGemini: !!env.GEMINI_API_KEY, hasLlmGroq: !!env.GROQ_API_KEY, hasLlmLocalAIHost: !!env.LOCALAI_API_HOST, @@ -59,7 +60,6 @@ export const backendRouter = createTRPCRouter({ hasLlmOpenRouter: !!env.OPENROUTER_API_KEY, hasLlmPerplexity: !!env.PERPLEXITY_API_KEY, hasLlmTogetherAI: !!env.TOGETHERAI_API_KEY, - hasLlmDeepseek: !!env.DEEPSEEK_API_KEY, hasVoiceElevenLabs: !!env.ELEVENLABS_API_KEY, llmConfigHash: generateLlmEnvConfigHash(env), }; diff --git a/src/modules/backend/store-backend-capabilities.ts b/src/modules/backend/store-backend-capabilities.ts index 7cae52679..2824eb7ff 100644 --- a/src/modules/backend/store-backend-capabilities.ts +++ b/src/modules/backend/store-backend-capabilities.ts @@ -13,6 +13,7 @@ export interface BackendCapabilities { hasImagingProdia: boolean; hasLlmAnthropic: boolean; hasLlmAzureOpenAI: boolean; + hasLlmDeepseek: boolean; hasLlmGemini: boolean; hasLlmGroq: boolean; hasLlmLocalAIHost: boolean; @@ -23,7 +24,6 @@ export interface BackendCapabilities { hasLlmOpenRouter: boolean; hasLlmPerplexity: boolean; hasLlmTogetherAI: boolean; - hasLlmDeepseek: boolean; hasVoiceElevenLabs: boolean; llmConfigHash: string; } @@ -43,6 +43,7 @@ const useBackendCapabilitiesStore = create()( hasImagingProdia: false, hasLlmAnthropic: false, hasLlmAzureOpenAI: false, + hasLlmDeepseek: false, hasLlmGemini: false, hasLlmGroq: false, hasLlmLocalAIHost: false, @@ -53,7 +54,6 @@ const useBackendCapabilitiesStore = create()( hasLlmOpenRouter: false, hasLlmPerplexity: false, hasLlmTogetherAI: false, - hasLlmDeepseek: false, hasVoiceElevenLabs: false, llmConfigHash: '', diff --git a/src/modules/llms/server/llm.server.streaming.ts b/src/modules/llms/server/llm.server.streaming.ts index 07fde012a..7daec37e1 100644 --- a/src/modules/llms/server/llm.server.streaming.ts +++ b/src/modules/llms/server/llm.server.streaming.ts @@ -526,6 +526,7 @@ function _prepareRequestData({ access, model, history, context: _context }: Chat }; case 'azure': + case 'deepseek': case 'groq': case 'lmstudio': case 'localai': @@ -535,7 +536,6 @@ function _prepareRequestData({ access, model, history, context: _context }: Chat case 'openrouter': case 'perplexity': case 'togetherai': - case 'deepseek': return { ...openAIAccess(access, model.id, '/v1/chat/completions'), body: openAIChatCompletionPayload(access.dialect, model, history, null, null, 1, true), diff --git a/src/modules/llms/server/openai/deepseek.wiretypes.ts b/src/modules/llms/server/openai/deepseek.wiretypes.ts deleted file mode 100644 index e035b8471..000000000 --- a/src/modules/llms/server/openai/deepseek.wiretypes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from 'zod'; - - -// [Deepseek AI] Models List API - Response - -export const wireDeepseekAIListOutputSchema = z.array(z.object({ - id: z.string(), - object: z.literal('model'), - owned_by: z.string(), -})); - diff --git a/src/modules/llms/server/openai/models.data.ts b/src/modules/llms/server/openai/models.data.ts index 0d1250fbb..a8a5d57ee 100644 --- a/src/modules/llms/server/openai/models.data.ts +++ b/src/modules/llms/server/openai/models.data.ts @@ -321,6 +321,49 @@ export function azureModelToModelDescription(azureDeploymentRef: string, openAIM } +// [Deepseek AI] +const _knownDeepseekChatModels: ManualMappings = [ + // [Models and Pricing](https://platform.deepseek.com/api-docs/pricing) + // [List Models](https://platform.deepseek.com/api-docs/api/list-models) + { + idPrefix: 'deepseek-chat', + label: 'Deepseek Chat V2', + description: 'Good at general tasks, 128K context length', + contextWindow: 128000, + interfaces: [LLM_IF_OAI_Chat], + maxCompletionTokens: 4096, + pricing: { + chatIn: 0.14, + chatOut: 0.28, + }, + }, + { + idPrefix: 'deepseek-coder', + label: 'Deepseek Coder V2', + description: 'Good at coding and math tasks, 128K context length', + contextWindow: 128000, + interfaces: [LLM_IF_OAI_Chat], + maxCompletionTokens: 4096, + pricing: { + chatIn: 0.14, + chatOut: 0.28, + }, + }, +]; + +export function deepseekModelToModelDescription(deepseekModelId: string): ModelDescriptionSchema { + return fromManualMapping(_knownDeepseekChatModels, deepseekModelId, undefined, undefined, { + idPrefix: deepseekModelId, + label: deepseekModelId.replaceAll(/[_-]/g, ' '), + description: 'New Deepseek Model', + contextWindow: 128000, + maxCompletionTokens: 4096, + interfaces: [LLM_IF_OAI_Chat], // assume.. + hidden: true, + }); +} + + // [LM Studio] export function lmStudioModelToModelDescription(modelId: string): ModelDescriptionSchema { @@ -775,37 +818,6 @@ export function togetherAIModelsToModelDescriptions(wireModels: unknown): ModelD .sort(togetherAIModelsSort); } -const _knownDeepseekModels: ModelDescriptionSchema[] = [ - { - id: 'deepseek-chat', - label: 'Deepseek Chat V2', - description: 'Good at general tasks, 128K context length', - contextWindow: 128000, - interfaces: [LLM_IF_OAI_Chat], - maxCompletionTokens: 4096, - pricing: { - chatIn: 0.14, - chatOut: 0.28, - } - }, - { - id: 'deepseek-code', - label: 'Deepseek Code V2', - description: 'Good at coding and math tasks, 128K context length', - contextWindow: 128000, - interfaces: [LLM_IF_OAI_Chat], - maxCompletionTokens: 4096, - pricing: { - chatIn: 0.14, - chatOut: 0.28, - } - } -] - -export function deepseekModelDescriptions() { - // change this implementation once upstream implements some form of models listing - return _knownDeepseekModels; -} // Perplexity diff --git a/src/modules/llms/server/openai/openai.router.ts b/src/modules/llms/server/openai/openai.router.ts index a486b7313..3467faa86 100644 --- a/src/modules/llms/server/openai/openai.router.ts +++ b/src/modules/llms/server/openai/openai.router.ts @@ -11,7 +11,7 @@ import { Brand } from '~/common/app.config'; import { fixupHost } from '~/common/util/urlUtils'; import { OpenAIWire, WireOpenAICreateImageOutput, wireOpenAICreateImageOutputSchema, WireOpenAICreateImageRequest } from './openai.wiretypes'; -import { azureModelToModelDescription, deepseekModelDescriptions, groqModelSortFn, groqModelToModelDescription, lmStudioModelToModelDescription, localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription, perplexityAIModelDescriptions, perplexityAIModelSort, togetherAIModelsToModelDescriptions } from './models.data'; +import { azureModelToModelDescription, deepseekModelToModelDescription, groqModelSortFn, groqModelToModelDescription, lmStudioModelToModelDescription, localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelFilter, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription, perplexityAIModelDescriptions, perplexityAIModelSort, togetherAIModelsToModelDescriptions } from './models.data'; import { llmsChatGenerateWithFunctionsOutputSchema, llmsGenerateContextSchema, llmsListModelsOutputSchema, ModelDescriptionSchema } from '../llm.server.types'; import { wilreLocalAIModelsApplyOutputSchema, wireLocalAIModelsAvailableOutputSchema, wireLocalAIModelsListOutputSchema } from './localai.wiretypes'; @@ -21,7 +21,7 @@ const ABERRATION_FIXUP_SQUASH = '\n\n\n---\n\n\n'; const openAIDialects = z.enum([ - 'azure', 'groq', 'lmstudio', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter', 'perplexity', 'togetherai', 'deepseek' + 'azure', 'deepseek', 'groq', 'lmstudio', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter', 'perplexity', 'togetherai', ]); type OpenAIDialects = z.infer; @@ -155,10 +155,6 @@ export const llmOpenAIRouter = createTRPCRouter({ if (access.dialect === 'togetherai') return { models: togetherAIModelsToModelDescriptions(openAIWireModelsResponse) }; - // [Deepseek] missing the .data property - if (access.dialect === 'deepseek') - return { models: deepseekModelDescriptions() }; - let openAIModels: OpenAIWire.Models.ModelDescription[] = openAIWireModelsResponse.data || []; // de-duplicate by ids (can happen for local servers.. upstream bugs) @@ -173,6 +169,11 @@ 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 'deepseek': + models = openAIModels + .map(({ id }) => deepseekModelToModelDescription(id)); + break; + case 'groq': models = openAIModels .map(groqModelToModelDescription) @@ -405,6 +406,7 @@ export const llmOpenAIRouter = createTRPCRouter({ 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'; const DEFAULT_LOCALAI_HOST = 'http://127.0.0.1:8080'; const DEFAULT_MISTRAL_HOST = 'https://api.mistral.ai'; @@ -412,7 +414,6 @@ const DEFAULT_OPENAI_HOST = 'api.openai.com'; const DEFAULT_OPENROUTER_HOST = 'https://openrouter.ai/api'; const DEFAULT_PERPLEXITY_HOST = 'https://api.perplexity.ai'; const DEFAULT_TOGETHERAI_HOST = 'https://api.together.xyz'; -const DEFAULT_DEEPSEEK_HOST = 'https://api.deepseek.com'; export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | null, apiPath: string): { headers: HeadersInit, url: string } { switch (access.dialect) { @@ -442,6 +443,22 @@ export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | nu }; + case 'deepseek': + // https://platform.deepseek.com/api-docs/ + const deepseekKey = access.oaiKey || env.DEEPSEEK_API_KEY || ''; + const deepseekHost = fixupHost(access.oaiHost || DEFAULT_DEEPSEEK_HOST, apiPath); + if (!deepseekKey || !deepseekHost) + throw new Error('Missing Deepseek API Key or Host. Add it on the UI (Models Setup) or server side (your deployment).'); + + return { + headers: { + 'Authorization': `Bearer ${deepseekKey}`, + 'Content-Type': 'application/json', + }, + url: deepseekHost + apiPath, + }; + + case 'lmstudio': case 'oobabooga': case 'openai': @@ -587,20 +604,6 @@ export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | nu url: togetherHost + apiPath, }; - case 'deepseek': - const deepseekKey = access.oaiKey || env.DEEPSEEK_API_KEY || ''; - const deepseekHost = fixupHost(access.oaiHost || DEFAULT_DEEPSEEK_HOST, apiPath); - if (!deepseekKey || !deepseekHost) - throw new Error('Missing Deepseek API Key or Host. Add it on the UI (Models Setup) or server side (your deployment).'); - - return { - headers: { - 'Authorization': `Bearer ${deepseekKey}`, - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - url: deepseekHost + apiPath, - }; } } diff --git a/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx b/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx index 754ab679b..a117d4d6e 100644 --- a/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx +++ b/src/modules/llms/vendors/deepseek/DeepseekAISourceSetup.tsx @@ -1,10 +1,7 @@ import * as React from 'react'; -import { Alert, Typography } from '@mui/joy'; - import { AlreadySet } from '~/common/components/AlreadySet'; import { FormInputKey } from '~/common/components/forms/FormInputKey'; -import { FormSwitchControl } from '~/common/components/forms/FormSwitchControl'; import { InlineError } from '~/common/components/InlineError'; import { Link } from '~/common/components/Link'; import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton'; @@ -28,7 +25,7 @@ export function DeepseekAISourceSetup(props: { sourceId: DModelSourceId }) { // external state const { source, sourceHasLLMs, access, - partialSetup, sourceSetupValid, hasNoBackendCap: needsUserKey, updateSetup, + sourceSetupValid, hasNoBackendCap: needsUserKey, updateSetup, } = useSourceSetup(props.sourceId, ModelVendorDeepseek); // derived state