From f376872e43800d5f58a837fe9e184db22857517c Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Thu, 22 Feb 2024 10:59:45 -0800 Subject: [PATCH 01/16] patch Puppeteer base URL --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 595099fb75..b1ea62a63f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -133,6 +133,7 @@ COPY --from=build-stage /app/frontend/dist ./server/public COPY --chown=anythingllm:anythingllm ./collector/ ./collector/ # Install collector dependencies +ENV PUPPETEER_DOWNLOAD_BASE_URL=https://storage.googleapis.com/chrome-for-testing-public RUN cd /app/collector && yarn install --production --network-timeout 100000 && yarn cache clean # Migrate and Run Prisma against known schema From 80ced5eba4f6eab477e8921de943cc5b88a681b6 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Thu, 22 Feb 2024 12:48:57 -0800 Subject: [PATCH 02/16] [FEAT] PerplexityAI Support (#778) * add LLM support for perplexity * update README & example env * fix ENV keys in example env files * slight changes for QA of perplexity support * Update Perplexity AI name --------- Co-authored-by: timothycarambat --- README.md | 5 +- docker/.env.example | 4 + .../LLMSelection/PerplexityOptions/index.jsx | 88 ++++++++ frontend/src/media/llmprovider/perplexity.png | Bin 0 -> 15863 bytes .../GeneralSettings/LLMPreference/index.jsx | 11 + .../Steps/DataHandling/index.jsx | 9 + .../Steps/LLMPreference/index.jsx | 12 +- server/.env.example | 4 + server/models/systemSettings.js | 12 ++ server/utils/AiProviders/perplexity/index.js | 204 ++++++++++++++++++ server/utils/AiProviders/perplexity/models.js | 49 +++++ .../AiProviders/perplexity/scripts/.gitignore | 1 + .../perplexity/scripts/chat_models.txt | 11 + .../AiProviders/perplexity/scripts/parse.mjs | 44 ++++ server/utils/helpers/customModels.js | 18 ++ server/utils/helpers/index.js | 3 + server/utils/helpers/updateENV.js | 11 + 17 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/LLMSelection/PerplexityOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/perplexity.png create mode 100644 server/utils/AiProviders/perplexity/index.js create mode 100644 server/utils/AiProviders/perplexity/models.js create mode 100644 server/utils/AiProviders/perplexity/scripts/.gitignore create mode 100644 server/utils/AiProviders/perplexity/scripts/chat_models.txt create mode 100644 server/utils/AiProviders/perplexity/scripts/parse.mjs diff --git a/README.md b/README.md index ff50a85872..200355707f 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Some cool features of AnythingLLM - [LM Studio (all models)](https://lmstudio.ai) - [LocalAi (all models)](https://localai.io/) - [Together AI (chat models)](https://www.together.ai/) +- [Perplexity (chat models)](https://www.perplexity.ai/) - [Mistral](https://mistral.ai/) **Supported Embedding models:** @@ -108,8 +109,8 @@ Mintplex Labs & the community maintain a number of deployment methods, scripts, |----------------------------------------|----:|-----|---------------|------------| | [![Deploy on Docker][docker-btn]][docker-deploy] | [![Deploy on AWS][aws-btn]][aws-deploy] | [![Deploy on GCP][gcp-btn]][gcp-deploy] | [![Deploy on DigitalOcean][do-btn]][aws-deploy] | [![Deploy on Render.com][render-btn]][render-deploy] | -| Railway | -|----------------------------------------| +| Railway | +| --------------------------------------------------- | | [![Deploy on Railway][railway-btn]][railway-deploy] | [or set up a production AnythingLLM instance without Docker →](./BARE_METAL.md) diff --git a/docker/.env.example b/docker/.env.example index b14d3c6ed3..eed505782d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -48,6 +48,10 @@ GID='1000' # MISTRAL_API_KEY='example-mistral-ai-api-key' # MISTRAL_MODEL_PREF='mistral-tiny' +# LLM_PROVIDER='perplexity' +# PERPLEXITY_API_KEY='my-perplexity-key' +# PERPLEXITY_MODEL_PREF='codellama-34b-instruct' + # LLM_PROVIDER='huggingface' # HUGGING_FACE_LLM_ENDPOINT=https://uuid-here.us-east-1.aws.endpoints.huggingface.cloud # HUGGING_FACE_LLM_API_KEY=hf_xxxxxx diff --git a/frontend/src/components/LLMSelection/PerplexityOptions/index.jsx b/frontend/src/components/LLMSelection/PerplexityOptions/index.jsx new file mode 100644 index 0000000000..0b392cf412 --- /dev/null +++ b/frontend/src/components/LLMSelection/PerplexityOptions/index.jsx @@ -0,0 +1,88 @@ +import System from "@/models/system"; +import { useState, useEffect } from "react"; + +export default function PerplexityOptions({ settings }) { + return ( +
+
+ + +
+ +
+ ); +} + +function PerplexityModelSelection({ settings }) { + const [customModels, setCustomModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + setLoading(true); + const { models } = await System.customModels("perplexity"); + setCustomModels(models || []); + setLoading(false); + } + findCustomModels(); + }, []); + + if (loading || customModels.length == 0) { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/media/llmprovider/perplexity.png b/frontend/src/media/llmprovider/perplexity.png new file mode 100644 index 0000000000000000000000000000000000000000..f4767169a12cbe2189eab51d8c59deafe4ae5b33 GIT binary patch literal 15863 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 literal 0 HcmV?d00001 diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx index 45ad5fd70a..8c51e559e1 100644 --- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx @@ -14,6 +14,7 @@ import LocalAiLogo from "@/media/llmprovider/localai.png"; import TogetherAILogo from "@/media/llmprovider/togetherai.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; import HuggingFaceLogo from "@/media/llmprovider/huggingface.png"; +import PerplexityLogo from "@/media/llmprovider/perplexity.png"; import PreLoader from "@/components/Preloader"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; import AzureAiOptions from "@/components/LLMSelection/AzureAiOptions"; @@ -26,8 +27,10 @@ import OllamaLLMOptions from "@/components/LLMSelection/OllamaLLMOptions"; import TogetherAiOptions from "@/components/LLMSelection/TogetherAiOptions"; import MistralOptions from "@/components/LLMSelection/MistralOptions"; import HuggingFaceOptions from "@/components/LLMSelection/HuggingFaceOptions"; + import LLMItem from "@/components/LLMSelection/LLMItem"; import { MagnifyingGlass } from "@phosphor-icons/react"; +import PerplexityOptions from "@/components/LLMSelection/PerplexityOptions"; export default function GeneralLLMPreference() { const [saving, setSaving] = useState(false); @@ -153,6 +156,14 @@ export default function GeneralLLMPreference() { options: , description: "Run open source models from Mistral AI.", }, + { + name: "Perplexity AI", + value: "perplexity", + logo: PerplexityLogo, + options: , + description: + "Run powerful and internet-connected models hosted by Perplexity AI.", + }, { name: "Native", value: "native", diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index c86a62a439..f9c4c4169e 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -11,6 +11,7 @@ import LMStudioLogo from "@/media/llmprovider/lmstudio.png"; import LocalAiLogo from "@/media/llmprovider/localai.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; import HuggingFaceLogo from "@/media/llmprovider/huggingface.png"; +import PerplexityLogo from "@/media/llmprovider/perplexity.png"; import ZillizLogo from "@/media/vectordbs/zilliz.png"; import AstraDBLogo from "@/media/vectordbs/astraDB.png"; import ChromaLogo from "@/media/vectordbs/chroma.png"; @@ -109,6 +110,14 @@ const LLM_SELECTION_PRIVACY = { ], logo: HuggingFaceLogo, }, + perplexity: { + name: "Perplexity AI", + description: [ + "Your chats will not be used for training", + "Your prompts and document text used in response creation are visible to Perplexity AI", + ], + logo: PerplexityLogo, + }, }; 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 6970dfa1ff..296a28d9e1 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -11,6 +11,7 @@ import TogetherAILogo from "@/media/llmprovider/togetherai.png"; import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; import HuggingFaceLogo from "@/media/llmprovider/huggingface.png"; +import PerplexityLogo from "@/media/llmprovider/perplexity.png"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; import AzureAiOptions from "@/components/LLMSelection/AzureAiOptions"; import AnthropicAiOptions from "@/components/LLMSelection/AnthropicAiOptions"; @@ -21,12 +22,13 @@ import GeminiLLMOptions from "@/components/LLMSelection/GeminiLLMOptions"; import OllamaLLMOptions from "@/components/LLMSelection/OllamaLLMOptions"; import MistralOptions from "@/components/LLMSelection/MistralOptions"; import HuggingFaceOptions from "@/components/LLMSelection/HuggingFaceOptions"; +import TogetherAiOptions from "@/components/LLMSelection/TogetherAiOptions"; +import PerplexityOptions from "@/components/LLMSelection/PerplexityOptions"; import LLMItem from "@/components/LLMSelection/LLMItem"; import System from "@/models/system"; import paths from "@/utils/paths"; import showToast from "@/utils/toast"; import { useNavigate } from "react-router-dom"; -import TogetherAiOptions from "@/components/LLMSelection/TogetherAiOptions"; const TITLE = "LLM Preference"; const DESCRIPTION = @@ -128,6 +130,14 @@ export default function LLMPreference({ options: , description: "Run open source models from Mistral AI.", }, + { + name: "Perplexity AI", + value: "perplexity", + logo: PerplexityLogo, + options: , + description: + "Run powerful and internet-connected models hosted by Perplexity AI.", + }, { name: "Native", value: "native", diff --git a/server/.env.example b/server/.env.example index ec6abcac9b..863486ad42 100644 --- a/server/.env.example +++ b/server/.env.example @@ -41,6 +41,10 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea # TOGETHER_AI_API_KEY='my-together-ai-key' # TOGETHER_AI_MODEL_PREF='mistralai/Mixtral-8x7B-Instruct-v0.1' +# LLM_PROVIDER='perplexity' +# PERPLEXITY_API_KEY='my-perplexity-key' +# PERPLEXITY_MODEL_PREF='codellama-34b-instruct' + # LLM_PROVIDER='mistral' # MISTRAL_API_KEY='example-mistral-ai-api-key' # MISTRAL_MODEL_PREF='mistral-tiny' diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 29949d3d74..4154482821 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -176,6 +176,18 @@ const SystemSettings = { TogetherAiApiKey: !!process.env.TOGETHER_AI_API_KEY, TogetherAiModelPref: process.env.TOGETHER_AI_MODEL_PREF, + // For embedding credentials when ollama is selected. + OpenAiKey: !!process.env.OPEN_AI_KEY, + AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY, + AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF, + } + : {}), + ...(llmProvider === "perplexity" + ? { + PerplexityApiKey: !!process.env.PERPLEXITY_API_KEY, + PerplexityModelPref: process.env.PERPLEXITY_MODEL_PREF, + // For embedding credentials when ollama is selected. OpenAiKey: !!process.env.OPEN_AI_KEY, AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT, diff --git a/server/utils/AiProviders/perplexity/index.js b/server/utils/AiProviders/perplexity/index.js new file mode 100644 index 0000000000..df20df2034 --- /dev/null +++ b/server/utils/AiProviders/perplexity/index.js @@ -0,0 +1,204 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { chatPrompt } = require("../../chats"); +const { handleDefaultStreamResponse } = require("../../helpers/chat/responses"); + +function perplexityModels() { + const { MODELS } = require("./models.js"); + return MODELS || {}; +} + +class PerplexityLLM { + constructor(embedder = null, modelPreference = null) { + const { Configuration, OpenAIApi } = require("openai"); + if (!process.env.PERPLEXITY_API_KEY) + throw new Error("No Perplexity API key was set."); + + const config = new Configuration({ + basePath: "https://api.perplexity.ai", + apiKey: process.env.PERPLEXITY_API_KEY, + }); + this.openai = new OpenAIApi(config); + this.model = + modelPreference || process.env.PERPLEXITY_MODEL_PREF || "pplx-7b-online"; // Give at least a unique model to the provider as last fallback. + this.limits = { + history: this.promptWindowLimit() * 0.15, + system: this.promptWindowLimit() * 0.15, + user: this.promptWindowLimit() * 0.7, + }; + + this.embedder = !embedder ? new NativeEmbedder() : embedder; + 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("") + ); + } + + allModelInformation() { + return perplexityModels(); + } + + streamingEnabled() { + return "streamChat" in this && "streamGetChatCompletion" in this; + } + + promptWindowLimit() { + const availableModels = this.allModelInformation(); + return availableModels[this.model]?.maxLength || 4096; + } + + async isValidChatCompletionModel(model = "") { + const availableModels = this.allModelInformation(); + return availableModels.hasOwnProperty(model); + } + + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [prompt, ...chatHistory, { role: "user", content: userPrompt }]; + } + + async isSafe(_input = "") { + // Not implemented so must be stubbed + return { safe: true, reasons: [] }; + } + + async sendChat(chatHistory = [], prompt, workspace = {}, rawHistory = []) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `Perplexity chat: ${this.model} is not valid for chat completion!` + ); + + const textResponse = await this.openai + .createChatCompletion({ + model: this.model, + temperature: Number(workspace?.openAiTemp ?? this.defaultTemp), + n: 1, + messages: await this.compressMessages( + { + systemPrompt: chatPrompt(workspace), + userPrompt: prompt, + chatHistory, + }, + rawHistory + ), + }) + .then((json) => { + const res = json.data; + if (!res.hasOwnProperty("choices")) + throw new Error("Perplexity chat: No results!"); + if (res.choices.length === 0) + throw new Error("Perplexity chat: No results length!"); + return res.choices[0].message.content; + }) + .catch((error) => { + throw new Error( + `Perplexity::createChatCompletion failed with: ${error.message}` + ); + }); + + return textResponse; + } + + async streamChat(chatHistory = [], prompt, workspace = {}, rawHistory = []) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `Perplexity chat: ${this.model} is not valid for chat completion!` + ); + + const streamRequest = await this.openai.createChatCompletion( + { + model: this.model, + stream: true, + temperature: Number(workspace?.openAiTemp ?? this.defaultTemp), + n: 1, + messages: await this.compressMessages( + { + systemPrompt: chatPrompt(workspace), + userPrompt: prompt, + chatHistory, + }, + rawHistory + ), + }, + { responseType: "stream" } + ); + return streamRequest; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `Perplexity chat: ${this.model} is not valid for chat completion!` + ); + + const { data } = await this.openai + .createChatCompletion({ + model: this.model, + messages, + temperature, + }) + .catch((e) => { + throw new Error(e.response.data.error.message); + }); + + if (!data.hasOwnProperty("choices")) return null; + return data.choices[0].message.content; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `Perplexity chat: ${this.model} is not valid for chat completion!` + ); + + const streamRequest = await this.openai.createChatCompletion( + { + model: this.model, + stream: true, + messages, + temperature, + }, + { responseType: "stream" } + ); + return streamRequest; + } + + handleStream(response, stream, responseProps) { + return handleDefaultStreamResponse(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 = { + PerplexityLLM, + perplexityModels, +}; diff --git a/server/utils/AiProviders/perplexity/models.js b/server/utils/AiProviders/perplexity/models.js new file mode 100644 index 0000000000..258cfeace4 --- /dev/null +++ b/server/utils/AiProviders/perplexity/models.js @@ -0,0 +1,49 @@ +const MODELS = { + "codellama-34b-instruct": { + id: "codellama-34b-instruct", + name: "codellama-34b-instruct", + maxLength: 16384, + }, + "codellama-70b-instruct": { + id: "codellama-70b-instruct", + name: "codellama-70b-instruct", + maxLength: 16384, + }, + "llama-2-70b-chat": { + id: "llama-2-70b-chat", + name: "llama-2-70b-chat", + maxLength: 4096, + }, + "mistral-7b-instruct": { + id: "mistral-7b-instruct", + name: "mistral-7b-instruct", + maxLength: 8192, + }, + "mixtral-8x7b-instruct": { + id: "mixtral-8x7b-instruct", + name: "mixtral-8x7b-instruct", + maxLength: 8192, + }, + "pplx-7b-chat": { + id: "pplx-7b-chat", + name: "pplx-7b-chat", + maxLength: 8192, + }, + "pplx-70b-chat": { + id: "pplx-70b-chat", + name: "pplx-70b-chat", + maxLength: 8192, + }, + "pplx-7b-online": { + id: "pplx-7b-online", + name: "pplx-7b-online", + maxLength: 8192, + }, + "pplx-70b-online": { + id: "pplx-70b-online", + name: "pplx-70b-online", + maxLength: 8192, + }, +}; + +module.exports.MODELS = MODELS; diff --git a/server/utils/AiProviders/perplexity/scripts/.gitignore b/server/utils/AiProviders/perplexity/scripts/.gitignore new file mode 100644 index 0000000000..94a2dd146a --- /dev/null +++ b/server/utils/AiProviders/perplexity/scripts/.gitignore @@ -0,0 +1 @@ +*.json \ No newline at end of file diff --git a/server/utils/AiProviders/perplexity/scripts/chat_models.txt b/server/utils/AiProviders/perplexity/scripts/chat_models.txt new file mode 100644 index 0000000000..83f6d2a809 --- /dev/null +++ b/server/utils/AiProviders/perplexity/scripts/chat_models.txt @@ -0,0 +1,11 @@ +| Model | Context Length | Model Type | +| :------------------------ | :------------- | :-------------- | +| `codellama-34b-instruct` | 16384 | Chat Completion | +| `codellama-70b-instruct` | 16384 | Chat Completion | +| `llama-2-70b-chat` | 4096 | Chat Completion | +| `mistral-7b-instruct` [2] | 8192 [1] | Chat Completion | +| `mixtral-8x7b-instruct` | 8192 [1] | Chat Completion | +| `pplx-7b-chat` | 8192 | Chat Completion | +| `pplx-70b-chat` | 8192 | Chat Completion | +| `pplx-7b-online` | 8192 | Chat Completion | +| `pplx-70b-online` | 8192 | Chat Completion | \ No newline at end of file diff --git a/server/utils/AiProviders/perplexity/scripts/parse.mjs b/server/utils/AiProviders/perplexity/scripts/parse.mjs new file mode 100644 index 0000000000..749a63dcea --- /dev/null +++ b/server/utils/AiProviders/perplexity/scripts/parse.mjs @@ -0,0 +1,44 @@ +// Perplexity does not provide a simple REST API to get models, +// so we have a table which we copy from their documentation +// https://docs.perplexity.ai/edit/model-cards that we can +// then parse and get all models from in a format that makes sense +// Why this does not exist is so bizarre, but whatever. + +// To run, cd into this directory and run `node parse.mjs` +// copy outputs into the export in ../models.js + +// Update the date below if you run this again because Perplexity added new models. +// Last Collected: Feb 22, 2024 + +import fs from "fs"; + +function parseChatModels() { + const models = {}; + const tableString = fs.readFileSync("chat_models.txt", { encoding: "utf-8" }); + const rows = tableString.split("\n").slice(2); + + rows.forEach((row) => { + let [model, contextLength] = row + .split("|") + .slice(1, -1) + .map((text) => text.trim()); + model = model.replace(/`|\s*\[\d+\]\s*/g, ""); + const maxLength = Number(contextLength.replace(/\s*\[\d+\]\s*/g, "")); + if (model && maxLength) { + models[model] = { + id: model, + name: model, + maxLength: maxLength, + }; + } + }); + + fs.writeFileSync( + "chat_models.json", + JSON.stringify(models, null, 2), + "utf-8" + ); + return models; +} + +parseChatModels(); diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index 53c641e75e..8f8ca06571 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -1,3 +1,4 @@ +const { perplexityModels } = require("../AiProviders/perplexity"); const { togetherAiModels } = require("../AiProviders/togetherAi"); const SUPPORT_CUSTOM_MODELS = [ "openai", @@ -6,6 +7,7 @@ const SUPPORT_CUSTOM_MODELS = [ "native-llm", "togetherai", "mistral", + "perplexity", ]; async function getCustomModels(provider = "", apiKey = null, basePath = null) { @@ -25,6 +27,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return await getMistralModels(apiKey); case "native-llm": return nativeLLMModels(); + case "perplexity": + return await getPerplexityModels(); default: return { models: [], error: "Invalid provider for custom models" }; } @@ -120,6 +124,20 @@ async function getTogetherAiModels() { return { models, error: null }; } +async function getPerplexityModels() { + const knownModels = perplexityModels(); + if (!Object.keys(knownModels).length === 0) + return { models: [], error: null }; + + const models = Object.values(knownModels).map((model) => { + return { + id: model.id, + name: model.name, + }; + }); + return { models, error: null }; +} + async function getMistralModels(apiKey = null) { const { Configuration, OpenAIApi } = require("openai"); const config = new Configuration({ diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index 42ed262f95..818d92dbce 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -58,6 +58,9 @@ function getLLMProvider(modelPreference = null) { case "togetherai": const { TogetherAiLLM } = require("../AiProviders/togetherAi"); return new TogetherAiLLM(embedder, modelPreference); + case "perplexity": + const { PerplexityLLM } = require("../AiProviders/perplexity"); + return new PerplexityLLM(embedder, modelPreference); case "mistral": const { MistralLLM } = require("../AiProviders/mistral"); return new MistralLLM(embedder, modelPreference); diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index f89a193f6e..5a384740bf 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -239,6 +239,16 @@ const KEY_MAPPING = { checks: [isNotEmpty], }, + // Perplexity Options + PerplexityApiKey: { + envKey: "PERPLEXITY_API_KEY", + checks: [isNotEmpty], + }, + PerplexityModelPref: { + envKey: "PERPLEXITY_MODEL_PREF", + checks: [isNotEmpty], + }, + // System Settings AuthToken: { envKey: "AUTH_TOKEN", @@ -314,6 +324,7 @@ function supportedLLM(input = "") { "togetherai", "mistral", "huggingface", + "perplexity", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; } From c87ef5b67447e0e3672d272e9a55be89c8a3ca4a Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Thu, 22 Feb 2024 13:16:20 -0800 Subject: [PATCH 03/16] [FEAT] show chunk score on citations (#773) * show similarity score in citation chunks * refactor combine like sources to handle separate similarity scores and improve UI for displaying chunks * fix parseChunkSource function to work with links * destructure properties in chunks mapping * check chunk length in parseChunkSource * change UI on how score is shown * remove duplicate import --------- Co-authored-by: timothycarambat --- .../ChatHistory/Citation/index.jsx | 66 ++++++++++++++----- frontend/src/utils/numbers.js | 8 +++ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx index 85b7fb7bb2..1dfeaaaf36 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx @@ -1,31 +1,37 @@ import { memo, useState } from "react"; import { v4 } from "uuid"; import { decode as HTMLDecode } from "he"; -import { CaretRight, FileText } from "@phosphor-icons/react"; import truncate from "truncate"; import ModalWrapper from "@/components/ModalWrapper"; import { middleTruncate } from "@/utils/directories"; import { + CaretRight, + FileText, + Info, ArrowSquareOut, GithubLogo, Link, X, YoutubeLogo, } from "@phosphor-icons/react"; +import { Tooltip } from "react-tooltip"; +import { toPercentString } from "@/utils/numbers"; function combineLikeSources(sources) { const combined = {}; sources.forEach((source) => { - const { id, title, text, chunkSource = "" } = source; + const { id, title, text, chunkSource = "", score = null } = source; if (combined.hasOwnProperty(title)) { - combined[title].text += `\n\n ---- Chunk ${id || ""} ---- \n\n${text}`; + combined[title].chunks.push({ id, text, chunkSource, score }); combined[title].references += 1; - combined[title].chunkSource = chunkSource; } else { - combined[title] = { title, text, chunkSource, references: 1 }; + combined[title] = { + title, + chunks: [{ id, text, chunkSource, score }], + references: 1, + }; } }); - return Object.values(combined); } @@ -109,7 +115,7 @@ function SkeletonLine() { } function CitationDetailModal({ source, onClose }) { - const { references, title, text } = source; + const { references, title, chunks } = source; const { isUrl, text: webpageUrl, href: linkTo } = parseChunkSource(source); return ( @@ -156,12 +162,39 @@ function CitationDetailModal({ source, onClose }) { {[...Array(3)].map((_, idx) => ( ))} -

{HTMLDecode(text)}

-
- {[...Array(3)].map((_, idx) => ( - - ))} -
+ {chunks.map(({ text, score }, idx) => ( +
+
+

+ {HTMLDecode(text)} +

+ + {!!score && ( + <> +
+
+ +

{toPercentString(score)} match

+
+
+ + + )} +
+ {[...Array(3)].map((_, idx) => ( + + ))} +
+ ))} +
@@ -180,7 +213,7 @@ const ICONS = { // which contain valid outbound links that can be clicked by the // user when viewing a citation. Optionally allows various icons // to show distinct types of sources. -function parseChunkSource({ title = "", chunkSource = "" }) { +function parseChunkSource({ title = "", chunks = [] }) { const nullResponse = { isUrl: false, text: null, @@ -188,9 +221,10 @@ function parseChunkSource({ title = "", chunkSource = "" }) { icon: "file", }; - if (!chunkSource.startsWith("link://")) return nullResponse; + if (!chunks.length || !chunks[0].chunkSource.startsWith("link://")) + return nullResponse; try { - const url = new URL(chunkSource.split("link://")[1]); + const url = new URL(chunks[0].chunkSource.split("link://")[1]); let text = url.host + url.pathname; let icon = "link"; diff --git a/frontend/src/utils/numbers.js b/frontend/src/utils/numbers.js index bac17787c5..0b4da3cbdb 100644 --- a/frontend/src/utils/numbers.js +++ b/frontend/src/utils/numbers.js @@ -15,6 +15,14 @@ export function dollarFormat(input) { }).format(input); } +export function toPercentString(input = null, decimals = 0) { + if (isNaN(input) || input === null) return ""; + const percentage = Math.round(input * 100); + return ( + (decimals > 0 ? percentage.toFixed(decimals) : percentage.toString()) + "%" + ); +} + export function humanFileSize(bytes, si = false, dp = 1) { const thresh = si ? 1000 : 1024; From 633f4252067a1b38a63f375c3fe8d4a6e78e9bc3 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Fri, 23 Feb 2024 17:18:58 -0800 Subject: [PATCH 04/16] [FEAT] OpenRouter integration (#784) * WIP openrouter integration * add OpenRouter options to onboarding flow and data handling * add todo to fix headers for rankings * OpenRouter LLM support complete * Fix hanging response stream with OpenRouter update tagline update comment * update timeout comment * wait for first chunk to start timer * sort OpenRouter models by organization * uppercase first letter of organization * sort grouped models by org --------- Co-authored-by: timothycarambat --- README.md | 1 + docker/.env.example | 4 + .../LLMSelection/OpenRouterOptions/index.jsx | 97 +++ .../LLMSelection/TogetherAiOptions/index.jsx | 28 +- .../src/media/llmprovider/openrouter.jpeg | Bin 0 -> 6366 bytes .../GeneralSettings/LLMPreference/index.jsx | 9 + .../Steps/DataHandling/index.jsx | 9 + .../Steps/LLMPreference/index.jsx | 9 + server/.env.example | 4 + server/models/systemSettings.js | 12 + server/utils/AiProviders/openRouter/index.js | 334 ++++++++++ server/utils/AiProviders/openRouter/models.js | 622 ++++++++++++++++++ .../AiProviders/openRouter/scripts/.gitignore | 1 + .../AiProviders/openRouter/scripts/parse.mjs | 37 ++ server/utils/helpers/customModels.js | 19 + server/utils/helpers/index.js | 3 + server/utils/helpers/updateENV.js | 11 + 17 files changed, 1187 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/LLMSelection/OpenRouterOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/openrouter.jpeg create mode 100644 server/utils/AiProviders/openRouter/index.js create mode 100644 server/utils/AiProviders/openRouter/models.js create mode 100644 server/utils/AiProviders/openRouter/scripts/.gitignore create mode 100644 server/utils/AiProviders/openRouter/scripts/parse.mjs diff --git a/README.md b/README.md index 200355707f..f77cdf2b89 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Some cool features of AnythingLLM - [LocalAi (all models)](https://localai.io/) - [Together AI (chat models)](https://www.together.ai/) - [Perplexity (chat models)](https://www.perplexity.ai/) +- [OpenRouter (chat models)](https://openrouter.ai/) - [Mistral](https://mistral.ai/) **Supported Embedding models:** diff --git a/docker/.env.example b/docker/.env.example index eed505782d..16413ad3c7 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -52,6 +52,10 @@ GID='1000' # PERPLEXITY_API_KEY='my-perplexity-key' # PERPLEXITY_MODEL_PREF='codellama-34b-instruct' +# LLM_PROVIDER='openrouter' +# OPENROUTER_API_KEY='my-openrouter-key' +# OPENROUTER_MODEL_PREF='openrouter/auto' + # LLM_PROVIDER='huggingface' # HUGGING_FACE_LLM_ENDPOINT=https://uuid-here.us-east-1.aws.endpoints.huggingface.cloud # HUGGING_FACE_LLM_API_KEY=hf_xxxxxx diff --git a/frontend/src/components/LLMSelection/OpenRouterOptions/index.jsx b/frontend/src/components/LLMSelection/OpenRouterOptions/index.jsx new file mode 100644 index 0000000000..aa4ccdb2eb --- /dev/null +++ b/frontend/src/components/LLMSelection/OpenRouterOptions/index.jsx @@ -0,0 +1,97 @@ +import System from "@/models/system"; +import { useState, useEffect } from "react"; + +export default function OpenRouterOptions({ settings }) { + return ( +
+
+ + +
+ +
+ ); +} + +function OpenRouterModelSelection({ settings }) { + const [groupedModels, setGroupedModels] = useState({}); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + setLoading(true); + const { models } = await System.customModels("openrouter"); + if (models?.length > 0) { + const modelsByOrganization = models.reduce((acc, model) => { + acc[model.organization] = acc[model.organization] || []; + acc[model.organization].push(model); + return acc; + }, {}); + + setGroupedModels(modelsByOrganization); + } + + setLoading(false); + } + findCustomModels(); + }, []); + + if (loading || Object.keys(groupedModels).length === 0) { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/components/LLMSelection/TogetherAiOptions/index.jsx b/frontend/src/components/LLMSelection/TogetherAiOptions/index.jsx index e526b3afec..66ba1715e1 100644 --- a/frontend/src/components/LLMSelection/TogetherAiOptions/index.jsx +++ b/frontend/src/components/LLMSelection/TogetherAiOptions/index.jsx @@ -76,19 +76,21 @@ function TogetherAiModelSelection({ settings }) { required={true} className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" > - {Object.entries(groupedModels).map(([organization, models]) => ( - - {models.map((model) => ( - - ))} - - ))} + {Object.keys(groupedModels) + .sort() + .map((organization) => ( + + {groupedModels[organization].map((model) => ( + + ))} + + ))} ); diff --git a/frontend/src/media/llmprovider/openrouter.jpeg b/frontend/src/media/llmprovider/openrouter.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ce272ac81e15a41ebc3b2724085275a103bb0837 GIT binary patch literal 6366 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 GhyDw4i6SZh literal 0 HcmV?d00001 diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx index 8c51e559e1..579c3903b7 100644 --- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx @@ -15,6 +15,7 @@ import TogetherAILogo from "@/media/llmprovider/togetherai.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; import HuggingFaceLogo from "@/media/llmprovider/huggingface.png"; import PerplexityLogo from "@/media/llmprovider/perplexity.png"; +import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg"; import PreLoader from "@/components/Preloader"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; import AzureAiOptions from "@/components/LLMSelection/AzureAiOptions"; @@ -31,6 +32,7 @@ import HuggingFaceOptions from "@/components/LLMSelection/HuggingFaceOptions"; import LLMItem from "@/components/LLMSelection/LLMItem"; import { MagnifyingGlass } from "@phosphor-icons/react"; import PerplexityOptions from "@/components/LLMSelection/PerplexityOptions"; +import OpenRouterOptions from "@/components/LLMSelection/OpenRouterOptions"; export default function GeneralLLMPreference() { const [saving, setSaving] = useState(false); @@ -164,6 +166,13 @@ export default function GeneralLLMPreference() { description: "Run powerful and internet-connected models hosted by Perplexity AI.", }, + { + name: "OpenRouter", + value: "openrouter", + logo: OpenRouterLogo, + options: , + description: "A unified interface for LLMs.", + }, { name: "Native", value: "native", diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index f9c4c4169e..51dc73004e 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -12,6 +12,7 @@ import LocalAiLogo from "@/media/llmprovider/localai.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; import HuggingFaceLogo from "@/media/llmprovider/huggingface.png"; import PerplexityLogo from "@/media/llmprovider/perplexity.png"; +import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg"; import ZillizLogo from "@/media/vectordbs/zilliz.png"; import AstraDBLogo from "@/media/vectordbs/astraDB.png"; import ChromaLogo from "@/media/vectordbs/chroma.png"; @@ -118,6 +119,14 @@ const LLM_SELECTION_PRIVACY = { ], logo: PerplexityLogo, }, + openrouter: { + name: "OpenRouter", + description: [ + "Your chats will not be used for training", + "Your prompts and document text used in response creation are visible to OpenRouter", + ], + logo: OpenRouterLogo, + }, }; 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 296a28d9e1..df94652a85 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -12,6 +12,7 @@ import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; import HuggingFaceLogo from "@/media/llmprovider/huggingface.png"; import PerplexityLogo from "@/media/llmprovider/perplexity.png"; +import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; import AzureAiOptions from "@/components/LLMSelection/AzureAiOptions"; import AnthropicAiOptions from "@/components/LLMSelection/AnthropicAiOptions"; @@ -29,6 +30,7 @@ import System from "@/models/system"; import paths from "@/utils/paths"; import showToast from "@/utils/toast"; import { useNavigate } from "react-router-dom"; +import OpenRouterOptions from "@/components/LLMSelection/OpenRouterOptions"; const TITLE = "LLM Preference"; const DESCRIPTION = @@ -138,6 +140,13 @@ export default function LLMPreference({ description: "Run powerful and internet-connected models hosted by Perplexity AI.", }, + { + name: "OpenRouter", + value: "openrouter", + logo: OpenRouterLogo, + options: , + description: "A unified interface for LLMs.", + }, { name: "Native", value: "native", diff --git a/server/.env.example b/server/.env.example index 863486ad42..bed943925a 100644 --- a/server/.env.example +++ b/server/.env.example @@ -45,6 +45,10 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea # PERPLEXITY_API_KEY='my-perplexity-key' # PERPLEXITY_MODEL_PREF='codellama-34b-instruct' +# LLM_PROVIDER='openrouter' +# OPENROUTER_API_KEY='my-openrouter-key' +# OPENROUTER_MODEL_PREF='openrouter/auto' + # LLM_PROVIDER='mistral' # MISTRAL_API_KEY='example-mistral-ai-api-key' # MISTRAL_MODEL_PREF='mistral-tiny' diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 4154482821..31d5c59a8d 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -188,6 +188,18 @@ const SystemSettings = { PerplexityApiKey: !!process.env.PERPLEXITY_API_KEY, PerplexityModelPref: process.env.PERPLEXITY_MODEL_PREF, + // For embedding credentials when ollama is selected. + OpenAiKey: !!process.env.OPEN_AI_KEY, + AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY, + AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF, + } + : {}), + ...(llmProvider === "openrouter" + ? { + OpenRouterApiKey: !!process.env.OPENROUTER_API_KEY, + OpenRouterModelPref: process.env.OPENROUTER_MODEL_PREF, + // For embedding credentials when ollama is selected. OpenAiKey: !!process.env.OPEN_AI_KEY, AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT, diff --git a/server/utils/AiProviders/openRouter/index.js b/server/utils/AiProviders/openRouter/index.js new file mode 100644 index 0000000000..38a6f9f09a --- /dev/null +++ b/server/utils/AiProviders/openRouter/index.js @@ -0,0 +1,334 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { chatPrompt } = require("../../chats"); +const { v4: uuidv4 } = require("uuid"); +const { writeResponseChunk } = require("../../helpers/chat/responses"); + +function openRouterModels() { + const { MODELS } = require("./models.js"); + return MODELS || {}; +} + +class OpenRouterLLM { + constructor(embedder = null, modelPreference = null) { + const { Configuration, OpenAIApi } = require("openai"); + if (!process.env.OPENROUTER_API_KEY) + throw new Error("No OpenRouter API key was set."); + + const config = new Configuration({ + basePath: "https://openrouter.ai/api/v1", + apiKey: process.env.OPENROUTER_API_KEY, + baseOptions: { + headers: { + "HTTP-Referer": "https://useanything.com", + "X-Title": "AnythingLLM", + }, + }, + }); + this.openai = new OpenAIApi(config); + this.model = + modelPreference || process.env.OPENROUTER_MODEL_PREF || "openrouter/auto"; + this.limits = { + history: this.promptWindowLimit() * 0.15, + system: this.promptWindowLimit() * 0.15, + user: this.promptWindowLimit() * 0.7, + }; + + this.embedder = !embedder ? new NativeEmbedder() : embedder; + 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("") + ); + } + + allModelInformation() { + return openRouterModels(); + } + + streamingEnabled() { + return "streamChat" in this && "streamGetChatCompletion" in this; + } + + promptWindowLimit() { + const availableModels = this.allModelInformation(); + return availableModels[this.model]?.maxLength || 4096; + } + + async isValidChatCompletionModel(model = "") { + const availableModels = this.allModelInformation(); + return availableModels.hasOwnProperty(model); + } + + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [prompt, ...chatHistory, { role: "user", content: userPrompt }]; + } + + async isSafe(_input = "") { + // Not implemented so must be stubbed + return { safe: true, reasons: [] }; + } + + async sendChat(chatHistory = [], prompt, workspace = {}, rawHistory = []) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `OpenRouter chat: ${this.model} is not valid for chat completion!` + ); + + const textResponse = await this.openai + .createChatCompletion({ + model: this.model, + temperature: Number(workspace?.openAiTemp ?? this.defaultTemp), + n: 1, + messages: await this.compressMessages( + { + systemPrompt: chatPrompt(workspace), + userPrompt: prompt, + chatHistory, + }, + rawHistory + ), + }) + .then((json) => { + const res = json.data; + if (!res.hasOwnProperty("choices")) + throw new Error("OpenRouter chat: No results!"); + if (res.choices.length === 0) + throw new Error("OpenRouter chat: No results length!"); + return res.choices[0].message.content; + }) + .catch((error) => { + throw new Error( + `OpenRouter::createChatCompletion failed with: ${error.message}` + ); + }); + + return textResponse; + } + + async streamChat(chatHistory = [], prompt, workspace = {}, rawHistory = []) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `OpenRouter chat: ${this.model} is not valid for chat completion!` + ); + + const streamRequest = await this.openai.createChatCompletion( + { + model: this.model, + stream: true, + temperature: Number(workspace?.openAiTemp ?? this.defaultTemp), + n: 1, + messages: await this.compressMessages( + { + systemPrompt: chatPrompt(workspace), + userPrompt: prompt, + chatHistory, + }, + rawHistory + ), + }, + { responseType: "stream" } + ); + return streamRequest; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `OpenRouter chat: ${this.model} is not valid for chat completion!` + ); + + const { data } = await this.openai + .createChatCompletion({ + model: this.model, + messages, + temperature, + }) + .catch((e) => { + throw new Error(e.response.data.error.message); + }); + + if (!data.hasOwnProperty("choices")) return null; + return data.choices[0].message.content; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `OpenRouter chat: ${this.model} is not valid for chat completion!` + ); + + const streamRequest = await this.openai.createChatCompletion( + { + model: this.model, + stream: true, + messages, + temperature, + }, + { responseType: "stream" } + ); + return streamRequest; + } + + handleStream(response, stream, responseProps) { + const timeoutThresholdMs = 500; + const { uuid = uuidv4(), sources = [] } = responseProps; + + return new Promise((resolve) => { + let fullText = ""; + let chunk = ""; + let lastChunkTime = null; // null when first token is still not received. + + // NOTICE: Not all OpenRouter models will return a stop reason + // which keeps the connection open and so the model never finalizes the stream + // like the traditional OpenAI response schema does. So in the case the response stream + // never reaches a formal close state we maintain an interval timer that if we go >=timeoutThresholdMs with + // no new chunks then we kill the stream and assume it to be complete. OpenRouter is quite fast + // so this threshold should permit most responses, but we can adjust `timeoutThresholdMs` if + // we find it is too aggressive. + const timeoutCheck = setInterval(() => { + if (lastChunkTime === null) return; + + const now = Number(new Date()); + const diffMs = now - lastChunkTime; + if (diffMs >= timeoutThresholdMs) { + console.log( + `OpenRouter stream did not self-close and has been stale for >${timeoutThresholdMs}ms. Closing response stream.` + ); + writeResponseChunk(response, { + uuid, + sources, + type: "textResponseChunk", + textResponse: "", + close: true, + error: false, + }); + clearInterval(timeoutCheck); + resolve(fullText); + } + }, 500); + + stream.data.on("data", (data) => { + const lines = data + ?.toString() + ?.split("\n") + .filter((line) => line.trim() !== ""); + + for (const line of lines) { + let validJSON = false; + const message = chunk + line.replace(/^data: /, ""); + + // JSON chunk is incomplete and has not ended yet + // so we need to stitch it together. You would think JSON + // chunks would only come complete - but they don't! + try { + JSON.parse(message); + validJSON = true; + } catch {} + + if (!validJSON) { + // It can be possible that the chunk decoding is running away + // and the message chunk fails to append due to string length. + // In this case abort the chunk and reset so we can continue. + // ref: https://github.com/Mintplex-Labs/anything-llm/issues/416 + try { + chunk += message; + } catch (e) { + console.error(`Chunk appending error`, e); + chunk = ""; + } + continue; + } else { + chunk = ""; + } + + if (message == "[DONE]") { + lastChunkTime = Number(new Date()); + writeResponseChunk(response, { + uuid, + sources, + type: "textResponseChunk", + textResponse: "", + close: true, + error: false, + }); + clearInterval(timeoutCheck); + resolve(fullText); + } else { + let finishReason = null; + let token = ""; + try { + const json = JSON.parse(message); + token = json?.choices?.[0]?.delta?.content; + finishReason = json?.choices?.[0]?.finish_reason || null; + } catch { + continue; + } + + if (token) { + fullText += token; + lastChunkTime = Number(new Date()); + writeResponseChunk(response, { + uuid, + sources: [], + type: "textResponseChunk", + textResponse: token, + close: false, + error: false, + }); + } + + if (finishReason !== null) { + lastChunkTime = Number(new Date()); + writeResponseChunk(response, { + uuid, + sources, + type: "textResponseChunk", + textResponse: "", + close: true, + error: false, + }); + clearInterval(timeoutCheck); + resolve(fullText); + } + } + } + }); + }); + } + + // 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 = { + OpenRouterLLM, + openRouterModels, +}; diff --git a/server/utils/AiProviders/openRouter/models.js b/server/utils/AiProviders/openRouter/models.js new file mode 100644 index 0000000000..c920b88a41 --- /dev/null +++ b/server/utils/AiProviders/openRouter/models.js @@ -0,0 +1,622 @@ +const MODELS = { + "nousresearch/nous-capybara-34b": { + id: "nousresearch/nous-capybara-34b", + name: "Nous: Capybara 34B", + organization: "Nousresearch", + maxLength: 32768, + }, + "openrouter/auto": { + id: "openrouter/auto", + name: "Auto (best for prompt)", + organization: "Openrouter", + maxLength: 128000, + }, + "nousresearch/nous-capybara-7b:free": { + id: "nousresearch/nous-capybara-7b:free", + name: "Nous: Capybara 7B (free)", + organization: "Nousresearch", + maxLength: 4096, + }, + "mistralai/mistral-7b-instruct:free": { + id: "mistralai/mistral-7b-instruct:free", + name: "Mistral 7B Instruct (free)", + organization: "Mistralai", + maxLength: 8192, + }, + "gryphe/mythomist-7b:free": { + id: "gryphe/mythomist-7b:free", + name: "MythoMist 7B (free)", + organization: "Gryphe", + maxLength: 32768, + }, + "undi95/toppy-m-7b:free": { + id: "undi95/toppy-m-7b:free", + name: "Toppy M 7B (free)", + organization: "Undi95", + maxLength: 4096, + }, + "openrouter/cinematika-7b:free": { + id: "openrouter/cinematika-7b:free", + name: "Cinematika 7B (alpha) (free)", + organization: "Openrouter", + maxLength: 8000, + }, + "google/gemma-7b-it:free": { + id: "google/gemma-7b-it:free", + name: "Google: Gemma 7B (free)", + organization: "Google", + maxLength: 8000, + }, + "jondurbin/bagel-34b": { + id: "jondurbin/bagel-34b", + name: "Bagel 34B v0.2", + organization: "Jondurbin", + maxLength: 8000, + }, + "jebcarter/psyfighter-13b": { + id: "jebcarter/psyfighter-13b", + name: "Psyfighter 13B", + organization: "Jebcarter", + maxLength: 4096, + }, + "koboldai/psyfighter-13b-2": { + id: "koboldai/psyfighter-13b-2", + name: "Psyfighter v2 13B", + organization: "Koboldai", + maxLength: 4096, + }, + "neversleep/noromaid-mixtral-8x7b-instruct": { + id: "neversleep/noromaid-mixtral-8x7b-instruct", + name: "Noromaid Mixtral 8x7B Instruct", + organization: "Neversleep", + maxLength: 8000, + }, + "nousresearch/nous-hermes-llama2-13b": { + id: "nousresearch/nous-hermes-llama2-13b", + name: "Nous: Hermes 13B", + organization: "Nousresearch", + maxLength: 4096, + }, + "meta-llama/codellama-34b-instruct": { + id: "meta-llama/codellama-34b-instruct", + name: "Meta: CodeLlama 34B Instruct", + organization: "Meta-llama", + maxLength: 8192, + }, + "phind/phind-codellama-34b": { + id: "phind/phind-codellama-34b", + name: "Phind: CodeLlama 34B v2", + organization: "Phind", + maxLength: 4096, + }, + "intel/neural-chat-7b": { + id: "intel/neural-chat-7b", + name: "Neural Chat 7B v3.1", + organization: "Intel", + maxLength: 4096, + }, + "mistralai/mixtral-8x7b-instruct": { + id: "mistralai/mixtral-8x7b-instruct", + name: "Mistral: Mixtral 8x7B Instruct", + organization: "Mistralai", + maxLength: 32768, + }, + "nousresearch/nous-hermes-2-mixtral-8x7b-dpo": { + id: "nousresearch/nous-hermes-2-mixtral-8x7b-dpo", + name: "Nous: Hermes 2 Mixtral 8x7B DPO", + organization: "Nousresearch", + maxLength: 32000, + }, + "nousresearch/nous-hermes-2-mixtral-8x7b-sft": { + id: "nousresearch/nous-hermes-2-mixtral-8x7b-sft", + name: "Nous: Hermes 2 Mixtral 8x7B SFT", + organization: "Nousresearch", + maxLength: 32000, + }, + "haotian-liu/llava-13b": { + id: "haotian-liu/llava-13b", + name: "Llava 13B", + organization: "Haotian-liu", + maxLength: 2048, + }, + "nousresearch/nous-hermes-2-vision-7b": { + id: "nousresearch/nous-hermes-2-vision-7b", + name: "Nous: Hermes 2 Vision 7B (alpha)", + organization: "Nousresearch", + maxLength: 4096, + }, + "meta-llama/llama-2-13b-chat": { + id: "meta-llama/llama-2-13b-chat", + name: "Meta: Llama v2 13B Chat", + organization: "Meta-llama", + maxLength: 4096, + }, + "migtissera/synthia-70b": { + id: "migtissera/synthia-70b", + name: "Synthia 70B", + organization: "Migtissera", + maxLength: 8192, + }, + "pygmalionai/mythalion-13b": { + id: "pygmalionai/mythalion-13b", + name: "Pygmalion: Mythalion 13B", + organization: "Pygmalionai", + maxLength: 8192, + }, + "undi95/remm-slerp-l2-13b-6k": { + id: "undi95/remm-slerp-l2-13b-6k", + name: "ReMM SLERP 13B 6k", + organization: "Undi95", + maxLength: 6144, + }, + "gryphe/mythomax-l2-13b": { + id: "gryphe/mythomax-l2-13b", + name: "MythoMax 13B", + organization: "Gryphe", + maxLength: 4096, + }, + "xwin-lm/xwin-lm-70b": { + id: "xwin-lm/xwin-lm-70b", + name: "Xwin 70B", + organization: "Xwin-lm", + maxLength: 8192, + }, + "gryphe/mythomax-l2-13b-8k": { + id: "gryphe/mythomax-l2-13b-8k", + name: "MythoMax 13B 8k", + organization: "Gryphe", + maxLength: 8192, + }, + "alpindale/goliath-120b": { + id: "alpindale/goliath-120b", + name: "Goliath 120B", + organization: "Alpindale", + maxLength: 6144, + }, + "neversleep/noromaid-20b": { + id: "neversleep/noromaid-20b", + name: "Noromaid 20B", + organization: "Neversleep", + maxLength: 8192, + }, + "gryphe/mythomist-7b": { + id: "gryphe/mythomist-7b", + name: "MythoMist 7B", + organization: "Gryphe", + maxLength: 32768, + }, + "mancer/weaver": { + id: "mancer/weaver", + name: "Mancer: Weaver (alpha)", + organization: "Mancer", + maxLength: 8000, + }, + "nousresearch/nous-hermes-llama2-70b": { + id: "nousresearch/nous-hermes-llama2-70b", + name: "Nous: Hermes 70B", + organization: "Nousresearch", + maxLength: 4096, + }, + "nousresearch/nous-capybara-7b": { + id: "nousresearch/nous-capybara-7b", + name: "Nous: Capybara 7B", + organization: "Nousresearch", + maxLength: 4096, + }, + "codellama/codellama-70b-instruct": { + id: "codellama/codellama-70b-instruct", + name: "Meta: CodeLlama 70B Instruct", + organization: "Codellama", + maxLength: 2048, + }, + "teknium/openhermes-2-mistral-7b": { + id: "teknium/openhermes-2-mistral-7b", + name: "OpenHermes 2 Mistral 7B", + organization: "Teknium", + maxLength: 4096, + }, + "teknium/openhermes-2.5-mistral-7b": { + id: "teknium/openhermes-2.5-mistral-7b", + name: "OpenHermes 2.5 Mistral 7B", + organization: "Teknium", + maxLength: 4096, + }, + "undi95/remm-slerp-l2-13b": { + id: "undi95/remm-slerp-l2-13b", + name: "ReMM SLERP 13B", + organization: "Undi95", + maxLength: 4096, + }, + "undi95/toppy-m-7b": { + id: "undi95/toppy-m-7b", + name: "Toppy M 7B", + organization: "Undi95", + maxLength: 4096, + }, + "openrouter/cinematika-7b": { + id: "openrouter/cinematika-7b", + name: "Cinematika 7B (alpha)", + organization: "Openrouter", + maxLength: 8000, + }, + "01-ai/yi-34b-chat": { + id: "01-ai/yi-34b-chat", + name: "Yi 34B Chat", + organization: "01-ai", + maxLength: 4096, + }, + "01-ai/yi-34b": { + id: "01-ai/yi-34b", + name: "Yi 34B (base)", + organization: "01-ai", + maxLength: 4096, + }, + "01-ai/yi-6b": { + id: "01-ai/yi-6b", + name: "Yi 6B (base)", + organization: "01-ai", + maxLength: 4096, + }, + "togethercomputer/stripedhyena-nous-7b": { + id: "togethercomputer/stripedhyena-nous-7b", + name: "StripedHyena Nous 7B", + organization: "Togethercomputer", + maxLength: 32768, + }, + "togethercomputer/stripedhyena-hessian-7b": { + id: "togethercomputer/stripedhyena-hessian-7b", + name: "StripedHyena Hessian 7B (base)", + organization: "Togethercomputer", + maxLength: 32768, + }, + "mistralai/mixtral-8x7b": { + id: "mistralai/mixtral-8x7b", + name: "Mistral: Mixtral 8x7B (base)", + organization: "Mistralai", + maxLength: 32768, + }, + "nousresearch/nous-hermes-yi-34b": { + id: "nousresearch/nous-hermes-yi-34b", + name: "Nous: Hermes 2 Yi 34B", + organization: "Nousresearch", + maxLength: 4096, + }, + "nousresearch/nous-hermes-2-mistral-7b-dpo": { + id: "nousresearch/nous-hermes-2-mistral-7b-dpo", + name: "Nous: Hermes 2 Mistral 7B DPO", + organization: "Nousresearch", + maxLength: 8192, + }, + "open-orca/mistral-7b-openorca": { + id: "open-orca/mistral-7b-openorca", + name: "Mistral OpenOrca 7B", + organization: "Open-orca", + maxLength: 8192, + }, + "huggingfaceh4/zephyr-7b-beta": { + id: "huggingfaceh4/zephyr-7b-beta", + name: "Hugging Face: Zephyr 7B", + organization: "Huggingfaceh4", + maxLength: 4096, + }, + "openai/gpt-3.5-turbo": { + id: "openai/gpt-3.5-turbo", + name: "OpenAI: GPT-3.5 Turbo", + organization: "Openai", + maxLength: 4095, + }, + "openai/gpt-3.5-turbo-0125": { + id: "openai/gpt-3.5-turbo-0125", + name: "OpenAI: GPT-3.5 Turbo 16k", + organization: "Openai", + maxLength: 16385, + }, + "openai/gpt-3.5-turbo-1106": { + id: "openai/gpt-3.5-turbo-1106", + name: "OpenAI: GPT-3.5 Turbo 16k (older v1106)", + organization: "Openai", + maxLength: 16385, + }, + "openai/gpt-3.5-turbo-0613": { + id: "openai/gpt-3.5-turbo-0613", + name: "OpenAI: GPT-3.5 Turbo (older v0613)", + organization: "Openai", + maxLength: 4095, + }, + "openai/gpt-3.5-turbo-0301": { + id: "openai/gpt-3.5-turbo-0301", + name: "OpenAI: GPT-3.5 Turbo (older v0301)", + organization: "Openai", + maxLength: 4095, + }, + "openai/gpt-3.5-turbo-16k": { + id: "openai/gpt-3.5-turbo-16k", + name: "OpenAI: GPT-3.5 Turbo 16k", + organization: "Openai", + maxLength: 16385, + }, + "openai/gpt-4-turbo-preview": { + id: "openai/gpt-4-turbo-preview", + name: "OpenAI: GPT-4 Turbo (preview)", + organization: "Openai", + maxLength: 128000, + }, + "openai/gpt-4-1106-preview": { + id: "openai/gpt-4-1106-preview", + name: "OpenAI: GPT-4 Turbo (older v1106)", + organization: "Openai", + maxLength: 128000, + }, + "openai/gpt-4": { + id: "openai/gpt-4", + name: "OpenAI: GPT-4", + organization: "Openai", + maxLength: 8191, + }, + "openai/gpt-4-0314": { + id: "openai/gpt-4-0314", + name: "OpenAI: GPT-4 (older v0314)", + organization: "Openai", + maxLength: 8191, + }, + "openai/gpt-4-32k": { + id: "openai/gpt-4-32k", + name: "OpenAI: GPT-4 32k", + organization: "Openai", + maxLength: 32767, + }, + "openai/gpt-4-32k-0314": { + id: "openai/gpt-4-32k-0314", + name: "OpenAI: GPT-4 32k (older v0314)", + organization: "Openai", + maxLength: 32767, + }, + "openai/gpt-4-vision-preview": { + id: "openai/gpt-4-vision-preview", + name: "OpenAI: GPT-4 Vision (preview)", + organization: "Openai", + maxLength: 128000, + }, + "openai/gpt-3.5-turbo-instruct": { + id: "openai/gpt-3.5-turbo-instruct", + name: "OpenAI: GPT-3.5 Turbo Instruct", + organization: "Openai", + maxLength: 4095, + }, + "google/palm-2-chat-bison": { + id: "google/palm-2-chat-bison", + name: "Google: PaLM 2 Chat", + organization: "Google", + maxLength: 36864, + }, + "google/palm-2-codechat-bison": { + id: "google/palm-2-codechat-bison", + name: "Google: PaLM 2 Code Chat", + organization: "Google", + maxLength: 28672, + }, + "google/palm-2-chat-bison-32k": { + id: "google/palm-2-chat-bison-32k", + name: "Google: PaLM 2 Chat 32k", + organization: "Google", + maxLength: 131072, + }, + "google/palm-2-codechat-bison-32k": { + id: "google/palm-2-codechat-bison-32k", + name: "Google: PaLM 2 Code Chat 32k", + organization: "Google", + maxLength: 131072, + }, + "google/gemini-pro": { + id: "google/gemini-pro", + name: "Google: Gemini Pro (preview)", + organization: "Google", + maxLength: 131040, + }, + "google/gemini-pro-vision": { + id: "google/gemini-pro-vision", + name: "Google: Gemini Pro Vision (preview)", + organization: "Google", + maxLength: 65536, + }, + "perplexity/pplx-70b-online": { + id: "perplexity/pplx-70b-online", + name: "Perplexity: PPLX 70B Online", + organization: "Perplexity", + maxLength: 4096, + }, + "perplexity/pplx-7b-online": { + id: "perplexity/pplx-7b-online", + name: "Perplexity: PPLX 7B Online", + organization: "Perplexity", + maxLength: 4096, + }, + "perplexity/pplx-7b-chat": { + id: "perplexity/pplx-7b-chat", + name: "Perplexity: PPLX 7B Chat", + organization: "Perplexity", + maxLength: 8192, + }, + "perplexity/pplx-70b-chat": { + id: "perplexity/pplx-70b-chat", + name: "Perplexity: PPLX 70B Chat", + organization: "Perplexity", + maxLength: 4096, + }, + "meta-llama/llama-2-70b-chat": { + id: "meta-llama/llama-2-70b-chat", + name: "Meta: Llama v2 70B Chat", + organization: "Meta-llama", + maxLength: 4096, + }, + "jondurbin/airoboros-l2-70b": { + id: "jondurbin/airoboros-l2-70b", + name: "Airoboros 70B", + organization: "Jondurbin", + maxLength: 4096, + }, + "austism/chronos-hermes-13b": { + id: "austism/chronos-hermes-13b", + name: "Chronos Hermes 13B v2", + organization: "Austism", + maxLength: 4096, + }, + "mistralai/mistral-7b-instruct": { + id: "mistralai/mistral-7b-instruct", + name: "Mistral 7B Instruct", + organization: "Mistralai", + maxLength: 8192, + }, + "openchat/openchat-7b": { + id: "openchat/openchat-7b", + name: "OpenChat 3.5", + organization: "Openchat", + maxLength: 8192, + }, + "lizpreciatior/lzlv-70b-fp16-hf": { + id: "lizpreciatior/lzlv-70b-fp16-hf", + name: "lzlv 70B", + organization: "Lizpreciatior", + maxLength: 4096, + }, + "cognitivecomputations/dolphin-mixtral-8x7b": { + id: "cognitivecomputations/dolphin-mixtral-8x7b", + name: "Dolphin 2.6 Mixtral 8x7B 🐬", + organization: "Cognitivecomputations", + maxLength: 32000, + }, + "rwkv/rwkv-5-world-3b": { + id: "rwkv/rwkv-5-world-3b", + name: "RWKV v5 World 3B", + organization: "Rwkv", + maxLength: 10000, + }, + "recursal/rwkv-5-3b-ai-town": { + id: "recursal/rwkv-5-3b-ai-town", + name: "RWKV v5 3B AI Town", + organization: "Recursal", + maxLength: 10000, + }, + "recursal/eagle-7b": { + id: "recursal/eagle-7b", + name: "RWKV v5: Eagle 7B", + organization: "Recursal", + maxLength: 10000, + }, + "google/gemma-7b-it": { + id: "google/gemma-7b-it", + name: "Google: Gemma 7B", + organization: "Google", + maxLength: 8000, + }, + "anthropic/claude-2": { + id: "anthropic/claude-2", + name: "Anthropic: Claude v2", + organization: "Anthropic", + maxLength: 200000, + }, + "anthropic/claude-2.1": { + id: "anthropic/claude-2.1", + name: "Anthropic: Claude v2.1", + organization: "Anthropic", + maxLength: 200000, + }, + "anthropic/claude-2.0": { + id: "anthropic/claude-2.0", + name: "Anthropic: Claude v2.0", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-instant-1": { + id: "anthropic/claude-instant-1", + name: "Anthropic: Claude Instant v1", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-instant-1.2": { + id: "anthropic/claude-instant-1.2", + name: "Anthropic: Claude Instant v1.2", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-1": { + id: "anthropic/claude-1", + name: "Anthropic: Claude v1", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-1.2": { + id: "anthropic/claude-1.2", + name: "Anthropic: Claude (older v1)", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-instant-1.0": { + id: "anthropic/claude-instant-1.0", + name: "Anthropic: Claude Instant (older v1)", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-instant-1.1": { + id: "anthropic/claude-instant-1.1", + name: "Anthropic: Claude Instant (older v1.1)", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-2:beta": { + id: "anthropic/claude-2:beta", + name: "Anthropic: Claude v2 (experimental)", + organization: "Anthropic", + maxLength: 200000, + }, + "anthropic/claude-2.1:beta": { + id: "anthropic/claude-2.1:beta", + name: "Anthropic: Claude v2.1 (experimental)", + organization: "Anthropic", + maxLength: 200000, + }, + "anthropic/claude-2.0:beta": { + id: "anthropic/claude-2.0:beta", + name: "Anthropic: Claude v2.0 (experimental)", + organization: "Anthropic", + maxLength: 100000, + }, + "anthropic/claude-instant-1:beta": { + id: "anthropic/claude-instant-1:beta", + name: "Anthropic: Claude Instant v1 (experimental)", + organization: "Anthropic", + maxLength: 100000, + }, + "huggingfaceh4/zephyr-7b-beta:free": { + id: "huggingfaceh4/zephyr-7b-beta:free", + name: "Hugging Face: Zephyr 7B (free)", + organization: "Huggingfaceh4", + maxLength: 4096, + }, + "openchat/openchat-7b:free": { + id: "openchat/openchat-7b:free", + name: "OpenChat 3.5 (free)", + organization: "Openchat", + maxLength: 8192, + }, + "mistralai/mistral-tiny": { + id: "mistralai/mistral-tiny", + name: "Mistral: Tiny", + organization: "Mistralai", + maxLength: 32000, + }, + "mistralai/mistral-small": { + id: "mistralai/mistral-small", + name: "Mistral: Small", + organization: "Mistralai", + maxLength: 32000, + }, + "mistralai/mistral-medium": { + id: "mistralai/mistral-medium", + name: "Mistral: Medium", + organization: "Mistralai", + maxLength: 32000, + }, +}; + +module.exports.MODELS = MODELS; diff --git a/server/utils/AiProviders/openRouter/scripts/.gitignore b/server/utils/AiProviders/openRouter/scripts/.gitignore new file mode 100644 index 0000000000..94a2dd146a --- /dev/null +++ b/server/utils/AiProviders/openRouter/scripts/.gitignore @@ -0,0 +1 @@ +*.json \ No newline at end of file diff --git a/server/utils/AiProviders/openRouter/scripts/parse.mjs b/server/utils/AiProviders/openRouter/scripts/parse.mjs new file mode 100644 index 0000000000..fb3b562b5c --- /dev/null +++ b/server/utils/AiProviders/openRouter/scripts/parse.mjs @@ -0,0 +1,37 @@ +// OpenRouter has lots of models we can use so we use this script +// to cache all the models. We can see the list of all the models +// here: https://openrouter.ai/docs#models + +// To run, cd into this directory and run `node parse.mjs` +// copy outputs into the export in ../models.js + +// Update the date below if you run this again because OpenRouter added new models. +// Last Collected: Feb 23, 2024 + +import fs from "fs"; + +async function parseChatModels() { + const models = {}; + const response = await fetch("https://openrouter.ai/api/v1/models"); + const data = await response.json(); + data.data.forEach((model) => { + models[model.id] = { + id: model.id, + name: model.name, + // capitalize first letter + organization: + model.id.split("/")[0].charAt(0).toUpperCase() + + model.id.split("/")[0].slice(1), + maxLength: model.context_length, + }; + }); + + fs.writeFileSync( + "chat_models.json", + JSON.stringify(models, null, 2), + "utf-8" + ); + return models; +} + +parseChatModels(); diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index 8f8ca06571..f434ac0786 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -1,3 +1,4 @@ +const { openRouterModels } = require("../AiProviders/openRouter"); const { perplexityModels } = require("../AiProviders/perplexity"); const { togetherAiModels } = require("../AiProviders/togetherAi"); const SUPPORT_CUSTOM_MODELS = [ @@ -8,6 +9,7 @@ const SUPPORT_CUSTOM_MODELS = [ "togetherai", "mistral", "perplexity", + "openrouter", ]; async function getCustomModels(provider = "", apiKey = null, basePath = null) { @@ -29,6 +31,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return nativeLLMModels(); case "perplexity": return await getPerplexityModels(); + case "openrouter": + return await getOpenRouterModels(); default: return { models: [], error: "Invalid provider for custom models" }; } @@ -138,6 +142,21 @@ async function getPerplexityModels() { return { models, error: null }; } +async function getOpenRouterModels() { + const knownModels = await openRouterModels(); + if (!Object.keys(knownModels).length === 0) + return { models: [], error: null }; + + const models = Object.values(knownModels).map((model) => { + return { + id: model.id, + organization: model.organization, + name: model.name, + }; + }); + return { models, error: null }; +} + async function getMistralModels(apiKey = null) { const { Configuration, OpenAIApi } = require("openai"); const config = new Configuration({ diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index 818d92dbce..8bda716aa6 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -61,6 +61,9 @@ function getLLMProvider(modelPreference = null) { case "perplexity": const { PerplexityLLM } = require("../AiProviders/perplexity"); return new PerplexityLLM(embedder, modelPreference); + case "openrouter": + const { OpenRouterLLM } = require("../AiProviders/openRouter"); + return new OpenRouterLLM(embedder, modelPreference); case "mistral": const { MistralLLM } = require("../AiProviders/mistral"); return new MistralLLM(embedder, modelPreference); diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 5a384740bf..247e3ba486 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -249,6 +249,16 @@ const KEY_MAPPING = { checks: [isNotEmpty], }, + // OpenRouter Options + OpenRouterApiKey: { + envKey: "OPENROUTER_API_KEY", + checks: [isNotEmpty], + }, + OpenRouterModelPref: { + envKey: "OPENROUTER_MODEL_PREF", + checks: [isNotEmpty], + }, + // System Settings AuthToken: { envKey: "AUTH_TOKEN", @@ -325,6 +335,7 @@ function supportedLLM(input = "") { "mistral", "huggingface", "perplexity", + "openrouter", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; } From a385ea3d82ddf0c822455169b6ef840c42662c41 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Fri, 23 Feb 2024 17:33:16 -0800 Subject: [PATCH 05/16] CHORE: bump pplx model support (#791) bump pplx model support --- server/utils/AiProviders/perplexity/models.js | 38 ++++++++++++++----- .../perplexity/scripts/chat_models.txt | 26 +++++++------ .../AiProviders/perplexity/scripts/parse.mjs | 4 +- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/server/utils/AiProviders/perplexity/models.js b/server/utils/AiProviders/perplexity/models.js index 258cfeace4..95cd8eac79 100644 --- a/server/utils/AiProviders/perplexity/models.js +++ b/server/utils/AiProviders/perplexity/models.js @@ -1,4 +1,24 @@ const MODELS = { + "sonar-small-chat": { + id: "sonar-small-chat", + name: "sonar-small-chat", + maxLength: 16384, + }, + "sonar-small-online": { + id: "sonar-small-online", + name: "sonar-small-online", + maxLength: 12000, + }, + "sonar-medium-chat": { + id: "sonar-medium-chat", + name: "sonar-medium-chat", + maxLength: 16384, + }, + "sonar-medium-online": { + id: "sonar-medium-online", + name: "sonar-medium-online", + maxLength: 12000, + }, "codellama-34b-instruct": { id: "codellama-34b-instruct", name: "codellama-34b-instruct", @@ -17,32 +37,32 @@ const MODELS = { "mistral-7b-instruct": { id: "mistral-7b-instruct", name: "mistral-7b-instruct", - maxLength: 8192, + maxLength: 16384, }, "mixtral-8x7b-instruct": { id: "mixtral-8x7b-instruct", name: "mixtral-8x7b-instruct", - maxLength: 8192, + maxLength: 16384, }, "pplx-7b-chat": { id: "pplx-7b-chat", name: "pplx-7b-chat", - maxLength: 8192, - }, - "pplx-70b-chat": { - id: "pplx-70b-chat", - name: "pplx-70b-chat", - maxLength: 8192, + maxLength: 16384, }, "pplx-7b-online": { id: "pplx-7b-online", name: "pplx-7b-online", + maxLength: 12000, + }, + "pplx-70b-chat": { + id: "pplx-70b-chat", + name: "pplx-70b-chat", maxLength: 8192, }, "pplx-70b-online": { id: "pplx-70b-online", name: "pplx-70b-online", - maxLength: 8192, + maxLength: 4000, }, }; diff --git a/server/utils/AiProviders/perplexity/scripts/chat_models.txt b/server/utils/AiProviders/perplexity/scripts/chat_models.txt index 83f6d2a809..97ba9017a4 100644 --- a/server/utils/AiProviders/perplexity/scripts/chat_models.txt +++ b/server/utils/AiProviders/perplexity/scripts/chat_models.txt @@ -1,11 +1,15 @@ -| Model | Context Length | Model Type | -| :------------------------ | :------------- | :-------------- | -| `codellama-34b-instruct` | 16384 | Chat Completion | -| `codellama-70b-instruct` | 16384 | Chat Completion | -| `llama-2-70b-chat` | 4096 | Chat Completion | -| `mistral-7b-instruct` [2] | 8192 [1] | Chat Completion | -| `mixtral-8x7b-instruct` | 8192 [1] | Chat Completion | -| `pplx-7b-chat` | 8192 | Chat Completion | -| `pplx-70b-chat` | 8192 | Chat Completion | -| `pplx-7b-online` | 8192 | Chat Completion | -| `pplx-70b-online` | 8192 | Chat Completion | \ No newline at end of file +| Model | Parameter Count | Context Length | Model Type | +| :-------------------------- | :-------------- | :------------- | :-------------- | +| `sonar-small-chat` | 7B | 16384 | Chat Completion | +| `sonar-small-online` | 7B | 12000 | Chat Completion | +| `sonar-medium-chat` | 8x7B | 16384 | Chat Completion | +| `sonar-medium-online` | 8x7B | 12000 | Chat Completion | +| `codellama-34b-instruct`[3] | 34B | 16384 | Chat Completion | +| `codellama-70b-instruct` | 70B | 16384 | Chat Completion | +| `llama-2-70b-chat`[3] | 70B | 4096 | Chat Completion | +| `mistral-7b-instruct` [1] | 7B | 16384 | Chat Completion | +| `mixtral-8x7b-instruct` | 8x7B | 16384 | Chat Completion | +| `pplx-7b-chat`[2] [3] | 7B | 16384 | Chat Completion | +| `pplx-7b-online`[2] [3] | 7B | 12000 | Chat Completion | +| `pplx-70b-chat`[3] | 70B | 8192 | Chat Completion | +| `pplx-70b-online`[3] | 70B | 4000 | Chat Completion | \ No newline at end of file diff --git a/server/utils/AiProviders/perplexity/scripts/parse.mjs b/server/utils/AiProviders/perplexity/scripts/parse.mjs index 749a63dcea..d2064354af 100644 --- a/server/utils/AiProviders/perplexity/scripts/parse.mjs +++ b/server/utils/AiProviders/perplexity/scripts/parse.mjs @@ -8,7 +8,7 @@ // copy outputs into the export in ../models.js // Update the date below if you run this again because Perplexity added new models. -// Last Collected: Feb 22, 2024 +// Last Collected: Feb 23, 2024 import fs from "fs"; @@ -18,7 +18,7 @@ function parseChatModels() { const rows = tableString.split("\n").slice(2); rows.forEach((row) => { - let [model, contextLength] = row + let [model, _, contextLength] = row .split("|") .slice(1, -1) .map((text) => text.trim()); From 8fe283dc5688f028218839a0cc5960e1a2b7a500 Mon Sep 17 00:00:00 2001 From: sherifButt <90522472+sherifButt@users.noreply.github.com> Date: Sat, 24 Feb 2024 02:02:23 +0000 Subject: [PATCH 06/16] #765 - Enhanced chat styling (#786) * enhanced chat style and remove list restriction * [FEAT]: Enhanced chat styling #765 * small changes in CSS to prevent collisions * remove commented code * linting --------- Co-authored-by: timothycarambat --- .../ChatHistory/HistoricalMessage/index.jsx | 2 +- .../ChatHistory/PromptReply/index.jsx | 2 +- .../ChatContainer/ChatHistory/index.jsx | 2 +- frontend/src/index.css | 154 ++++++++++++++++++ frontend/src/utils/chat/markdown.js | 4 +- 5 files changed, 158 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx index f4c5c86a71..1a3f1a0db5 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx @@ -52,7 +52,7 @@ const HistoricalMessage = ({ ) : ( diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx index 5be8afc132..6af66ea653 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/index.jsx @@ -83,7 +83,7 @@ export default function ChatHistory({ history = [], workspace, sendCommand }) { return (
diff --git a/frontend/src/index.css b/frontend/src/index.css index 2c43798221..e2141d8deb 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -443,3 +443,157 @@ dialog::backdrop { .input-label { @apply text-[14px] font-bold text-white; } + +/** + * ============================================== + * Markdown Styles + * ============================================== + */ +.markdown, +.markdown > * { + font-weight: 400; +} + +.markdown h1 { + font-size: xx-large; + line-height: 1.7; + padding-left: 0.3rem; +} + +.markdown h2 { + line-height: 1.5; + font-size: x-large; + padding-left: 0.3rem; +} + +.markdown h3 { + line-height: 1.4; + font-size: large; + padding-left: 0.3rem; +} + +/* Table Styles */ + +.markdown table { + border-collapse: separate; +} + +.markdown th { + border-top: none; +} + +.markdown td:first-child, +.markdown th:first-child { + border-left: none; +} + +.markdown table { + width: 100%; + border-collapse: collapse; + color: #bdbdbe; + font-size: 13px; + margin: 30px 0px; + border-radius: 10px; + overflow: hidden; + font-weight: normal; +} + +.markdown table thead { + color: #fff; + text-transform: uppercase; + font-weight: bolder; +} + +.markdown hr { + border: 0; + border-top: 1px solid #cdcdcd40; + margin: 1rem 0; +} + +.markdown table th, +.markdown table td { + padding: 8px 15px; + border-bottom: 1px solid #cdcdcd2e; + text-align: left; +} + +.markdown table th { + padding: 14px 15px; +} + +@media (max-width: 600px) { + .markdown table th, + .markdown table td { + padding: 10px; + } +} + +/* List Styles */ +.markdown ol { + list-style: decimal-leading-zero; + padding-left: 0px; + padding-top: 10px; + margin: 10px; +} + +.markdown ol li { + margin-left: 20px; + padding-left: 10px; + position: relative; + transition: all 0.3s ease; + line-height: 1.4rem; +} + +.markdown ol li::marker { + padding-top: 10px; +} + +.markdown ol li p { + margin: 0.5rem; + padding-top: 10px; +} + +.markdown ul { + list-style: revert-layer; + /* color: #cfcfcfcf; */ + padding-left: 0px; + padding-top: 10px; + padding-bottom: 10px; + margin: 10px; +} + +.markdown ul li::marker { + color: #d0d0d0cf; + padding-top: 10px; +} + +.markdownul li { + margin-left: 20px; + + padding-left: 10px; + transition: all 0.3s ease; + line-height: 1.4rem; +} + +.markdown ul li > ul { + padding-left: 20px; + margin: 0px; +} + +.markdown p { + font-weight: 400; + margin: 0.35rem; +} + +.markdown { + text-wrap: wrap; +} + +.markdown pre { + margin: 20px 0; +} + +.markdown strong { + font-weight: 600; + color: #fff; +} diff --git a/frontend/src/utils/chat/markdown.js b/frontend/src/utils/chat/markdown.js index 47b4773459..ff4af77bc4 100644 --- a/frontend/src/utils/chat/markdown.js +++ b/frontend/src/utils/chat/markdown.js @@ -43,9 +43,7 @@ const markdown = markdownIt({ "
" ); }, -}) - // Enable
    and
      items to not assume an HTML structure so we can keep numbering from responses. - .disable("list"); +}); export default function renderMarkdown(text = "") { return markdown.render(text); From c50311fe1a652e77ced26e5334b00f2d9e92d8bc Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Mon, 26 Feb 2024 12:35:05 -0800 Subject: [PATCH 07/16] [STYLE] Fix heights of search bars (#801) fix height of search bars for LLM providers, embedding providers, and vectorDB providers --- .../src/pages/GeneralSettings/EmbeddingPreference/index.jsx | 2 +- frontend/src/pages/GeneralSettings/LLMPreference/index.jsx | 2 +- frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx | 2 +- .../pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx | 2 +- frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx | 2 +- .../OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx index c4900cf64c..0629fb9021 100644 --- a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx @@ -189,7 +189,7 @@ export default function GeneralEmbeddingPreference() { setSearchQuery(e.target.value)} autoComplete="off" onKeyDown={(e) => { diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx index 579c3903b7..ac4c1e3d1e 100644 --- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx @@ -239,7 +239,7 @@ export default function GeneralLLMPreference() { setSearchQuery(e.target.value)} autoComplete="off" onKeyDown={(e) => { diff --git a/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx b/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx index 32de0709d3..e5eacaea72 100644 --- a/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx +++ b/frontend/src/pages/GeneralSettings/VectorDatabase/index.jsx @@ -218,7 +218,7 @@ export default function GeneralVectorDatabase() { { e.preventDefault(); setSearchQuery(e.target.value); diff --git a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx index 024fc23058..6fce2bf96d 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx @@ -128,7 +128,7 @@ export default function EmbeddingPreference({ setSearchQuery(e.target.value)} autoComplete="off" onKeyDown={(e) => { diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index df94652a85..5f27815869 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -211,7 +211,7 @@ export default function LLMPreference({ setSearchQuery(e.target.value)} autoComplete="off" onKeyDown={(e) => { diff --git a/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx index 98034528d5..fb63cb8bcf 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx @@ -166,7 +166,7 @@ export default function VectorDatabaseConnection({ setSearchQuery(e.target.value)} autoComplete="off" onKeyDown={(e) => { From ca2e0f8e6febec1b7feaec864544ce52e396aecb Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Mon, 26 Feb 2024 12:35:52 -0800 Subject: [PATCH 08/16] [STYLE] Fix styles of LLMItem, EmbedderItem, and VectorDBItem (#803) update opacity and spacing of LLMItem, EmbedderItem, and VectorDBItem --- .../components/EmbeddingSelection/EmbedderItem/index.jsx | 6 ++---- frontend/src/components/LLMSelection/LLMItem/index.jsx | 6 ++---- .../src/components/VectorDBSelection/VectorDBItem/index.jsx | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/EmbeddingSelection/EmbedderItem/index.jsx b/frontend/src/components/EmbeddingSelection/EmbedderItem/index.jsx index b37b645f95..e1f164a615 100644 --- a/frontend/src/components/EmbeddingSelection/EmbedderItem/index.jsx +++ b/frontend/src/components/EmbeddingSelection/EmbedderItem/index.jsx @@ -27,11 +27,9 @@ export default function EmbedderItem({ alt={`${name} logo`} className="w-10 h-10 rounded-md" /> -
      +
      {name}
      -
      - {description} -
      +
      {description}
      diff --git a/frontend/src/components/LLMSelection/LLMItem/index.jsx b/frontend/src/components/LLMSelection/LLMItem/index.jsx index b6db5d1303..5e37738c55 100644 --- a/frontend/src/components/LLMSelection/LLMItem/index.jsx +++ b/frontend/src/components/LLMSelection/LLMItem/index.jsx @@ -27,11 +27,9 @@ export default function LLMItem({ alt={`${name} logo`} className="w-10 h-10 rounded-md" /> -
      +
      {name}
      -
      - {description} -
      +
      {description}
      diff --git a/frontend/src/components/VectorDBSelection/VectorDBItem/index.jsx b/frontend/src/components/VectorDBSelection/VectorDBItem/index.jsx index 47f067f5c2..ec35537bf6 100644 --- a/frontend/src/components/VectorDBSelection/VectorDBItem/index.jsx +++ b/frontend/src/components/VectorDBSelection/VectorDBItem/index.jsx @@ -27,9 +27,9 @@ export default function VectorDBItem({ alt={`${name} logo`} className="w-10 h-10 rounded-md" /> -
      +
      {name}
      -
      {description}
      +
      {description}
      From 6d18d79bb779203d8c05007ebf2c0f2c595c315c Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Mon, 26 Feb 2024 13:43:54 -0800 Subject: [PATCH 09/16] Generic upload fallback as text file. (#808) * Do not block any file upload fallback unknown/unsupported types to text if possible * reduce call for frontend * patch --- collector/processSingleFile/index.js | 26 +++++++++++------ collector/utils/files/index.js | 29 +++++++++++++++++++ .../Documents/Directory/index.jsx | 3 -- .../UploadFile/FileUploadProgress/index.jsx | 4 +-- .../Documents/UploadFile/index.jsx | 14 ++------- .../Modals/MangeWorkspace/Documents/index.jsx | 7 +---- .../Modals/MangeWorkspace/index.jsx | 13 ++------- 7 files changed, 54 insertions(+), 42 deletions(-) diff --git a/collector/processSingleFile/index.js b/collector/processSingleFile/index.js index 9efd3a70f8..569a2cde27 100644 --- a/collector/processSingleFile/index.js +++ b/collector/processSingleFile/index.js @@ -4,7 +4,7 @@ const { WATCH_DIRECTORY, SUPPORTED_FILETYPE_CONVERTERS, } = require("../utils/constants"); -const { trashFile } = require("../utils/files"); +const { trashFile, isTextType } = require("../utils/files"); const RESERVED_FILES = ["__HOTDIR__.md"]; async function processSingleFile(targetFilename) { @@ -31,17 +31,25 @@ async function processSingleFile(targetFilename) { }; } - if (!Object.keys(SUPPORTED_FILETYPE_CONVERTERS).includes(fileExtension)) { - trashFile(fullFilePath); - return { - success: false, - reason: `File extension ${fileExtension} not supported for parsing.`, - documents: [], - }; + let processFileAs = fileExtension; + if (!SUPPORTED_FILETYPE_CONVERTERS.hasOwnProperty(fileExtension)) { + if (isTextType(fullFilePath)) { + console.log( + `\x1b[33m[Collector]\x1b[0m The provided filetype of ${fileExtension} does not have a preset and will be processed as .txt.` + ); + processFileAs = ".txt"; + } else { + trashFile(fullFilePath); + return { + success: false, + reason: `File extension ${fileExtension} not supported for parsing and cannot be assumed as text file type.`, + documents: [], + }; + } } const FileTypeProcessor = require(SUPPORTED_FILETYPE_CONVERTERS[ - fileExtension + processFileAs ]); return await FileTypeProcessor({ fullFilePath, diff --git a/collector/utils/files/index.js b/collector/utils/files/index.js index caf33c888a..3e6ce3445e 100644 --- a/collector/utils/files/index.js +++ b/collector/utils/files/index.js @@ -1,5 +1,33 @@ const fs = require("fs"); const path = require("path"); +const { getType } = require("mime"); + +function isTextType(filepath) { + if (!fs.existsSync(filepath)) return false; + // These are types of mime primary classes that for sure + // cannot also for forced into a text type. + const nonTextTypes = ["multipart", "image", "model", "audio", "video"]; + // These are full-mimes we for sure cannot parse or interpret as text + // documents + const BAD_MIMES = [ + "application/octet-stream", + "application/zip", + "application/pkcs8", + "application/vnd.microsoft.portable-executable", + "application/x-msdownload", + ]; + + try { + const mime = getType(filepath); + if (BAD_MIMES.includes(mime)) return false; + + const type = mime.split("/")[0]; + if (nonTextTypes.includes(type)) return false; + return true; + } catch { + return false; + } +} function trashFile(filepath) { if (!fs.existsSync(filepath)) return; @@ -94,6 +122,7 @@ async function wipeCollectorStorage() { module.exports = { trashFile, + isTextType, createdDate, writeToServerDocuments, wipeCollectorStorage, diff --git a/frontend/src/components/Modals/MangeWorkspace/Documents/Directory/index.jsx b/frontend/src/components/Modals/MangeWorkspace/Documents/Directory/index.jsx index 557fe41814..158719445a 100644 --- a/frontend/src/components/Modals/MangeWorkspace/Documents/Directory/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/Documents/Directory/index.jsx @@ -8,7 +8,6 @@ function Directory({ files, loading, setLoading, - fileTypes, workspace, fetchKeys, selectedItems, @@ -135,9 +134,7 @@ function Directory({ )} - -
      +
      @@ -76,7 +76,7 @@ function FileUploadProgressComponent({ return (
      -
      +
      {status !== "complete" ? (
      diff --git a/frontend/src/components/Modals/MangeWorkspace/Documents/UploadFile/index.jsx b/frontend/src/components/Modals/MangeWorkspace/Documents/UploadFile/index.jsx index a6cee8c803..182cebcd21 100644 --- a/frontend/src/components/Modals/MangeWorkspace/Documents/UploadFile/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/Documents/UploadFile/index.jsx @@ -7,12 +7,7 @@ import { v4 } from "uuid"; import FileUploadProgress from "./FileUploadProgress"; import Workspace from "../../../../../models/workspace"; -export default function UploadFile({ - workspace, - fileTypes, - fetchKeys, - setLoading, -}) { +export default function UploadFile({ workspace, fetchKeys, setLoading }) { const [ready, setReady] = useState(false); const [files, setFiles] = useState([]); const [fetchingUrl, setFetchingUrl] = useState(false); @@ -76,9 +71,6 @@ export default function UploadFile({ const { getRootProps, getInputProps } = useDropzone({ onDrop, - accept: { - ...fileTypes, - }, disabled: !ready, }); @@ -109,9 +101,7 @@ export default function UploadFile({ Click to upload or drag and drop
      - {Object.values(fileTypes ?? []) - .flat() - .join(" ")} + supports text files, csv's, spreadsheets, audio files, and more!
      ) : ( diff --git a/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx b/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx index e8b63c903c..736a1476f6 100644 --- a/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx @@ -15,11 +15,7 @@ const MODEL_COSTS = { "text-embedding-3-large": 0.00000013, // $0.00013 / 1K tokens }; -export default function DocumentSettings({ - workspace, - fileTypes, - systemSettings, -}) { +export default function DocumentSettings({ workspace, systemSettings }) { const [highlightWorkspace, setHighlightWorkspace] = useState(false); const [availableDocs, setAvailableDocs] = useState([]); const [loading, setLoading] = useState(true); @@ -201,7 +197,6 @@ export default function DocumentSettings({ loading={loading} loadingMessage={loadingMessage} setLoading={setLoading} - fileTypes={fileTypes} workspace={workspace} fetchKeys={fetchKeys} selectedItems={selectedItems} diff --git a/frontend/src/components/Modals/MangeWorkspace/index.jsx b/frontend/src/components/Modals/MangeWorkspace/index.jsx index 6696a87569..ef3a58afb7 100644 --- a/frontend/src/components/Modals/MangeWorkspace/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/index.jsx @@ -11,17 +11,14 @@ const noop = () => {}; const ManageWorkspace = ({ hideModal = noop, providedSlug = null }) => { const { slug } = useParams(); const [workspace, setWorkspace] = useState(null); - const [fileTypes, setFileTypes] = useState(null); const [settings, setSettings] = useState({}); useEffect(() => { - async function checkSupportedFiletypes() { - const acceptedTypes = await System.acceptedDocumentTypes(); + async function getSettings() { const _settings = await System.keys(); - setFileTypes(acceptedTypes ?? {}); setSettings(_settings ?? {}); } - checkSupportedFiletypes(); + getSettings(); }, []); useEffect(() => { @@ -78,11 +75,7 @@ const ManageWorkspace = ({ hideModal = noop, providedSlug = null }) => {
      - +
      From 4d74f23c8246e1c02212cb45efef83e576300fae Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Mon, 26 Feb 2024 13:50:28 -0800 Subject: [PATCH 10/16] [CHORE]: Improve UX of custom logo screens (#806) improve UX of custom logo screens --- .../Appearance/CustomLogo/index.jsx | 14 +++++++----- .../OnboardingFlow/Steps/CustomLogo/index.jsx | 22 +++++++++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx b/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx index 274bcfa31c..1e81f563c3 100644 --- a/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx +++ b/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx @@ -107,12 +107,14 @@ export default function CustomLogo() { - + {!isDefaultLogo && ( + + )} diff --git a/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx index bb421bc39a..47464dab62 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx @@ -123,13 +123,21 @@ export default function CustomLogo({ setHeader, setForwardBtn, setBackBtn }) { /> )} - - + {!isDefaultLogo ? ( + + ) : ( + + )} ); From b20e3ce52c1b736bc0918bb3c90f7c6590075eb8 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Mon, 26 Feb 2024 13:54:22 -0800 Subject: [PATCH 11/16] Only show critical toasts during onboarding (#810) --- frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx | 4 ---- .../pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx | 3 --- .../src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx | 1 - frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx | 4 ---- .../OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx | 3 --- 5 files changed, 15 deletions(-) diff --git a/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx index 47464dab62..377157c410 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/CustomLogo/index.jsx @@ -58,8 +58,6 @@ export default function CustomLogo({ setHeader, setForwardBtn, setBackBtn }) { const logoURL = await System.fetchLogo(); _setLogo(logoURL); - - showToast("Image uploaded successfully.", "success", { clear: true }); setIsDefaultLogo(false); }; @@ -79,8 +77,6 @@ export default function CustomLogo({ setHeader, setForwardBtn, setBackBtn }) { const logoURL = await System.fetchLogo(); _setLogo(logoURL); - - showToast("Image successfully removed.", "success", { clear: true }); }; return ( diff --git a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx index 6fce2bf96d..fa17eebdf7 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx @@ -95,9 +95,6 @@ export default function EmbeddingPreference({ showToast(`Failed to save embedding settings: ${error}`, "error"); return; } - showToast("Embedder settings saved successfully.", "success", { - clear: true, - }); navigate(paths.onboarding.vectorDatabase()); }; diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index 5f27815869..433914ae66 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -180,7 +180,6 @@ export default function LLMPreference({ showToast(`Failed to save LLM settings: ${error}`, "error"); return; } - showToast("LLM settings saved successfully.", "success", { clear: true }); navigate(paths.onboarding.embeddingPreference()); }; diff --git a/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx index 500a483a18..2e619e3957 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx @@ -124,8 +124,6 @@ const JustMe = ({ return; } - showToast("Password set successfully!", "success", { clear: true }); - // Auto-request token with password that was just set so they // are not redirected to login after completion. const { token } = await System.requestToken({ @@ -245,9 +243,7 @@ const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => { return; } - showToast("Multi-user login enabled.", "success", { clear: true }); navigate(paths.onboarding.dataHandling()); - // Auto-request token with credentials that was just set so they // are not redirected to login after completion. const { user, token } = await System.requestToken(data); diff --git a/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx index fb63cb8bcf..bca7e98879 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx @@ -133,9 +133,6 @@ export default function VectorDatabaseConnection({ showToast(`Failed to save Vector Database settings: ${error}`, "error"); return; } - showToast("Vector Database settings saved successfully.", "success", { - clear: true, - }); navigate(paths.onboarding.customLogo()); }; From b64cb199f90a1c1528572b323170bd88488d013a Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Mon, 26 Feb 2024 16:12:20 -0800 Subject: [PATCH 12/16] 788 ollama embedder (#814) * Add Ollama embedder model support calls * update docs --- README.md | 1 + docker/.env.example | 5 + .../OllamaOptions/index.jsx | 120 ++++++++++++++++++ .../EmbeddingPreference/index.jsx | 9 ++ .../Steps/DataHandling/index.jsx | 7 + .../Steps/EmbeddingPreference/index.jsx | 9 ++ server/.env.example | 5 + server/utils/EmbeddingEngines/ollama/index.js | 90 +++++++++++++ server/utils/helpers/index.js | 3 + server/utils/helpers/updateENV.js | 4 +- 10 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx create mode 100644 server/utils/EmbeddingEngines/ollama/index.js diff --git a/README.md b/README.md index f77cdf2b89..68653bddc5 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Some cool features of AnythingLLM - [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) - [LM Studio (all)](https://lmstudio.ai) - [LocalAi (all)](https://localai.io/) +- [Ollama (all)](https://ollama.ai/) **Supported Vector Databases:** diff --git a/docker/.env.example b/docker/.env.example index 16413ad3c7..ba33bd5c04 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -79,6 +79,11 @@ GID='1000' # EMBEDDING_MODEL_PREF='text-embedding-ada-002' # EMBEDDING_MODEL_MAX_CHUNK_LENGTH=1000 # The max chunk size in chars a string to embed can be +# EMBEDDING_ENGINE='ollama' +# EMBEDDING_BASE_PATH='http://127.0.0.1:11434' +# EMBEDDING_MODEL_PREF='nomic-embed-text:latest' +# EMBEDDING_MODEL_MAX_CHUNK_LENGTH=8192 + ########################################### ######## Vector Database Selection ######## ########################################### diff --git a/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx b/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx new file mode 100644 index 0000000000..dff697f888 --- /dev/null +++ b/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx @@ -0,0 +1,120 @@ +import React, { useEffect, useState } from "react"; +import System from "@/models/system"; + +export default function OllamaEmbeddingOptions({ settings }) { + const [basePathValue, setBasePathValue] = useState( + settings?.EmbeddingBasePath + ); + const [basePath, setBasePath] = useState(settings?.EmbeddingBasePath); + + return ( +
      +
      +
      + + setBasePathValue(e.target.value)} + onBlur={() => setBasePath(basePathValue)} + required={true} + autoComplete="off" + spellCheck={false} + /> +
      + +
      + + e.target.blur()} + defaultValue={settings?.EmbeddingModelMaxChunkLength} + required={false} + autoComplete="off" + /> +
      +
      +
      + ); +} + +function OllamaLLMModelSelection({ settings, basePath = null }) { + const [customModels, setCustomModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + if (!basePath) { + setCustomModels([]); + setLoading(false); + return; + } + setLoading(true); + const { models } = await System.customModels("ollama", null, basePath); + setCustomModels(models || []); + setLoading(false); + } + findCustomModels(); + }, [basePath]); + + if (loading || customModels.length == 0) { + return ( +
      + + +
      + ); + } + + return ( +
      + + +
      + ); +} diff --git a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx index 0629fb9021..2e400ad632 100644 --- a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx @@ -7,12 +7,14 @@ import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import OpenAiLogo from "@/media/llmprovider/openai.png"; import AzureOpenAiLogo from "@/media/llmprovider/azure.png"; import LocalAiLogo from "@/media/llmprovider/localai.png"; +import OllamaLogo from "@/media/llmprovider/ollama.png"; import PreLoader from "@/components/Preloader"; import ChangeWarningModal from "@/components/ChangeWarning"; import OpenAiOptions from "@/components/EmbeddingSelection/OpenAiOptions"; import AzureAiOptions from "@/components/EmbeddingSelection/AzureAiOptions"; import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions"; import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions"; +import OllamaEmbeddingOptions from "@/components/EmbeddingSelection/OllamaOptions"; import EmbedderItem from "@/components/EmbeddingSelection/EmbedderItem"; import { MagnifyingGlass } from "@phosphor-icons/react"; import { useModal } from "@/hooks/useModal"; @@ -108,6 +110,13 @@ export default function GeneralEmbeddingPreference() { options: , description: "Run embedding models locally on your own machine.", }, + { + name: "Ollama", + value: "ollama", + logo: OllamaLogo, + options: , + description: "Run embedding models locally on your own machine.", + }, ]; useEffect(() => { diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index 51dc73004e..5beec3c176 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -221,6 +221,13 @@ const EMBEDDING_ENGINE_PRIVACY = { ], logo: LocalAiLogo, }, + ollama: { + name: "Ollama", + description: [ + "Your document text is embedded privately on the server running Ollama", + ], + logo: OllamaLogo, + }, }; export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) { diff --git a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx index fa17eebdf7..1932309e43 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx @@ -4,10 +4,12 @@ import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import OpenAiLogo from "@/media/llmprovider/openai.png"; import AzureOpenAiLogo from "@/media/llmprovider/azure.png"; import LocalAiLogo from "@/media/llmprovider/localai.png"; +import OllamaLogo from "@/media/llmprovider/ollama.png"; import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions"; import OpenAiOptions from "@/components/EmbeddingSelection/OpenAiOptions"; import AzureAiOptions from "@/components/EmbeddingSelection/AzureAiOptions"; import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions"; +import OllamaEmbeddingOptions from "@/components/EmbeddingSelection/OllamaOptions"; import EmbedderItem from "@/components/EmbeddingSelection/EmbedderItem"; import System from "@/models/system"; import paths from "@/utils/paths"; @@ -70,6 +72,13 @@ export default function EmbeddingPreference({ options: , description: "Run embedding models locally on your own machine.", }, + { + name: "Ollama", + value: "ollama", + logo: OllamaLogo, + options: , + description: "Run embedding models locally on your own machine.", + }, ]; function handleForward() { diff --git a/server/.env.example b/server/.env.example index bed943925a..0ca826e895 100644 --- a/server/.env.example +++ b/server/.env.example @@ -76,6 +76,11 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea # EMBEDDING_MODEL_PREF='text-embedding-ada-002' # EMBEDDING_MODEL_MAX_CHUNK_LENGTH=1000 # The max chunk size in chars a string to embed can be +# EMBEDDING_ENGINE='ollama' +# EMBEDDING_BASE_PATH='http://127.0.0.1:11434' +# EMBEDDING_MODEL_PREF='nomic-embed-text:latest' +# EMBEDDING_MODEL_MAX_CHUNK_LENGTH=8192 + ########################################### ######## Vector Database Selection ######## ########################################### diff --git a/server/utils/EmbeddingEngines/ollama/index.js b/server/utils/EmbeddingEngines/ollama/index.js new file mode 100644 index 0000000000..1f77c36e8e --- /dev/null +++ b/server/utils/EmbeddingEngines/ollama/index.js @@ -0,0 +1,90 @@ +const { maximumChunkLength } = require("../../helpers"); + +class OllamaEmbedder { + constructor() { + if (!process.env.EMBEDDING_BASE_PATH) + throw new Error("No embedding base path was set."); + if (!process.env.EMBEDDING_MODEL_PREF) + throw new Error("No embedding model was set."); + + this.basePath = `${process.env.EMBEDDING_BASE_PATH}/api/embeddings`; + this.model = process.env.EMBEDDING_MODEL_PREF; + // Limit of how many strings we can process in a single pass to stay with resource or network limits + this.maxConcurrentChunks = 1; + this.embeddingMaxChunkLength = maximumChunkLength(); + } + + log(text, ...args) { + console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args); + } + + async embedTextInput(textInput) { + const result = await this.embedChunks([textInput]); + return result?.[0] || []; + } + + async embedChunks(textChunks = []) { + const embeddingRequests = []; + this.log( + `Embedding ${textChunks.length} chunks of text with ${this.model}.` + ); + + for (const chunk of textChunks) { + embeddingRequests.push( + new Promise((resolve) => { + fetch(this.basePath, { + method: "POST", + body: JSON.stringify({ + model: this.model, + prompt: chunk, + }), + }) + .then((res) => res.json()) + .then(({ embedding }) => { + resolve({ data: embedding, error: null }); + return; + }) + .catch((error) => { + resolve({ data: [], error: error.message }); + return; + }); + }) + ); + } + + const { data = [], error = null } = await Promise.all( + embeddingRequests + ).then((results) => { + // If any errors were returned from Ollama abort the entire sequence because the embeddings + // will be incomplete. + + const errors = results + .filter((res) => !!res.error) + .map((res) => res.error) + .flat(); + if (errors.length > 0) { + let uniqueErrors = new Set(); + errors.map((error) => + uniqueErrors.add(`[${error.type}]: ${error.message}`) + ); + + return { + data: [], + error: Array.from(uniqueErrors).join(", "), + }; + } + + return { + data: results.map((res) => res?.data || []), + error: null, + }; + }); + + if (!!error) throw new Error(`Ollama Failed to embed: ${error}`); + return data.length > 0 ? data : null; + } +} + +module.exports = { + OllamaEmbedder, +}; diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index 8bda716aa6..a31a3e4f99 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -92,6 +92,9 @@ function getEmbeddingEngineSelection() { case "localai": const { LocalAiEmbedder } = require("../EmbeddingEngines/localAi"); return new LocalAiEmbedder(); + case "ollama": + const { OllamaEmbedder } = require("../EmbeddingEngines/ollama"); + return new OllamaEmbedder(); case "native": const { NativeEmbedder } = require("../EmbeddingEngines/native"); console.log("\x1b[34m[INFO]\x1b[0m Using Native Embedder"); diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 247e3ba486..1ca9368204 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -135,7 +135,7 @@ const KEY_MAPPING = { }, EmbeddingBasePath: { envKey: "EMBEDDING_BASE_PATH", - checks: [isNotEmpty, validLLMExternalBasePath, validDockerizedUrl], + checks: [isNotEmpty, validDockerizedUrl], }, EmbeddingModelPref: { envKey: "EMBEDDING_MODEL_PREF", @@ -355,7 +355,7 @@ function validAnthropicModel(input = "") { } function supportedEmbeddingModel(input = "") { - const supported = ["openai", "azure", "localai", "native"]; + const supported = ["openai", "azure", "localai", "native", "ollama"]; return supported.includes(input) ? null : `Invalid Embedding model type. Must be one of ${supported.join(", ")}.`; From e87cba3468c6a3239977973d1ec4973151dfe28a Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Tue, 27 Feb 2024 11:47:01 -0800 Subject: [PATCH 13/16] [CHORE] Normalize styles of all input and select elements (#804) * normalize styles of all input and select elements * missed placeholder text input * missed input fields on onboarding flow --- .../EmbeddingSelection/AzureAiOptions/index.jsx | 6 +++--- .../EmbeddingSelection/LocalAiOptions/index.jsx | 10 +++++----- .../EmbeddingSelection/OpenAiOptions/index.jsx | 4 ++-- .../LLMSelection/AnthropicAiOptions/index.jsx | 4 ++-- .../components/LLMSelection/AzureAiOptions/index.jsx | 10 +++++----- .../components/LLMSelection/GeminiLLMOptions/index.jsx | 4 ++-- .../LLMSelection/HuggingFaceOptions/index.jsx | 6 +++--- .../components/LLMSelection/LMStudioOptions/index.jsx | 4 ++-- .../components/LLMSelection/LocalAiOptions/index.jsx | 10 +++++----- .../components/LLMSelection/MistralOptions/index.jsx | 6 +++--- .../components/LLMSelection/NativeLLMOptions/index.jsx | 6 +++--- .../components/LLMSelection/OllamaLLMOptions/index.jsx | 8 ++++---- .../components/LLMSelection/OpenAiOptions/index.jsx | 6 +++--- .../LLMSelection/OpenRouterOptions/index.jsx | 6 +++--- .../LLMSelection/PerplexityOptions/index.jsx | 6 +++--- .../LLMSelection/TogetherAiOptions/index.jsx | 6 +++--- .../MangeWorkspace/Documents/UploadFile/index.jsx | 2 +- frontend/src/components/Modals/NewWorkspace.jsx | 2 +- .../src/components/UserMenu/AccountModal/index.jsx | 4 ++-- .../VectorDBSelection/AstraDBOptions/index.jsx | 4 ++-- .../VectorDBSelection/ChromaDBOptions/index.jsx | 6 +++--- .../VectorDBSelection/MilvusDBOptions/index.jsx | 6 +++--- .../VectorDBSelection/PineconeDBOptions/index.jsx | 4 ++-- .../VectorDBSelection/QDrantDBOptions/index.jsx | 4 ++-- .../VectorDBSelection/WeaviateDBOptions/index.jsx | 4 ++-- .../VectorDBSelection/ZillizCloudOptions/index.jsx | 4 ++-- frontend/src/pages/Admin/Users/NewUserModal/index.jsx | 6 +++--- .../pages/Admin/Users/UserRow/EditUserModal/index.jsx | 6 +++--- .../pages/Admin/Workspaces/NewWorkspaceModal/index.jsx | 2 +- .../FooterCustomization/NewIconForm/index.jsx | 2 +- .../GeneralSettings/Appearance/SupportEmail/index.jsx | 2 +- .../DataConnectors/Connectors/Github/index.jsx | 10 +++++----- .../DataConnectors/Connectors/Youtube/index.jsx | 2 +- .../EmbedConfigs/EmbedRow/CodeSnippetModal/index.jsx | 8 ++++---- .../EmbedConfigs/NewEmbedModal/index.jsx | 6 +++--- frontend/src/pages/GeneralSettings/Security/index.jsx | 6 +++--- .../OnboardingFlow/Steps/CreateWorkspace/index.jsx | 2 +- .../src/pages/OnboardingFlow/Steps/Survey/index.jsx | 4 ++-- .../ChatSettings/ChatHistorySettings/index.jsx | 2 +- .../ChatSettings/ChatPromptSettings/index.jsx | 2 +- .../ChatSettings/ChatTemperatureSettings/index.jsx | 2 +- .../GeneralAppearance/SuggestedChatMessages/index.jsx | 4 ++-- .../GeneralAppearance/WorkspaceName/index.jsx | 2 +- .../VectorDatabase/MaxContextSnippets/index.jsx | 2 +- 44 files changed, 106 insertions(+), 106 deletions(-) diff --git a/frontend/src/components/EmbeddingSelection/AzureAiOptions/index.jsx b/frontend/src/components/EmbeddingSelection/AzureAiOptions/index.jsx index c782c51f33..209c0aa21f 100644 --- a/frontend/src/components/EmbeddingSelection/AzureAiOptions/index.jsx +++ b/frontend/src/components/EmbeddingSelection/AzureAiOptions/index.jsx @@ -9,7 +9,7 @@ export default function AzureAiOptions({ settings }) { setBasePathValue(e.target.value)} @@ -41,7 +41,7 @@ export default function LocalAiOptions({ settings }) { e.target.blur()} @@ -62,7 +62,7 @@ export default function LocalAiOptions({ settings }) { {[ diff --git a/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx b/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx index 52bbbfd897..3d493f1c43 100644 --- a/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx +++ b/frontend/src/components/LLMSelection/AnthropicAiOptions/index.jsx @@ -29,7 +29,7 @@ export default function AnthropicAiOptions({ settings, showAlert = false }) { {["claude-2", "claude-instant-1"].map((model) => { return ( diff --git a/frontend/src/components/LLMSelection/AzureAiOptions/index.jsx b/frontend/src/components/LLMSelection/AzureAiOptions/index.jsx index ce54d3d60d..224582eb83 100644 --- a/frontend/src/components/LLMSelection/AzureAiOptions/index.jsx +++ b/frontend/src/components/LLMSelection/AzureAiOptions/index.jsx @@ -9,7 +9,7 @@ export default function AzureAiOptions({ settings }) { @@ -77,7 +77,7 @@ export default function AzureAiOptions({ settings }) { {["gemini-pro"].map((model) => { return ( diff --git a/frontend/src/components/LLMSelection/HuggingFaceOptions/index.jsx b/frontend/src/components/LLMSelection/HuggingFaceOptions/index.jsx index 7e8747da1a..c93a9457fa 100644 --- a/frontend/src/components/LLMSelection/HuggingFaceOptions/index.jsx +++ b/frontend/src/components/LLMSelection/HuggingFaceOptions/index.jsx @@ -9,7 +9,7 @@ export default function HuggingFaceOptions({ settings }) { e.target.blur()} diff --git a/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx b/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx index 7e9e8a8b6d..fbba7666f0 100644 --- a/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx +++ b/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx @@ -29,7 +29,7 @@ export default function LMStudioOptions({ settings, showAlert = false }) { e.target.blur()} diff --git a/frontend/src/components/LLMSelection/LocalAiOptions/index.jsx b/frontend/src/components/LLMSelection/LocalAiOptions/index.jsx index c7abd09af2..91e3867027 100644 --- a/frontend/src/components/LLMSelection/LocalAiOptions/index.jsx +++ b/frontend/src/components/LLMSelection/LocalAiOptions/index.jsx @@ -36,7 +36,7 @@ export default function LocalAiOptions({ settings, showAlert = false }) { e.target.blur()} @@ -80,7 +80,7 @@ export default function LocalAiOptions({ settings, showAlert = false }) { diff --git a/frontend/src/components/LLMSelection/OpenAiOptions/index.jsx b/frontend/src/components/LLMSelection/OpenAiOptions/index.jsx index b1718afe90..1e34930961 100644 --- a/frontend/src/components/LLMSelection/OpenAiOptions/index.jsx +++ b/frontend/src/components/LLMSelection/OpenAiOptions/index.jsx @@ -14,7 +14,7 @@ export default function OpenAiOptions({ settings }) { diff --git a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx index 959b04b4c6..313f81f234 100644 --- a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx +++ b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx @@ -50,7 +50,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) { @@ -85,7 +85,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) { required={true} defaultValue={user.role} onChange={(e) => setRole(e.target.value)} - className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500" + className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500" > diff --git a/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx b/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx index 5179b3fb7a..0667809a51 100644 --- a/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx +++ b/frontend/src/pages/Admin/Workspaces/NewWorkspaceModal/index.jsx @@ -42,7 +42,7 @@ export default function NewWorkspaceModal({ closeModal }) { {selectedIcon !== "" && ( diff --git a/frontend/src/pages/GeneralSettings/Appearance/SupportEmail/index.jsx b/frontend/src/pages/GeneralSettings/Appearance/SupportEmail/index.jsx index 548001e22c..5ccbec8cd1 100644 --- a/frontend/src/pages/GeneralSettings/Appearance/SupportEmail/index.jsx +++ b/frontend/src/pages/GeneralSettings/Appearance/SupportEmail/index.jsx @@ -64,7 +64,7 @@ export default function SupportEmail() { @@ -257,7 +257,7 @@ function GitHubBranchSelection({ repo, accessToken }) { {allBranches.map((branch) => { return ( diff --git a/frontend/src/pages/GeneralSettings/DataConnectors/Connectors/Youtube/index.jsx b/frontend/src/pages/GeneralSettings/DataConnectors/Connectors/Youtube/index.jsx index 021eeaf98c..9c8588d3d2 100644 --- a/frontend/src/pages/GeneralSettings/DataConnectors/Connectors/Youtube/index.jsx +++ b/frontend/src/pages/GeneralSettings/DataConnectors/Connectors/Youtube/index.jsx @@ -79,7 +79,7 @@ export default function YouTubeTranscriptConnectorSetup() { tag. See more style and config options on our docs https://github.com/Mintplex-Labs/anything-llm/tree/master/embed/README.md --> - @@ -98,7 +98,7 @@ const ScriptTag = ({ embed }) => {