From ab8a21a8387d37e0aff1fe2e83b94fafcc91a4b3 Mon Sep 17 00:00:00 2001 From: nsdou Date: Wed, 8 May 2024 12:57:25 +0800 Subject: [PATCH] feat: add deepseek support --- src/globals.ts | 2 + src/providers/deepseek/api.ts | 18 +++ src/providers/deepseek/chatComplete.ts | 175 +++++++++++++++++++++++++ src/providers/deepseek/index.ts | 18 +++ src/providers/index.ts | 2 + 5 files changed, 215 insertions(+) create mode 100644 src/providers/deepseek/api.ts create mode 100644 src/providers/deepseek/chatComplete.ts create mode 100644 src/providers/deepseek/index.ts diff --git a/src/globals.ts b/src/globals.ts index 555adeab1..8fdb453c1 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -51,6 +51,7 @@ export const LINGYI: string = 'lingyi'; export const ZHIPU: string = 'zhipu'; export const NOVITA_AI: string = 'novita-ai'; export const MONSTERAPI: string = 'monsterapi'; +export const DEEPSEEK: string = 'deepseek'; export const VALID_PROVIDERS = [ ANTHROPIC, @@ -82,6 +83,7 @@ export const VALID_PROVIDERS = [ ZHIPU, NOVITA_AI, MONSTERAPI, + DEEPSEEK, ]; export const CONTENT_TYPES = { diff --git a/src/providers/deepseek/api.ts b/src/providers/deepseek/api.ts new file mode 100644 index 000000000..e0ab4a6bc --- /dev/null +++ b/src/providers/deepseek/api.ts @@ -0,0 +1,18 @@ +import { ProviderAPIConfig } from '../types'; + +const DeepSeekAPIConfig: ProviderAPIConfig = { + getBaseURL: () => 'https://api.deepseek.com', + headers: ({ providerOptions }) => { + return { Authorization: `Bearer ${providerOptions.apiKey}` }; // https://platform.deepseek.com/api_keys + }, + getEndpoint: ({ fn }) => { + switch (fn) { + case 'chatComplete': + return '/v1/chat/completions'; + default: + return ''; + } + }, +}; + +export default DeepSeekAPIConfig; diff --git a/src/providers/deepseek/chatComplete.ts b/src/providers/deepseek/chatComplete.ts new file mode 100644 index 000000000..9d15c9754 --- /dev/null +++ b/src/providers/deepseek/chatComplete.ts @@ -0,0 +1,175 @@ +import { DEEPSEEK } from '../../globals'; + +import { + ChatCompletionResponse, + ErrorResponse, + ProviderConfig, +} from '../types'; +import { + generateErrorResponse, + generateInvalidProviderResponseError, +} from '../utils'; + +export const DeepSeekChatCompleteConfig: ProviderConfig = { + model: { + param: 'model', + required: true, + default: 'deepseek-chat', + }, + messages: { + param: 'messages', + default: '', + }, + max_tokens: { + param: 'max_tokens', + default: 100, + min: 0, + }, + temperature: { + param: 'temperature', + default: 1, + min: 0, + max: 2, + }, + top_p: { + param: 'top_p', + default: 1, + min: 0, + max: 1, + }, + stream: { + param: 'stream', + default: false, + }, + frequency_penalty: { + param: 'frequency_penalty', + default: 0, + min: -2, + max: 2, + }, + presence_penalty: { + param: 'presence_penalty', + default: 0, + min: -2, + max: 2, + }, + stop: { + param: 'stop', + default: null, + }, + logprobs: { + param: 'logprobs', + default: false, + }, + top_logprobs: { + param: 'top_logprobs', + default: 0, + min: 0, + max: 20, + }, +}; + +interface DeepSeekChatCompleteResponse extends ChatCompletionResponse { + id: string; + object: string; + created: number; + model: 'deepseek-chat' | 'deepseek-coder'; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +export interface DeepSeekErrorResponse { + object: string; + message: string; + type: string; + param: string | null; + code: string; +} + +interface DeepSeekStreamChunk { + id: string; + object: string; + created: number; + model: string; + choices: { + delta: { + role?: string | null; + content?: string; + }; + index: number; + finish_reason: string | null; + }[]; +} + +export const DeepSeekChatCompleteResponseTransform: ( + response: DeepSeekChatCompleteResponse | DeepSeekErrorResponse, + responseStatus: number +) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => { + if ('message' in response && responseStatus !== 200) { + return generateErrorResponse( + { + message: response.message, + type: response.type, + param: response.param, + code: response.code, + }, + DEEPSEEK + ); + } + + if ('choices' in response) { + return { + id: response.id, + object: response.object, + created: response.created, + model: response.model, + provider: DEEPSEEK, + choices: response.choices.map((c) => ({ + index: c.index, + message: { + role: c.message.role, + content: c.message.content, + }, + finish_reason: c.finish_reason, + })), + usage: { + prompt_tokens: response.usage?.prompt_tokens, + completion_tokens: response.usage?.completion_tokens, + total_tokens: response.usage?.total_tokens, + }, + }; + } + + return generateInvalidProviderResponseError(response, DEEPSEEK); +}; + +export const DeepSeekChatCompleteStreamChunkTransform: ( + response: string +) => string = (responseChunk) => { + let chunk = responseChunk.trim(); + chunk = chunk.replace(/^data: /, ''); + chunk = chunk.trim(); + if (chunk === '[DONE]') { + return `data: ${chunk}\n\n`; + } + const parsedChunk: DeepSeekStreamChunk = JSON.parse(chunk); + return ( + `data: ${JSON.stringify({ + id: parsedChunk.id, + object: parsedChunk.object, + created: parsedChunk.created, + model: parsedChunk.model, + provider: DEEPSEEK, + choices: [ + { + index: parsedChunk.choices[0].index, + delta: parsedChunk.choices[0].delta, + finish_reason: parsedChunk.choices[0].finish_reason, + }, + ], + })}` + '\n\n' + ); +}; diff --git a/src/providers/deepseek/index.ts b/src/providers/deepseek/index.ts new file mode 100644 index 000000000..6de8be46c --- /dev/null +++ b/src/providers/deepseek/index.ts @@ -0,0 +1,18 @@ +import { ProviderConfigs } from '../types'; +import DeepSeekAPIConfig from './api'; +import { + DeepSeekChatCompleteConfig, + DeepSeekChatCompleteResponseTransform, + DeepSeekChatCompleteStreamChunkTransform, +} from './chatComplete'; + +const DeepSeekConfig: ProviderConfigs = { + chatComplete: DeepSeekChatCompleteConfig, + api: DeepSeekAPIConfig, + responseTransforms: { + chatComplete: DeepSeekChatCompleteResponseTransform, + 'stream-chatComplete': DeepSeekChatCompleteStreamChunkTransform, + }, +}; + +export default DeepSeekConfig; diff --git a/src/providers/index.ts b/src/providers/index.ts index 593a90b4d..7f4b100fc 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -28,6 +28,7 @@ import LingYiConfig from './lingyi'; import ZhipuConfig from './zhipu'; import NovitaAIConfig from './novita-ai'; import MonsterAPIConfig from './monsterapi'; +import DeepSeekAPIConfig from './deepseek'; const Providers: { [key: string]: ProviderConfigs } = { openai: OpenAIConfig, @@ -59,6 +60,7 @@ const Providers: { [key: string]: ProviderConfigs } = { zhipu: ZhipuConfig, 'novita-ai': NovitaAIConfig, monsterapi: MonsterAPIConfig, + deepseek: DeepSeekAPIConfig, }; export default Providers;