diff --git a/docker/.env.example b/docker/.env.example
index b17517e5..e20fa82e 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -167,6 +167,10 @@ GID='1000'
# DOCKER_MODEL_RUNNER_LLM_MODEL_PREF='phi-3.5-mini'
# DOCKER_MODEL_RUNNER_LLM_MODEL_TOKEN_LIMIT=4096
+# LLM_PROVIDER='privatemode'
+# PRIVATEMODE_LLM_BASE_PATH='http://127.0.0.1:8080'
+# PRIVATEMODE_LLM_MODEL_PREF='gemma-3-27b'
+
###########################################
######## Embedding API SElECTION ##########
###########################################
diff --git a/frontend/src/components/LLMSelection/PrivateModeOptions/index.jsx b/frontend/src/components/LLMSelection/PrivateModeOptions/index.jsx
new file mode 100644
index 00000000..5238e03b
--- /dev/null
+++ b/frontend/src/components/LLMSelection/PrivateModeOptions/index.jsx
@@ -0,0 +1,129 @@
+import { useEffect, useState } from "react";
+import { Info } from "@phosphor-icons/react";
+import { Tooltip } from "react-tooltip";
+import System from "@/models/system";
+import { Link } from "react-router-dom";
+
+export default function PrivateModeOptions({ settings }) {
+ const [models, setModels] = useState([]);
+ const [loading, setLoading] = useState(!!settings?.PrivateModeBasePath);
+ const [basePath, setBasePath] = useState(settings?.PrivateModeBasePath);
+ const [model, setModel] = useState(settings?.PrivateModeModelPref || "");
+
+ useEffect(() => {
+ setModel(settings?.PrivateModeModelPref || "");
+ }, [settings?.PrivateModeModelPref]);
+
+ useEffect(() => {
+ async function fetchModels() {
+ try {
+ setLoading(true);
+ if (!basePath) throw new Error("Base path is required");
+ const { models, error } = await System.customModels(
+ "privatemode",
+ null,
+ basePath
+ );
+ if (error) throw new Error(error);
+ setModels(models);
+ } catch (error) {
+ console.error("Error fetching Private Mode models:", error);
+ setModels([]);
+ } finally {
+ setLoading(false);
+ }
+ }
+ fetchModels();
+ }, [basePath]);
+
+ return (
+
+
+
+
+
+
+
+ Enter the URL where Privatemode Proxy is running.
+
+
+
+ Learn more →
+
+
+
+
setBasePath(e.target.value)}
+ />
+
+
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/components/ProviderPrivacy/constants.js b/frontend/src/components/ProviderPrivacy/constants.js
index 2e6a32d0..1d99fff7 100644
--- a/frontend/src/components/ProviderPrivacy/constants.js
+++ b/frontend/src/components/ProviderPrivacy/constants.js
@@ -42,6 +42,7 @@ import CometApiLogo from "@/media/llmprovider/cometapi.png";
import FoundryLogo from "@/media/llmprovider/foundry-local.png";
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
import DockerModelRunnerLogo from "@/media/llmprovider/docker-model-runner.png";
+import PrivateModeLogo from "@/media/llmprovider/privatemode.png";
const LLM_PROVIDER_PRIVACY_MAP = {
openai: {
@@ -232,6 +233,11 @@ const LLM_PROVIDER_PRIVACY_MAP = {
],
logo: DockerModelRunnerLogo,
},
+ privatemode: {
+ name: "Privatemode",
+ policyUrl: "https://docs.privatemode.ai/getting-started/faq#q2",
+ logo: PrivateModeLogo,
+ },
};
const VECTOR_DB_PROVIDER_PRIVACY_MAP = {
diff --git a/frontend/src/media/llmprovider/privatemode.png b/frontend/src/media/llmprovider/privatemode.png
new file mode 100644
index 00000000..e4bcf382
Binary files /dev/null and b/frontend/src/media/llmprovider/privatemode.png differ
diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx
index 7eb71f03..dd4c58ce 100644
--- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx
+++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx
@@ -38,6 +38,7 @@ import CometApiLogo from "@/media/llmprovider/cometapi.png";
import FoundryLogo from "@/media/llmprovider/foundry-local.png";
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
import DockerModelRunnerLogo from "@/media/llmprovider/docker-model-runner.png";
+import PrivateModeLogo from "@/media/llmprovider/privatemode.png";
import PreLoader from "@/components/Preloader";
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
@@ -73,6 +74,7 @@ import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions";
import FoundryOptions from "@/components/LLMSelection/FoundryOptions";
import GiteeAIOptions from "@/components/LLMSelection/GiteeAIOptions/index.jsx";
import DockerModelRunnerOptions from "@/components/LLMSelection/DockerModelRunnerOptions";
+import PrivateModeOptions from "@/components/LLMSelection/PrivateModeOptions";
import LLMItem from "@/components/LLMSelection/LLMItem";
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
@@ -316,6 +318,14 @@ export const AVAILABLE_LLM_PROVIDERS = [
description: "Run Moonshot AI's powerful LLMs.",
requiredConfig: ["MoonshotAiApiKey"],
},
+ {
+ name: "Privatemode",
+ value: "privatemode",
+ logo: PrivateModeLogo,
+ options: (settings) => ,
+ description: "Run LLMs with end-to-end encryption.",
+ requiredConfig: ["PrivateModeBasePath"],
+ },
{
name: "Novita AI",
value: "novita",
diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx
index f56bb6bd..a9b58e4e 100644
--- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx
@@ -32,6 +32,7 @@ import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";
import CometApiLogo from "@/media/llmprovider/cometapi.png";
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
import DockerModelRunnerLogo from "@/media/llmprovider/docker-model-runner.png";
+import PrivateModeLogo from "@/media/llmprovider/privatemode.png";
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
import GenericOpenAiOptions from "@/components/LLMSelection/GenericOpenAiOptions";
@@ -65,6 +66,7 @@ import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions";
import CometApiLLMOptions from "@/components/LLMSelection/CometApiLLMOptions";
import GiteeAiOptions from "@/components/LLMSelection/GiteeAIOptions";
import DockerModelRunnerOptions from "@/components/LLMSelection/DockerModelRunnerOptions";
+import PrivateModeOptions from "@/components/LLMSelection/PrivateModeOptions";
import LLMItem from "@/components/LLMSelection/LLMItem";
import System from "@/models/system";
@@ -273,6 +275,13 @@ const LLMS = [
options: (settings) => ,
description: "Run powerful foundation models privately with AWS Bedrock.",
},
+ {
+ name: "Privatemode",
+ value: "privatemode",
+ logo: PrivateModeLogo,
+ options: (settings) => ,
+ description: "Run LLMs with end-to-end encryption.",
+ },
{
name: "xAI",
value: "xai",
diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx
index b2a5945b..0ac04a3c 100644
--- a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx
+++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx
@@ -38,6 +38,7 @@ const ENABLED_PROVIDERS = [
"giteeai",
"cohere",
"docker-model-runner",
+ "privatemode",
// TODO: More agent support.
// "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested.
];
diff --git a/server/.env.example b/server/.env.example
index b408b6fa..6523d100 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -166,6 +166,10 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long.
# DOCKER_MODEL_RUNNER_LLM_MODEL_PREF='phi-3.5-mini'
# DOCKER_MODEL_RUNNER_LLM_MODEL_TOKEN_LIMIT=4096
+# LLM_PROVIDER='privatemode'
+# PRIVATEMODE_LLM_BASE_PATH='http://127.0.0.1:8080'
+# PRIVATEMODE_LLM_MODEL_PREF='gemma-3-27b'
+
###########################################
######## Embedding API SElECTION ##########
###########################################
diff --git a/server/endpoints/utils.js b/server/endpoints/utils.js
index aa449709..61a61d6a 100644
--- a/server/endpoints/utils.js
+++ b/server/endpoints/utils.js
@@ -162,6 +162,9 @@ function getModelTag() {
case "docker-model-runner":
model = process.env.DOCKER_MODEL_RUNNER_LLM_MODEL_PREF;
break;
+ case "privatemode":
+ model = process.env.PRIVATEMODE_LLM_MODEL_PREF;
+ break;
default:
model = "--";
break;
diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index 9cc89a5d..ce9df3dc 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -674,6 +674,10 @@ const SystemSettings = {
process.env.DOCKER_MODEL_RUNNER_LLM_MODEL_PREF,
DockerModelRunnerModelTokenLimit:
process.env.DOCKER_MODEL_RUNNER_LLM_MODEL_TOKEN_LIMIT || 8192,
+
+ // Privatemode Keys
+ PrivateModeBasePath: process.env.PRIVATEMODE_LLM_BASE_PATH,
+ PrivateModeModelPref: process.env.PRIVATEMODE_LLM_MODEL_PREF,
};
},
diff --git a/server/utils/AiProviders/privatemode/index.js b/server/utils/AiProviders/privatemode/index.js
new file mode 100644
index 00000000..47e8f80e
--- /dev/null
+++ b/server/utils/AiProviders/privatemode/index.js
@@ -0,0 +1,218 @@
+const { NativeEmbedder } = require("../../EmbeddingEngines/native");
+const {
+ handleDefaultStreamResponseV2,
+ formatChatHistory,
+} = require("../../helpers/chat/responses");
+const {
+ LLMPerformanceMonitor,
+} = require("../../helpers/chat/LLMPerformanceMonitor");
+
+class PrivatemodeLLM {
+ static contextWindows = {
+ "leon-se/gemma-3-27b-it-fp8-dynamic": 128000,
+ "gemma-3-27b": 128000,
+ "qwen3-coder-30b-a3b": 128000,
+ "gpt-oss-120b": 128000,
+ "openai/gpt-oss-120b": 128000,
+ };
+
+ constructor(embedder = null, modelPreference = null) {
+ if (!process.env.PRIVATEMODE_LLM_BASE_PATH)
+ throw new Error("No Privatemode Base Path was set.");
+
+ this.className = "PrivatemodeLLM";
+ const { OpenAI: OpenAIApi } = require("openai");
+ this.client = new OpenAIApi({
+ baseURL: PrivatemodeLLM.parseBasePath(),
+ apiKey: null,
+ });
+
+ this.model = modelPreference || process.env.PRIVATEMODE_LLM_MODEL_PREF;
+ 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;
+ this.log(
+ `Privatemode LLM initialized with ${this.model}. ctx: ${this.promptWindowLimit()}`
+ );
+ }
+
+ /**
+ * Parse the base path for the Privatemode API
+ * so we can use it for inference requests
+ * @param {string} providedBasePath
+ * @returns {string}
+ */
+ static parseBasePath(
+ providedBasePath = process.env.PRIVATEMODE_LLM_BASE_PATH
+ ) {
+ try {
+ const baseURL = new URL(providedBasePath);
+ const basePath = `${baseURL.origin}/v1`;
+ return basePath;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ log(text, ...args) {
+ console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
+ }
+
+ #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) {
+ const limit = PrivatemodeLLM.contextWindows[_modelName] || 16384;
+ return Number(limit);
+ }
+
+ promptWindowLimit() {
+ const limit = PrivatemodeLLM.contextWindows[this.model] || 16384;
+ return Number(limit);
+ }
+
+ async isValidChatCompletionModel(_ = "") {
+ return true;
+ }
+
+ /**
+ * Generates appropriate content array for a message + attachments.
+ * @param {{userPrompt:string, attachments: import("../../helpers").Attachment[]}}
+ * @returns {string|object[]}
+ */
+ #generateContent({ userPrompt, attachments = [] }) {
+ if (!attachments.length) return userPrompt;
+
+ const content = [{ type: "text", text: userPrompt }];
+ for (let attachment of attachments) {
+ content.push({
+ type: "image_url",
+ image_url: {
+ url: attachment.contentString,
+ detail: "auto",
+ },
+ });
+ }
+ return content.flat();
+ }
+
+ /**
+ * Construct the user prompt for this model.
+ * @param {{attachments: import("../../helpers").Attachment[]}} param0
+ * @returns
+ */
+ constructPrompt({
+ systemPrompt = "",
+ contextTexts = [],
+ chatHistory = [],
+ userPrompt = "",
+ attachments = [],
+ }) {
+ const prompt = {
+ role: "system",
+ content: `${systemPrompt}${this.#appendContext(contextTexts)}`,
+ };
+ return [
+ prompt,
+ ...formatChatHistory(chatHistory, this.#generateContent),
+ {
+ role: "user",
+ content: this.#generateContent({ userPrompt, attachments }),
+ },
+ ];
+ }
+
+ async getChatCompletion(messages = null, { temperature = 0.7 }) {
+ if (!this.model)
+ throw new Error(
+ `Privatemode chat: ${this.model} is not valid or defined model for chat completion!`
+ );
+
+ const result = await LLMPerformanceMonitor.measureAsyncFunction(
+ this.client.chat.completions.create({
+ model: this.model,
+ messages,
+ temperature,
+ })
+ );
+
+ if (
+ !result.output.hasOwnProperty("choices") ||
+ result.output.choices.length === 0
+ )
+ return null;
+
+ return {
+ textResponse: result.output.choices[0].message.content,
+ metrics: {
+ prompt_tokens: result.output.usage?.prompt_tokens || 0,
+ completion_tokens: result.output.usage?.completion_tokens || 0,
+ total_tokens: result.output.usage?.total_tokens || 0,
+ outputTps: result.output.usage?.completion_tokens / result.duration,
+ duration: result.duration,
+ model: this.model,
+ timestamp: new Date(),
+ },
+ };
+ }
+
+ async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
+ if (!this.model)
+ throw new Error(
+ `Privatemode chat: ${this.model} is not valid or defined model for chat completion!`
+ );
+
+ const measuredStreamRequest = await LLMPerformanceMonitor.measureStream({
+ func: this.client.chat.completions.create({
+ model: this.model,
+ stream: true,
+ messages,
+ temperature,
+ }),
+ messages,
+ runPromptTokenCalculation: true,
+ modelTag: this.model,
+ });
+ return measuredStreamRequest;
+ }
+
+ handleStream(response, stream, responseProps) {
+ return handleDefaultStreamResponseV2(response, stream, responseProps);
+ }
+
+ // Simple wrapper for dynamic embedder & normalize interface for all LLM implementations
+ 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 = {
+ PrivatemodeLLM,
+};
diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js
index 0a2f6f45..86e1a214 100644
--- a/server/utils/agents/aibitat/index.js
+++ b/server/utils/agents/aibitat/index.js
@@ -994,6 +994,8 @@ ${this.getHistory({ to: route.to })
return new Providers.CohereProvider({ model: config.model });
case "docker-model-runner":
return new Providers.DockerModelRunnerProvider({ model: config.model });
+ case "privatemode":
+ return new Providers.PrivatemodeProvider({ model: config.model });
default:
throw new Error(
`Unknown provider: ${config.provider}. Please use a valid provider.`
diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js
index d92a5ad8..d1da6bae 100644
--- a/server/utils/agents/aibitat/providers/ai-provider.js
+++ b/server/utils/agents/aibitat/providers/ai-provider.js
@@ -249,6 +249,14 @@ class Provider {
apiKey: process.env.COHERE_API_KEY ?? null,
...config,
});
+ case "privatemode":
+ return new ChatOpenAI({
+ configuration: {
+ baseURL: process.env.PRIVATEMODE_LLM_BASE_PATH,
+ },
+ apiKey: null,
+ ...config,
+ });
// OSS Model Runners
// case "anythingllm_ollama":
// return new ChatOllama({
diff --git a/server/utils/agents/aibitat/providers/index.js b/server/utils/agents/aibitat/providers/index.js
index c53c01c3..1862dc66 100644
--- a/server/utils/agents/aibitat/providers/index.js
+++ b/server/utils/agents/aibitat/providers/index.js
@@ -30,6 +30,7 @@ const FoundryProvider = require("./foundry.js");
const GiteeAIProvider = require("./giteeai.js");
const CohereProvider = require("./cohere.js");
const DockerModelRunnerProvider = require("./dockerModelRunner.js");
+const PrivatemodeProvider = require("./privatemode.js");
module.exports = {
OpenAIProvider,
@@ -64,4 +65,5 @@ module.exports = {
GiteeAIProvider,
CohereProvider,
DockerModelRunnerProvider,
+ PrivatemodeProvider,
};
diff --git a/server/utils/agents/aibitat/providers/privatemode.js b/server/utils/agents/aibitat/providers/privatemode.js
new file mode 100644
index 00000000..eadb1af6
--- /dev/null
+++ b/server/utils/agents/aibitat/providers/privatemode.js
@@ -0,0 +1,98 @@
+const OpenAI = require("openai");
+const Provider = require("./ai-provider.js");
+const InheritMultiple = require("./helpers/classes.js");
+const UnTooled = require("./helpers/untooled.js");
+const { PrivatemodeLLM } = require("../../../AiProviders/privatemode/index.js");
+
+/**
+ * The agent provider for the Privatemodel provider.
+ * @extends {Provider}
+ * @extends {UnTooled}
+ */
+class PrivatemodelProvider extends InheritMultiple([Provider, UnTooled]) {
+ model;
+
+ constructor(config = {}) {
+ const { model = process.env.PRIVATEMODE_LLM_MODEL_PREF } = config;
+ super();
+ const client = new OpenAI({
+ baseURL: PrivatemodeLLM.parseBasePath(
+ process.env.PRIVATEMODE_LLM_BASE_PATH
+ ),
+ apiKey: null,
+ maxRetries: 3,
+ });
+
+ this._client = client;
+ this.model = model;
+ this.verbose = true;
+ }
+
+ get client() {
+ return this._client;
+ }
+
+ get supportsAgentStreaming() {
+ return true;
+ }
+
+ async #handleFunctionCallChat({ messages = [] }) {
+ return await this.client.chat.completions
+ .create({
+ model: this.model,
+ messages,
+ user: this.executingUserId,
+ })
+ .then((result) => {
+ if (!result.hasOwnProperty("choices"))
+ throw new Error("Privatemodel chat: No results!");
+ if (result.choices.length === 0)
+ throw new Error("Privatemodel chat: No results length!");
+ return result.choices[0].message.content;
+ })
+ .catch((_) => {
+ return null;
+ });
+ }
+
+ async #handleFunctionCallStream({ messages = [] }) {
+ return await this.client.chat.completions.create({
+ model: this.model,
+ stream: true,
+ messages,
+ user: this.executingUserId,
+ });
+ }
+
+ async stream(messages, functions = [], eventHandler = null) {
+ return await UnTooled.prototype.stream.call(
+ this,
+ messages,
+ functions,
+ this.#handleFunctionCallStream.bind(this),
+ eventHandler
+ );
+ }
+
+ async complete(messages, functions = []) {
+ return await UnTooled.prototype.complete.call(
+ this,
+ messages,
+ functions,
+ this.#handleFunctionCallChat.bind(this)
+ );
+ }
+
+ /**
+ * Get the cost of the completion.
+ *
+ * @param _usage The completion to get the cost for.
+ * @returns The cost of the completion.
+ * Stubbed since Privatemodel has no cost basis.
+ */
+ getCost(_usage) {
+ return 0;
+ }
+}
+
+module.exports = PrivatemodelProvider;
diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js
index 24c496b6..582d5495 100644
--- a/server/utils/agents/index.js
+++ b/server/utils/agents/index.js
@@ -223,6 +223,12 @@ class AgentHandler {
"Docker Model Runner base path must be provided to use agents."
);
break;
+ case "privatemode":
+ if (!process.env.PRIVATEMODE_LLM_BASE_PATH)
+ throw new Error(
+ "Privatemode base path must be provided to use agents."
+ );
+ break;
default:
throw new Error(
"No workspace agent provider set. Please set your agent provider in the workspace's settings"
@@ -305,6 +311,8 @@ class AgentHandler {
return process.env.COHERE_MODEL_PREF ?? "command-r-08-2024";
case "docker-model-runner":
return process.env.DOCKER_MODEL_RUNNER_LLM_MODEL_PREF ?? null;
+ case "privatemode":
+ return process.env.PRIVATEMODE_LLM_MODEL_PREF ?? null;
default:
return null;
}
diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js
index 649d2e56..974099af 100644
--- a/server/utils/helpers/customModels.js
+++ b/server/utils/helpers/customModels.js
@@ -45,6 +45,7 @@ const SUPPORT_CUSTOM_MODELS = [
"zai",
"giteeai",
"docker-model-runner",
+ "privatemode",
// Embedding Engines
"native-embedder",
"cohere-embedder",
@@ -120,6 +121,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) {
return await getGiteeAIModels(apiKey);
case "docker-model-runner":
return await getDockerModelRunnerModels(basePath);
+ case "privatemode":
+ return await getPrivatemodeModels(basePath, "generate");
default:
return { models: [], error: "Invalid provider for custom models" };
}
@@ -881,6 +884,54 @@ async function getDockerModelRunnerModels(basePath = null) {
}
}
+/**
+ * Get Privatemode models
+ * @param {string} basePath - The base path of the Privatemode endpoint.
+ * @param {'any' | 'generate' | 'embed' | 'transcribe'} task - The task to fetch the models for.
+ * @returns {Promise<{models: Array<{id: string, organization: string, name: string}>, error: string | null}>}
+ */
+async function getPrivatemodeModels(basePath = null, task = "any") {
+ try {
+ const { PrivatemodeLLM } = require("../AiProviders/privatemode");
+ const { OpenAI: OpenAIApi } = require("openai");
+ const openai = new OpenAIApi({
+ baseURL: PrivatemodeLLM.parseBasePath(
+ basePath || process.env.PRIVATEMODE_LLM_BASE_PATH
+ ),
+ apiKey: null,
+ });
+ const models = await openai.models
+ .list()
+ .then((results) => results.data)
+ .then(
+ (models) =>
+ models
+ .filter((model) => !model.id.includes("/")) // remove legacy prefixed models
+ .filter((model) =>
+ task === "any" ? true : model.tasks.includes(task)
+ ) // filter by task or show all if task is any
+ )
+ .then((models) =>
+ models.map((model) => ({
+ id: model.id,
+ organization: "Privatemode",
+ name: model.id
+ .split("-")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" "),
+ }))
+ )
+ .catch((e) => {
+ console.error(`Privatemode:listModels`, e.message);
+ return [];
+ });
+ return { models, error: null };
+ } catch (e) {
+ console.error(`Privatemode:getPrivatemodeModels`, e.message);
+ return { models: [], error: "Could not fetch Privatemode Models" };
+ }
+}
+
module.exports = {
getCustomModels,
SUPPORT_CUSTOM_MODELS,
diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js
index d508f7ee..38f82306 100644
--- a/server/utils/helpers/index.js
+++ b/server/utils/helpers/index.js
@@ -234,6 +234,9 @@ function getLLMProvider({ provider = null, model = null } = {}) {
DockerModelRunnerLLM,
} = require("../AiProviders/dockerModelRunner");
return new DockerModelRunnerLLM(embedder, model);
+ case "privatemode":
+ const { PrivatemodeLLM } = require("../AiProviders/privatemode");
+ return new PrivatemodeLLM(embedder, model);
default:
throw new Error(
`ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}`
@@ -404,6 +407,9 @@ function getLLMProviderClass({ provider = null } = {}) {
DockerModelRunnerLLM,
} = require("../AiProviders/dockerModelRunner");
return DockerModelRunnerLLM;
+ case "privatemode":
+ const { PrivateModeLLM } = require("../AiProviders/privatemode");
+ return PrivateModeLLM;
default:
return null;
}
@@ -482,6 +488,8 @@ function getBaseLLMProviderModel({ provider = null } = {}) {
return process.env.GITEE_AI_MODEL_PREF;
case "docker-model-runner":
return process.env.DOCKER_MODEL_RUNNER_LLM_MODEL_PREF;
+ case "privatemode":
+ return process.env.PRIVATEMODE_LLM_MODEL_PREF;
default:
return null;
}
diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js
index 5e4cffbe..fdac6018 100644
--- a/server/utils/helpers/updateENV.js
+++ b/server/utils/helpers/updateENV.js
@@ -799,6 +799,16 @@ const KEY_MAPPING = {
envKey: "DOCKER_MODEL_RUNNER_LLM_MODEL_TOKEN_LIMIT",
checks: [nonZero],
},
+
+ // Privatemode Options
+ PrivateModeBasePath: {
+ envKey: "PRIVATEMODE_LLM_BASE_PATH",
+ checks: [isValidURL],
+ },
+ PrivateModeModelPref: {
+ envKey: "PRIVATEMODE_LLM_MODEL_PREF",
+ checks: [isNotEmpty],
+ },
};
function isNotEmpty(input = "") {
@@ -913,6 +923,7 @@ function supportedLLM(input = "") {
"zai",
"giteeai",
"docker-model-runner",
+ "privatemode",
].includes(input);
return validSelection ? null : `${input} is not a valid LLM provider.`;
}