Skip to content

Commit

Permalink
Support DeepSeek (#2377)
Browse files Browse the repository at this point in the history
* add deepseek support

* lint

* update deepseek context length

* add deepseek to onboarding

---------

Co-authored-by: Timothy Carambat <[email protected]>
  • Loading branch information
shatfield4 and timothycarambat authored Sep 26, 2024
1 parent 3064583 commit 7390bae
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/components/LLMSelection/DeepSeekOptions/index.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex gap-[36px] mt-1.5">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
API Key
</label>
<input
type="password"
name="DeepSeekApiKey"
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="DeepSeek API Key"
defaultValue={settings?.DeepSeekApiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
onChange={(e) => setInputValue(e.target.value)}
onBlur={() => setDeepSeekApiKey(inputValue)}
/>
</div>
{!settings?.credentialsOnly && (
<DeepSeekModelSelection settings={settings} apiKey={deepSeekApiKey} />
)}
</div>
);
}

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 (
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Chat Model Selection
</label>
<select
name="DeepSeekModelPref"
disabled={true}
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
<option disabled={true} selected={true}>
-- loading available models --
</option>
</select>
</div>
);
}

return (
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Chat Model Selection
</label>
<select
name="DeepSeekModelPref"
required={true}
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
{models.map((model) => (
<option
key={model.id}
value={model.id}
selected={settings?.DeepSeekModelPref === model.id}
>
{model.name}
</option>
))}
</select>
</div>
);
}
Binary file added frontend/src/media/llmprovider/deepseek.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions frontend/src/pages/GeneralSettings/LLMPreference/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
import CohereLogo from "@/media/llmprovider/cohere.png";
import LiteLLMLogo from "@/media/llmprovider/litellm.png";
import AWSBedrockLogo from "@/media/llmprovider/bedrock.png";
import DeepSeekLogo from "@/media/llmprovider/deepseek.png";

import PreLoader from "@/components/Preloader";
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
Expand All @@ -48,6 +49,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 { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
Expand Down Expand Up @@ -211,6 +213,14 @@ export const AVAILABLE_LLM_PROVIDERS = [
description: "Run LiteLLM's OpenAI compatible proxy for various LLMs.",
requiredConfig: ["LiteLLMBasePath"],
},
{
name: "DeepSeek",
value: "deepseek",
logo: DeepSeekLogo,
options: (settings) => <DeepSeekOptions settings={settings} />,
description: "Run DeepSeek's powerful LLMs.",
requiredConfig: ["DeepSeekApiKey"],
},
{
name: "Generic OpenAI",
value: "generic-openai",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -186,6 +188,13 @@ const LLMS = [
options: (settings) => <LiteLLMOptions settings={settings} />,
description: "Run LiteLLM's OpenAI compatible proxy for various LLMs.",
},
{
name: "DeepSeek",
value: "deepseek",
logo: DeepSeekLogo,
options: (settings) => <DeepSeekOptions settings={settings} />,
description: "Run DeepSeek's powerful LLMs.",
},
{
name: "Generic OpenAI",
value: "generic-openai",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions server/models/systemSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
},

Expand Down
127 changes: 127 additions & 0 deletions server/utils/AiProviders/deepseek/index.js
Original file line number Diff line number Diff line change
@@ -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,
};
4 changes: 4 additions & 0 deletions server/utils/AiProviders/modelMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
2 changes: 2 additions & 0 deletions server/utils/agents/aibitat/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 8 additions & 0 deletions server/utils/agents/aibitat/providers/ai-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.`);
}
Expand Down
Loading

0 comments on commit 7390bae

Please sign in to comment.