From 7390bae6f61f9afb321a36bf89dde7acc866be53 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Thu, 26 Sep 2024 12:55:12 -0700 Subject: [PATCH] Support DeepSeek (#2377) * add deepseek support * lint * update deepseek context length * add deepseek to onboarding --------- Co-authored-by: Timothy Carambat --- README.md | 1 + .../LLMSelection/DeepSeekOptions/index.jsx | 100 ++++++++++++++ frontend/src/media/llmprovider/deepseek.png | Bin 0 -> 30205 bytes .../GeneralSettings/LLMPreference/index.jsx | 10 ++ .../Steps/DataHandling/index.jsx | 6 + .../Steps/LLMPreference/index.jsx | 9 ++ .../AgentConfig/AgentLLMSelection/index.jsx | 1 + server/models/systemSettings.js | 4 + server/utils/AiProviders/deepseek/index.js | 127 ++++++++++++++++++ server/utils/AiProviders/modelMap.js | 4 + server/utils/agents/aibitat/index.js | 2 + .../agents/aibitat/providers/ai-provider.js | 8 ++ .../agents/aibitat/providers/deepseek.js | 118 ++++++++++++++++ .../utils/agents/aibitat/providers/index.js | 2 + server/utils/agents/index.js | 6 + server/utils/helpers/customModels.js | 28 ++++ server/utils/helpers/index.js | 3 + server/utils/helpers/updateENV.js | 11 ++ 18 files changed, 440 insertions(+) create mode 100644 frontend/src/components/LLMSelection/DeepSeekOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/deepseek.png create mode 100644 server/utils/AiProviders/deepseek/index.js create mode 100644 server/utils/agents/aibitat/providers/deepseek.js diff --git a/README.md b/README.md index d42f6fe91..68c21e4b5 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace - [Fireworks AI (chat models)](https://fireworks.ai/) - [Perplexity (chat models)](https://www.perplexity.ai/) - [OpenRouter (chat models)](https://openrouter.ai/) +- [DeepSeek (chat models)](https://deepseek.com/) - [Mistral](https://mistral.ai/) - [Groq](https://groq.com/) - [Cohere](https://cohere.com/) diff --git a/frontend/src/components/LLMSelection/DeepSeekOptions/index.jsx b/frontend/src/components/LLMSelection/DeepSeekOptions/index.jsx new file mode 100644 index 000000000..5c83d65a9 --- /dev/null +++ b/frontend/src/components/LLMSelection/DeepSeekOptions/index.jsx @@ -0,0 +1,100 @@ +import { useState, useEffect } from "react"; +import System from "@/models/system"; + +export default function DeepSeekOptions({ settings }) { + const [inputValue, setInputValue] = useState(settings?.DeepSeekApiKey); + const [deepSeekApiKey, setDeepSeekApiKey] = useState( + settings?.DeepSeekApiKey + ); + + return ( +
+
+ + setInputValue(e.target.value)} + onBlur={() => setDeepSeekApiKey(inputValue)} + /> +
+ {!settings?.credentialsOnly && ( + + )} +
+ ); +} + +function DeepSeekModelSelection({ apiKey, settings }) { + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + if (!apiKey) { + setModels([]); + setLoading(true); + return; + } + + setLoading(true); + const { models } = await System.customModels( + "deepseek", + typeof apiKey === "boolean" ? null : apiKey + ); + setModels(models || []); + setLoading(false); + } + findCustomModels(); + }, [apiKey]); + + if (loading) { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/media/llmprovider/deepseek.png b/frontend/src/media/llmprovider/deepseek.png new file mode 100644 index 0000000000000000000000000000000000000000..bb8b9f2734e58208c65e477617c7d23ac76c7ada GIT binary patch literal 30205 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 , + description: "Run DeepSeek's powerful LLMs.", + requiredConfig: ["DeepSeekApiKey"], + }, { name: "Generic OpenAI", value: "generic-openai", diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index b739d502a..39d10e77f 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -20,6 +20,7 @@ import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png"; import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png"; import LiteLLMLogo from "@/media/llmprovider/litellm.png"; import AWSBedrockLogo from "@/media/llmprovider/bedrock.png"; +import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import ZillizLogo from "@/media/vectordbs/zilliz.png"; @@ -196,6 +197,11 @@ export const LLM_SELECTION_PRIVACY = { ], logo: AWSBedrockLogo, }, + deepseek: { + name: "DeepSeek", + description: ["Your model and chat contents are visible to DeepSeek"], + logo: DeepSeekLogo, + }, }; export const VECTOR_DB_PRIVACY = { diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index 52996b695..81b26f66a 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -20,6 +20,7 @@ import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png"; import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png"; import LiteLLMLogo from "@/media/llmprovider/litellm.png"; import AWSBedrockLogo from "@/media/llmprovider/bedrock.png"; +import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; @@ -43,6 +44,7 @@ import KoboldCPPOptions from "@/components/LLMSelection/KoboldCPPOptions"; import TextGenWebUIOptions from "@/components/LLMSelection/TextGenWebUIOptions"; import LiteLLMOptions from "@/components/LLMSelection/LiteLLMOptions"; import AWSBedrockLLMOptions from "@/components/LLMSelection/AwsBedrockLLMOptions"; +import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions"; import LLMItem from "@/components/LLMSelection/LLMItem"; import System from "@/models/system"; @@ -186,6 +188,13 @@ const LLMS = [ options: (settings) => , description: "Run LiteLLM's OpenAI compatible proxy for various LLMs.", }, + { + name: "DeepSeek", + value: "deepseek", + logo: DeepSeekLogo, + options: (settings) => , + description: "Run DeepSeek's powerful LLMs.", + }, { name: "Generic OpenAI", value: "generic-openai", diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx index 00a0aef95..97193d5a0 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx @@ -23,6 +23,7 @@ const ENABLED_PROVIDERS = [ "generic-openai", "bedrock", "fireworksai", + "deepseek", // TODO: More agent support. // "cohere", // Has tool calling and will need to build explicit support // "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested. diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index e3012ec4e..c51000191 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -506,6 +506,10 @@ const SystemSettings = { // VoyageAi API Keys VoyageAiApiKey: !!process.env.VOYAGEAI_API_KEY, + + // DeepSeek API Keys + DeepSeekApiKey: !!process.env.DEEPSEEK_API_KEY, + DeepSeekModelPref: process.env.DEEPSEEK_MODEL_PREF, }; }, diff --git a/server/utils/AiProviders/deepseek/index.js b/server/utils/AiProviders/deepseek/index.js new file mode 100644 index 000000000..5ef4c9a1c --- /dev/null +++ b/server/utils/AiProviders/deepseek/index.js @@ -0,0 +1,127 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { + handleDefaultStreamResponseV2, +} = require("../../helpers/chat/responses"); +const { MODEL_MAP } = require("../modelMap"); + +class DeepSeekLLM { + constructor(embedder = null, modelPreference = null) { + if (!process.env.DEEPSEEK_API_KEY) + throw new Error("No DeepSeek API key was set."); + const { OpenAI: OpenAIApi } = require("openai"); + + this.openai = new OpenAIApi({ + apiKey: process.env.DEEPSEEK_API_KEY, + baseURL: "https://api.deepseek.com/v1", + }); + this.model = + modelPreference || process.env.DEEPSEEK_MODEL_PREF || "deepseek-chat"; + this.limits = { + history: this.promptWindowLimit() * 0.15, + system: this.promptWindowLimit() * 0.15, + user: this.promptWindowLimit() * 0.7, + }; + + this.embedder = embedder ?? new NativeEmbedder(); + this.defaultTemp = 0.7; + } + + #appendContext(contextTexts = []) { + if (!contextTexts || !contextTexts.length) return ""; + return ( + "\nContext:\n" + + contextTexts + .map((text, i) => { + return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`; + }) + .join("") + ); + } + + streamingEnabled() { + return "streamGetChatCompletion" in this; + } + + static promptWindowLimit(modelName) { + return MODEL_MAP.deepseek[modelName] ?? 8192; + } + + promptWindowLimit() { + return MODEL_MAP.deepseek[this.model] ?? 8192; + } + + async isValidChatCompletionModel(modelName = "") { + const models = await this.openai.models.list().catch(() => ({ data: [] })); + return models.data.some((model) => model.id === modelName); + } + + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [prompt, ...chatHistory, { role: "user", content: userPrompt }]; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `DeepSeek chat: ${this.model} is not valid for chat completion!` + ); + + const result = await this.openai.chat.completions + .create({ + model: this.model, + messages, + temperature, + }) + .catch((e) => { + throw new Error(e.message); + }); + + if (!result.hasOwnProperty("choices") || result.choices.length === 0) + return null; + return result.choices[0].message.content; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `DeepSeek chat: ${this.model} is not valid for chat completion!` + ); + + const streamRequest = await this.openai.chat.completions.create({ + model: this.model, + stream: true, + messages, + temperature, + }); + return streamRequest; + } + + handleStream(response, stream, responseProps) { + return handleDefaultStreamResponseV2(response, stream, responseProps); + } + + async embedTextInput(textInput) { + return await this.embedder.embedTextInput(textInput); + } + async embedChunks(textChunks = []) { + return await this.embedder.embedChunks(textChunks); + } + + async compressMessages(promptArgs = {}, rawHistory = []) { + const { messageArrayCompressor } = require("../../helpers/chat"); + const messageArray = this.constructPrompt(promptArgs); + return await messageArrayCompressor(this, messageArray, rawHistory); + } +} + +module.exports = { + DeepSeekLLM, +}; diff --git a/server/utils/AiProviders/modelMap.js b/server/utils/AiProviders/modelMap.js index b7604b69a..99d78dc14 100644 --- a/server/utils/AiProviders/modelMap.js +++ b/server/utils/AiProviders/modelMap.js @@ -53,6 +53,10 @@ const MODEL_MAP = { "gpt-4": 8_192, "gpt-4-32k": 32_000, }, + deepseek: { + "deepseek-chat": 128_000, + "deepseek-coder": 128_000, + }, }; module.exports = { MODEL_MAP }; diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js index 90d6069c0..1d356f00a 100644 --- a/server/utils/agents/aibitat/index.js +++ b/server/utils/agents/aibitat/index.js @@ -783,6 +783,8 @@ ${this.getHistory({ to: route.to }) return new Providers.AWSBedrockProvider({}); case "fireworksai": return new Providers.FireworksAIProvider({ model: config.model }); + case "deepseek": + return new Providers.DeepSeekProvider({ model: config.model }); default: throw new Error( diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js index 23d107647..3a144ec6c 100644 --- a/server/utils/agents/aibitat/providers/ai-provider.js +++ b/server/utils/agents/aibitat/providers/ai-provider.js @@ -174,6 +174,14 @@ class Provider { apiKey: process.env.TEXT_GEN_WEB_UI_API_KEY ?? "not-used", ...config, }); + case "deepseek": + return new ChatOpenAI({ + configuration: { + baseURL: "https://api.deepseek.com/v1", + }, + apiKey: process.env.DEEPSEEK_API_KEY ?? null, + ...config, + }); default: throw new Error(`Unsupported provider ${provider} for this task.`); } diff --git a/server/utils/agents/aibitat/providers/deepseek.js b/server/utils/agents/aibitat/providers/deepseek.js new file mode 100644 index 000000000..aec1ee39e --- /dev/null +++ b/server/utils/agents/aibitat/providers/deepseek.js @@ -0,0 +1,118 @@ +const OpenAI = require("openai"); +const Provider = require("./ai-provider.js"); +const InheritMultiple = require("./helpers/classes.js"); +const UnTooled = require("./helpers/untooled.js"); +const { toValidNumber } = require("../../../http/index.js"); + +class DeepSeekProvider extends InheritMultiple([Provider, UnTooled]) { + model; + + constructor(config = {}) { + super(); + const { model = "deepseek-chat" } = config; + const client = new OpenAI({ + baseURL: "https://api.deepseek.com/v1", + apiKey: process.env.DEEPSEEK_API_KEY ?? null, + maxRetries: 3, + }); + + this._client = client; + this.model = model; + this.verbose = true; + this.maxTokens = process.env.DEEPSEEK_MAX_TOKENS + ? toValidNumber(process.env.DEEPSEEK_MAX_TOKENS, 1024) + : 1024; + } + + get client() { + return this._client; + } + + async #handleFunctionCallChat({ messages = [] }) { + return await this.client.chat.completions + .create({ + model: this.model, + temperature: 0, + messages, + max_tokens: this.maxTokens, + }) + .then((result) => { + if (!result.hasOwnProperty("choices")) + throw new Error("DeepSeek chat: No results!"); + if (result.choices.length === 0) + throw new Error("DeepSeek chat: No results length!"); + return result.choices[0].message.content; + }) + .catch((_) => { + return null; + }); + } + + /** + * Create a completion based on the received messages. + * + * @param messages A list of messages to send to the API. + * @param functions + * @returns The completion. + */ + async complete(messages, functions = null) { + try { + let completion; + if (functions.length > 0) { + const { toolCall, text } = await this.functionCall( + messages, + functions, + this.#handleFunctionCallChat.bind(this) + ); + + if (toolCall !== null) { + this.providerLog(`Valid tool call found - running ${toolCall.name}.`); + this.deduplicator.trackRun(toolCall.name, toolCall.arguments); + return { + result: null, + functionCall: { + name: toolCall.name, + arguments: toolCall.arguments, + }, + cost: 0, + }; + } + completion = { content: text }; + } + + if (!completion?.content) { + this.providerLog( + "Will assume chat completion without tool call inputs." + ); + const response = await this.client.chat.completions.create({ + model: this.model, + messages: this.cleanMsgs(messages), + }); + completion = response.choices[0].message; + } + + // The UnTooled class inherited Deduplicator is mostly useful to prevent the agent + // from calling the exact same function over and over in a loop within a single chat exchange + // _but_ we should enable it to call previously used tools in a new chat interaction. + this.deduplicator.reset("runs"); + return { + result: completion.content, + cost: 0, + }; + } catch (error) { + throw error; + } + } + + /** + * Get the cost of the completion. + * + * @param _usage The completion to get the cost for. + * @returns The cost of the completion. + */ + getCost(_usage) { + return 0; + } +} + +module.exports = DeepSeekProvider; diff --git a/server/utils/agents/aibitat/providers/index.js b/server/utils/agents/aibitat/providers/index.js index dd95bb54a..086e0ccf0 100644 --- a/server/utils/agents/aibitat/providers/index.js +++ b/server/utils/agents/aibitat/providers/index.js @@ -14,6 +14,7 @@ const PerplexityProvider = require("./perplexity.js"); const TextWebGenUiProvider = require("./textgenwebui.js"); const AWSBedrockProvider = require("./bedrock.js"); const FireworksAIProvider = require("./fireworksai.js"); +const DeepSeekProvider = require("./deepseek.js"); module.exports = { OpenAIProvider, @@ -28,6 +29,7 @@ module.exports = { OpenRouterProvider, MistralProvider, GenericOpenAiProvider, + DeepSeekProvider, PerplexityProvider, TextWebGenUiProvider, AWSBedrockProvider, diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index 389d9d71b..3936f9388 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -162,6 +162,10 @@ class AgentHandler { "FireworksAI API Key must be provided to use agents." ); break; + case "deepseek": + if (!process.env.DEEPSEEK_API_KEY) + throw new Error("DeepSeek API Key must be provided to use agents."); + break; default: throw new Error( @@ -206,6 +210,8 @@ class AgentHandler { return null; case "fireworksai": return null; + case "deepseek": + return "deepseek-chat"; default: return "unknown"; } diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index a25896ef4..f061d35ff 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -18,6 +18,7 @@ const SUPPORT_CUSTOM_MODELS = [ "litellm", "elevenlabs-tts", "groq", + "deepseek", ]; async function getCustomModels(provider = "", apiKey = null, basePath = null) { @@ -53,6 +54,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return await getElevenLabsModels(apiKey); case "groq": return await getGroqAiModels(apiKey); + case "deepseek": + return await getDeepSeekModels(apiKey); default: return { models: [], error: "Invalid provider for custom models" }; } @@ -419,6 +422,31 @@ async function getElevenLabsModels(apiKey = null) { return { models, error: null }; } +async function getDeepSeekModels(apiKey = null) { + const { OpenAI: OpenAIApi } = require("openai"); + const openai = new OpenAIApi({ + apiKey: apiKey || process.env.DEEPSEEK_API_KEY, + baseURL: "https://api.deepseek.com/v1", + }); + const models = await openai.models + .list() + .then((results) => results.data) + .then((models) => + models.map((model) => ({ + id: model.id, + name: model.id, + organization: model.owned_by, + })) + ) + .catch((e) => { + console.error(`DeepSeek:listModels`, e.message); + return []; + }); + + if (models.length > 0 && !!apiKey) process.env.DEEPSEEK_API_KEY = apiKey; + return { models, error: null }; +} + module.exports = { getCustomModels, }; diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index 71e352c30..6f2dd79d4 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -159,6 +159,9 @@ function getLLMProvider({ provider = null, model = null } = {}) { case "bedrock": const { AWSBedrockLLM } = require("../AiProviders/bedrock"); return new AWSBedrockLLM(embedder, model); + case "deepseek": + const { DeepSeekLLM } = require("../AiProviders/deepseek"); + return new DeepSeekLLM(embedder, model); default: throw new Error( `ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}` diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 7b70efa23..e898d4b09 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -501,6 +501,16 @@ const KEY_MAPPING = { envKey: "TTS_PIPER_VOICE_MODEL", checks: [], }, + + // DeepSeek Options + DeepSeekApiKey: { + envKey: "DEEPSEEK_API_KEY", + checks: [isNotEmpty], + }, + DeepSeekModelPref: { + envKey: "DEEPSEEK_MODEL_PREF", + checks: [isNotEmpty], + }, }; function isNotEmpty(input = "") { @@ -602,6 +612,7 @@ function supportedLLM(input = "") { "litellm", "generic-openai", "bedrock", + "deepseek", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; }