mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-04-17 18:18:11 +00:00
Multiple LLM Support framework + AzureOpenAI Support (#180)
* Remove LangchainJS for chat support chaining Implement runtime LLM selection Implement AzureOpenAI Support for LLM + Emebedding WIP on frontend Update env to reflect the new fields * Remove LangchainJS for chat support chaining Implement runtime LLM selection Implement AzureOpenAI Support for LLM + Emebedding WIP on frontend Update env to reflect the new fields * Replace keys with LLM Selection in settings modal Enforce checks for new ENVs depending on LLM selection
This commit is contained in:
parent
285bddb0fb
commit
1f29cec918
21 changed files with 699 additions and 336 deletions
.vscode
docker
frontend/src
components/Modals/Settings
media/llmprovider
server
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"openai"
|
||||
]
|
||||
}
|
|
@ -1,8 +1,24 @@
|
|||
SERVER_PORT=3001
|
||||
OPEN_AI_KEY=
|
||||
OPEN_MODEL_PREF='gpt-3.5-turbo'
|
||||
CACHE_VECTORS="true"
|
||||
# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long.
|
||||
|
||||
###########################################
|
||||
######## LLM API SElECTION ################
|
||||
###########################################
|
||||
LLM_PROVIDER='openai'
|
||||
# OPEN_AI_KEY=
|
||||
OPEN_MODEL_PREF='gpt-3.5-turbo'
|
||||
|
||||
# LLM_PROVIDER='azure'
|
||||
# AZURE_OPENAI_ENDPOINT=
|
||||
# AZURE_OPENAI_KEY=
|
||||
# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model.
|
||||
# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002
|
||||
|
||||
|
||||
###########################################
|
||||
######## Vector Database Selection ########
|
||||
###########################################
|
||||
# Enable all below if you are using vector database: Chroma.
|
||||
# VECTOR_DB="chroma"
|
||||
# CHROMA_ENDPOINT='http://localhost:8000'
|
||||
|
@ -18,7 +34,6 @@ PINECONE_INDEX=
|
|||
|
||||
# CLOUD DEPLOYMENT VARIRABLES ONLY
|
||||
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
|
||||
# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long.
|
||||
# NO_DEBUG="true"
|
||||
STORAGE_DIR="./server/storage"
|
||||
GOOGLE_APIS_KEY=
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { AlertCircle, Loader } from "react-feather";
|
||||
import System from "../../../../models/system";
|
||||
|
||||
const noop = () => false;
|
||||
export default function SystemKeys({ hideModal = noop, user, settings = {} }) {
|
||||
const canDebug = settings.MultiUserMode
|
||||
? settings?.CanDebug && user?.role === "admin"
|
||||
: settings?.CanDebug;
|
||||
function validSettings(settings) {
|
||||
return (
|
||||
settings?.OpenAiKey &&
|
||||
!!settings?.OpenAiModelPref &&
|
||||
!!settings?.VectorDB &&
|
||||
(settings?.VectorDB === "chroma" ? !!settings?.ChromaEndpoint : true) &&
|
||||
(settings?.VectorDB === "pinecone"
|
||||
? !!settings?.PineConeKey &&
|
||||
!!settings?.PineConeEnvironment &&
|
||||
!!settings?.PineConeIndex
|
||||
: true)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative w-full max-w-2xl max-h-full">
|
||||
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
|
||||
<div className="flex items-start justify-between px-6 py-4">
|
||||
<p className="text-gray-800 dark:text-stone-200 text-base ">
|
||||
These are the credentials and settings for how your AnythingLLM
|
||||
instance will function. Its important these keys are current and
|
||||
correct.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-6 flex h-full w-full">
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
{!validSettings(settings) && (
|
||||
<div className="bg-orange-300 p-4 rounded-lg border border-orange-600 text-orange-700 w-full items-center flex gap-x-2">
|
||||
<AlertCircle className="h-8 w-8" />
|
||||
<p className="text-sm md:text-base ">
|
||||
Ensure all fields are green before attempting to use
|
||||
AnythingLLM or it may not function as expected!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<ShowKey
|
||||
name="OpenAI API Key"
|
||||
env="OpenAiKey"
|
||||
value={settings?.OpenAiKey ? "*".repeat(20) : ""}
|
||||
valid={settings?.OpenAiKey}
|
||||
allowDebug={canDebug}
|
||||
/>
|
||||
<ShowKey
|
||||
name="OpenAI Model for chats"
|
||||
env="OpenAiModelPref"
|
||||
value={settings?.OpenAiModelPref}
|
||||
valid={!!settings?.OpenAiModelPref}
|
||||
allowDebug={canDebug}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<button
|
||||
onClick={hideModal}
|
||||
type="button"
|
||||
className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShowKey({ name, env, value, valid, allowDebug = true }) {
|
||||
const [isValid, setIsValid] = useState(valid);
|
||||
const [debug, setDebug] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
const data = {};
|
||||
const form = new FormData(e.target);
|
||||
for (var [key, value] of form.entries()) data[key] = value;
|
||||
const { error } = await System.updateSystem(data);
|
||||
if (!!error) {
|
||||
alert(error);
|
||||
setSaving(false);
|
||||
setIsValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(false);
|
||||
setDebug(false);
|
||||
setIsValid(true);
|
||||
};
|
||||
|
||||
if (!isValid) {
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="error"
|
||||
className="block mb-2 text-sm font-medium text-red-700 dark:text-red-500"
|
||||
>
|
||||
{name}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="error"
|
||||
name={env}
|
||||
disabled={!debug}
|
||||
className="bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 dark:bg-gray-700 focus:border-red-500 block w-full p-2.5 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500"
|
||||
placeholder={name}
|
||||
defaultValue={value}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="mt-2 text-sm text-red-600 dark:text-red-500">
|
||||
Need setup in .env file.
|
||||
</p>
|
||||
{allowDebug && (
|
||||
<>
|
||||
{debug ? (
|
||||
<div className="flex items-center gap-x-2 mt-2">
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDebug(false)}
|
||||
className="text-xs text-slate-300 dark:text-slate-500"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="text-xs text-blue-300 dark:text-blue-500"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDebug(true)}
|
||||
className="mt-2 text-xs text-slate-300 dark:text-slate-500"
|
||||
>
|
||||
Change
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-6">
|
||||
<label
|
||||
htmlFor="success"
|
||||
className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"
|
||||
>
|
||||
{name}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="success"
|
||||
name={env}
|
||||
disabled={!debug}
|
||||
className="border border-white text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 text-sm rounded-lg focus:ring-green-500 focus:border-green-500 block w-full p-2.5 dark:bg-gray-700 dark:border-green-500"
|
||||
defaultValue={value}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{allowDebug && (
|
||||
<div className="flex items-center justify-end">
|
||||
{debug ? (
|
||||
<div className="flex items-center gap-x-2 mt-2">
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setDebug(false)}
|
||||
className="text-xs text-slate-300 dark:text-slate-500"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="text-xs text-blue-300 dark:text-blue-500">
|
||||
Save
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setDebug(true)}
|
||||
className="mt-2 text-xs text-slate-300 dark:text-slate-500"
|
||||
>
|
||||
Change
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
281
frontend/src/components/Modals/Settings/LLMSelection/index.jsx
Normal file
281
frontend/src/components/Modals/Settings/LLMSelection/index.jsx
Normal file
|
@ -0,0 +1,281 @@
|
|||
import React, { useState } from "react";
|
||||
import System from "../../../../models/system";
|
||||
import OpenAiLogo from "../../../../media/llmprovider/openai.png";
|
||||
import AzureOpenAiLogo from "../../../../media/llmprovider/azure.png";
|
||||
import AnthropicLogo from "../../../../media/llmprovider/anthropic.png";
|
||||
|
||||
const noop = () => false;
|
||||
export default function LLMSelection({
|
||||
hideModal = noop,
|
||||
user,
|
||||
settings = {},
|
||||
}) {
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [llmChoice, setLLMChoice] = useState(settings?.LLMProvider || "openai");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const canDebug = settings.MultiUserMode
|
||||
? settings?.CanDebug && user?.role === "admin"
|
||||
: settings?.CanDebug;
|
||||
|
||||
function updateLLMChoice(selection) {
|
||||
if (!canDebug || selection === llmChoice) return false;
|
||||
setHasChanges(true);
|
||||
setLLMChoice(selection);
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
const data = {};
|
||||
const form = new FormData(e.target);
|
||||
for (var [key, value] of form.entries()) data[key] = value;
|
||||
const { error } = await System.updateSystem(data);
|
||||
setError(error);
|
||||
setSaving(false);
|
||||
setHasChanges(!!error ? true : false);
|
||||
};
|
||||
return (
|
||||
<div className="relative w-full max-w-2xl max-h-full">
|
||||
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
|
||||
<div className="flex items-start justify-between px-6 py-4">
|
||||
<p className="text-gray-800 dark:text-stone-200 text-base ">
|
||||
These are the credentials and settings for your preferred LLM chat &
|
||||
embedding provider. Its important these keys are current and correct
|
||||
or else AnythingLLM will not function properly.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!!error && (
|
||||
<div className="mb-8 bg-red-700 dark:bg-orange-800 bg-opacity-30 border border-red-800 dark:border-orange-600 p-4 rounded-lg w-[90%] flex mx-auto">
|
||||
<p className="text-red-800 dark:text-orange-300 text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} onChange={() => setHasChanges(true)}>
|
||||
<div className="px-6 space-y-6 flex h-full w-full">
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
LLM providers
|
||||
</p>
|
||||
<div className="w-full flex overflow-x-scroll gap-x-4 no-scroll">
|
||||
<input hidden={true} name="LLMProvider" value={llmChoice} />
|
||||
<LLMProviderOption
|
||||
name="OpenAI"
|
||||
value="openai"
|
||||
link="openai.com"
|
||||
description="The standard option for most non-commercial use. Provides both chat and embedding."
|
||||
checked={llmChoice === "openai"}
|
||||
image={OpenAiLogo}
|
||||
onClick={updateLLMChoice}
|
||||
/>
|
||||
<LLMProviderOption
|
||||
name="Azure OpenAi"
|
||||
value="azure"
|
||||
link="azure.microsoft.com"
|
||||
description="The enterprise option of OpenAI hosted on Azure services. Provides both chat and embedding."
|
||||
checked={llmChoice === "azure"}
|
||||
image={AzureOpenAiLogo}
|
||||
onClick={updateLLMChoice}
|
||||
/>
|
||||
<LLMProviderOption
|
||||
name="Anthropic Claude 2"
|
||||
value="anthropic-claude-2"
|
||||
link="anthropic.com"
|
||||
description="[COMING SOON] A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
|
||||
checked={llmChoice === "anthropic-claude-2"}
|
||||
image={AnthropicLogo}
|
||||
/>
|
||||
</div>
|
||||
{llmChoice === "openai" && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="OpenAiKey"
|
||||
disabled={!canDebug}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
|
||||
placeholder="OpenAI API Key"
|
||||
defaultValue={settings?.OpenAiKey ? "*".repeat(20) : ""}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
Chat Model Selection
|
||||
</label>
|
||||
<select
|
||||
disabled={!canDebug}
|
||||
name="OpenAiModelPref"
|
||||
defaultValue={settings?.OpenAiModelPref}
|
||||
required={true}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-stone-700 dark:border-slate-200 dark:placeholder-stone-500 dark:text-slate-200"
|
||||
>
|
||||
{[
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-4",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0613",
|
||||
].map((model) => {
|
||||
return (
|
||||
<option key={model} value={model}>
|
||||
{model}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{llmChoice === "azure" && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
Azure Service Endpoint
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
name="AzureOpenAiEndpoint"
|
||||
disabled={!canDebug}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
|
||||
placeholder="https://my-azure.openai.azure.com"
|
||||
defaultValue={settings?.AzureOpenAiEndpoint}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="AzureOpenAiKey"
|
||||
disabled={!canDebug}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
|
||||
placeholder="Azure OpenAI API Key"
|
||||
defaultValue={
|
||||
settings?.AzureOpenAiKey ? "*".repeat(20) : ""
|
||||
}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
Chat Model Deployment Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="AzureOpenAiModelPref"
|
||||
disabled={!canDebug}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
|
||||
placeholder="Azure OpenAI chat model deployment name"
|
||||
defaultValue={settings?.AzureOpenAiModelPref}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
Embedding Model Deployment Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="AzureOpenAiEmbeddingModelPref"
|
||||
disabled={!canDebug}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
|
||||
placeholder="Azure OpenAI embedding model deployment name"
|
||||
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{llmChoice === "anthropic-claude-2" && (
|
||||
<div className="w-full h-40 items-center justify-center flex">
|
||||
<p className="text-gray-800 dark:text-slate-400">
|
||||
This provider is unavailable and cannot be used in
|
||||
AnythingLLM currently.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full p-4">
|
||||
<button
|
||||
hidden={!hasChanges}
|
||||
disabled={saving}
|
||||
type="submit"
|
||||
className="w-full text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<button
|
||||
onClick={hideModal}
|
||||
type="button"
|
||||
className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const LLMProviderOption = ({
|
||||
name,
|
||||
link,
|
||||
description,
|
||||
value,
|
||||
image,
|
||||
checked = false,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<div onClick={() => onClick(value)}>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={value}
|
||||
className="peer hidden"
|
||||
checked={checked}
|
||||
readOnly={true}
|
||||
formNoValidate={true}
|
||||
/>
|
||||
<label className="transition-all duration-300 inline-flex h-full w-60 cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:dark:bg-stone-800 peer-checked:text-gray-600 dark:border-slate-200 dark:bg-stone-800 dark:text-slate-400 dark:hover:bg-stone-700 dark:hover:text-slate-300 dark:peer-checked:text-slate-300">
|
||||
<div className="block">
|
||||
<img src={image} alt={name} className="mb-2 h-10 w-10 rounded-full" />
|
||||
<div className="w-full text-lg font-semibold">{name}</div>
|
||||
<div className="flex w-full flex-col gap-y-1 text-sm">
|
||||
<p className="text-xs text-slate-400">{link}</p>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -57,7 +57,7 @@ export default function VectorDBSelection({
|
|||
<div className="px-6 space-y-6 flex h-full w-full">
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
|
||||
Vector database provider
|
||||
Vector database providers
|
||||
</p>
|
||||
<div className="w-full flex overflow-x-scroll gap-x-4 no-scroll">
|
||||
<input hidden={true} name="VectorDB" value={vectorDB} />
|
||||
|
@ -96,7 +96,7 @@ export default function VectorDBSelection({
|
|||
Pinecone DB API Key
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
type="password"
|
||||
name="PineConeKey"
|
||||
disabled={!canDebug}
|
||||
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Archive, Lock, Key, X, Users, Database } from "react-feather";
|
||||
import SystemKeys from "./Keys";
|
||||
import {
|
||||
Archive,
|
||||
Lock,
|
||||
X,
|
||||
Users,
|
||||
Database,
|
||||
MessageSquare,
|
||||
} from "react-feather";
|
||||
import ExportOrImportData from "./ExportImport";
|
||||
import PasswordProtection from "./PasswordProtection";
|
||||
import System from "../../../models/system";
|
||||
import MultiUserMode from "./MultiUserMode";
|
||||
import useUser from "../../../hooks/useUser";
|
||||
import VectorDBSelection from "./VectorDbs";
|
||||
import LLMSelection from "./LLMSelection";
|
||||
|
||||
const TABS = {
|
||||
keys: SystemKeys,
|
||||
llm: LLMSelection,
|
||||
exportimport: ExportOrImportData,
|
||||
password: PasswordProtection,
|
||||
multiuser: MultiUserMode,
|
||||
|
@ -20,9 +27,9 @@ const noop = () => false;
|
|||
export default function SystemSettingsModal({ hideModal = noop }) {
|
||||
const { user } = useUser();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedTab, setSelectedTab] = useState("keys");
|
||||
const [selectedTab, setSelectedTab] = useState("llm");
|
||||
const [settings, setSettings] = useState(null);
|
||||
const Component = TABS[selectedTab || "keys"];
|
||||
const Component = TABS[selectedTab || "llm"];
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
|
@ -87,10 +94,10 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
|
|||
return (
|
||||
<ul className="flex overflow-x-scroll no-scroll -mb-px text-sm gap-x-2 font-medium text-center text-gray-500 dark:text-gray-400">
|
||||
<SettingTab
|
||||
active={selectedTab === "keys"}
|
||||
displayName="Keys"
|
||||
tabName="keys"
|
||||
icon={<Key className="h-4 w-4 flex-shrink-0" />}
|
||||
active={selectedTab === "llm"}
|
||||
displayName="LLM Choice"
|
||||
tabName="llm"
|
||||
icon={<MessageSquare className="h-4 w-4 flex-shrink-0" />}
|
||||
onClick={changeTab}
|
||||
/>
|
||||
<SettingTab
|
||||
|
|
BIN
frontend/src/media/llmprovider/anthropic.png
Normal file
BIN
frontend/src/media/llmprovider/anthropic.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 12 KiB |
BIN
frontend/src/media/llmprovider/azure.png
Normal file
BIN
frontend/src/media/llmprovider/azure.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 34 KiB |
BIN
frontend/src/media/llmprovider/openai.png
Normal file
BIN
frontend/src/media/llmprovider/openai.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 22 KiB |
|
@ -1,8 +1,23 @@
|
|||
SERVER_PORT=3001
|
||||
OPEN_AI_KEY=
|
||||
OPEN_MODEL_PREF='gpt-3.5-turbo'
|
||||
CACHE_VECTORS="true"
|
||||
JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long.
|
||||
|
||||
###########################################
|
||||
######## LLM API SElECTION ################
|
||||
###########################################
|
||||
LLM_PROVIDER='openai'
|
||||
# OPEN_AI_KEY=
|
||||
OPEN_MODEL_PREF='gpt-3.5-turbo'
|
||||
|
||||
# LLM_PROVIDER='azure'
|
||||
# AZURE_OPENAI_ENDPOINT=
|
||||
# AZURE_OPENAI_KEY=
|
||||
# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model.
|
||||
# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002
|
||||
|
||||
###########################################
|
||||
######## Vector Database Selection ########
|
||||
###########################################
|
||||
# Enable all below if you are using vector database: Chroma.
|
||||
# VECTOR_DB="chroma"
|
||||
# CHROMA_ENDPOINT='http://localhost:8000'
|
||||
|
@ -16,8 +31,6 @@ PINECONE_INDEX=
|
|||
# Enable all below if you are using vector database: LanceDB.
|
||||
# VECTOR_DB="lancedb"
|
||||
|
||||
JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long.
|
||||
|
||||
# CLOUD DEPLOYMENT VARIRABLES ONLY
|
||||
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
|
||||
# STORAGE_DIR= # absolute filesystem path with no trailing slash
|
||||
|
|
|
@ -38,17 +38,16 @@ function systemEndpoints(app) {
|
|||
|
||||
app.get("/setup-complete", async (_, response) => {
|
||||
try {
|
||||
const llmProvider = process.env.LLM_PROVIDER || "openai";
|
||||
const vectorDB = process.env.VECTOR_DB || "pinecone";
|
||||
const results = {
|
||||
CanDebug: !!!process.env.NO_DEBUG,
|
||||
RequiresAuth: !!process.env.AUTH_TOKEN,
|
||||
VectorDB: vectorDB,
|
||||
OpenAiKey: !!process.env.OPEN_AI_KEY,
|
||||
OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo",
|
||||
AuthToken: !!process.env.AUTH_TOKEN,
|
||||
JWTSecret: !!process.env.JWT_SECRET,
|
||||
StorageDir: process.env.STORAGE_DIR,
|
||||
MultiUserMode: await SystemSettings.isMultiUserMode(),
|
||||
VectorDB: vectorDB,
|
||||
...(vectorDB === "pinecone"
|
||||
? {
|
||||
PineConeEnvironment: process.env.PINECONE_ENVIRONMENT,
|
||||
|
@ -61,6 +60,22 @@ function systemEndpoints(app) {
|
|||
ChromaEndpoint: process.env.CHROMA_ENDPOINT,
|
||||
}
|
||||
: {}),
|
||||
LLMProvider: llmProvider,
|
||||
...(llmProvider === "openai"
|
||||
? {
|
||||
OpenAiKey: !!process.env.OPEN_AI_KEY,
|
||||
OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo",
|
||||
}
|
||||
: {}),
|
||||
|
||||
...(llmProvider === "azure"
|
||||
? {
|
||||
AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
|
||||
AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY,
|
||||
AzureOpenAiModelPref: process.env.OPEN_MODEL_PREF,
|
||||
AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
response.status(200).json({ results });
|
||||
} catch (e) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"lint": "yarn prettier --write ./endpoints ./models ./utils index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/openai": "^1.0.0-beta.3",
|
||||
"@googleapis/youtube": "^9.0.0",
|
||||
"@pinecone-database/pinecone": "^0.1.6",
|
||||
"archiver": "^5.3.1",
|
||||
|
@ -43,4 +44,4 @@
|
|||
"nodemon": "^2.0.22",
|
||||
"prettier": "^2.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
99
server/utils/AiProviders/azureOpenAi/index.js
Normal file
99
server/utils/AiProviders/azureOpenAi/index.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
class AzureOpenAi {
|
||||
constructor() {
|
||||
const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");
|
||||
const openai = new OpenAIClient(
|
||||
process.env.AZURE_OPENAI_ENDPOINT,
|
||||
new AzureKeyCredential(process.env.AZURE_OPENAI_KEY)
|
||||
);
|
||||
this.openai = openai;
|
||||
}
|
||||
|
||||
isValidChatModel(_modelName = "") {
|
||||
// The Azure user names their "models" as deployments and they can be any name
|
||||
// so we rely on the user to put in the correct deployment as only they would
|
||||
// know it.
|
||||
return true;
|
||||
}
|
||||
|
||||
async isSafe(_input = "") {
|
||||
// Not implemented by Azure OpenAI so must be stubbed
|
||||
return { safe: true, reasons: [] };
|
||||
}
|
||||
|
||||
async sendChat(chatHistory = [], prompt, workspace = {}) {
|
||||
const model = process.env.OPEN_MODEL_PREF;
|
||||
if (!model)
|
||||
throw new Error(
|
||||
"No OPEN_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5."
|
||||
);
|
||||
|
||||
const textResponse = await this.openai
|
||||
.getChatCompletions(
|
||||
model,
|
||||
[
|
||||
{ role: "system", content: "" },
|
||||
...chatHistory,
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
{
|
||||
temperature: Number(workspace?.openAiTemp ?? 0.7),
|
||||
n: 1,
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
if (!res.hasOwnProperty("choices"))
|
||||
throw new Error("OpenAI chat: No results!");
|
||||
if (res.choices.length === 0)
|
||||
throw new Error("OpenAI chat: No results length!");
|
||||
return res.choices[0].message.content;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
throw new Error(
|
||||
`AzureOpenAI::getChatCompletions failed with: ${error.message}`
|
||||
);
|
||||
});
|
||||
return textResponse;
|
||||
}
|
||||
|
||||
async getChatCompletion(messages = [], { temperature = 0.7 }) {
|
||||
const model = process.env.OPEN_MODEL_PREF;
|
||||
if (!model)
|
||||
throw new Error(
|
||||
"No OPEN_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5."
|
||||
);
|
||||
|
||||
const data = await this.openai.getChatCompletions(model, messages, {
|
||||
temperature,
|
||||
});
|
||||
if (!data.hasOwnProperty("choices")) return null;
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
|
||||
async embedTextInput(textInput) {
|
||||
const result = await this.embedChunks(textInput);
|
||||
return result?.[0] || [];
|
||||
}
|
||||
|
||||
async embedChunks(textChunks = []) {
|
||||
const textEmbeddingModel =
|
||||
process.env.EMBEDDING_MODEL_PREF || "text-embedding-ada-002";
|
||||
if (!textEmbeddingModel)
|
||||
throw new Error(
|
||||
"No EMBEDDING_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an embedding model."
|
||||
);
|
||||
|
||||
const { data = [] } = await this.openai.getEmbeddings(
|
||||
textEmbeddingModel,
|
||||
textChunks
|
||||
);
|
||||
return data.length > 0 &&
|
||||
data.every((embd) => embd.hasOwnProperty("embedding"))
|
||||
? data.map((embd) => embd.embedding)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AzureOpenAi,
|
||||
};
|
|
@ -1,7 +1,6 @@
|
|||
const { Configuration, OpenAIApi } = require("openai");
|
||||
|
||||
class OpenAi {
|
||||
constructor() {
|
||||
const { Configuration, OpenAIApi } = require("openai");
|
||||
const config = new Configuration({
|
||||
apiKey: process.env.OPEN_AI_KEY,
|
||||
});
|
||||
|
|
|
@ -3,7 +3,8 @@ const { OpenAi } = require("../AiProviders/openAi");
|
|||
const { WorkspaceChats } = require("../../models/workspaceChats");
|
||||
const { resetMemory } = require("./commands/reset");
|
||||
const moment = require("moment");
|
||||
const { getVectorDbClass } = require("../helpers");
|
||||
const { getVectorDbClass, getLLMProvider } = require("../helpers");
|
||||
const { AzureOpenAi } = require("../AiProviders/azureOpenAi");
|
||||
|
||||
function convertToChatHistory(history = []) {
|
||||
const formattedHistory = [];
|
||||
|
@ -66,7 +67,7 @@ async function chatWithWorkspace(
|
|||
user = null
|
||||
) {
|
||||
const uuid = uuidv4();
|
||||
const openai = new OpenAi();
|
||||
const LLMConnector = getLLMProvider();
|
||||
const VectorDb = getVectorDbClass();
|
||||
const command = grepCommand(message);
|
||||
|
||||
|
@ -74,7 +75,7 @@ async function chatWithWorkspace(
|
|||
return await VALID_COMMANDS[command](workspace, message, uuid, user);
|
||||
}
|
||||
|
||||
const { safe, reasons = [] } = await openai.isSafe(message);
|
||||
const { safe, reasons = [] } = await LLMConnector.isSafe(message);
|
||||
if (!safe) {
|
||||
return {
|
||||
id: uuid,
|
||||
|
@ -93,7 +94,11 @@ async function chatWithWorkspace(
|
|||
if (!hasVectorizedSpace || embeddingsCount === 0) {
|
||||
const rawHistory = await WorkspaceChats.forWorkspace(workspace.id);
|
||||
const chatHistory = convertToPromptHistory(rawHistory);
|
||||
const response = await openai.sendChat(chatHistory, message, workspace);
|
||||
const response = await LLMConnector.sendChat(
|
||||
chatHistory,
|
||||
message,
|
||||
workspace
|
||||
);
|
||||
const data = { text: response, sources: [], type: "chat" };
|
||||
|
||||
await WorkspaceChats.new({
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
function getVectorDbClass() {
|
||||
const { Pinecone } = require("../vectorDbProviders/pinecone");
|
||||
const { Chroma } = require("../vectorDbProviders/chroma");
|
||||
const { LanceDb } = require("../vectorDbProviders/lance");
|
||||
|
||||
const vectorSelection = process.env.VECTOR_DB || "pinecone";
|
||||
switch (vectorSelection) {
|
||||
case "pinecone":
|
||||
const { Pinecone } = require("../vectorDbProviders/pinecone");
|
||||
return Pinecone;
|
||||
case "chroma":
|
||||
const { Chroma } = require("../vectorDbProviders/chroma");
|
||||
return Chroma;
|
||||
case "lancedb":
|
||||
const { LanceDb } = require("../vectorDbProviders/lance");
|
||||
return LanceDb;
|
||||
default:
|
||||
throw new Error("ENV: No VECTOR_DB value found in environment!");
|
||||
}
|
||||
}
|
||||
|
||||
function getLLMProvider() {
|
||||
const vectorSelection = process.env.LLM_PROVIDER || "openai";
|
||||
switch (vectorSelection) {
|
||||
case "openai":
|
||||
const { OpenAi } = require("../AiProviders/openAi");
|
||||
return new OpenAi();
|
||||
case "azure":
|
||||
const { AzureOpenAi } = require("../AiProviders/azureOpenAi");
|
||||
return new AzureOpenAi();
|
||||
default:
|
||||
throw new Error("ENV: No LLM_PROVIDER value found in environment!");
|
||||
}
|
||||
}
|
||||
|
||||
function toChunks(arr, size) {
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
|
||||
arr.slice(i * size, i * size + size)
|
||||
|
@ -24,5 +37,6 @@ function toChunks(arr, size) {
|
|||
|
||||
module.exports = {
|
||||
getVectorDbClass,
|
||||
getLLMProvider,
|
||||
toChunks,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
const KEY_MAPPING = {
|
||||
LLMProvider: {
|
||||
envKey: "LLM_PROVIDER",
|
||||
checks: [isNotEmpty, supportedLLM],
|
||||
},
|
||||
// OpenAI Settings
|
||||
OpenAiKey: {
|
||||
envKey: "OPEN_AI_KEY",
|
||||
checks: [isNotEmpty, validOpenAIKey],
|
||||
|
@ -7,6 +12,25 @@ const KEY_MAPPING = {
|
|||
envKey: "OPEN_MODEL_PREF",
|
||||
checks: [isNotEmpty, validOpenAIModel],
|
||||
},
|
||||
// Azure OpenAI Settings
|
||||
AzureOpenAiEndpoint: {
|
||||
envKey: "AZURE_OPENAI_ENDPOINT",
|
||||
checks: [isNotEmpty, validAzureURL],
|
||||
},
|
||||
AzureOpenAiKey: {
|
||||
envKey: "AZURE_OPENAI_KEY",
|
||||
checks: [isNotEmpty],
|
||||
},
|
||||
AzureOpenAiModelPref: {
|
||||
envKey: "OPEN_MODEL_PREF",
|
||||
checks: [isNotEmpty],
|
||||
},
|
||||
AzureOpenAiEmbeddingModelPref: {
|
||||
envKey: "EMBEDDING_MODEL_PREF",
|
||||
checks: [isNotEmpty],
|
||||
},
|
||||
|
||||
// Vector Database Selection Settings
|
||||
VectorDB: {
|
||||
envKey: "VECTOR_DB",
|
||||
checks: [isNotEmpty, supportedVectorDB],
|
||||
|
@ -27,6 +51,8 @@ const KEY_MAPPING = {
|
|||
envKey: "PINECONE_INDEX",
|
||||
checks: [],
|
||||
},
|
||||
|
||||
// System Settings
|
||||
AuthToken: {
|
||||
envKey: "AUTH_TOKEN",
|
||||
checks: [],
|
||||
|
@ -56,6 +82,10 @@ function validOpenAIKey(input = "") {
|
|||
return input.startsWith("sk-") ? null : "OpenAI Key must start with sk-";
|
||||
}
|
||||
|
||||
function supportedLLM(input = "") {
|
||||
return ["openai", "azure"].includes(input);
|
||||
}
|
||||
|
||||
function validOpenAIModel(input = "") {
|
||||
const validModels = [
|
||||
"gpt-4",
|
||||
|
@ -85,6 +115,17 @@ function validChromaURL(input = "") {
|
|||
: null;
|
||||
}
|
||||
|
||||
function validAzureURL(input = "") {
|
||||
try {
|
||||
new URL(input);
|
||||
if (!input.includes("openai.azure.com"))
|
||||
return "URL must include openai.azure.com";
|
||||
return null;
|
||||
} catch {
|
||||
return "Not a valid URL";
|
||||
}
|
||||
}
|
||||
|
||||
// This will force update .env variables which for any which reason were not able to be parsed or
|
||||
// read from an ENV file as this seems to be a complicating step for many so allowing people to write
|
||||
// to the process will at least alleviate that issue. It does not perform comprehensive validity checks or sanity checks
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
const { ChromaClient, OpenAIEmbeddingFunction } = require("chromadb");
|
||||
const { Chroma: ChromaStore } = require("langchain/vectorstores/chroma");
|
||||
const { OpenAI } = require("langchain/llms/openai");
|
||||
const { VectorDBQAChain } = require("langchain/chains");
|
||||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
||||
const { ChromaClient } = require("chromadb");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { toChunks } = require("../../helpers");
|
||||
const { toChunks, getLLMProvider } = require("../../helpers");
|
||||
const { chatPrompt } = require("../../chats");
|
||||
const { OpenAi } = require("../../AiProviders/openAi");
|
||||
|
||||
const Chroma = {
|
||||
name: "Chroma",
|
||||
|
@ -49,22 +44,6 @@ const Chroma = {
|
|||
const namespace = await this.namespace(client, _namespace);
|
||||
return namespace?.vectorCount || 0;
|
||||
},
|
||||
embeddingFunc: function () {
|
||||
return new OpenAIEmbeddingFunction({
|
||||
openai_api_key: process.env.OPEN_AI_KEY,
|
||||
});
|
||||
},
|
||||
embedder: function () {
|
||||
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
|
||||
},
|
||||
llm: function ({ temperature = 0.7 }) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
return new OpenAI({
|
||||
openAIApiKey: process.env.OPEN_AI_KEY,
|
||||
modelName: model,
|
||||
temperature,
|
||||
});
|
||||
},
|
||||
similarityResponse: async function (client, namespace, queryVector) {
|
||||
const collection = await client.getCollection({ name: namespace });
|
||||
const result = {
|
||||
|
@ -131,7 +110,6 @@ const Chroma = {
|
|||
const collection = await client.getOrCreateCollection({
|
||||
name: namespace,
|
||||
metadata: { "hnsw:space": "cosine" },
|
||||
embeddingFunction: this.embeddingFunc(),
|
||||
});
|
||||
const { chunks } = cacheResult;
|
||||
const documentVectors = [];
|
||||
|
@ -176,10 +154,10 @@ const Chroma = {
|
|||
const textChunks = await textSplitter.splitText(pageContent);
|
||||
|
||||
console.log("Chunks created from document:", textChunks.length);
|
||||
const openAiConnector = new OpenAi();
|
||||
const LLMConnector = getLLMProvider();
|
||||
const documentVectors = [];
|
||||
const vectors = [];
|
||||
const vectorValues = await openAiConnector.embedChunks(textChunks);
|
||||
const vectorValues = await LLMConnector.embedChunks(textChunks);
|
||||
const submission = {
|
||||
ids: [],
|
||||
embeddings: [],
|
||||
|
@ -216,7 +194,6 @@ const Chroma = {
|
|||
const collection = await client.getOrCreateCollection({
|
||||
name: namespace,
|
||||
metadata: { "hnsw:space": "cosine" },
|
||||
embeddingFunction: this.embeddingFunc(),
|
||||
});
|
||||
|
||||
if (vectors.length > 0) {
|
||||
|
@ -245,7 +222,6 @@ const Chroma = {
|
|||
if (!(await this.namespaceExists(client, namespace))) return;
|
||||
const collection = await client.getCollection({
|
||||
name: namespace,
|
||||
embeddingFunction: this.embeddingFunc(),
|
||||
});
|
||||
|
||||
const knownDocuments = await DocumentVectors.where(`docId = '${docId}'`);
|
||||
|
@ -271,22 +247,36 @@ const Chroma = {
|
|||
};
|
||||
}
|
||||
|
||||
const vectorStore = await ChromaStore.fromExistingCollection(
|
||||
this.embedder(),
|
||||
{ collectionName: namespace, url: process.env.CHROMA_ENDPOINT }
|
||||
const LLMConnector = getLLMProvider();
|
||||
const queryVector = await LLMConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
queryVector
|
||||
);
|
||||
const model = this.llm({
|
||||
const prompt = {
|
||||
role: "system",
|
||||
content: `${chatPrompt(workspace)}
|
||||
Context:
|
||||
${contextTexts
|
||||
.map((text, i) => {
|
||||
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
|
||||
})
|
||||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, { role: "user", content: input }];
|
||||
const responseText = await LLMConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
const chain = VectorDBQAChain.fromLLM(model, vectorStore, {
|
||||
k: 5,
|
||||
returnSourceDocuments: true,
|
||||
// When we roll out own response we have separate metadata and texts,
|
||||
// so for source collection we need to combine them.
|
||||
const sources = sourceDocuments.map((metadata, i) => {
|
||||
return { metadata: { ...metadata, text: contextTexts[i] } };
|
||||
});
|
||||
const response = await chain.call({ query: input });
|
||||
return {
|
||||
response: response.text,
|
||||
sources: this.curateSources(response.sourceDocuments),
|
||||
response: responseText,
|
||||
sources: this.curateSources(sources),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -312,8 +302,8 @@ const Chroma = {
|
|||
};
|
||||
}
|
||||
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const LLMConnector = getLLMProvider();
|
||||
const queryVector = await LLMConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
|
@ -330,7 +320,7 @@ const Chroma = {
|
|||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
const responseText = await LLMConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
const lancedb = require("vectordb");
|
||||
const { toChunks } = require("../../helpers");
|
||||
const { toChunks, getLLMProvider } = require("../../helpers");
|
||||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { chatPrompt } = require("../../chats");
|
||||
const { OpenAi } = require("../../AiProviders/openAi");
|
||||
|
||||
const LanceDb = {
|
||||
uri: `${
|
||||
|
@ -169,11 +168,11 @@ const LanceDb = {
|
|||
const textChunks = await textSplitter.splitText(pageContent);
|
||||
|
||||
console.log("Chunks created from document:", textChunks.length);
|
||||
const openAiConnector = new OpenAi();
|
||||
const LLMConnector = getLLMProvider();
|
||||
const documentVectors = [];
|
||||
const vectors = [];
|
||||
const submissions = [];
|
||||
const vectorValues = await openAiConnector.embedChunks(textChunks);
|
||||
const vectorValues = await LLMConnector.embedChunks(textChunks);
|
||||
|
||||
if (!!vectorValues && vectorValues.length > 0) {
|
||||
for (const [i, vector] of vectorValues.entries()) {
|
||||
|
@ -230,9 +229,8 @@ const LanceDb = {
|
|||
};
|
||||
}
|
||||
|
||||
// LanceDB does not have langchainJS support so we roll our own here.
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const LLMConnector = getLLMProvider();
|
||||
const queryVector = await LLMConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
|
@ -249,7 +247,7 @@ const LanceDb = {
|
|||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, { role: "user", content: input }];
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
const responseText = await LLMConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
|
@ -281,8 +279,8 @@ const LanceDb = {
|
|||
};
|
||||
}
|
||||
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const LLMConnector = getLLMProvider();
|
||||
const queryVector = await LLMConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
client,
|
||||
namespace,
|
||||
|
@ -299,7 +297,7 @@ const LanceDb = {
|
|||
.join("")}`,
|
||||
};
|
||||
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
const responseText = await LLMConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
const { PineconeClient } = require("@pinecone-database/pinecone");
|
||||
const { PineconeStore } = require("langchain/vectorstores/pinecone");
|
||||
const { OpenAI } = require("langchain/llms/openai");
|
||||
const { VectorDBQAChain } = require("langchain/chains");
|
||||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { toChunks } = require("../../helpers");
|
||||
const { toChunks, getLLMProvider } = require("../../helpers");
|
||||
const { chatPrompt } = require("../../chats");
|
||||
const { OpenAi } = require("../../AiProviders/openAi");
|
||||
|
||||
const Pinecone = {
|
||||
name: "Pinecone",
|
||||
|
@ -29,17 +24,6 @@ const Pinecone = {
|
|||
if (!status.ready) throw new Error("Pinecode::Index not ready.");
|
||||
return { client, pineconeIndex, indexName: process.env.PINECONE_INDEX };
|
||||
},
|
||||
embedder: function () {
|
||||
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
|
||||
},
|
||||
llm: function ({ temperature = 0.7 }) {
|
||||
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
|
||||
return new OpenAI({
|
||||
openAIApiKey: process.env.OPEN_AI_KEY,
|
||||
modelName: model,
|
||||
temperature,
|
||||
});
|
||||
},
|
||||
totalIndicies: async function () {
|
||||
const { pineconeIndex } = await this.connect();
|
||||
const { namespaces } = await pineconeIndex.describeIndexStats1();
|
||||
|
@ -144,10 +128,10 @@ const Pinecone = {
|
|||
const textChunks = await textSplitter.splitText(pageContent);
|
||||
|
||||
console.log("Chunks created from document:", textChunks.length);
|
||||
const openAiConnector = new OpenAi();
|
||||
const LLMConnector = getLLMProvider();
|
||||
const documentVectors = [];
|
||||
const vectors = [];
|
||||
const vectorValues = await openAiConnector.embedChunks(textChunks);
|
||||
const vectorValues = await LLMConnector.embedChunks(textChunks);
|
||||
|
||||
if (!!vectorValues && vectorValues.length > 0) {
|
||||
for (const [i, vector] of vectorValues.entries()) {
|
||||
|
@ -246,22 +230,32 @@ const Pinecone = {
|
|||
};
|
||||
}
|
||||
|
||||
const vectorStore = await PineconeStore.fromExistingIndex(this.embedder(), {
|
||||
const LLMConnector = getLLMProvider();
|
||||
const queryVector = await LLMConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
pineconeIndex,
|
||||
namespace,
|
||||
});
|
||||
queryVector
|
||||
);
|
||||
const prompt = {
|
||||
role: "system",
|
||||
content: `${chatPrompt(workspace)}
|
||||
Context:
|
||||
${contextTexts
|
||||
.map((text, i) => {
|
||||
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
|
||||
})
|
||||
.join("")}`,
|
||||
};
|
||||
|
||||
const model = this.llm({
|
||||
const memory = [prompt, { role: "user", content: input }];
|
||||
const responseText = await LLMConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
const chain = VectorDBQAChain.fromLLM(model, vectorStore, {
|
||||
k: 5,
|
||||
returnSourceDocuments: true,
|
||||
});
|
||||
const response = await chain.call({ query: input });
|
||||
|
||||
return {
|
||||
response: response.text,
|
||||
sources: this.curateSources(response.sourceDocuments),
|
||||
response: responseText,
|
||||
sources: this.curateSources(sourceDocuments),
|
||||
message: false,
|
||||
};
|
||||
},
|
||||
|
@ -284,8 +278,8 @@ const Pinecone = {
|
|||
"Invalid namespace - has it been collected and seeded yet?"
|
||||
);
|
||||
|
||||
const openAiConnector = new OpenAi();
|
||||
const queryVector = await openAiConnector.embedTextInput(input);
|
||||
const LLMConnector = getLLMProvider();
|
||||
const queryVector = await LLMConnector.embedTextInput(input);
|
||||
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||
pineconeIndex,
|
||||
namespace,
|
||||
|
@ -303,7 +297,7 @@ const Pinecone = {
|
|||
};
|
||||
|
||||
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
|
||||
const responseText = await openAiConnector.getChatCompletion(memory, {
|
||||
const responseText = await LLMConnector.getChatCompletion(memory, {
|
||||
temperature: workspace?.openAiTemp ?? 0.7,
|
||||
});
|
||||
|
||||
|
|
106
server/yarn.lock
106
server/yarn.lock
|
@ -26,6 +26,93 @@
|
|||
pad-left "^2.1.0"
|
||||
tslib "^2.5.0"
|
||||
|
||||
"@azure-rest/core-client@^1.1.3":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@azure-rest/core-client/-/core-client-1.1.4.tgz#628381c3653f6dbae584ca6f2ae5f74a5c015526"
|
||||
integrity sha512-RUIQOA8T0WcbNlddr8hjl2MuC5GVRqmMwPXqBVsgvdKesLy+eg3y/6nf3qe2fvcJMI1gF6VtgU5U4hRaR4w4ag==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.1.0"
|
||||
"@azure/core-auth" "^1.3.0"
|
||||
"@azure/core-rest-pipeline" "^1.5.0"
|
||||
"@azure/core-tracing" "^1.0.1"
|
||||
"@azure/core-util" "^1.0.0"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/abort-controller@^1.0.0", "@azure/abort-controller@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249"
|
||||
integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==
|
||||
dependencies:
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.5.0.tgz#a41848c5c31cb3b7c84c409885267d55a2c92e44"
|
||||
integrity sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
"@azure/core-util" "^1.1.0"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-lro@^2.5.3":
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.5.4.tgz#b21e2bcb8bd9a8a652ff85b61adeea51a8055f90"
|
||||
integrity sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
"@azure/core-util" "^1.2.0"
|
||||
"@azure/logger" "^1.0.0"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-rest-pipeline@^1.10.2", "@azure/core-rest-pipeline@^1.5.0":
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.0.tgz#a36dd361807494845522824532c076daa27c2786"
|
||||
integrity sha512-+MnSB0vGZjszSzr5AW8z93/9fkDu2RLtWmAN8gskURq7EW2sSwqy8jZa0V26rjuBVkwhdA3Hw8z3VWoeBUOw+A==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
"@azure/core-auth" "^1.4.0"
|
||||
"@azure/core-tracing" "^1.0.1"
|
||||
"@azure/core-util" "^1.3.0"
|
||||
"@azure/logger" "^1.0.0"
|
||||
form-data "^4.0.0"
|
||||
http-proxy-agent "^5.0.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-tracing@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503"
|
||||
integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==
|
||||
dependencies:
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.3.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.4.0.tgz#c120a56b3e48a9e4d20619a0b00268ae9de891c7"
|
||||
integrity sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/logger@^1.0.0", "@azure/logger@^1.0.3":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1"
|
||||
integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==
|
||||
dependencies:
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/openai@^1.0.0-beta.3":
|
||||
version "1.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@azure/openai/-/openai-1.0.0-beta.3.tgz#bf4f5ec0a5644b3a9ce4372620856a65e7721e24"
|
||||
integrity sha512-gW4odbuy/X/W34SdvXomj/JzR09MyMHCY5Kd2ZxJkQo3IUGqJXz1rEv6QER7IAGgBFgNawE97K6UuJfMmoT0rw==
|
||||
dependencies:
|
||||
"@azure-rest/core-client" "^1.1.3"
|
||||
"@azure/core-auth" "^1.4.0"
|
||||
"@azure/core-lro" "^2.5.3"
|
||||
"@azure/core-rest-pipeline" "^1.10.2"
|
||||
"@azure/logger" "^1.0.3"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@fortaine/fetch-event-source@^3.0.6":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz#b8552a2ca2c5202f5699b93a92be0188d422b06e"
|
||||
|
@ -86,6 +173,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
|
||||
|
||||
"@types/command-line-args@5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.0.tgz#adbb77980a1cc376bb208e3f4142e907410430f6"
|
||||
|
@ -1128,6 +1220,15 @@ http-proxy-agent@^4.0.1:
|
|||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
http-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
|
||||
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
|
||||
dependencies:
|
||||
"@tootallnate/once" "2"
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
https-proxy-agent@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
|
||||
|
@ -2301,6 +2402,11 @@ tr46@~0.0.3:
|
|||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
tslib@^2.2.0, tslib@^2.4.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
|
||||
integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
|
||||
|
||||
tslib@^2.5.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3"
|
||||
|
|
Loading…
Add table
Reference in a new issue