mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-19 12:40:09 +01:00
merge with master
This commit is contained in:
commit
c65ab6d863
32
README.md
32
README.md
@ -67,9 +67,9 @@ Some cool features of AnythingLLM
|
||||
- Extremely efficient cost-saving measures for managing very large documents. You'll never pay to embed a massive document or transcript more than once. 90% more cost effective than other document chatbot solutions.
|
||||
- Full Developer API for custom integrations!
|
||||
|
||||
### Supported LLMs, Embedders, Transcriptions models, and Vector Databases
|
||||
### Supported LLMs, Embedder Models, Speech models, and Vector Databases
|
||||
|
||||
**Supported LLMs:**
|
||||
**Language Learning Models:**
|
||||
|
||||
- [Any open-source llama.cpp compatible model](/server/storage/models/README.md#text-generation-llm-selection)
|
||||
- [OpenAI](https://openai.com)
|
||||
@ -88,9 +88,10 @@ Some cool features of AnythingLLM
|
||||
- [Groq](https://groq.com/)
|
||||
- [Cohere](https://cohere.com/)
|
||||
- [KoboldCPP](https://github.com/LostRuins/koboldcpp)
|
||||
- [LiteLLM](https://github.com/BerriAI/litellm)
|
||||
- [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui)
|
||||
|
||||
**Supported Embedding models:**
|
||||
**Embedder models:**
|
||||
|
||||
- [AnythingLLM Native Embedder](/server/storage/models/README.md) (default)
|
||||
- [OpenAI](https://openai.com)
|
||||
@ -100,12 +101,22 @@ Some cool features of AnythingLLM
|
||||
- [LM Studio (all)](https://lmstudio.ai)
|
||||
- [Cohere](https://cohere.com/)
|
||||
|
||||
**Supported Transcription models:**
|
||||
**Audio Transcription models:**
|
||||
|
||||
- [AnythingLLM Built-in](https://github.com/Mintplex-Labs/anything-llm/tree/master/server/storage/models#audiovideo-transcription) (default)
|
||||
- [OpenAI](https://openai.com/)
|
||||
|
||||
**Supported Vector Databases:**
|
||||
**TTS (text-to-speech) support:**
|
||||
|
||||
- Native Browser Built-in (default)
|
||||
- [OpenAI TTS](https://platform.openai.com/docs/guides/text-to-speech/voice-options)
|
||||
- [ElevenLabs](https://elevenlabs.io/)
|
||||
|
||||
**STT (speech-to-text) support:**
|
||||
|
||||
- Native Browser Built-in (default)
|
||||
|
||||
**Vector Databases:**
|
||||
|
||||
- [LanceDB](https://github.com/lancedb/lancedb) (default)
|
||||
- [Astra DB](https://www.datastax.com/products/datastax-astra)
|
||||
@ -122,8 +133,9 @@ This monorepo consists of three main sections:
|
||||
|
||||
- `frontend`: A viteJS + React frontend that you can run to easily create and manage all your content the LLM can use.
|
||||
- `server`: A NodeJS express server to handle all the interactions and do all the vectorDB management and LLM interactions.
|
||||
- `docker`: Docker instructions and build process + information for building from source.
|
||||
- `collector`: NodeJS express server that process and parses documents from the UI.
|
||||
- `docker`: Docker instructions and build process + information for building from source.
|
||||
- `embed`: Code specifically for generation of the [embed widget](./embed/README.md).
|
||||
|
||||
## 🛳 Self Hosting
|
||||
|
||||
@ -132,9 +144,9 @@ 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]][do-deploy] | [![Deploy on Render.com][render-btn]][render-deploy] |
|
||||
|
||||
| Railway |
|
||||
| --------------------------------------------------- |
|
||||
| [![Deploy on Railway][railway-btn]][railway-deploy] |
|
||||
| Railway | RepoCloud |
|
||||
| --- | --- |
|
||||
| [![Deploy on Railway][railway-btn]][railway-deploy] | [![Deploy on RepoCloud][repocloud-btn]][repocloud-deploy] |
|
||||
|
||||
[or set up a production AnythingLLM instance without Docker →](./BARE_METAL.md)
|
||||
|
||||
@ -223,3 +235,5 @@ This project is [MIT](./LICENSE) licensed.
|
||||
[render-deploy]: https://render.com/deploy?repo=https://github.com/Mintplex-Labs/anything-llm&branch=render
|
||||
[railway-btn]: https://railway.app/button.svg
|
||||
[railway-deploy]: https://railway.app/template/HNSCS1?referralCode=WFgJkn
|
||||
[repocloud-btn]: https://d16t0pc4846x52.cloudfront.net/deploylobe.svg
|
||||
[repocloud-deploy]: https://repocloud.io/details/?app_id=276
|
||||
|
@ -1,19 +1,23 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { v4 } = require("uuid");
|
||||
const defaultWhisper = "Xenova/whisper-small"; // Model Card: https://huggingface.co/Xenova/whisper-small
|
||||
const fileSize = {
|
||||
"Xenova/whisper-small": "250mb",
|
||||
"Xenova/whisper-large": "1.56GB",
|
||||
};
|
||||
|
||||
class LocalWhisper {
|
||||
constructor() {
|
||||
// Model Card: https://huggingface.co/Xenova/whisper-small
|
||||
this.model = "Xenova/whisper-small";
|
||||
constructor({ options }) {
|
||||
this.model = options?.WhisperModelPref ?? defaultWhisper;
|
||||
this.fileSize = fileSize[this.model];
|
||||
this.cacheDir = path.resolve(
|
||||
process.env.STORAGE_DIR
|
||||
? path.resolve(process.env.STORAGE_DIR, `models`)
|
||||
: path.resolve(__dirname, `../../../server/storage/models`)
|
||||
);
|
||||
|
||||
this.modelPath = path.resolve(this.cacheDir, "Xenova", "whisper-small");
|
||||
|
||||
this.modelPath = path.resolve(this.cacheDir, ...this.model.split("/"));
|
||||
// Make directory when it does not exist in existing installations
|
||||
if (!fs.existsSync(this.cacheDir))
|
||||
fs.mkdirSync(this.cacheDir, { recursive: true });
|
||||
@ -104,7 +108,7 @@ class LocalWhisper {
|
||||
async client() {
|
||||
if (!fs.existsSync(this.modelPath)) {
|
||||
this.#log(
|
||||
`The native whisper model has never been run and will be downloaded right now. Subsequent runs will be faster. (~250MB)`
|
||||
`The native whisper model has never been run and will be downloaded right now. Subsequent runs will be faster. (~${this.fileSize})`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -12,18 +12,23 @@ const {
|
||||
function validSpaceUrl(spaceUrl = "") {
|
||||
// Atlassian default URL match
|
||||
const atlassianPattern = new UrlPattern(
|
||||
"https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)/*"
|
||||
"https\\://(:subdomain).atlassian.net/wiki/spaces/(:spaceKey)*"
|
||||
);
|
||||
const atlassianMatch = atlassianPattern.match(spaceUrl);
|
||||
if (atlassianMatch) {
|
||||
return { valid: true, result: atlassianMatch };
|
||||
}
|
||||
|
||||
// Custom Confluence URL match
|
||||
const customPattern = new UrlPattern(
|
||||
"https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)/*"
|
||||
);
|
||||
const customMatch = customPattern.match(spaceUrl);
|
||||
let customMatch = null;
|
||||
[
|
||||
"https\\://(:subdomain.):domain.:tld/wiki/spaces/(:spaceKey)*", // Custom Confluence space
|
||||
"https\\://(:subdomain.):domain.:tld/display/(:spaceKey)*", // Custom Confluence space + Human-readable space tag.
|
||||
].forEach((matchPattern) => {
|
||||
if (!!customMatch) return;
|
||||
const pattern = new UrlPattern(matchPattern);
|
||||
customMatch = pattern.match(spaceUrl);
|
||||
});
|
||||
|
||||
if (customMatch) {
|
||||
customMatch.customDomain =
|
||||
(customMatch.subdomain ? `${customMatch.subdomain}.` : "") + //
|
||||
|
@ -82,6 +82,12 @@ GID='1000'
|
||||
# GENERIC_OPEN_AI_MODEL_TOKEN_LIMIT=4096
|
||||
# GENERIC_OPEN_AI_API_KEY=sk-123abc
|
||||
|
||||
# LLM_PROVIDER='litellm'
|
||||
# LITE_LLM_MODEL_PREF='gpt-3.5-turbo'
|
||||
# LITE_LLM_MODEL_TOKEN_LIMIT=4096
|
||||
# LITE_LLM_BASE_PATH='http://127.0.0.1:4000'
|
||||
# LITE_LLM_API_KEY='sk-123abc'
|
||||
|
||||
# LLM_PROVIDER='cohere'
|
||||
# COHERE_API_KEY=
|
||||
# COHERE_MODEL_PREF='command-r'
|
||||
@ -118,6 +124,10 @@ GID='1000'
|
||||
# COHERE_API_KEY=
|
||||
# EMBEDDING_MODEL_PREF='embed-english-v3.0'
|
||||
|
||||
# EMBEDDING_ENGINE='voyageai'
|
||||
# VOYAGEAI_API_KEY=
|
||||
# EMBEDDING_MODEL_PREF='voyage-large-2-instruct'
|
||||
|
||||
###########################################
|
||||
######## Vector Database Selection ########
|
||||
###########################################
|
||||
|
@ -1,4 +1,8 @@
|
||||
<!doctype html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<html lang="en">
|
||||
|
||||
<body>
|
||||
|
@ -28,18 +28,18 @@ export default function App() {
|
||||
|
||||
const position = embedSettings.position || "bottom-right";
|
||||
const windowWidth = embedSettings.windowWidth
|
||||
? `md:max-w-[${embedSettings.windowWidth}]`
|
||||
: "md:max-w-[400px]";
|
||||
? `max-w-[${embedSettings.windowWidth}]`
|
||||
: "max-w-[400px]";
|
||||
const windowHeight = embedSettings.windowHeight
|
||||
? `md:max-h-[${embedSettings.windowHeight}]`
|
||||
: "md:max-h-[700px]";
|
||||
? `max-h-[${embedSettings.windowHeight}]`
|
||||
: "max-h-[700px]";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head />
|
||||
<div className={`fixed inset-0 z-50 ${isChatOpen ? "block" : "hidden"}`}>
|
||||
<div
|
||||
className={`${windowHeight} ${windowWidth} h-full w-full bg-white md:fixed md:bottom-0 md:right-0 md:mb-4 md:mr-4 md:rounded-2xl md:border md:border-gray-300 md:shadow-[0_4px_14px_rgba(0,0,0,0.25)] ${positionClasses[position]}`}
|
||||
className={`${windowHeight} ${windowWidth} h-full w-full bg-white fixed bottom-0 right-0 mb-4 md:mr-4 rounded-2xl border border-gray-300 shadow-[0_4px_14px_rgba(0,0,0,0.25)] ${positionClasses[position]}`}
|
||||
id="anything-llm-chat"
|
||||
>
|
||||
{isChatOpen && (
|
||||
|
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -9,10 +9,8 @@ lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
lib
|
||||
dist-ssr
|
||||
*.local
|
||||
!frontend/components/lib
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,50 @@
|
||||
export default function VoyageAiOptions({ settings }) {
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="w-full flex items-center gap-4">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="VoyageAiApiKey"
|
||||
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="Voyage AI API Key"
|
||||
defaultValue={settings?.VoyageAiApiKey ? "*".repeat(20) : ""}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Model Preference
|
||||
</label>
|
||||
<select
|
||||
name="EmbeddingModelPref"
|
||||
required={true}
|
||||
defaultValue={settings?.EmbeddingModelPref}
|
||||
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
<optgroup label="Available embedding models">
|
||||
{[
|
||||
"voyage-large-2-instruct",
|
||||
"voyage-law-2",
|
||||
"voyage-code-2",
|
||||
"voyage-large-2",
|
||||
"voyage-2",
|
||||
].map((model) => {
|
||||
return (
|
||||
<option key={model} value={model}>
|
||||
{model}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -14,6 +14,8 @@ import {
|
||||
import React, { useEffect, useState } from "react";
|
||||
import SettingsButton from "../SettingsButton";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
export const MAX_ICONS = 3;
|
||||
export const ICON_COMPONENTS = {
|
||||
@ -47,36 +49,48 @@ export default function Footer() {
|
||||
return (
|
||||
<div className="flex justify-center mb-2">
|
||||
<div className="flex space-x-4">
|
||||
<ToolTipWrapper id="open-github">
|
||||
<a
|
||||
href={paths.github()}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
aria-label="Find us on Github"
|
||||
data-tooltip-id="open-github"
|
||||
data-tooltip-content="View source code on Github"
|
||||
>
|
||||
<GithubLogo weight="fill" className="h-5 w-5 " />
|
||||
</a>
|
||||
</ToolTipWrapper>
|
||||
<ToolTipWrapper id="open-documentation">
|
||||
<a
|
||||
href={paths.docs()}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
className="w-fit transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
aria-label="Docs"
|
||||
data-tooltip-id="open-documentation"
|
||||
data-tooltip-content="Open AnythingLLM help docs"
|
||||
>
|
||||
<BookOpen weight="fill" className="h-5 w-5 " />
|
||||
</a>
|
||||
</ToolTipWrapper>
|
||||
<ToolTipWrapper id="open-discord">
|
||||
<a
|
||||
href={paths.discord()}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
aria-label="Join our Discord server"
|
||||
data-tooltip-id="open-discord"
|
||||
data-tooltip-content="Join the AnythingLLM Discord"
|
||||
>
|
||||
<DiscordLogo
|
||||
weight="fill"
|
||||
className="h-5 w-5 stroke-slate-200 group-hover:stroke-slate-200"
|
||||
/>
|
||||
</a>
|
||||
</ToolTipWrapper>
|
||||
{!isMobile && <SettingsButton />}
|
||||
</div>
|
||||
</div>
|
||||
@ -105,3 +119,17 @@ export default function Footer() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ToolTipWrapper({ id = v4(), children }) {
|
||||
return (
|
||||
<div className="flex w-fit">
|
||||
{children}
|
||||
<Tooltip
|
||||
id={id}
|
||||
place="top"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs z-99"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export default function GeminiLLMOptions({ settings }) {
|
||||
</div>
|
||||
|
||||
{!settings?.credentialsOnly && (
|
||||
<>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Chat Model Selection
|
||||
@ -38,6 +39,27 @@ export default function GeminiLLMOptions({ settings }) {
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Safety Setting
|
||||
</label>
|
||||
<select
|
||||
name="GeminiSafetySetting"
|
||||
defaultValue={
|
||||
settings?.GeminiSafetySetting || "BLOCK_MEDIUM_AND_ABOVE"
|
||||
}
|
||||
required={true}
|
||||
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
<option value="BLOCK_NONE">None</option>
|
||||
<option value="BLOCK_ONLY_HIGH">Block few</option>
|
||||
<option value="BLOCK_MEDIUM_AND_ABOVE">
|
||||
Block some (default)
|
||||
</option>
|
||||
<option value="BLOCK_LOW_AND_ABOVE">Block most</option>
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
export default function GenericOpenAiOptions({ settings }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
@ -45,6 +46,8 @@ export default function GenericOpenAiOptions({ settings }) {
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-x-4 flex-wrap">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Token context window
|
||||
@ -77,5 +80,6 @@ export default function GenericOpenAiOptions({ settings }) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
148
frontend/src/components/LLMSelection/LiteLLMOptions/index.jsx
Normal file
148
frontend/src/components/LLMSelection/LiteLLMOptions/index.jsx
Normal file
@ -0,0 +1,148 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import System from "@/models/system";
|
||||
|
||||
export default function LiteLLMOptions({ settings }) {
|
||||
const [basePathValue, setBasePathValue] = useState(settings?.LiteLLMBasePath);
|
||||
const [basePath, setBasePath] = useState(settings?.LiteLLMBasePath);
|
||||
const [apiKeyValue, setApiKeyValue] = useState(settings?.LiteLLMAPIKey);
|
||||
const [apiKey, setApiKey] = useState(settings?.LiteLLMAPIKey);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="w-full flex items-center gap-4">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Base URL
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
name="LiteLLMBasePath"
|
||||
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="http://127.0.0.1:4000"
|
||||
defaultValue={settings?.LiteLLMBasePath}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
onChange={(e) => setBasePathValue(e.target.value)}
|
||||
onBlur={() => setBasePath(basePathValue)}
|
||||
/>
|
||||
</div>
|
||||
<LiteLLMModelSelection
|
||||
settings={settings}
|
||||
basePath={basePath}
|
||||
apiKey={apiKey}
|
||||
/>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Token context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="LiteLLMTokenLimit"
|
||||
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="4096"
|
||||
min={1}
|
||||
onScroll={(e) => e.target.blur()}
|
||||
defaultValue={settings?.LiteLLMTokenLimit}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex items-center gap-4">
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="flex flex-col gap-y-1 mb-4">
|
||||
<label className="text-white text-sm font-semibold flex items-center gap-x-2">
|
||||
API Key <p className="!text-xs !italic !font-thin">optional</p>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="LiteLLMAPIKey"
|
||||
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="sk-mysecretkey"
|
||||
defaultValue={settings?.LiteLLMAPIKey ? "*".repeat(20) : ""}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
onChange={(e) => setApiKeyValue(e.target.value)}
|
||||
onBlur={() => setApiKey(apiKeyValue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LiteLLMModelSelection({ settings, basePath = null, apiKey = 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(
|
||||
"litellm",
|
||||
typeof apiKey === "boolean" ? null : apiKey,
|
||||
basePath
|
||||
);
|
||||
setCustomModels(models || []);
|
||||
setLoading(false);
|
||||
}
|
||||
findCustomModels();
|
||||
}, [basePath, apiKey]);
|
||||
|
||||
if (loading || customModels.length == 0) {
|
||||
return (
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Chat Model Selection
|
||||
</label>
|
||||
<select
|
||||
name="LiteLLMModelPref"
|
||||
disabled={true}
|
||||
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
<option disabled={true} selected={true}>
|
||||
{basePath?.includes("/v1")
|
||||
? "-- loading available models --"
|
||||
: "-- waiting for URL --"}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Chat Model Selection
|
||||
</label>
|
||||
<select
|
||||
name="LiteLLMModelPref"
|
||||
required={true}
|
||||
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
{customModels.length > 0 && (
|
||||
<optgroup label="Your loaded models">
|
||||
{customModels.map((model) => {
|
||||
return (
|
||||
<option
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
selected={settings.LiteLLMModelPref === model.id}
|
||||
>
|
||||
{model.id}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -3,7 +3,7 @@ import System from "@/models/system";
|
||||
import showToast from "@/utils/toast";
|
||||
import pluralize from "pluralize";
|
||||
import { TagsInput } from "react-tag-input-component";
|
||||
import { Warning } from "@phosphor-icons/react";
|
||||
import { Info, Warning } from "@phosphor-icons/react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
const DEFAULT_BRANCHES = ["main", "master"];
|
||||
@ -92,45 +92,7 @@ export default function GithubOptions() {
|
||||
<p className="font-bold text-white">Github Access Token</p>{" "}
|
||||
<p className="text-xs text-white/50 font-light flex items-center">
|
||||
optional
|
||||
{!accessToken && (
|
||||
<Warning
|
||||
size={14}
|
||||
className="ml-1 text-orange-500 cursor-pointer"
|
||||
data-tooltip-id="access-token-tooltip"
|
||||
data-tooltip-place="right"
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
delayHide={300}
|
||||
id="access-token-tooltip"
|
||||
className="max-w-xs"
|
||||
clickable={true}
|
||||
>
|
||||
<p className="text-sm">
|
||||
Without a{" "}
|
||||
<a
|
||||
href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Personal Access Token
|
||||
</a>
|
||||
, the GitHub API may limit the number of files that
|
||||
can be collected due to rate limits. You can{" "}
|
||||
<a
|
||||
href="https://github.com/settings/personal-access-tokens/new"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
create a temporary Access Token
|
||||
</a>{" "}
|
||||
to avoid this issue.
|
||||
</p>
|
||||
</Tooltip>
|
||||
<PATTooltip accessToken={accessToken} />
|
||||
</p>
|
||||
</label>
|
||||
<p className="text-xs font-normal text-white/50">
|
||||
@ -180,6 +142,7 @@ export default function GithubOptions() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-2 w-full pr-10">
|
||||
<PATAlert accessToken={accessToken} />
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
@ -269,3 +232,78 @@ function GitHubBranchSelection({ repo, accessToken }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PATAlert({ accessToken }) {
|
||||
if (!!accessToken) return null;
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Info className="shrink-0" size={25} />
|
||||
<p className="text-sm">
|
||||
Without filling out the <b>Github Access Token</b> this data connector
|
||||
will only be able to collect the <b>top-level</b> files of the repo
|
||||
due to GitHub's public API rate-limits.
|
||||
<br />
|
||||
<br />
|
||||
<a
|
||||
href="https://github.com/settings/personal-access-tokens/new"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{" "}
|
||||
Get a free Personal Access Token with a GitHub account here.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PATTooltip({ accessToken }) {
|
||||
if (!!accessToken) return null;
|
||||
return (
|
||||
<>
|
||||
{!accessToken && (
|
||||
<Warning
|
||||
size={14}
|
||||
className="ml-1 text-orange-500 cursor-pointer"
|
||||
data-tooltip-id="access-token-tooltip"
|
||||
data-tooltip-place="right"
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
delayHide={300}
|
||||
id="access-token-tooltip"
|
||||
className="max-w-xs"
|
||||
clickable={true}
|
||||
>
|
||||
<p className="text-sm">
|
||||
Without a{" "}
|
||||
<a
|
||||
href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Personal Access Token
|
||||
</a>
|
||||
, the GitHub API may limit the number of files that can be collected
|
||||
due to rate limits. You can{" "}
|
||||
<a
|
||||
href="https://github.com/settings/personal-access-tokens/new"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
create a temporary Access Token
|
||||
</a>{" "}
|
||||
to avoid this issue.
|
||||
</p>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import paths from "@/utils/paths";
|
||||
import { ArrowUUpLeft, Wrench } from "@phosphor-icons/react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMatch } from "react-router-dom";
|
||||
import { ToolTipWrapper } from "../Footer";
|
||||
|
||||
export default function SettingsButton() {
|
||||
const isInSettings = !!useMatch("/settings/*");
|
||||
@ -12,22 +13,32 @@ export default function SettingsButton() {
|
||||
|
||||
if (isInSettings)
|
||||
return (
|
||||
<ToolTipWrapper id="go-home">
|
||||
<Link
|
||||
to={paths.home()}
|
||||
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
aria-label="Home"
|
||||
data-tooltip-id="go-home"
|
||||
data-tooltip-content="Back to workspaces"
|
||||
>
|
||||
<ArrowUUpLeft className="h-5 w-5" weight="fill" />
|
||||
</Link>
|
||||
</ToolTipWrapper>
|
||||
);
|
||||
|
||||
return (
|
||||
<ToolTipWrapper id="open-settings">
|
||||
<Link
|
||||
to={!!user?.role ? paths.settings.system() : paths.settings.appearance()}
|
||||
to={
|
||||
!!user?.role ? paths.settings.system() : paths.settings.appearance()
|
||||
}
|
||||
className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
aria-label="Settings"
|
||||
data-tooltip-id="open-settings"
|
||||
data-tooltip-content="Open settings"
|
||||
>
|
||||
<Wrench className="h-5 w-5" weight="fill" />
|
||||
</Link>
|
||||
</ToolTipWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
<Option
|
||||
href={paths.settings.embedSetup()}
|
||||
childLinks={[paths.settings.embedChats()]}
|
||||
btnText="Embedded Chat"
|
||||
btnText="Chat Embed Widgets"
|
||||
icon={<CodeBlock className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -338,7 +338,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
<>
|
||||
<Option
|
||||
href={paths.settings.embedChats()}
|
||||
btnText="Embedded Chat History"
|
||||
btnText="Chat Embed History"
|
||||
icon={<Barcode className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
|
@ -84,6 +84,7 @@ function ElevenLabsModelSelection({ apiKey, settings }) {
|
||||
<select
|
||||
name="TTSElevenLabsVoiceModel"
|
||||
required={true}
|
||||
defaultValue={settings?.TTSElevenLabsVoiceModel}
|
||||
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
{Object.keys(groupedModels)
|
||||
@ -91,11 +92,7 @@ function ElevenLabsModelSelection({ apiKey, settings }) {
|
||||
.map((organization) => (
|
||||
<optgroup key={organization} label={organization}>
|
||||
{groupedModels[organization].map((model) => (
|
||||
<option
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
selected={settings?.OpenAiModelPref === model.id}
|
||||
>
|
||||
<option key={model.id} value={model.id}>
|
||||
{model.name}
|
||||
</option>
|
||||
))}
|
||||
|
@ -35,7 +35,11 @@ export default function OpenAiTextToSpeechOptions({ settings }) {
|
||||
>
|
||||
{["alloy", "echo", "fable", "onyx", "nova", "shimmer"].map(
|
||||
(voice) => {
|
||||
return <option value={voice}>{toProperCase(voice)}</option>;
|
||||
return (
|
||||
<option key={voice} value={voice}>
|
||||
{toProperCase(voice)}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</select>
|
||||
|
@ -1,38 +1,89 @@
|
||||
import { Gauge } from "@phosphor-icons/react";
|
||||
export default function NativeTranscriptionOptions() {
|
||||
import { useState } from "react";
|
||||
|
||||
export default function NativeTranscriptionOptions({ settings }) {
|
||||
const [model, setModel] = useState(settings?.WhisperModelPref);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Gauge size={25} />
|
||||
<p className="text-sm">
|
||||
Using the local whisper model on machines with limited RAM or CPU
|
||||
can stall AnythingLLM when processing media files.
|
||||
<br />
|
||||
We recommend at least 2GB of RAM and upload files <10Mb.
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
The built-in model will automatically download on the first use.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<LocalWarning model={model} />
|
||||
<div className="w-full flex items-center gap-4">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Model Selection
|
||||
</label>
|
||||
<select
|
||||
disabled={true}
|
||||
name="WhisperModelPref"
|
||||
defaultValue={model}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
<option disabled={true} selected={true}>
|
||||
Xenova/whisper-small
|
||||
{["Xenova/whisper-small", "Xenova/whisper-large"].map(
|
||||
(value, i) => {
|
||||
return (
|
||||
<option key={i} value={value}>
|
||||
{value}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LocalWarning({ model }) {
|
||||
switch (model) {
|
||||
case "Xenova/whisper-small":
|
||||
return <WhisperSmall />;
|
||||
case "Xenova/whisper-large":
|
||||
return <WhisperLarge />;
|
||||
default:
|
||||
return <WhisperSmall />;
|
||||
}
|
||||
}
|
||||
|
||||
function WhisperSmall() {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Gauge size={25} />
|
||||
<p className="text-sm">
|
||||
Running the <b>whisper-small</b> model on a machine with limited RAM
|
||||
or CPU can stall AnythingLLM when processing media files.
|
||||
<br />
|
||||
We recommend at least 2GB of RAM and upload files <10Mb.
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
This model will automatically download on the first use. (250mb)
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WhisperLarge() {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Gauge size={25} />
|
||||
<p className="text-sm">
|
||||
Using the <b>whisper-large</b> model on machines with limited RAM or
|
||||
CPU can stall AnythingLLM when processing media files. This model is
|
||||
substantially larger than the whisper-small.
|
||||
<br />
|
||||
We recommend at least 8GB of RAM and upload files <10Mb.
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
This model will automatically download on the first use. (1.56GB)
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -115,6 +115,11 @@ function SkeletonLine() {
|
||||
);
|
||||
}
|
||||
|
||||
function omitChunkHeader(text) {
|
||||
if (!text.startsWith("<document_metadata>")) return text;
|
||||
return text.split("</document_metadata>")[1].trim();
|
||||
}
|
||||
|
||||
function CitationDetailModal({ source, onClose }) {
|
||||
const { references, title, chunks } = source;
|
||||
const { isUrl, text: webpageUrl, href: linkTo } = parseChunkSource(source);
|
||||
@ -167,7 +172,7 @@ function CitationDetailModal({ source, onClose }) {
|
||||
<div key={idx} className="pt-6 text-white">
|
||||
<div className="flex flex-col w-full justify-start pb-6 gap-y-1">
|
||||
<p className="text-white whitespace-pre-line">
|
||||
{HTMLDecode(text)}
|
||||
{HTMLDecode(omitChunkHeader(text))}
|
||||
</p>
|
||||
|
||||
{!!score && (
|
||||
|
@ -32,8 +32,7 @@ const Actions = ({
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<div className="flex justify-start items-center gap-x-4">
|
||||
<CopyMessage message={message} />
|
||||
{isLastMessage &&
|
||||
!message?.includes("Workspace chat memory was reset!") && (
|
||||
{isLastMessage && (
|
||||
<RegenerateMessage
|
||||
regenerateMessage={regenerateMessage}
|
||||
slug={slug}
|
||||
@ -127,6 +126,7 @@ function CopyMessage({ message }) {
|
||||
}
|
||||
|
||||
function RegenerateMessage({ regenerateMessage, chatId }) {
|
||||
if (!chatId) return null;
|
||||
return (
|
||||
<div className="mt-3 relative">
|
||||
<button
|
||||
|
@ -57,7 +57,7 @@ const HistoricalMessage = ({
|
||||
<div className="flex gap-x-5">
|
||||
<div className="relative w-[35px] h-[35px] rounded-full flex-shrink-0 overflow-hidden" />
|
||||
<Actions
|
||||
message={DOMPurify.sanitize(message)}
|
||||
message={message}
|
||||
feedbackScore={feedbackScore}
|
||||
chatId={chatId}
|
||||
slug={workspace?.slug}
|
||||
|
@ -31,10 +31,7 @@ export default function EditPresetModal({
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
const confirmDelete = window.confirm(
|
||||
"Are you sure you want to delete this preset?"
|
||||
);
|
||||
if (!confirmDelete) return;
|
||||
if (!window.confirm("Are you sure you want to delete this preset?")) return;
|
||||
|
||||
setDeleting(true);
|
||||
await onDelete(preset.id);
|
||||
|
@ -3,7 +3,6 @@ import SlashCommandsButton, {
|
||||
SlashCommands,
|
||||
useSlashCommands,
|
||||
} from "./SlashCommands";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import debounce from "lodash.debounce";
|
||||
import { PaperPlaneRight } from "@phosphor-icons/react";
|
||||
import StopGenerationButton from "./StopGenerationButton";
|
||||
@ -13,6 +12,7 @@ import AvailableAgentsButton, {
|
||||
} from "./AgentMenu";
|
||||
import TextSizeButton from "./TextSizeMenu";
|
||||
import SpeechToText from "./SpeechToText";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
export const PROMPT_INPUT_EVENT = "set_prompt_input";
|
||||
export default function PromptInput({
|
||||
@ -83,7 +83,6 @@ export default function PromptInput({
|
||||
};
|
||||
|
||||
const adjustTextArea = (event) => {
|
||||
if (isMobile) return false;
|
||||
const element = event.target;
|
||||
element.style.height = "auto";
|
||||
element.style.height = `${element.scrollHeight}px`;
|
||||
@ -130,20 +129,31 @@ export default function PromptInput({
|
||||
adjustTextArea(e);
|
||||
}}
|
||||
value={promptInput}
|
||||
className="cursor-text max-h-[100px] md:min-h-[40px] mx-2 md:mx-0 py-2 w-full text-[16px] md:text-md text-white bg-transparent placeholder:text-white/60 resize-none active:outline-none focus:outline-none flex-grow"
|
||||
className="cursor-text max-h-[50vh] md:max-h-[350px] md:min-h-[40px] mx-2 md:mx-0 py-2 w-full text-[16px] md:text-md text-white bg-transparent placeholder:text-white/60 resize-none active:outline-none focus:outline-none flex-grow"
|
||||
placeholder={"Send a message"}
|
||||
/>
|
||||
{buttonDisabled ? (
|
||||
<StopGenerationButton />
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
ref={formRef}
|
||||
type="submit"
|
||||
className="inline-flex justify-center rounded-2xl cursor-pointer text-white/60 hover:text-white group ml-4"
|
||||
data-tooltip-id="send-prompt"
|
||||
data-tooltip-content="Send prompt message to workspace"
|
||||
aria-label="Send prompt message to workspace"
|
||||
>
|
||||
<PaperPlaneRight className="w-7 h-7 my-3" weight="fill" />
|
||||
<span className="sr-only">Send message</span>
|
||||
</button>
|
||||
<Tooltip
|
||||
id="send-prompt"
|
||||
place="bottom"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs z-99"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between py-3.5">
|
||||
|
BIN
frontend/src/media/embeddingprovider/voyageai.png
Normal file
BIN
frontend/src/media/embeddingprovider/voyageai.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
frontend/src/media/llmprovider/litellm.png
Normal file
BIN
frontend/src/media/llmprovider/litellm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
@ -47,7 +47,7 @@ export default function TextToSpeechProvider({ settings }) {
|
||||
const searchInputRef = useRef(null);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e?.preventDefault();
|
||||
const form = e.target;
|
||||
const data = { TextToSpeechProvider: selectedProvider };
|
||||
const formData = new FormData(form);
|
||||
@ -110,10 +110,7 @@ export default function TextToSpeechProvider({ settings }) {
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
{hasChanges && (
|
||||
<CTAButton
|
||||
onClick={() => handleSubmit()}
|
||||
className="mt-3 mr-0 -mb-14 z-10"
|
||||
>
|
||||
<CTAButton className="mt-3 mr-0 -mb-14 z-10">
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
</CTAButton>
|
||||
)}
|
||||
|
@ -10,6 +10,8 @@ import LocalAiLogo from "@/media/llmprovider/localai.png";
|
||||
import OllamaLogo from "@/media/llmprovider/ollama.png";
|
||||
import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
|
||||
import CohereLogo from "@/media/llmprovider/cohere.png";
|
||||
import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png";
|
||||
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import ChangeWarningModal from "@/components/ChangeWarning";
|
||||
import OpenAiOptions from "@/components/EmbeddingSelection/OpenAiOptions";
|
||||
@ -19,6 +21,7 @@ import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbedd
|
||||
import OllamaEmbeddingOptions from "@/components/EmbeddingSelection/OllamaOptions";
|
||||
import LMStudioEmbeddingOptions from "@/components/EmbeddingSelection/LMStudioOptions";
|
||||
import CohereEmbeddingOptions from "@/components/EmbeddingSelection/CohereOptions";
|
||||
import VoyageAiOptions from "@/components/EmbeddingSelection/VoyageAiOptions";
|
||||
|
||||
import EmbedderItem from "@/components/EmbeddingSelection/EmbedderItem";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
@ -78,6 +81,13 @@ const EMBEDDERS = [
|
||||
options: (settings) => <CohereEmbeddingOptions settings={settings} />,
|
||||
description: "Run powerful embedding models from Cohere.",
|
||||
},
|
||||
{
|
||||
name: "Voyage AI",
|
||||
value: "voyageai",
|
||||
logo: VoyageAiLogo,
|
||||
options: (settings) => <VoyageAiOptions settings={settings} />,
|
||||
description: "Run powerful embedding models from Voyage AI.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function GeneralEmbeddingPreference() {
|
||||
|
@ -20,6 +20,7 @@ import GroqLogo from "@/media/llmprovider/groq.png";
|
||||
import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png";
|
||||
import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
|
||||
import CohereLogo from "@/media/llmprovider/cohere.png";
|
||||
import LiteLLMLogo from "@/media/llmprovider/litellm.png";
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
|
||||
import GenericOpenAiOptions from "@/components/LLMSelection/GenericOpenAiOptions";
|
||||
@ -36,12 +37,13 @@ import PerplexityOptions from "@/components/LLMSelection/PerplexityOptions";
|
||||
import OpenRouterOptions from "@/components/LLMSelection/OpenRouterOptions";
|
||||
import GroqAiOptions from "@/components/LLMSelection/GroqAiOptions";
|
||||
import CohereAiOptions from "@/components/LLMSelection/CohereAiOptions";
|
||||
import KoboldCPPOptions from "@/components/LLMSelection/KoboldCPPOptions";
|
||||
import TextGenWebUIOptions from "@/components/LLMSelection/TextGenWebUIOptions";
|
||||
import LiteLLMOptions from "@/components/LLMSelection/LiteLLMOptions";
|
||||
|
||||
import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import KoboldCPPOptions from "@/components/LLMSelection/KoboldCPPOptions";
|
||||
import TextGenWebUIOptions from "@/components/LLMSelection/TextGenWebUIOptions";
|
||||
|
||||
export const AVAILABLE_LLM_PROVIDERS = [
|
||||
{
|
||||
@ -184,6 +186,14 @@ export const AVAILABLE_LLM_PROVIDERS = [
|
||||
description: "Run Cohere's powerful Command models.",
|
||||
requiredConfig: ["CohereApiKey"],
|
||||
},
|
||||
{
|
||||
name: "LiteLLM",
|
||||
value: "litellm",
|
||||
logo: LiteLLMLogo,
|
||||
options: (settings) => <LiteLLMOptions settings={settings} />,
|
||||
description: "Run LiteLLM's OpenAI compatible proxy for various LLMs.",
|
||||
requiredConfig: ["LiteLLMBasePath"],
|
||||
},
|
||||
{
|
||||
name: "Generic OpenAI",
|
||||
value: "generic-openai",
|
||||
|
@ -12,6 +12,23 @@ import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
name: "OpenAI",
|
||||
value: "openai",
|
||||
logo: OpenAiLogo,
|
||||
options: (settings) => <OpenAiWhisperOptions settings={settings} />,
|
||||
description: "Leverage the OpenAI Whisper-large model using your API key.",
|
||||
},
|
||||
{
|
||||
name: "AnythingLLM Built-In",
|
||||
value: "local",
|
||||
logo: AnythingLLMIcon,
|
||||
options: (settings) => <NativeTranscriptionOptions settings={settings} />,
|
||||
description: "Run a built-in whisper model on this instance privately.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function TranscriptionModelPreference() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
@ -68,24 +85,6 @@ export default function TranscriptionModelPreference() {
|
||||
fetchKeys();
|
||||
}, []);
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
name: "OpenAI",
|
||||
value: "openai",
|
||||
logo: OpenAiLogo,
|
||||
options: <OpenAiWhisperOptions settings={settings} />,
|
||||
description:
|
||||
"Leverage the OpenAI Whisper-large model using your API key.",
|
||||
},
|
||||
{
|
||||
name: "AnythingLLM Built-In",
|
||||
value: "local",
|
||||
logo: AnythingLLMIcon,
|
||||
options: <NativeTranscriptionOptions settings={settings} />,
|
||||
description: "Run a built-in whisper model on this instance privately.",
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = PROVIDERS.filter((provider) =>
|
||||
provider.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
@ -228,7 +227,7 @@ export default function TranscriptionModelPreference() {
|
||||
{selectedProvider &&
|
||||
PROVIDERS.find(
|
||||
(provider) => provider.value === selectedProvider
|
||||
)?.options}
|
||||
)?.options(settings)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -17,6 +17,8 @@ import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg";
|
||||
import GroqLogo from "@/media/llmprovider/groq.png";
|
||||
import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png";
|
||||
import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
|
||||
import LiteLLMLogo from "@/media/llmprovider/litellm.png";
|
||||
|
||||
import CohereLogo from "@/media/llmprovider/cohere.png";
|
||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
@ -26,6 +28,8 @@ import LanceDbLogo from "@/media/vectordbs/lancedb.png";
|
||||
import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
||||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||
import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import paths from "@/utils/paths";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -168,6 +172,13 @@ export const LLM_SELECTION_PRIVACY = {
|
||||
],
|
||||
logo: CohereLogo,
|
||||
},
|
||||
litellm: {
|
||||
name: "LiteLLM",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LiteLLM",
|
||||
],
|
||||
logo: LiteLLMLogo,
|
||||
},
|
||||
};
|
||||
|
||||
export const VECTOR_DB_PRIVACY = {
|
||||
@ -283,6 +294,13 @@ export const EMBEDDING_ENGINE_PRIVACY = {
|
||||
],
|
||||
logo: CohereLogo,
|
||||
},
|
||||
voyageai: {
|
||||
name: "Voyage AI",
|
||||
description: [
|
||||
"Data sent to Voyage AI's servers is shared according to the terms of service of voyageai.com.",
|
||||
],
|
||||
logo: VoyageAiLogo,
|
||||
},
|
||||
};
|
||||
|
||||
export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
|
@ -16,6 +16,8 @@ import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg";
|
||||
import GroqLogo from "@/media/llmprovider/groq.png";
|
||||
import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png";
|
||||
import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
|
||||
import LiteLLMLogo from "@/media/llmprovider/litellm.png";
|
||||
|
||||
import CohereLogo from "@/media/llmprovider/cohere.png";
|
||||
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
|
||||
import GenericOpenAiOptions from "@/components/LLMSelection/GenericOpenAiOptions";
|
||||
@ -32,14 +34,15 @@ import PerplexityOptions from "@/components/LLMSelection/PerplexityOptions";
|
||||
import OpenRouterOptions from "@/components/LLMSelection/OpenRouterOptions";
|
||||
import GroqAiOptions from "@/components/LLMSelection/GroqAiOptions";
|
||||
import CohereAiOptions from "@/components/LLMSelection/CohereAiOptions";
|
||||
import KoboldCPPOptions from "@/components/LLMSelection/KoboldCPPOptions";
|
||||
import TextGenWebUIOptions from "@/components/LLMSelection/TextGenWebUIOptions";
|
||||
import LiteLLMOptions from "@/components/LLMSelection/LiteLLMOptions";
|
||||
|
||||
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 KoboldCPPOptions from "@/components/LLMSelection/KoboldCPPOptions";
|
||||
import TextGenWebUIOptions from "@/components/LLMSelection/TextGenWebUIOptions";
|
||||
|
||||
const TITLE = "LLM Preference";
|
||||
const DESCRIPTION =
|
||||
@ -162,6 +165,13 @@ const LLMS = [
|
||||
options: (settings) => <CohereAiOptions settings={settings} />,
|
||||
description: "Run Cohere's powerful Command models.",
|
||||
},
|
||||
{
|
||||
name: "LiteLLM",
|
||||
value: "litellm",
|
||||
logo: LiteLLMLogo,
|
||||
options: (settings) => <LiteLLMOptions settings={settings} />,
|
||||
description: "Run LiteLLM's OpenAI compatible proxy for various LLMs.",
|
||||
},
|
||||
{
|
||||
name: "Generic OpenAI",
|
||||
value: "generic-openai",
|
||||
|
@ -0,0 +1,47 @@
|
||||
import PostgreSQLLogo from "./icons/postgresql.png";
|
||||
import MySQLLogo from "./icons/mysql.png";
|
||||
import MSSQLLogo from "./icons/mssql.png";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
|
||||
export const DB_LOGOS = {
|
||||
postgresql: PostgreSQLLogo,
|
||||
mysql: MySQLLogo,
|
||||
"sql-server": MSSQLLogo,
|
||||
};
|
||||
|
||||
export default function DBConnection({ connection, onRemove }) {
|
||||
const { database_id, engine } = connection;
|
||||
function removeConfirmation() {
|
||||
if (
|
||||
!window.confirm(
|
||||
`Delete ${database_id} from the list of available SQL connections? This cannot be undone.`
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
onRemove(database_id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<img
|
||||
src={DB_LOGOS?.[engine] ?? null}
|
||||
alt={`${engine} logo`}
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold text-white">{database_id}</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">{engine}</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={removeConfirmation}
|
||||
className="border-none text-white/40 hover:text-red-500"
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,271 @@
|
||||
import { useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import { WarningOctagon, X } from "@phosphor-icons/react";
|
||||
import { DB_LOGOS } from "./DBConnection";
|
||||
|
||||
function assembleConnectionString({
|
||||
engine,
|
||||
username = "",
|
||||
password = "",
|
||||
host = "",
|
||||
port = "",
|
||||
database = "",
|
||||
}) {
|
||||
if ([username, password, host, database].every((i) => !!i) === false)
|
||||
return `Please fill out all the fields above.`;
|
||||
switch (engine) {
|
||||
case "postgresql":
|
||||
return `postgres://${username}:${password}@${host}:${port}/${database}`;
|
||||
case "mysql":
|
||||
return `mysql://${username}:${password}@${host}:${port}/${database}`;
|
||||
case "sql-server":
|
||||
return `mssql://${username}:${password}@${host}:${port}/${database}`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_ENGINE = "postgresql";
|
||||
const DEFAULT_CONFIG = {
|
||||
username: null,
|
||||
password: null,
|
||||
host: null,
|
||||
port: null,
|
||||
database: null,
|
||||
};
|
||||
|
||||
export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
|
||||
const [engine, setEngine] = useState(DEFAULT_ENGINE);
|
||||
const [config, setConfig] = useState(DEFAULT_CONFIG);
|
||||
if (!isOpen) return null;
|
||||
|
||||
function handleClose() {
|
||||
setEngine(DEFAULT_ENGINE);
|
||||
setConfig(DEFAULT_CONFIG);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function onFormChange() {
|
||||
const form = new FormData(document.getElementById("sql-connection-form"));
|
||||
setConfig({
|
||||
username: form.get("username").trim(),
|
||||
password: form.get("password"),
|
||||
host: form.get("host").trim(),
|
||||
port: form.get("port").trim(),
|
||||
database: form.get("database").trim(),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleUpdate(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const form = new FormData(e.target);
|
||||
onSubmit({
|
||||
engine,
|
||||
database_id: form.get("name"),
|
||||
connectionString: assembleConnectionString({ engine, ...config }),
|
||||
});
|
||||
handleClose();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot do nested forms, it will cause all sorts of issues, so we portal this out
|
||||
// to the parent container form so we don't have nested forms.
|
||||
return createPortal(
|
||||
<ModalWrapper isOpen={isOpen}>
|
||||
<div className="relative w-1/3 max-h-full ">
|
||||
<div className="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)] max-h-[90vh] overflow-y-scroll no-scroll">
|
||||
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
|
||||
<h3 className="text-xl font-semibold text-white">
|
||||
New SQL Connection
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
type="button"
|
||||
className="border-none transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||
data-modal-hide="staticModal"
|
||||
>
|
||||
<X className="text-gray-300 text-lg" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
id="sql-connection-form"
|
||||
onSubmit={handleUpdate}
|
||||
onChange={onFormChange}
|
||||
>
|
||||
<div className="py-[17px] px-[20px] flex flex-col gap-y-6">
|
||||
<p className="text-sm text-white">
|
||||
Add the connection information for your database below and it
|
||||
will be available for future SQL agent calls.
|
||||
</p>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="border border-red-800 bg-zinc-800 p-4 rounded-lg flex items-center gap-x-2 text-sm text-red-400">
|
||||
<WarningOctagon size={28} className="shrink-0" />
|
||||
<p>
|
||||
<b>WARNING:</b> The SQL agent has been <i>instructed</i> to
|
||||
only perform non-modifying queries. This <b>does not</b>{" "}
|
||||
prevent a hallucination from still deleting data. Only
|
||||
connect with a user who has <b>READ_ONLY</b> permissions.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label className="text-white text-sm font-semibold block my-4">
|
||||
Select your SQL engine
|
||||
</label>
|
||||
<div className="flex w-full flex-wrap gap-x-4">
|
||||
<DBEngine
|
||||
provider="postgresql"
|
||||
active={engine === "postgresql"}
|
||||
onClick={() => setEngine("postgresql")}
|
||||
/>
|
||||
<DBEngine
|
||||
provider="mysql"
|
||||
active={engine === "mysql"}
|
||||
onClick={() => setEngine("mysql")}
|
||||
/>
|
||||
<DBEngine
|
||||
provider="sql-server"
|
||||
active={engine === "sql-server"}
|
||||
onClick={() => setEngine("sql-server")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Connection name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="a unique name to identify this SQL connection"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-x-2">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Database user
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="root"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Database user password
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="password"
|
||||
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="password123"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-x-2">
|
||||
<div className="flex flex-col w-full">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Server endpoint
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="host"
|
||||
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="the hostname or endpoint for your database"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-30">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Port
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="port"
|
||||
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="3306"
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Database
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="database"
|
||||
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||
placeholder="the database the agent will interact with"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-white/40 text-sm">
|
||||
{assembleConnectionString({ engine, ...config })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex w-full justify-between items-center p-3 space-x-2 border-t rounded-b border-gray-500/50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClose}
|
||||
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-white hover:bg-transparent border-2 border-transparent hover:border-white hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
form="sql-connection-form"
|
||||
className="border-none text-xs px-2 py-1 font-semibold rounded-lg bg-[#46C8FF] hover:bg-[#2C2F36] border-2 border-transparent hover:border-[#46C8FF] hover:text-white h-[32px] w-fit -mr-8 whitespace-nowrap shadow-[0_4px_14px_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
Save connection
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>,
|
||||
document.getElementById("workspace-agent-settings-container")
|
||||
);
|
||||
}
|
||||
|
||||
function DBEngine({ provider, active, onClick }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`flex flex-col p-4 border border-white/40 bg-zinc-800 rounded-lg w-fit hover:bg-zinc-700 ${
|
||||
active ? "!bg-blue-500/50" : ""
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={DB_LOGOS[provider]}
|
||||
className="h-[100px] rounded-md"
|
||||
alt="PostgreSQL"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
@ -0,0 +1,109 @@
|
||||
import React, { useState } from "react";
|
||||
import DBConnection from "./DBConnection";
|
||||
import { Plus } from "@phosphor-icons/react";
|
||||
import NewSQLConnection from "./NewConnectionModal";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
|
||||
export default function AgentSQLConnectorSelection({
|
||||
skill,
|
||||
settings,
|
||||
toggleSkill,
|
||||
enabled = false,
|
||||
}) {
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const [connections, setConnections] = useState(
|
||||
settings?.preferences?.agent_sql_connections || []
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="border-b border-white/40 pb-4">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
SQL Agent
|
||||
</label>
|
||||
<label className="border-none relative inline-flex cursor-pointer items-center mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="peer sr-only"
|
||||
checked={enabled}
|
||||
onClick={() => toggleSkill(skill)}
|
||||
/>
|
||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
|
||||
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
Enable your agent to be able to leverage SQL to answer you questions
|
||||
by connecting to various SQL database providers.
|
||||
</p>
|
||||
</div>
|
||||
{enabled && (
|
||||
<>
|
||||
<input
|
||||
name="system::agent_sql_connections"
|
||||
type="hidden"
|
||||
value={JSON.stringify(connections)}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
value={JSON.stringify(
|
||||
connections.filter((conn) => conn.action !== "remove")
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col mt-2 gap-y-2">
|
||||
<p className="text-white font-semibold text-sm">
|
||||
Your database connections
|
||||
</p>
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{connections
|
||||
.filter((connection) => connection.action !== "remove")
|
||||
.map((connection) => (
|
||||
<DBConnection
|
||||
key={connection.database_id}
|
||||
connection={connection}
|
||||
onRemove={(databaseId) => {
|
||||
setConnections((prev) =>
|
||||
prev.map((conn) => {
|
||||
if (conn.database_id === databaseId)
|
||||
return { ...conn, action: "remove" };
|
||||
return conn;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={openModal}
|
||||
className="w-fit relative flex h-[40px] items-center border-none hover:bg-slate-600/20 rounded-lg"
|
||||
>
|
||||
<div className="flex w-full gap-x-2 items-center p-4">
|
||||
<div className="bg-zinc-600 p-2 rounded-lg h-[24px] w-[24px] flex items-center justify-center">
|
||||
<Plus
|
||||
weight="bold"
|
||||
size={14}
|
||||
className="shrink-0 text-slate-100"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-left text-slate-100 text-sm">
|
||||
New SQL connection
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<NewSQLConnection
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
onSubmit={(newDb) =>
|
||||
setConnections((prev) => [...prev, { action: "add", ...newDb }])
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -5,6 +5,7 @@ import { castToType } from "@/utils/types";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import AgentLLMSelection from "./AgentLLMSelection";
|
||||
import AgentWebSearchSelection from "./WebSearchSelection";
|
||||
import AgentSQLConnectorSelection from "./SQLConnectorSelection";
|
||||
import GenericSkill from "./GenericSkill";
|
||||
import Admin from "@/models/admin";
|
||||
import * as Skeleton from "react-loading-skeleton";
|
||||
@ -205,6 +206,12 @@ function AvailableAgentSkills({ skills, settings, toggleAgentSkill }) {
|
||||
toggleSkill={toggleAgentSkill}
|
||||
enabled={skills.includes("web-browsing")}
|
||||
/>
|
||||
<AgentSQLConnectorSelection
|
||||
skill="sql-agent"
|
||||
settings={settings}
|
||||
toggleSkill={toggleAgentSkill}
|
||||
enabled={skills.includes("sql-agent")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -20,19 +20,23 @@ export default function ChatTemperatureSettings({
|
||||
LLM Temperature
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
This setting controls how "random" or dynamic your chat
|
||||
responses will be.
|
||||
This setting controls how "creative" your LLM responses will
|
||||
be.
|
||||
<br />
|
||||
The higher the number (1.0 maximum) the more random and incoherent.
|
||||
The higher the number the more creative. For some models this can lead
|
||||
to incoherent responses when set too high.
|
||||
<br />
|
||||
<i>Recommended: {defaults.temp}</i>
|
||||
<br />
|
||||
<i>
|
||||
Most LLMs have various acceptable ranges of valid values. Consult
|
||||
your LLM provider for that information.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="openAiTemp"
|
||||
type="number"
|
||||
min={0.0}
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
onWheel={(e) => e.target.blur()}
|
||||
defaultValue={workspace?.openAiTemp ?? defaults.temp}
|
||||
|
@ -2,7 +2,6 @@ import Workspace from "@/models/workspace";
|
||||
import { castToType } from "@/utils/types";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import VectorCount from "./VectorCount";
|
||||
import WorkspaceName from "./WorkspaceName";
|
||||
import SuggestedChatMessages from "./SuggestedChatMessages";
|
||||
import DeleteWorkspace from "./DeleteWorkspace";
|
||||
@ -51,7 +50,6 @@ export default function GeneralInfo({ slug }) {
|
||||
onSubmit={handleUpdate}
|
||||
className="w-1/2 flex flex-col gap-y-6"
|
||||
>
|
||||
<VectorCount reload={true} workspace={workspace} />
|
||||
<WorkspaceName
|
||||
key={workspace.slug}
|
||||
workspace={workspace}
|
||||
|
@ -28,9 +28,6 @@ export default function VectorCount({ reload, workspace }) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="input-label">Number of vectors</h3>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1">
|
||||
Total number of vectors in your vector database.
|
||||
</p>
|
||||
<p className="text-white text-opacity-60 text-sm font-medium">
|
||||
{totalVectors}
|
||||
</p>
|
@ -6,6 +6,7 @@ import VectorDBIdentifier from "./VectorDBIdentifier";
|
||||
import MaxContextSnippets from "./MaxContextSnippets";
|
||||
import DocumentSimilarityThreshold from "./DocumentSimilarityThreshold";
|
||||
import ResetDatabase from "./ResetDatabase";
|
||||
import VectorCount from "./VectorCount";
|
||||
|
||||
export default function VectorDatabase({ workspace }) {
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
@ -38,7 +39,10 @@ export default function VectorDatabase({ workspace }) {
|
||||
onSubmit={handleUpdate}
|
||||
className="w-1/2 flex flex-col gap-y-6"
|
||||
>
|
||||
<div className="flex items-start gap-x-5">
|
||||
<VectorDBIdentifier workspace={workspace} />
|
||||
<VectorCount reload={true} workspace={workspace} />
|
||||
</div>
|
||||
<MaxContextSnippets workspace={workspace} setHasChanges={setHasChanges} />
|
||||
<DocumentSimilarityThreshold
|
||||
workspace={workspace}
|
||||
|
@ -79,6 +79,12 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea
|
||||
# GENERIC_OPEN_AI_MODEL_TOKEN_LIMIT=4096
|
||||
# GENERIC_OPEN_AI_API_KEY=sk-123abc
|
||||
|
||||
# LLM_PROVIDER='litellm'
|
||||
# LITE_LLM_MODEL_PREF='gpt-3.5-turbo'
|
||||
# LITE_LLM_MODEL_TOKEN_LIMIT=4096
|
||||
# LITE_LLM_BASE_PATH='http://127.0.0.1:4000'
|
||||
# LITE_LLM_API_KEY='sk-123abc'
|
||||
|
||||
# LLM_PROVIDER='cohere'
|
||||
# COHERE_API_KEY=
|
||||
# COHERE_MODEL_PREF='command-r'
|
||||
@ -115,6 +121,10 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea
|
||||
# COHERE_API_KEY=
|
||||
# EMBEDDING_MODEL_PREF='embed-english-v3.0'
|
||||
|
||||
# EMBEDDING_ENGINE='voyageai'
|
||||
# VOYAGEAI_API_KEY=
|
||||
# EMBEDDING_MODEL_PREF='voyage-large-2-instruct'
|
||||
|
||||
###########################################
|
||||
######## Vector Database Selection ########
|
||||
###########################################
|
||||
|
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@ -19,3 +19,6 @@ public/
|
||||
documents
|
||||
vector-cache
|
||||
yarn-error.log
|
||||
|
||||
# Local SSL Certs for HTTPS
|
||||
sslcert
|
@ -350,6 +350,8 @@ function adminEndpoints(app) {
|
||||
agent_search_provider:
|
||||
(await SystemSettings.get({ label: "agent_search_provider" }))
|
||||
?.value || null,
|
||||
agent_sql_connections:
|
||||
await SystemSettings.brief.agent_sql_connections(),
|
||||
default_agent_skills:
|
||||
safeJsonParse(
|
||||
(await SystemSettings.get({ label: "default_agent_skills" }))
|
||||
|
@ -447,6 +447,76 @@ function apiWorkspaceEndpoints(app) {
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/v1/workspace/:slug/update-pin",
|
||||
[validApiKey],
|
||||
async (request, response) => {
|
||||
/*
|
||||
#swagger.tags = ['Workspaces']
|
||||
#swagger.description = 'Add or remove pin from a document in a workspace by its unique slug.'
|
||||
#swagger.path = '/workspace/{slug}/update-pin'
|
||||
#swagger.parameters['slug'] = {
|
||||
in: 'path',
|
||||
description: 'Unique slug of workspace to find',
|
||||
required: true,
|
||||
type: 'string'
|
||||
}
|
||||
#swagger.requestBody = {
|
||||
description: 'JSON object with the document path and pin status to update.',
|
||||
required: true,
|
||||
type: 'object',
|
||||
content: {
|
||||
"application/json": {
|
||||
example: {
|
||||
docPath: "custom-documents/my-pdf.pdf-hash.json",
|
||||
pinStatus: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#swagger.responses[200] = {
|
||||
description: 'OK',
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: 'object',
|
||||
example: {
|
||||
message: 'Pin status updated successfully'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#swagger.responses[404] = {
|
||||
description: 'Document not found'
|
||||
}
|
||||
#swagger.responses[500] = {
|
||||
description: 'Internal Server Error'
|
||||
}
|
||||
*/
|
||||
try {
|
||||
const { slug = null } = request.params;
|
||||
const { docPath, pinStatus = false } = reqBody(request);
|
||||
const workspace = await Workspace.get({ slug });
|
||||
|
||||
const document = await Document.get({
|
||||
workspaceId: workspace.id,
|
||||
docpath: docPath,
|
||||
});
|
||||
if (!document) return response.sendStatus(404).end();
|
||||
|
||||
await Document.update(document.id, { pinned: pinStatus });
|
||||
return response
|
||||
.status(200)
|
||||
.json({ message: "Pin status updated successfully" })
|
||||
.end();
|
||||
} catch (error) {
|
||||
console.error("Error processing the pin status update:", error);
|
||||
return response.status(500).end();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/v1/workspace/:slug/chat",
|
||||
[validApiKey],
|
||||
@ -533,6 +603,7 @@ function apiWorkspaceEndpoints(app) {
|
||||
});
|
||||
response.status(200).json({ ...result });
|
||||
} catch (e) {
|
||||
console.log(e.message, e);
|
||||
response.status(500).json({
|
||||
id: uuidv4(),
|
||||
type: "abort",
|
||||
@ -655,7 +726,7 @@ function apiWorkspaceEndpoints(app) {
|
||||
});
|
||||
response.end();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.log(e.message, e);
|
||||
writeResponseChunk(response, {
|
||||
id: uuidv4(),
|
||||
type: "abort",
|
||||
|
@ -40,7 +40,12 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
require("express-ws")(app);
|
||||
if (!!process.env.ENABLE_HTTPS) {
|
||||
bootSSL(app, process.env.SERVER_PORT || 3001);
|
||||
} else {
|
||||
require("express-ws")(app); // load WebSockets in non-SSL mode.
|
||||
}
|
||||
|
||||
app.use("/api", apiRouter);
|
||||
systemEndpoints(apiRouter);
|
||||
extensionEndpoints(apiRouter);
|
||||
@ -113,8 +118,6 @@ app.all("*", function (_, response) {
|
||||
response.sendStatus(404);
|
||||
});
|
||||
|
||||
if (!!process.env.ENABLE_HTTPS) {
|
||||
bootSSL(app, process.env.SERVER_PORT || 3001);
|
||||
} else {
|
||||
bootHTTP(app, process.env.SERVER_PORT || 3001);
|
||||
}
|
||||
// In non-https mode we need to boot at the end since the server has not yet
|
||||
// started and is `.listen`ing.
|
||||
if (!process.env.ENABLE_HTTPS) bootHTTP(app, process.env.SERVER_PORT || 3001);
|
||||
|
@ -1,6 +1,9 @@
|
||||
const path = require("path");
|
||||
const prisma = require("../utils/prisma");
|
||||
const { isValidUrl } = require("../utils/http");
|
||||
const { isValidUrl, safeJsonParse } = require("../utils/http");
|
||||
const { default: slugify } = require("slugify");
|
||||
const { v4 } = require("uuid");
|
||||
|
||||
process.env.NODE_ENV === "development"
|
||||
? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
|
||||
: require("dotenv").config({
|
||||
@ -28,6 +31,7 @@ const SystemSettings = {
|
||||
"text_splitter_chunk_overlap",
|
||||
"agent_search_provider",
|
||||
"default_agent_skills",
|
||||
"agent_sql_connections",
|
||||
],
|
||||
validations: {
|
||||
footer_data: (updates) => {
|
||||
@ -69,6 +73,7 @@ const SystemSettings = {
|
||||
},
|
||||
agent_search_provider: (update) => {
|
||||
try {
|
||||
if (update === "none") return null;
|
||||
if (!["google-search-engine", "serper-dot-dev"].includes(update))
|
||||
throw new Error("Invalid SERP provider.");
|
||||
return String(update);
|
||||
@ -89,6 +94,22 @@ const SystemSettings = {
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
},
|
||||
agent_sql_connections: async (updates) => {
|
||||
const existingConnections = safeJsonParse(
|
||||
(await SystemSettings.get({ label: "agent_sql_connections" }))?.value,
|
||||
[]
|
||||
);
|
||||
try {
|
||||
const updatedConnections = mergeConnections(
|
||||
existingConnections,
|
||||
safeJsonParse(updates, [])
|
||||
);
|
||||
return JSON.stringify(updatedConnections);
|
||||
} catch (e) {
|
||||
console.error(`Failed to merge connections`);
|
||||
return JSON.stringify(existingConnections ?? []);
|
||||
}
|
||||
},
|
||||
},
|
||||
currentSettings: async function () {
|
||||
const { hasVectorCachedFiles } = require("../utils/files");
|
||||
@ -134,6 +155,8 @@ const SystemSettings = {
|
||||
// - then it can be shared.
|
||||
// --------------------------------------------------------
|
||||
WhisperProvider: process.env.WHISPER_PROVIDER || "local",
|
||||
WhisperModelPref:
|
||||
process.env.WHISPER_MODEL_PREF || "Xenova/whisper-small",
|
||||
|
||||
// --------------------------------------------------------
|
||||
// TTS/STT Selection Settings & Configs
|
||||
@ -208,12 +231,19 @@ const SystemSettings = {
|
||||
// that takes no user input for the keys being modified.
|
||||
_updateSettings: async function (updates = {}) {
|
||||
try {
|
||||
const updatePromises = Object.keys(updates).map((key) => {
|
||||
const validatedValue = this.validations.hasOwnProperty(key)
|
||||
? this.validations[key](updates[key])
|
||||
: updates[key];
|
||||
const updatePromises = [];
|
||||
for (const key of Object.keys(updates)) {
|
||||
let validatedValue = updates[key];
|
||||
if (this.validations.hasOwnProperty(key)) {
|
||||
if (this.validations[key].constructor.name === "AsyncFunction") {
|
||||
validatedValue = await this.validations[key](updates[key]);
|
||||
} else {
|
||||
validatedValue = this.validations[key](updates[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return prisma.system_settings.upsert({
|
||||
updatePromises.push(
|
||||
prisma.system_settings.upsert({
|
||||
where: { label: key },
|
||||
update: {
|
||||
value: validatedValue === null ? null : String(validatedValue),
|
||||
@ -222,8 +252,9 @@ const SystemSettings = {
|
||||
label: key,
|
||||
value: validatedValue === null ? null : String(validatedValue),
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
return { success: true, error: null };
|
||||
@ -328,6 +359,8 @@ const SystemSettings = {
|
||||
// Gemini Keys
|
||||
GeminiLLMApiKey: !!process.env.GEMINI_API_KEY,
|
||||
GeminiLLMModelPref: process.env.GEMINI_LLM_MODEL_PREF || "gemini-pro",
|
||||
GeminiSafetySetting:
|
||||
process.env.GEMINI_SAFETY_SETTING || "BLOCK_MEDIUM_AND_ABOVE",
|
||||
|
||||
// LMStudio Keys
|
||||
LMStudioBasePath: process.env.LMSTUDIO_BASE_PATH,
|
||||
@ -384,6 +417,12 @@ const SystemSettings = {
|
||||
TextGenWebUITokenLimit: process.env.TEXT_GEN_WEB_UI_MODEL_TOKEN_LIMIT,
|
||||
TextGenWebUIAPIKey: !!process.env.TEXT_GEN_WEB_UI_API_KEY,
|
||||
|
||||
// LiteLLM Keys
|
||||
LiteLLMModelPref: process.env.LITE_LLM_MODEL_PREF,
|
||||
LiteLLMTokenLimit: process.env.LITE_LLM_MODEL_TOKEN_LIMIT,
|
||||
LiteLLMBasePath: process.env.LITE_LLM_BASE_PATH,
|
||||
LiteLLMApiKey: !!process.env.LITE_LLM_API_KEY,
|
||||
|
||||
// Generic OpenAI Keys
|
||||
GenericOpenAiBasePath: process.env.GENERIC_OPEN_AI_BASE_PATH,
|
||||
GenericOpenAiModelPref: process.env.GENERIC_OPEN_AI_MODEL_PREF,
|
||||
@ -394,8 +433,63 @@ const SystemSettings = {
|
||||
// Cohere API Keys
|
||||
CohereApiKey: !!process.env.COHERE_API_KEY,
|
||||
CohereModelPref: process.env.COHERE_MODEL_PREF,
|
||||
|
||||
// VoyageAi API Keys
|
||||
VoyageAiApiKey: !!process.env.VOYAGEAI_API_KEY,
|
||||
};
|
||||
},
|
||||
|
||||
// For special retrieval of a key setting that does not expose any credential information
|
||||
brief: {
|
||||
agent_sql_connections: async function () {
|
||||
const setting = await SystemSettings.get({
|
||||
label: "agent_sql_connections",
|
||||
});
|
||||
if (!setting) return [];
|
||||
return safeJsonParse(setting.value, []).map((dbConfig) => {
|
||||
const { connectionString, ...rest } = dbConfig;
|
||||
return rest;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function mergeConnections(existingConnections = [], updates = []) {
|
||||
let updatedConnections = [...existingConnections];
|
||||
const existingDbIds = existingConnections.map((conn) => conn.database_id);
|
||||
|
||||
// First remove all 'action:remove' candidates from existing connections.
|
||||
const toRemove = updates
|
||||
.filter((conn) => conn.action === "remove")
|
||||
.map((conn) => conn.database_id);
|
||||
updatedConnections = updatedConnections.filter(
|
||||
(conn) => !toRemove.includes(conn.database_id)
|
||||
);
|
||||
|
||||
// Next add all 'action:add' candidates into the updatedConnections; We DO NOT validate the connection strings.
|
||||
// but we do validate their database_id is unique.
|
||||
updates
|
||||
.filter((conn) => conn.action === "add")
|
||||
.forEach((update) => {
|
||||
if (!update.connectionString) return; // invalid connection string
|
||||
|
||||
// Remap name to be unique to entire set.
|
||||
if (existingDbIds.includes(update.database_id)) {
|
||||
update.database_id = slugify(
|
||||
`${update.database_id}-${v4().slice(0, 4)}`
|
||||
);
|
||||
} else {
|
||||
update.database_id = slugify(update.database_id);
|
||||
}
|
||||
|
||||
updatedConnections.push({
|
||||
engine: update.engine,
|
||||
database_id: update.database_id,
|
||||
connectionString: update.connectionString,
|
||||
});
|
||||
});
|
||||
|
||||
return updatedConnections;
|
||||
}
|
||||
|
||||
module.exports.SystemSettings = SystemSettings;
|
||||
|
@ -58,11 +58,14 @@
|
||||
"langchain": "0.1.36",
|
||||
"mime": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"mssql": "^10.0.2",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.9.7",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"node-llama-cpp": "^2.8.0",
|
||||
"ollama": "^0.5.0",
|
||||
"openai": "4.38.5",
|
||||
"pg": "^8.11.5",
|
||||
"pinecone-client": "^1.1.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"posthog-node": "^3.1.1",
|
||||
@ -72,6 +75,7 @@
|
||||
"sqlite3": "^5.1.6",
|
||||
"swagger-autogen": "^2.23.5",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"url-pattern": "^1.0.3",
|
||||
"uuid": "^9.0.0",
|
||||
"uuid-apikey": "^1.5.3",
|
||||
"vectordb": "0.4.11",
|
||||
|
@ -2000,6 +2000,69 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspace/{slug}/update-pin": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Workspaces"
|
||||
],
|
||||
"description": "Add or remove pin from a document in a workspace by its unique slug.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "slug",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Unique slug of workspace to find"
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"message": "Pin status updated successfully"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Document not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"description": "JSON object with the document path and pin status to update.",
|
||||
"required": true,
|
||||
"type": "object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"docPath": "custom-documents/my-pdf.pdf-hash.json",
|
||||
"pinStatus": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/workspace/{slug}/chat": {
|
||||
"post": {
|
||||
"tags": [
|
||||
|
@ -3,6 +3,7 @@ const {
|
||||
writeResponseChunk,
|
||||
clientAbortedHandler,
|
||||
} = require("../../helpers/chat/responses");
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
|
||||
class AnthropicLLM {
|
||||
constructor(embedder = null, modelPreference = null) {
|
||||
@ -23,11 +24,7 @@ class AnthropicLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
throw new Error(
|
||||
"INVALID ANTHROPIC SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use Anthropic as your LLM."
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { AzureOpenAiEmbedder } = require("../../EmbeddingEngines/azureOpenAi");
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
writeResponseChunk,
|
||||
clientAbortedHandler,
|
||||
@ -23,11 +23,7 @@ class AzureOpenAiLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
console.warn(
|
||||
"No embedding provider defined for AzureOpenAiLLM - falling back to AzureOpenAiEmbedder for embedding!"
|
||||
);
|
||||
this.embedder = !embedder ? new AzureOpenAiEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,8 @@ class CohereLLM {
|
||||
system: this.promptWindowLimit() * 0.15,
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
this.embedder = !!embedder ? embedder : new NativeEmbedder();
|
||||
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
}
|
||||
|
||||
#appendContext(contextTexts = []) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
writeResponseChunk,
|
||||
clientAbortedHandler,
|
||||
@ -26,12 +27,9 @@ class GeminiLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
throw new Error(
|
||||
"INVALID GEMINI LLM SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use Gemini as your LLM."
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7; // not used for Gemini
|
||||
this.safetyThreshold = this.#fetchSafetyThreshold();
|
||||
}
|
||||
|
||||
#appendContext(contextTexts = []) {
|
||||
@ -46,6 +44,41 @@ class GeminiLLM {
|
||||
);
|
||||
}
|
||||
|
||||
// BLOCK_NONE can be a special candidate for some fields
|
||||
// https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-attributes#how_to_remove_automated_response_blocking_for_select_safety_attributes
|
||||
// so if you are wondering why BLOCK_NONE still failed, the link above will explain why.
|
||||
#fetchSafetyThreshold() {
|
||||
const threshold =
|
||||
process.env.GEMINI_SAFETY_SETTING ?? "BLOCK_MEDIUM_AND_ABOVE";
|
||||
const safetyThresholds = [
|
||||
"BLOCK_NONE",
|
||||
"BLOCK_ONLY_HIGH",
|
||||
"BLOCK_MEDIUM_AND_ABOVE",
|
||||
"BLOCK_LOW_AND_ABOVE",
|
||||
];
|
||||
return safetyThresholds.includes(threshold)
|
||||
? threshold
|
||||
: "BLOCK_MEDIUM_AND_ABOVE";
|
||||
}
|
||||
|
||||
#safetySettings() {
|
||||
return [
|
||||
{
|
||||
category: "HARM_CATEGORY_HATE_SPEECH",
|
||||
threshold: this.safetyThreshold,
|
||||
},
|
||||
{
|
||||
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||
threshold: this.safetyThreshold,
|
||||
},
|
||||
{ category: "HARM_CATEGORY_HARASSMENT", threshold: this.safetyThreshold },
|
||||
{
|
||||
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||
threshold: this.safetyThreshold,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
streamingEnabled() {
|
||||
return "streamGetChatCompletion" in this;
|
||||
}
|
||||
@ -146,6 +179,7 @@ class GeminiLLM {
|
||||
)?.content;
|
||||
const chatThread = this.gemini.startChat({
|
||||
history: this.formatMessages(messages),
|
||||
safetySettings: this.#safetySettings(),
|
||||
});
|
||||
const result = await chatThread.sendMessage(prompt);
|
||||
const response = result.response;
|
||||
@ -167,6 +201,7 @@ class GeminiLLM {
|
||||
)?.content;
|
||||
const chatThread = this.gemini.startChat({
|
||||
history: this.formatMessages(messages),
|
||||
safetySettings: this.#safetySettings(),
|
||||
});
|
||||
const responseStream = await chatThread.sendMessageStream(prompt);
|
||||
if (!responseStream.stream)
|
||||
|
@ -2,6 +2,7 @@ const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
const { toValidNumber } = require("../../http");
|
||||
|
||||
class GenericOpenAiLLM {
|
||||
constructor(embedder = null, modelPreference = null) {
|
||||
@ -18,7 +19,9 @@ class GenericOpenAiLLM {
|
||||
});
|
||||
this.model =
|
||||
modelPreference ?? process.env.GENERIC_OPEN_AI_MODEL_PREF ?? null;
|
||||
this.maxTokens = process.env.GENERIC_OPEN_AI_MAX_TOKENS ?? 1024;
|
||||
this.maxTokens = process.env.GENERIC_OPEN_AI_MAX_TOKENS
|
||||
? toValidNumber(process.env.GENERIC_OPEN_AI_MAX_TOKENS, 1024)
|
||||
: 1024;
|
||||
if (!this.model)
|
||||
throw new Error("GenericOpenAI must have a valid model set.");
|
||||
this.limits = {
|
||||
@ -27,11 +30,7 @@ class GenericOpenAiLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
console.warn(
|
||||
"No embedding provider defined for GenericOpenAiLLM - falling back to NativeEmbedder for embedding!"
|
||||
);
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
this.log(`Inference API: ${this.basePath} Model: ${this.model}`);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class GroqLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const { OpenAiEmbedder } = require("../../EmbeddingEngines/openAi");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
@ -26,11 +25,7 @@ class HuggingFaceLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
console.warn(
|
||||
"No embedding provider defined for HuggingFaceLLM - falling back to Native for embedding!"
|
||||
);
|
||||
this.embedder = !embedder ? new OpenAiEmbedder() : new NativeEmbedder();
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.2;
|
||||
}
|
||||
|
||||
|
@ -26,11 +26,7 @@ class KoboldCPPLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
console.warn(
|
||||
"No embedding provider defined for KoboldCPPLLM - falling back to NativeEmbedder for embedding!"
|
||||
);
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
this.log(`Inference API: ${this.basePath} Model: ${this.model}`);
|
||||
}
|
||||
|
174
server/utils/AiProviders/liteLLM/index.js
Normal file
174
server/utils/AiProviders/liteLLM/index.js
Normal file
@ -0,0 +1,174 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
writeResponseChunk,
|
||||
clientAbortedHandler,
|
||||
} = require("../../helpers/chat/responses");
|
||||
|
||||
class LiteLLM {
|
||||
constructor(embedder = null, modelPreference = null) {
|
||||
const { OpenAI: OpenAIApi } = require("openai");
|
||||
if (!process.env.LITE_LLM_BASE_PATH)
|
||||
throw new Error(
|
||||
"LiteLLM must have a valid base path to use for the api."
|
||||
);
|
||||
|
||||
this.basePath = process.env.LITE_LLM_BASE_PATH;
|
||||
this.openai = new OpenAIApi({
|
||||
baseURL: this.basePath,
|
||||
apiKey: process.env.LITE_LLM_API_KEY ?? null,
|
||||
});
|
||||
this.model = modelPreference ?? process.env.LITE_LLM_MODEL_PREF ?? null;
|
||||
this.maxTokens = process.env.LITE_LLM_MODEL_TOKEN_LIMIT ?? 1024;
|
||||
if (!this.model) throw new Error("LiteLLM must have a valid model set.");
|
||||
this.limits = {
|
||||
history: this.promptWindowLimit() * 0.15,
|
||||
system: this.promptWindowLimit() * 0.15,
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
this.log(`Inference API: ${this.basePath} Model: ${this.model}`);
|
||||
}
|
||||
|
||||
log(text, ...args) {
|
||||
console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args);
|
||||
}
|
||||
|
||||
#appendContext(contextTexts = []) {
|
||||
if (!contextTexts || !contextTexts.length) return "";
|
||||
return (
|
||||
"\nContext:\n" +
|
||||
contextTexts
|
||||
.map((text, i) => {
|
||||
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
}
|
||||
|
||||
streamingEnabled() {
|
||||
return "streamGetChatCompletion" in this;
|
||||
}
|
||||
|
||||
// Ensure the user set a value for the token limit
|
||||
// and if undefined - assume 4096 window.
|
||||
promptWindowLimit() {
|
||||
const limit = process.env.LITE_LLM_MODEL_TOKEN_LIMIT || 4096;
|
||||
if (!limit || isNaN(Number(limit)))
|
||||
throw new Error("No token context limit was set.");
|
||||
return Number(limit);
|
||||
}
|
||||
|
||||
// Short circuit since we have no idea if the model is valid or not
|
||||
// in pre-flight for generic endpoints
|
||||
isValidChatCompletionModel(_modelName = "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 getChatCompletion(messages = null, { temperature = 0.7 }) {
|
||||
const result = await this.openai.chat.completions
|
||||
.create({
|
||||
model: this.model,
|
||||
messages,
|
||||
temperature,
|
||||
max_tokens: parseInt(this.maxTokens), // LiteLLM requires int
|
||||
})
|
||||
.catch((e) => {
|
||||
throw new Error(e.response.data.error.message);
|
||||
});
|
||||
|
||||
if (!result.hasOwnProperty("choices") || result.choices.length === 0)
|
||||
return null;
|
||||
return result.choices[0].message.content;
|
||||
}
|
||||
|
||||
async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
|
||||
const streamRequest = await this.openai.chat.completions.create({
|
||||
model: this.model,
|
||||
stream: true,
|
||||
messages,
|
||||
temperature,
|
||||
max_tokens: parseInt(this.maxTokens), // LiteLLM requires int
|
||||
});
|
||||
return streamRequest;
|
||||
}
|
||||
|
||||
handleStream(response, stream, responseProps) {
|
||||
const { uuid = uuidv4(), sources = [] } = responseProps;
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
let fullText = "";
|
||||
|
||||
const handleAbort = () => clientAbortedHandler(resolve, fullText);
|
||||
response.on("close", handleAbort);
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const message = chunk?.choices?.[0];
|
||||
const token = message?.delta?.content;
|
||||
|
||||
if (token) {
|
||||
fullText += token;
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources: [],
|
||||
type: "textResponseChunk",
|
||||
textResponse: token,
|
||||
close: false,
|
||||
error: false,
|
||||
});
|
||||
}
|
||||
|
||||
// LiteLLM does not give a finish reason in stream until the final chunk
|
||||
if (message.finish_reason || message.finish_reason === "stop") {
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources,
|
||||
type: "textResponseChunk",
|
||||
textResponse: "",
|
||||
close: true,
|
||||
error: false,
|
||||
});
|
||||
response.removeListener("close", handleAbort);
|
||||
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 = {
|
||||
LiteLLM,
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
@ -27,11 +28,7 @@ class LMStudioLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
throw new Error(
|
||||
"INVALID LM STUDIO SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use LMStudio as your LLM."
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
@ -19,11 +20,7 @@ class LocalAiLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
throw new Error(
|
||||
"INVALID LOCAL AI SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use LocalAI as your LLM."
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
@ -20,11 +21,7 @@ class MistralLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
console.warn(
|
||||
"No embedding provider defined for MistralLLM - falling back to OpenAiEmbedder for embedding!"
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.0;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ class NativeLLM {
|
||||
system: this.promptWindowLimit() * 0.15,
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
this.embedder = embedder || new NativeEmbedder();
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.cacheDir = path.resolve(
|
||||
process.env.STORAGE_DIR
|
||||
? path.resolve(process.env.STORAGE_DIR, "models", "downloaded")
|
||||
|
@ -3,6 +3,7 @@ const {
|
||||
writeResponseChunk,
|
||||
clientAbortedHandler,
|
||||
} = require("../../helpers/chat/responses");
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
|
||||
// Docs: https://github.com/jmorganca/ollama/blob/main/docs/api.md
|
||||
class OllamaAILLM {
|
||||
@ -18,11 +19,7 @@ class OllamaAILLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
throw new Error(
|
||||
"INVALID OLLAMA SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use Ollama as your LLM."
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { OpenAiEmbedder } = require("../../EmbeddingEngines/openAi");
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
@ -18,11 +18,7 @@ class OpenAiLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
console.warn(
|
||||
"No embedding provider defined for OpenAiLLM - falling back to OpenAiEmbedder for embedding!"
|
||||
);
|
||||
this.embedder = !embedder ? new OpenAiEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ class OpenRouterLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
|
||||
if (!fs.existsSync(cacheFolder))
|
||||
|
@ -28,7 +28,7 @@ class PerplexityLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ class TextGenWebUILLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.embedder = embedder ?? new NativeEmbedder();
|
||||
this.defaultTemp = 0.7;
|
||||
this.log(`Inference API: ${this.basePath} Model: ${this.model}`);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
} = require("../../helpers/chat/responses");
|
||||
@ -23,11 +24,7 @@ class TogetherAiLLM {
|
||||
user: this.promptWindowLimit() * 0.7,
|
||||
};
|
||||
|
||||
if (!embedder)
|
||||
throw new Error(
|
||||
"INVALID TOGETHER AI SETUP. No embedding engine has been set. Go to instance settings and set up an embedding interface to use Together AI as your LLM."
|
||||
);
|
||||
this.embedder = embedder;
|
||||
this.embedder = !embedder ? new NativeEmbedder() : embedder;
|
||||
this.defaultTemp = 0.7;
|
||||
}
|
||||
|
||||
|
45
server/utils/EmbeddingEngines/voyageAi/index.js
Normal file
45
server/utils/EmbeddingEngines/voyageAi/index.js
Normal file
@ -0,0 +1,45 @@
|
||||
class VoyageAiEmbedder {
|
||||
constructor() {
|
||||
if (!process.env.VOYAGEAI_API_KEY)
|
||||
throw new Error("No Voyage AI API key was set.");
|
||||
|
||||
const {
|
||||
VoyageEmbeddings,
|
||||
} = require("@langchain/community/embeddings/voyage");
|
||||
const voyage = new VoyageEmbeddings({
|
||||
apiKey: process.env.VOYAGEAI_API_KEY,
|
||||
});
|
||||
|
||||
this.voyage = voyage;
|
||||
this.model = process.env.EMBEDDING_MODEL_PREF || "voyage-large-2-instruct";
|
||||
|
||||
// Limit of how many strings we can process in a single pass to stay with resource or network limits
|
||||
this.batchSize = 128; // Voyage AI's limit per request is 128 https://docs.voyageai.com/docs/rate-limits#use-larger-batches
|
||||
this.embeddingMaxChunkLength = 4000; // https://docs.voyageai.com/docs/embeddings - assume a token is roughly 4 letters with some padding
|
||||
}
|
||||
|
||||
async embedTextInput(textInput) {
|
||||
const result = await this.voyage.embedDocuments(
|
||||
Array.isArray(textInput) ? textInput : [textInput],
|
||||
{ modelName: this.model }
|
||||
);
|
||||
return result || [];
|
||||
}
|
||||
|
||||
async embedChunks(textChunks = []) {
|
||||
try {
|
||||
const embeddings = await this.voyage.embedDocuments(textChunks, {
|
||||
modelName: this.model,
|
||||
batchSize: this.batchSize,
|
||||
});
|
||||
return embeddings;
|
||||
} catch (error) {
|
||||
console.error("Voyage AI Failed to embed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
VoyageAiEmbedder,
|
||||
};
|
@ -17,6 +17,7 @@ class TextSplitter {
|
||||
Config: {
|
||||
chunkSize: number,
|
||||
chunkOverlap: number,
|
||||
chunkHeaderMeta: object | null, // Gets appended to top of each chunk as metadata
|
||||
}
|
||||
------
|
||||
*/
|
||||
@ -44,6 +45,18 @@ class TextSplitter {
|
||||
return prefValue > limit ? limit : prefValue;
|
||||
}
|
||||
|
||||
stringifyHeader() {
|
||||
if (!this.config.chunkHeaderMeta) return null;
|
||||
let content = "";
|
||||
Object.entries(this.config.chunkHeaderMeta).map(([key, value]) => {
|
||||
if (!key || !value) return;
|
||||
content += `${key}: ${value}\n`;
|
||||
});
|
||||
|
||||
if (!content) return null;
|
||||
return `<document_metadata>\n${content}</document_metadata>\n\n`;
|
||||
}
|
||||
|
||||
#setSplitter(config = {}) {
|
||||
// if (!config?.splitByFilename) {// TODO do something when specific extension is present? }
|
||||
return new RecursiveSplitter({
|
||||
@ -51,6 +64,7 @@ class TextSplitter {
|
||||
chunkOverlap: isNaN(config?.chunkOverlap)
|
||||
? 20
|
||||
: Number(config?.chunkOverlap),
|
||||
chunkHeader: this.stringifyHeader(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,11 +75,12 @@ class TextSplitter {
|
||||
|
||||
// Wrapper for Langchain default RecursiveCharacterTextSplitter class.
|
||||
class RecursiveSplitter {
|
||||
constructor({ chunkSize, chunkOverlap }) {
|
||||
constructor({ chunkSize, chunkOverlap, chunkHeader = null }) {
|
||||
const {
|
||||
RecursiveCharacterTextSplitter,
|
||||
} = require("@langchain/textsplitters");
|
||||
this.log(`Will split with`, { chunkSize, chunkOverlap });
|
||||
this.chunkHeader = chunkHeader;
|
||||
this.engine = new RecursiveCharacterTextSplitter({
|
||||
chunkSize,
|
||||
chunkOverlap,
|
||||
@ -77,7 +92,14 @@ class RecursiveSplitter {
|
||||
}
|
||||
|
||||
async _splitText(documentText) {
|
||||
return this.engine.splitText(documentText);
|
||||
if (!this.chunkHeader) return this.engine.splitText(documentText);
|
||||
const strings = await this.engine.splitText(documentText);
|
||||
const documents = await this.engine.createDocuments(strings, [], {
|
||||
chunkHeader: this.chunkHeader,
|
||||
});
|
||||
return documents
|
||||
.filter((doc) => !!doc.pageContent)
|
||||
.map((doc) => doc.pageContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,6 +498,17 @@ Only return the role.
|
||||
return availableNodes[Math.floor(Math.random() * availableNodes.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pluginName this name of the plugin being called
|
||||
* @returns string of the plugin to be called compensating for children denoted by # in the string.
|
||||
* eg: sql-agent:list-database-connections
|
||||
*/
|
||||
#parseFunctionName(pluginName = "") {
|
||||
if (!pluginName.includes("#")) return pluginName;
|
||||
return pluginName.split("#")[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the chat has reached the maximum number of rounds.
|
||||
*/
|
||||
@ -550,7 +561,7 @@ ${this.getHistory({ to: route.to })
|
||||
|
||||
// get the functions that the node can call
|
||||
const functions = fromConfig.functions
|
||||
?.map((name) => this.functions.get(name))
|
||||
?.map((name) => this.functions.get(this.#parseFunctionName(name)))
|
||||
.filter((a) => !!a);
|
||||
|
||||
const provider = this.getProviderForConfig({
|
||||
|
@ -6,6 +6,7 @@ const { saveFileInBrowser } = require("./save-file-browser.js");
|
||||
const { chatHistory } = require("./chat-history.js");
|
||||
const { memory } = require("./memory.js");
|
||||
const { rechart } = require("./rechart.js");
|
||||
const { sqlAgent } = require("./sql-agent/index.js");
|
||||
|
||||
module.exports = {
|
||||
webScraping,
|
||||
@ -16,6 +17,7 @@ module.exports = {
|
||||
chatHistory,
|
||||
memory,
|
||||
rechart,
|
||||
sqlAgent,
|
||||
|
||||
// Plugin name aliases so they can be pulled by slug as well.
|
||||
[webScraping.name]: webScraping,
|
||||
@ -26,4 +28,5 @@ module.exports = {
|
||||
[chatHistory.name]: chatHistory,
|
||||
[memory.name]: memory,
|
||||
[rechart.name]: rechart,
|
||||
[sqlAgent.name]: sqlAgent,
|
||||
};
|
||||
|
@ -0,0 +1,89 @@
|
||||
const mssql = require("mssql");
|
||||
const UrlPattern = require("url-pattern");
|
||||
|
||||
class MSSQLConnector {
|
||||
#connected = false;
|
||||
database_id = "";
|
||||
connectionConfig = {
|
||||
user: null,
|
||||
password: null,
|
||||
database: null,
|
||||
server: null,
|
||||
port: null,
|
||||
pool: {
|
||||
max: 10,
|
||||
min: 0,
|
||||
idleTimeoutMillis: 30000,
|
||||
},
|
||||
options: {
|
||||
encrypt: false,
|
||||
trustServerCertificate: true,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(
|
||||
config = {
|
||||
// we will force into RFC-3986 from DB
|
||||
// eg: mssql://user:password@server:port/database?{...opts}
|
||||
connectionString: null, // we will force into RFC-3986
|
||||
}
|
||||
) {
|
||||
this.connectionString = config.connectionString;
|
||||
this._client = null;
|
||||
this.#parseDatabase();
|
||||
}
|
||||
|
||||
#parseDatabase() {
|
||||
const connectionPattern = new UrlPattern(
|
||||
"mssql\\://:username\\::password@*\\::port/:database*"
|
||||
);
|
||||
const match = connectionPattern.match(this.connectionString);
|
||||
this.database_id = match?.database;
|
||||
this.connectionConfig = {
|
||||
...this.connectionConfig,
|
||||
user: match?.username,
|
||||
password: match?.password,
|
||||
database: match?.database,
|
||||
server: match?._[0],
|
||||
port: match?.port ? Number(match.port) : null,
|
||||
};
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this._client = await mssql.connect(this.connectionConfig);
|
||||
this.#connected = true;
|
||||
return this._client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} queryString the SQL query to be run
|
||||
* @returns {import(".").QueryResult}
|
||||
*/
|
||||
async runQuery(queryString = "") {
|
||||
const result = { rows: [], count: 0, error: null };
|
||||
try {
|
||||
if (!this.#connected) await this.connect();
|
||||
|
||||
const query = await this._client.query(queryString);
|
||||
result.rows = query.recordset;
|
||||
result.count = query.rowsAffected.reduce((sum, a) => sum + a, 0);
|
||||
} catch (err) {
|
||||
console.log(this.constructor.name, err);
|
||||
result.error = err.message;
|
||||
} finally {
|
||||
await this._client.close();
|
||||
this.#connected = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getTablesSql() {
|
||||
return `SELECT name FROM sysobjects WHERE xtype='U';`;
|
||||
}
|
||||
getTableSchemaSql(table_name) {
|
||||
return `SELECT COLUMN_NAME,COLUMN_DEFAULT,IS_NULLABLE,DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='${table_name}'`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.MSSQLConnector = MSSQLConnector;
|
@ -0,0 +1,59 @@
|
||||
const mysql = require("mysql2/promise");
|
||||
const UrlPattern = require("url-pattern");
|
||||
|
||||
class MySQLConnector {
|
||||
#connected = false;
|
||||
database_id = "";
|
||||
constructor(
|
||||
config = {
|
||||
connectionString: null,
|
||||
}
|
||||
) {
|
||||
this.connectionString = config.connectionString;
|
||||
this._client = null;
|
||||
this.database_id = this.#parseDatabase();
|
||||
}
|
||||
|
||||
#parseDatabase() {
|
||||
const connectionPattern = new UrlPattern("mysql\\://*@*/:database*");
|
||||
const match = connectionPattern.match(this.connectionString);
|
||||
return match?.database;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this._client = await mysql.createConnection({ uri: this.connectionString });
|
||||
this.#connected = true;
|
||||
return this._client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} queryString the SQL query to be run
|
||||
* @returns {import(".").QueryResult}
|
||||
*/
|
||||
async runQuery(queryString = "") {
|
||||
const result = { rows: [], count: 0, error: null };
|
||||
try {
|
||||
if (!this.#connected) await this.connect();
|
||||
const [query] = await this._client.query(queryString);
|
||||
result.rows = query;
|
||||
result.count = query?.length;
|
||||
} catch (err) {
|
||||
console.log(this.constructor.name, err);
|
||||
result.error = err.message;
|
||||
} finally {
|
||||
await this._client.end();
|
||||
this.#connected = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getTablesSql() {
|
||||
return `SELECT table_name FROM information_schema.tables WHERE table_schema = '${this.database_id}'`;
|
||||
}
|
||||
getTableSchemaSql(table_name) {
|
||||
return `SHOW COLUMNS FROM ${this.database_id}.${table_name};`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.MySQLConnector = MySQLConnector;
|
@ -0,0 +1,52 @@
|
||||
const pgSql = require("pg");
|
||||
|
||||
class PostgresSQLConnector {
|
||||
#connected = false;
|
||||
constructor(
|
||||
config = {
|
||||
connectionString: null,
|
||||
}
|
||||
) {
|
||||
this.connectionString = config.connectionString;
|
||||
this._client = new pgSql.Client({
|
||||
connectionString: this.connectionString,
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
await this._client.connect();
|
||||
this.#connected = true;
|
||||
return this._client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} queryString the SQL query to be run
|
||||
* @returns {import(".").QueryResult}
|
||||
*/
|
||||
async runQuery(queryString = "") {
|
||||
const result = { rows: [], count: 0, error: null };
|
||||
try {
|
||||
if (!this.#connected) await this.connect();
|
||||
const query = await this._client.query(queryString);
|
||||
result.rows = query.rows;
|
||||
result.count = query.rowCount;
|
||||
} catch (err) {
|
||||
console.log(this.constructor.name, err);
|
||||
result.error = err.message;
|
||||
} finally {
|
||||
await this._client.end();
|
||||
this.#connected = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getTablesSql() {
|
||||
return `SELECT * FROM pg_catalog.pg_tables WHERE schemaname = 'public'`;
|
||||
}
|
||||
getTableSchemaSql(table_name) {
|
||||
return ` select column_name, data_type, character_maximum_length, column_default, is_nullable from INFORMATION_SCHEMA.COLUMNS where table_name = '${table_name}'`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.PostgresSQLConnector = PostgresSQLConnector;
|
@ -0,0 +1,60 @@
|
||||
const { SystemSettings } = require("../../../../../../models/systemSettings");
|
||||
const { safeJsonParse } = require("../../../../../http");
|
||||
|
||||
/**
|
||||
* @typedef {('postgresql'|'mysql'|'sql-server')} SQLEngine
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} QueryResult
|
||||
* @property {[number]} rows - The query result rows
|
||||
* @property {number} count - Number of rows the query returned/changed
|
||||
* @property {string|null} error - Error string if there was an issue
|
||||
*/
|
||||
|
||||
/**
|
||||
* A valid database SQL connection object
|
||||
* @typedef {Object} SQLConnection
|
||||
* @property {string} database_id - Unique identifier of the database connection
|
||||
* @property {SQLEngine} engine - Engine used by connection
|
||||
* @property {string} connectionString - RFC connection string for db
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {SQLEngine} identifier
|
||||
* @param {object} connectionConfig
|
||||
* @returns Database Connection Engine Class for SQLAgent or throws error
|
||||
*/
|
||||
function getDBClient(identifier = "", connectionConfig = {}) {
|
||||
switch (identifier) {
|
||||
case "mysql":
|
||||
const { MySQLConnector } = require("./MySQL");
|
||||
return new MySQLConnector(connectionConfig);
|
||||
case "postgresql":
|
||||
const { PostgresSQLConnector } = require("./Postgresql");
|
||||
return new PostgresSQLConnector(connectionConfig);
|
||||
case "sql-server":
|
||||
const { MSSQLConnector } = require("./MSSQL");
|
||||
return new MSSQLConnector(connectionConfig);
|
||||
default:
|
||||
throw new Error(
|
||||
`There is no supported database connector for ${identifier}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all of the known database connection that can be used by the agent.
|
||||
* @returns {Promise<[SQLConnection]>}
|
||||
*/
|
||||
async function listSQLConnections() {
|
||||
return safeJsonParse(
|
||||
(await SystemSettings.get({ label: "agent_sql_connections" }))?.value,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDBClient,
|
||||
listSQLConnections,
|
||||
};
|
@ -0,0 +1,98 @@
|
||||
module.exports.SqlAgentGetTableSchema = {
|
||||
name: "sql-get-table-schema",
|
||||
plugin: function () {
|
||||
const {
|
||||
listSQLConnections,
|
||||
getDBClient,
|
||||
} = require("./SQLConnectors/index.js");
|
||||
|
||||
return {
|
||||
name: "sql-get-table-schema",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Gets the table schema in SQL for a given `table` and `database_id`",
|
||||
examples: [
|
||||
{
|
||||
prompt: "What does the customers table in access-logs look like?",
|
||||
call: JSON.stringify({
|
||||
database_id: "access-logs",
|
||||
table_name: "customers",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt:
|
||||
"Get me the full name of a company in records-main, the table should be call comps",
|
||||
call: JSON.stringify({
|
||||
database_id: "records-main",
|
||||
table_name: "comps",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
database_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"The database identifier for which we will connect to to query the table schema. This is a required field.",
|
||||
},
|
||||
table_name: {
|
||||
type: "string",
|
||||
description:
|
||||
"The database identifier for the table name we want the schema for. This is a required field.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
required: ["database_id", "table_name"],
|
||||
handler: async function ({ database_id = "", table_name = "" }) {
|
||||
this.super.handlerProps.log(`Using the sql-get-table-schema tool.`);
|
||||
try {
|
||||
const databaseConfig = (await listSQLConnections()).find(
|
||||
(db) => db.database_id === database_id
|
||||
);
|
||||
if (!databaseConfig) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-get-table-schema to find config!.`,
|
||||
database_id
|
||||
);
|
||||
return `No database connection for ${database_id} was found!`;
|
||||
}
|
||||
|
||||
const db = getDBClient(databaseConfig.engine, databaseConfig);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Querying the table schema for ${table_name} in the ${databaseConfig.database_id} database.`
|
||||
);
|
||||
this.super.introspect(
|
||||
`Running SQL: ${db.getTableSchemaSql(table_name)}`
|
||||
);
|
||||
const result = await db.runQuery(
|
||||
db.getTableSchemaSql(table_name)
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-get-table-schema tool reported error`,
|
||||
result.error
|
||||
);
|
||||
this.super.introspect(`Error: ${result.error}`);
|
||||
return `There was an error running the query: ${result.error}`;
|
||||
}
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (e) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-get-table-schema raised an error. ${e.message}`
|
||||
);
|
||||
return e.message;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
21
server/utils/agents/aibitat/plugins/sql-agent/index.js
Normal file
21
server/utils/agents/aibitat/plugins/sql-agent/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
const { SqlAgentGetTableSchema } = require("./get-table-schema");
|
||||
const { SqlAgentListDatabase } = require("./list-database");
|
||||
const { SqlAgentListTables } = require("./list-table");
|
||||
const { SqlAgentQuery } = require("./query");
|
||||
|
||||
const sqlAgent = {
|
||||
name: "sql-agent",
|
||||
startupConfig: {
|
||||
params: {},
|
||||
},
|
||||
plugin: [
|
||||
SqlAgentListDatabase,
|
||||
SqlAgentListTables,
|
||||
SqlAgentGetTableSchema,
|
||||
SqlAgentQuery,
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sqlAgent,
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
module.exports.SqlAgentListDatabase = {
|
||||
name: "sql-list-databases",
|
||||
plugin: function () {
|
||||
const { listSQLConnections } = require("./SQLConnectors");
|
||||
return {
|
||||
name: "sql-list-databases",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"List all available databases via `list_databases` you currently have access to. Returns a unique string identifier `database_id` that can be used for future calls.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "What databases can you access?",
|
||||
call: JSON.stringify({}),
|
||||
},
|
||||
{
|
||||
prompt: "What databases can you tell me about?",
|
||||
call: JSON.stringify({}),
|
||||
},
|
||||
{
|
||||
prompt: "Is there a database named erp-logs you can access?",
|
||||
call: JSON.stringify({}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function () {
|
||||
this.super.handlerProps.log(`Using the sql-list-databases tool.`);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Checking what are the available databases.`
|
||||
);
|
||||
|
||||
const connections = (await listSQLConnections()).map((conn) => {
|
||||
const { connectionString, ...rest } = conn;
|
||||
return rest;
|
||||
});
|
||||
return JSON.stringify(connections);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
85
server/utils/agents/aibitat/plugins/sql-agent/list-table.js
Normal file
85
server/utils/agents/aibitat/plugins/sql-agent/list-table.js
Normal file
@ -0,0 +1,85 @@
|
||||
module.exports.SqlAgentListTables = {
|
||||
name: "sql-list-tables",
|
||||
plugin: function () {
|
||||
const {
|
||||
listSQLConnections,
|
||||
getDBClient,
|
||||
} = require("./SQLConnectors/index.js");
|
||||
|
||||
return {
|
||||
name: "sql-list-tables",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"List all available tables in a database via its `database_id`.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "What tables are there in the `access-logs` database?",
|
||||
call: JSON.stringify({ database_id: "access-logs" }),
|
||||
},
|
||||
{
|
||||
prompt:
|
||||
"What information can you access in the customer_accts postgres db?",
|
||||
call: JSON.stringify({ database_id: "customer_accts" }),
|
||||
},
|
||||
{
|
||||
prompt: "Can you tell me what is in the primary-logs db?",
|
||||
call: JSON.stringify({ database_id: "primary-logs" }),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
database_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"The database identifier for which we will list all tables for. This is a required parameter",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
required: ["database_id"],
|
||||
handler: async function ({ database_id = "" }) {
|
||||
try {
|
||||
this.super.handlerProps.log(`Using the sql-list-tables tool.`);
|
||||
const databaseConfig = (await listSQLConnections()).find(
|
||||
(db) => db.database_id === database_id
|
||||
);
|
||||
if (!databaseConfig) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-list-tables failed to find config!.`,
|
||||
database_id
|
||||
);
|
||||
return `No database connection for ${database_id} was found!`;
|
||||
}
|
||||
|
||||
const db = getDBClient(databaseConfig.engine, databaseConfig);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Checking what are the available tables in the ${databaseConfig.database_id} database.`
|
||||
);
|
||||
|
||||
this.super.introspect(`Running SQL: ${db.getTablesSql()}`);
|
||||
const result = await db.runQuery(db.getTablesSql(database_id));
|
||||
if (result.error) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-list-tables tool reported error`,
|
||||
result.error
|
||||
);
|
||||
this.super.introspect(`Error: ${result.error}`);
|
||||
return `There was an error running the query: ${result.error}`;
|
||||
}
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return e.message;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
101
server/utils/agents/aibitat/plugins/sql-agent/query.js
Normal file
101
server/utils/agents/aibitat/plugins/sql-agent/query.js
Normal file
@ -0,0 +1,101 @@
|
||||
module.exports.SqlAgentQuery = {
|
||||
name: "sql-query",
|
||||
plugin: function () {
|
||||
const {
|
||||
getDBClient,
|
||||
listSQLConnections,
|
||||
} = require("./SQLConnectors/index.js");
|
||||
|
||||
return {
|
||||
name: "sql-query",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Run a read-only SQL query on a `database_id` which will return up rows of data related to the query. The query must only be SELECT statements which do not modify the table data. There should be a reasonable LIMIT on the return quantity to prevent long-running or queries which crash the db.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "How many customers are in dvd-rentals?",
|
||||
call: JSON.stringify({
|
||||
database_id: "dvd-rentals",
|
||||
sql_query: "SELECT * FROM customers",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Can you tell me the total volume of sales last month?",
|
||||
call: JSON.stringify({
|
||||
database_id: "sales-db",
|
||||
sql_query:
|
||||
"SELECT SUM(sale_amount) AS total_sales FROM sales WHERE sale_date >= DATEADD(month, -1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)) AND sale_date < DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt:
|
||||
"Do we have anyone in the staff table for our production db named 'sam'? ",
|
||||
call: JSON.stringify({
|
||||
database_id: "production",
|
||||
sql_query:
|
||||
"SElECT * FROM staff WHERE first_name='sam%' OR last_name='sam%'",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
database_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"The database identifier for which we will connect to to query the table schema. This is required to run the SQL query.",
|
||||
},
|
||||
sql_query: {
|
||||
type: "string",
|
||||
description:
|
||||
"The raw SQL query to run. Should be a query which does not modify the table and will return results.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
required: ["database_id", "table_name"],
|
||||
handler: async function ({ database_id = "", sql_query = "" }) {
|
||||
this.super.handlerProps.log(`Using the sql-query tool.`);
|
||||
try {
|
||||
const databaseConfig = (await listSQLConnections()).find(
|
||||
(db) => db.database_id === database_id
|
||||
);
|
||||
if (!databaseConfig) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-query failed to find config!.`,
|
||||
database_id
|
||||
);
|
||||
return `No database connection for ${database_id} was found!`;
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Im going to run a query on the ${database_id} to get an answer.`
|
||||
);
|
||||
const db = getDBClient(databaseConfig.engine, databaseConfig);
|
||||
|
||||
this.super.introspect(`Running SQL: ${sql_query}`);
|
||||
const result = await db.runQuery(sql_query);
|
||||
if (result.error) {
|
||||
this.super.handlerProps.log(
|
||||
`sql-query tool reported error`,
|
||||
result.error
|
||||
);
|
||||
this.super.introspect(`Error: ${result.error}`);
|
||||
return `There was an error running the query: ${result.error}`;
|
||||
}
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return e.message;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
const { Document } = require("../../../../models/documents");
|
||||
const { safeJsonParse } = require("../../../http");
|
||||
const { validate } = require("uuid");
|
||||
const { summarizeContent } = require("../utils/summarize");
|
||||
const Provider = require("../providers/ai-provider");
|
||||
|
||||
|
@ -49,7 +49,7 @@ const websocket = {
|
||||
setup(aibitat) {
|
||||
aibitat.onError(async (error) => {
|
||||
if (!!error?.message) {
|
||||
console.error(chalk.red(` error: ${error.message}`));
|
||||
console.error(chalk.red(` error: ${error.message}`), error);
|
||||
aibitat.introspect(
|
||||
`Error encountered while running: ${error.message}`
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ const { RetryError } = require("../error.js");
|
||||
const Provider = require("./ai-provider.js");
|
||||
|
||||
/**
|
||||
* The provider for the Anthropic API.
|
||||
* The agent provider for the Anthropic API.
|
||||
* By default, the model is set to 'claude-2'.
|
||||
*/
|
||||
class AnthropicProvider extends Provider {
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the Azure OpenAI API.
|
||||
* The agent provider for the Azure OpenAI API.
|
||||
*/
|
||||
class AzureOpenAiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -84,6 +84,11 @@ class AzureOpenAiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
);
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return { result: completion.content, cost: 0 };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
@ -2,9 +2,10 @@ const OpenAI = require("openai");
|
||||
const Provider = require("./ai-provider.js");
|
||||
const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
const { toValidNumber } = require("../../../http/index.js");
|
||||
|
||||
/**
|
||||
* The provider for the Generic OpenAI provider.
|
||||
* The agent provider for the Generic OpenAI provider.
|
||||
* Since we cannot promise the generic provider even supports tool calling
|
||||
* which is nearly 100% likely it does not, we can just wrap it in untooled
|
||||
* which often is far better anyway.
|
||||
@ -24,7 +25,9 @@ class GenericOpenAiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
this._client = client;
|
||||
this.model = model;
|
||||
this.verbose = true;
|
||||
this.maxTokens = process.env.GENERIC_OPEN_AI_MAX_TOKENS ?? 1024;
|
||||
this.maxTokens = process.env.GENERIC_OPEN_AI_MAX_TOKENS
|
||||
? toValidNumber(process.env.GENERIC_OPEN_AI_MAX_TOKENS, 1024)
|
||||
: 1024;
|
||||
}
|
||||
|
||||
get client() {
|
||||
@ -94,6 +97,10 @@ class GenericOpenAiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -3,7 +3,7 @@ const Provider = require("./ai-provider.js");
|
||||
const { RetryError } = require("../error.js");
|
||||
|
||||
/**
|
||||
* The provider for the Groq provider.
|
||||
* The agent provider for the Groq provider.
|
||||
* Using OpenAI tool calling with groq really sucks right now
|
||||
* its just fast and bad. We should probably migrate this to Untooled to improve
|
||||
* coherence.
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the KoboldCPP provider.
|
||||
* The agent provider for the KoboldCPP provider.
|
||||
*/
|
||||
class KoboldCPPProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -89,6 +89,10 @@ class KoboldCPPProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the LMStudio provider.
|
||||
* The agent provider for the LMStudio.
|
||||
*/
|
||||
class LMStudioProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -89,6 +89,10 @@ class LMStudioProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the LocalAI provider.
|
||||
* The agent provider for the LocalAI provider.
|
||||
*/
|
||||
class LocalAiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -93,6 +93,10 @@ class LocalAiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return { result: completion.content, cost: 0 };
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the Mistral provider.
|
||||
* The agent provider for the Mistral provider.
|
||||
* Mistral limits what models can call tools and even when using those
|
||||
* the model names change and dont match docs. When you do have the right model
|
||||
* it still fails and is not truly OpenAI compatible so its easier to just wrap
|
||||
@ -93,6 +93,10 @@ class MistralProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -4,7 +4,7 @@ const UnTooled = require("./helpers/untooled.js");
|
||||
const { Ollama } = require("ollama");
|
||||
|
||||
/**
|
||||
* The provider for the Ollama provider.
|
||||
* The agent provider for the Ollama provider.
|
||||
*/
|
||||
class OllamaProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -83,6 +83,10 @@ class OllamaProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -3,7 +3,7 @@ const Provider = require("./ai-provider.js");
|
||||
const { RetryError } = require("../error.js");
|
||||
|
||||
/**
|
||||
* The provider for the OpenAI API.
|
||||
* The agent provider for the OpenAI API.
|
||||
* By default, the model is set to 'gpt-3.5-turbo'.
|
||||
*/
|
||||
class OpenAIProvider extends Provider {
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the OpenRouter provider.
|
||||
* The agent provider for the OpenRouter provider.
|
||||
*/
|
||||
class OpenRouterProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -93,6 +93,10 @@ class OpenRouterProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the Perplexity provider.
|
||||
* The agent provider for the Perplexity provider.
|
||||
*/
|
||||
class PerplexityProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -89,6 +89,10 @@ class PerplexityProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the Oobabooga provider.
|
||||
* The agent provider for the Oobabooga provider.
|
||||
*/
|
||||
class TextWebGenUiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -88,6 +88,10 @@ class TextWebGenUiProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -4,7 +4,7 @@ const InheritMultiple = require("./helpers/classes.js");
|
||||
const UnTooled = require("./helpers/untooled.js");
|
||||
|
||||
/**
|
||||
* The provider for the TogetherAI provider.
|
||||
* The agent provider for the TogetherAI provider.
|
||||
*/
|
||||
class TogetherAIProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
model;
|
||||
@ -89,6 +89,10 @@ class TogetherAIProvider extends InheritMultiple([Provider, UnTooled]) {
|
||||
completion = response.choices[0].message;
|
||||
}
|
||||
|
||||
// The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
|
||||
// from calling the exact same function over and over in a loop within a single chat exchange
|
||||
// _but_ we should enable it to call previously used tools in a new chat interaction.
|
||||
this.deduplicator.reset("runs");
|
||||
return {
|
||||
result: completion.content,
|
||||
cost: 0,
|
||||
|
@ -38,6 +38,25 @@ class Deduplicator {
|
||||
return this.#hashes.hasOwnProperty(newSig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the object property for this instance of the Deduplicator class
|
||||
* @param {('runs'|'cooldowns'|'uniques')} type - The type of prop to reset
|
||||
*/
|
||||
reset(type = "runs") {
|
||||
switch (type) {
|
||||
case "runs":
|
||||
this.#hashes = {};
|
||||
break;
|
||||
case "cooldowns":
|
||||
this.#cooldowns = {};
|
||||
break;
|
||||
case "uniques":
|
||||
this.#uniques = {};
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
startCooldown(
|
||||
key,
|
||||
parameters = {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user