Multiple LLM Support framework + AzureOpenAI Support (#180)

* Remove LangchainJS for chat support chaining
Implement runtime LLM selection
Implement AzureOpenAI Support for LLM + Emebedding
WIP on frontend
Update env to reflect the new fields

* Remove LangchainJS for chat support chaining
Implement runtime LLM selection
Implement AzureOpenAI Support for LLM + Emebedding
WIP on frontend
Update env to reflect the new fields

* Replace keys with LLM Selection in settings modal
Enforce checks for new ENVs depending on LLM selection
This commit is contained in:
Timothy Carambat 2023-08-04 14:56:27 -07:00 committed by GitHub
parent 285bddb0fb
commit 1f29cec918
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 699 additions and 336 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"openai"
]
}

View File

@ -1,8 +1,24 @@
SERVER_PORT=3001
OPEN_AI_KEY=
OPEN_MODEL_PREF='gpt-3.5-turbo'
CACHE_VECTORS="true"
# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long.
###########################################
######## LLM API SElECTION ################
###########################################
LLM_PROVIDER='openai'
# OPEN_AI_KEY=
OPEN_MODEL_PREF='gpt-3.5-turbo'
# LLM_PROVIDER='azure'
# AZURE_OPENAI_ENDPOINT=
# AZURE_OPENAI_KEY=
# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model.
# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002
###########################################
######## Vector Database Selection ########
###########################################
# Enable all below if you are using vector database: Chroma.
# VECTOR_DB="chroma"
# CHROMA_ENDPOINT='http://localhost:8000'
@ -18,7 +34,6 @@ PINECONE_INDEX=
# CLOUD DEPLOYMENT VARIRABLES ONLY
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long.
# NO_DEBUG="true"
STORAGE_DIR="./server/storage"
GOOGLE_APIS_KEY=

View File

@ -1,220 +0,0 @@
import React, { useState } from "react";
import { AlertCircle, Loader } from "react-feather";
import System from "../../../../models/system";
const noop = () => false;
export default function SystemKeys({ hideModal = noop, user, settings = {} }) {
const canDebug = settings.MultiUserMode
? settings?.CanDebug && user?.role === "admin"
: settings?.CanDebug;
function validSettings(settings) {
return (
settings?.OpenAiKey &&
!!settings?.OpenAiModelPref &&
!!settings?.VectorDB &&
(settings?.VectorDB === "chroma" ? !!settings?.ChromaEndpoint : true) &&
(settings?.VectorDB === "pinecone"
? !!settings?.PineConeKey &&
!!settings?.PineConeEnvironment &&
!!settings?.PineConeIndex
: true)
);
}
return (
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
These are the credentials and settings for how your AnythingLLM
instance will function. Its important these keys are current and
correct.
</p>
</div>
<div className="p-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
{!validSettings(settings) && (
<div className="bg-orange-300 p-4 rounded-lg border border-orange-600 text-orange-700 w-full items-center flex gap-x-2">
<AlertCircle className="h-8 w-8" />
<p className="text-sm md:text-base ">
Ensure all fields are green before attempting to use
AnythingLLM or it may not function as expected!
</p>
</div>
)}
<ShowKey
name="OpenAI API Key"
env="OpenAiKey"
value={settings?.OpenAiKey ? "*".repeat(20) : ""}
valid={settings?.OpenAiKey}
allowDebug={canDebug}
/>
<ShowKey
name="OpenAI Model for chats"
env="OpenAiModelPref"
value={settings?.OpenAiModelPref}
valid={!!settings?.OpenAiModelPref}
allowDebug={canDebug}
/>
</div>
</div>
<div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button
onClick={hideModal}
type="button"
className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
>
Close
</button>
</div>
</div>
</div>
);
}
function ShowKey({ name, env, value, valid, allowDebug = true }) {
const [isValid, setIsValid] = useState(valid);
const [debug, setDebug] = useState(false);
const [saving, setSaving] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
const data = {};
const form = new FormData(e.target);
for (var [key, value] of form.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
if (!!error) {
alert(error);
setSaving(false);
setIsValid(false);
return;
}
setSaving(false);
setDebug(false);
setIsValid(true);
};
if (!isValid) {
return (
<form onSubmit={handleSubmit}>
<div>
<label
htmlFor="error"
className="block mb-2 text-sm font-medium text-red-700 dark:text-red-500"
>
{name}
</label>
<input
type="text"
id="error"
name={env}
disabled={!debug}
className="bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 dark:bg-gray-700 focus:border-red-500 block w-full p-2.5 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500"
placeholder={name}
defaultValue={value}
required={true}
autoComplete="off"
/>
<div className="flex items-center justify-between">
<p className="mt-2 text-sm text-red-600 dark:text-red-500">
Need setup in .env file.
</p>
{allowDebug && (
<>
{debug ? (
<div className="flex items-center gap-x-2 mt-2">
{saving ? (
<>
<Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" />
</>
) : (
<>
<button
type="button"
onClick={() => setDebug(false)}
className="text-xs text-slate-300 dark:text-slate-500"
>
Cancel
</button>
<button
type="submit"
className="text-xs text-blue-300 dark:text-blue-500"
>
Save
</button>
</>
)}
</div>
) : (
<button
type="button"
onClick={() => setDebug(true)}
className="mt-2 text-xs text-slate-300 dark:text-slate-500"
>
Change
</button>
)}
</>
)}
</div>
</div>
</form>
);
}
return (
<form onSubmit={handleSubmit}>
<div className="mb-6">
<label
htmlFor="success"
className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"
>
{name}
</label>
<input
type="text"
id="success"
name={env}
disabled={!debug}
className="border border-white text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 text-sm rounded-lg focus:ring-green-500 focus:border-green-500 block w-full p-2.5 dark:bg-gray-700 dark:border-green-500"
defaultValue={value}
required={true}
autoComplete="off"
/>
{allowDebug && (
<div className="flex items-center justify-end">
{debug ? (
<div className="flex items-center gap-x-2 mt-2">
{saving ? (
<>
<Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" />
</>
) : (
<>
<button
onClick={() => setDebug(false)}
className="text-xs text-slate-300 dark:text-slate-500"
>
Cancel
</button>
<button className="text-xs text-blue-300 dark:text-blue-500">
Save
</button>
</>
)}
</div>
) : (
<button
onClick={() => setDebug(true)}
className="mt-2 text-xs text-slate-300 dark:text-slate-500"
>
Change
</button>
)}
</div>
)}
</div>
</form>
);
}

View File

@ -0,0 +1,281 @@
import React, { useState } from "react";
import System from "../../../../models/system";
import OpenAiLogo from "../../../../media/llmprovider/openai.png";
import AzureOpenAiLogo from "../../../../media/llmprovider/azure.png";
import AnthropicLogo from "../../../../media/llmprovider/anthropic.png";
const noop = () => false;
export default function LLMSelection({
hideModal = noop,
user,
settings = {},
}) {
const [hasChanges, setHasChanges] = useState(false);
const [llmChoice, setLLMChoice] = useState(settings?.LLMProvider || "openai");
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const canDebug = settings.MultiUserMode
? settings?.CanDebug && user?.role === "admin"
: settings?.CanDebug;
function updateLLMChoice(selection) {
if (!canDebug || selection === llmChoice) return false;
setHasChanges(true);
setLLMChoice(selection);
}
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
setError(null);
const data = {};
const form = new FormData(e.target);
for (var [key, value] of form.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
setError(error);
setSaving(false);
setHasChanges(!!error ? true : false);
};
return (
<div className="relative w-full max-w-2xl max-h-full">
<div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
<div className="flex items-start justify-between px-6 py-4">
<p className="text-gray-800 dark:text-stone-200 text-base ">
These are the credentials and settings for your preferred LLM chat &
embedding provider. Its important these keys are current and correct
or else AnythingLLM will not function properly.
</p>
</div>
{!!error && (
<div className="mb-8 bg-red-700 dark:bg-orange-800 bg-opacity-30 border border-red-800 dark:border-orange-600 p-4 rounded-lg w-[90%] flex mx-auto">
<p className="text-red-800 dark:text-orange-300 text-sm">{error}</p>
</div>
)}
<form onSubmit={handleSubmit} onChange={() => setHasChanges(true)}>
<div className="px-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
LLM providers
</p>
<div className="w-full flex overflow-x-scroll gap-x-4 no-scroll">
<input hidden={true} name="LLMProvider" value={llmChoice} />
<LLMProviderOption
name="OpenAI"
value="openai"
link="openai.com"
description="The standard option for most non-commercial use. Provides both chat and embedding."
checked={llmChoice === "openai"}
image={OpenAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Azure OpenAi"
value="azure"
link="azure.microsoft.com"
description="The enterprise option of OpenAI hosted on Azure services. Provides both chat and embedding."
checked={llmChoice === "azure"}
image={AzureOpenAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Anthropic Claude 2"
value="anthropic-claude-2"
link="anthropic.com"
description="[COMING SOON] A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic-claude-2"}
image={AnthropicLogo}
/>
</div>
{llmChoice === "openai" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
API Key
</label>
<input
type="text"
name="OpenAiKey"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="OpenAI API Key"
defaultValue={settings?.OpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Chat Model Selection
</label>
<select
disabled={!canDebug}
name="OpenAiModelPref"
defaultValue={settings?.OpenAiModelPref}
required={true}
className="bg-gray-50 border border-gray-500 text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-stone-700 dark:border-slate-200 dark:placeholder-stone-500 dark:text-slate-200"
>
{[
"gpt-3.5-turbo",
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo-16k",
"gpt-4",
"gpt-4-0613",
"gpt-4-32k",
"gpt-4-32k-0613",
].map((model) => {
return (
<option key={model} value={model}>
{model}
</option>
);
})}
</select>
</div>
</>
)}
{llmChoice === "azure" && (
<>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="Azure OpenAI API Key"
defaultValue={
settings?.AzureOpenAiKey ? "*".repeat(20) : ""
}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Chat Model Deployment Name
</label>
<input
type="text"
name="AzureOpenAiModelPref"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="Azure OpenAI chat model deployment name"
defaultValue={settings?.AzureOpenAiModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
Embedding Model Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{llmChoice === "anthropic-claude-2" && (
<div className="w-full h-40 items-center justify-center flex">
<p className="text-gray-800 dark:text-slate-400">
This provider is unavailable and cannot be used in
AnythingLLM currently.
</p>
</div>
)}
</div>
</div>
<div className="w-full p-4">
<button
hidden={!hasChanges}
disabled={saving}
type="submit"
className="w-full text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
>
{saving ? "Saving..." : "Save changes"}
</button>
</div>
</form>
<div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button
onClick={hideModal}
type="button"
className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
>
Close
</button>
</div>
</div>
</div>
);
}
const LLMProviderOption = ({
name,
link,
description,
value,
image,
checked = false,
onClick,
}) => {
return (
<div onClick={() => onClick(value)}>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<label className="transition-all duration-300 inline-flex h-full w-60 cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:dark:bg-stone-800 peer-checked:text-gray-600 dark:border-slate-200 dark:bg-stone-800 dark:text-slate-400 dark:hover:bg-stone-700 dark:hover:text-slate-300 dark:peer-checked:text-slate-300">
<div className="block">
<img src={image} alt={name} className="mb-2 h-10 w-10 rounded-full" />
<div className="w-full text-lg font-semibold">{name}</div>
<div className="flex w-full flex-col gap-y-1 text-sm">
<p className="text-xs text-slate-400">{link}</p>
{description}
</div>
</div>
</label>
</div>
);
};

View File

@ -57,7 +57,7 @@ export default function VectorDBSelection({
<div className="px-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
Vector database provider
Vector database providers
</p>
<div className="w-full flex overflow-x-scroll gap-x-4 no-scroll">
<input hidden={true} name="VectorDB" value={vectorDB} />
@ -96,7 +96,7 @@ export default function VectorDBSelection({
Pinecone DB API Key
</label>
<input
type="text"
type="password"
name="PineConeKey"
disabled={!canDebug}
className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"

View File

@ -1,15 +1,22 @@
import React, { useEffect, useState } from "react";
import { Archive, Lock, Key, X, Users, Database } from "react-feather";
import SystemKeys from "./Keys";
import {
Archive,
Lock,
X,
Users,
Database,
MessageSquare,
} from "react-feather";
import ExportOrImportData from "./ExportImport";
import PasswordProtection from "./PasswordProtection";
import System from "../../../models/system";
import MultiUserMode from "./MultiUserMode";
import useUser from "../../../hooks/useUser";
import VectorDBSelection from "./VectorDbs";
import LLMSelection from "./LLMSelection";
const TABS = {
keys: SystemKeys,
llm: LLMSelection,
exportimport: ExportOrImportData,
password: PasswordProtection,
multiuser: MultiUserMode,
@ -20,9 +27,9 @@ const noop = () => false;
export default function SystemSettingsModal({ hideModal = noop }) {
const { user } = useUser();
const [loading, setLoading] = useState(true);
const [selectedTab, setSelectedTab] = useState("keys");
const [selectedTab, setSelectedTab] = useState("llm");
const [settings, setSettings] = useState(null);
const Component = TABS[selectedTab || "keys"];
const Component = TABS[selectedTab || "llm"];
useEffect(() => {
async function fetchKeys() {
@ -87,10 +94,10 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
return (
<ul className="flex overflow-x-scroll no-scroll -mb-px text-sm gap-x-2 font-medium text-center text-gray-500 dark:text-gray-400">
<SettingTab
active={selectedTab === "keys"}
displayName="Keys"
tabName="keys"
icon={<Key className="h-4 w-4 flex-shrink-0" />}
active={selectedTab === "llm"}
displayName="LLM Choice"
tabName="llm"
icon={<MessageSquare className="h-4 w-4 flex-shrink-0" />}
onClick={changeTab}
/>
<SettingTab

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,8 +1,23 @@
SERVER_PORT=3001
OPEN_AI_KEY=
OPEN_MODEL_PREF='gpt-3.5-turbo'
CACHE_VECTORS="true"
JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long.
###########################################
######## LLM API SElECTION ################
###########################################
LLM_PROVIDER='openai'
# OPEN_AI_KEY=
OPEN_MODEL_PREF='gpt-3.5-turbo'
# LLM_PROVIDER='azure'
# AZURE_OPENAI_ENDPOINT=
# AZURE_OPENAI_KEY=
# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model.
# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002
###########################################
######## Vector Database Selection ########
###########################################
# Enable all below if you are using vector database: Chroma.
# VECTOR_DB="chroma"
# CHROMA_ENDPOINT='http://localhost:8000'
@ -16,8 +31,6 @@ PINECONE_INDEX=
# Enable all below if you are using vector database: LanceDB.
# VECTOR_DB="lancedb"
JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long.
# CLOUD DEPLOYMENT VARIRABLES ONLY
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
# STORAGE_DIR= # absolute filesystem path with no trailing slash

View File

@ -38,17 +38,16 @@ function systemEndpoints(app) {
app.get("/setup-complete", async (_, response) => {
try {
const llmProvider = process.env.LLM_PROVIDER || "openai";
const vectorDB = process.env.VECTOR_DB || "pinecone";
const results = {
CanDebug: !!!process.env.NO_DEBUG,
RequiresAuth: !!process.env.AUTH_TOKEN,
VectorDB: vectorDB,
OpenAiKey: !!process.env.OPEN_AI_KEY,
OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo",
AuthToken: !!process.env.AUTH_TOKEN,
JWTSecret: !!process.env.JWT_SECRET,
StorageDir: process.env.STORAGE_DIR,
MultiUserMode: await SystemSettings.isMultiUserMode(),
VectorDB: vectorDB,
...(vectorDB === "pinecone"
? {
PineConeEnvironment: process.env.PINECONE_ENVIRONMENT,
@ -61,6 +60,22 @@ function systemEndpoints(app) {
ChromaEndpoint: process.env.CHROMA_ENDPOINT,
}
: {}),
LLMProvider: llmProvider,
...(llmProvider === "openai"
? {
OpenAiKey: !!process.env.OPEN_AI_KEY,
OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo",
}
: {}),
...(llmProvider === "azure"
? {
AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY,
AzureOpenAiModelPref: process.env.OPEN_MODEL_PREF,
AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF,
}
: {}),
};
response.status(200).json({ results });
} catch (e) {

View File

@ -15,6 +15,7 @@
"lint": "yarn prettier --write ./endpoints ./models ./utils index.js"
},
"dependencies": {
"@azure/openai": "^1.0.0-beta.3",
"@googleapis/youtube": "^9.0.0",
"@pinecone-database/pinecone": "^0.1.6",
"archiver": "^5.3.1",
@ -43,4 +44,4 @@
"nodemon": "^2.0.22",
"prettier": "^2.4.1"
}
}
}

View File

@ -0,0 +1,99 @@
class AzureOpenAi {
constructor() {
const { OpenAIClient, AzureKeyCredential } = require("@azure/openai");
const openai = new OpenAIClient(
process.env.AZURE_OPENAI_ENDPOINT,
new AzureKeyCredential(process.env.AZURE_OPENAI_KEY)
);
this.openai = openai;
}
isValidChatModel(_modelName = "") {
// The Azure user names their "models" as deployments and they can be any name
// so we rely on the user to put in the correct deployment as only they would
// know it.
return true;
}
async isSafe(_input = "") {
// Not implemented by Azure OpenAI so must be stubbed
return { safe: true, reasons: [] };
}
async sendChat(chatHistory = [], prompt, workspace = {}) {
const model = process.env.OPEN_MODEL_PREF;
if (!model)
throw new Error(
"No OPEN_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5."
);
const textResponse = await this.openai
.getChatCompletions(
model,
[
{ role: "system", content: "" },
...chatHistory,
{ role: "user", content: prompt },
],
{
temperature: Number(workspace?.openAiTemp ?? 0.7),
n: 1,
}
)
.then((res) => {
if (!res.hasOwnProperty("choices"))
throw new Error("OpenAI chat: No results!");
if (res.choices.length === 0)
throw new Error("OpenAI chat: No results length!");
return res.choices[0].message.content;
})
.catch((error) => {
console.log(error);
throw new Error(
`AzureOpenAI::getChatCompletions failed with: ${error.message}`
);
});
return textResponse;
}
async getChatCompletion(messages = [], { temperature = 0.7 }) {
const model = process.env.OPEN_MODEL_PREF;
if (!model)
throw new Error(
"No OPEN_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5."
);
const data = await this.openai.getChatCompletions(model, messages, {
temperature,
});
if (!data.hasOwnProperty("choices")) return null;
return data.choices[0].message.content;
}
async embedTextInput(textInput) {
const result = await this.embedChunks(textInput);
return result?.[0] || [];
}
async embedChunks(textChunks = []) {
const textEmbeddingModel =
process.env.EMBEDDING_MODEL_PREF || "text-embedding-ada-002";
if (!textEmbeddingModel)
throw new Error(
"No EMBEDDING_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an embedding model."
);
const { data = [] } = await this.openai.getEmbeddings(
textEmbeddingModel,
textChunks
);
return data.length > 0 &&
data.every((embd) => embd.hasOwnProperty("embedding"))
? data.map((embd) => embd.embedding)
: null;
}
}
module.exports = {
AzureOpenAi,
};

View File

@ -1,7 +1,6 @@
const { Configuration, OpenAIApi } = require("openai");
class OpenAi {
constructor() {
const { Configuration, OpenAIApi } = require("openai");
const config = new Configuration({
apiKey: process.env.OPEN_AI_KEY,
});

View File

@ -3,7 +3,8 @@ const { OpenAi } = require("../AiProviders/openAi");
const { WorkspaceChats } = require("../../models/workspaceChats");
const { resetMemory } = require("./commands/reset");
const moment = require("moment");
const { getVectorDbClass } = require("../helpers");
const { getVectorDbClass, getLLMProvider } = require("../helpers");
const { AzureOpenAi } = require("../AiProviders/azureOpenAi");
function convertToChatHistory(history = []) {
const formattedHistory = [];
@ -66,7 +67,7 @@ async function chatWithWorkspace(
user = null
) {
const uuid = uuidv4();
const openai = new OpenAi();
const LLMConnector = getLLMProvider();
const VectorDb = getVectorDbClass();
const command = grepCommand(message);
@ -74,7 +75,7 @@ async function chatWithWorkspace(
return await VALID_COMMANDS[command](workspace, message, uuid, user);
}
const { safe, reasons = [] } = await openai.isSafe(message);
const { safe, reasons = [] } = await LLMConnector.isSafe(message);
if (!safe) {
return {
id: uuid,
@ -93,7 +94,11 @@ async function chatWithWorkspace(
if (!hasVectorizedSpace || embeddingsCount === 0) {
const rawHistory = await WorkspaceChats.forWorkspace(workspace.id);
const chatHistory = convertToPromptHistory(rawHistory);
const response = await openai.sendChat(chatHistory, message, workspace);
const response = await LLMConnector.sendChat(
chatHistory,
message,
workspace
);
const data = { text: response, sources: [], type: "chat" };
await WorkspaceChats.new({

View File

@ -1,21 +1,34 @@
function getVectorDbClass() {
const { Pinecone } = require("../vectorDbProviders/pinecone");
const { Chroma } = require("../vectorDbProviders/chroma");
const { LanceDb } = require("../vectorDbProviders/lance");
const vectorSelection = process.env.VECTOR_DB || "pinecone";
switch (vectorSelection) {
case "pinecone":
const { Pinecone } = require("../vectorDbProviders/pinecone");
return Pinecone;
case "chroma":
const { Chroma } = require("../vectorDbProviders/chroma");
return Chroma;
case "lancedb":
const { LanceDb } = require("../vectorDbProviders/lance");
return LanceDb;
default:
throw new Error("ENV: No VECTOR_DB value found in environment!");
}
}
function getLLMProvider() {
const vectorSelection = process.env.LLM_PROVIDER || "openai";
switch (vectorSelection) {
case "openai":
const { OpenAi } = require("../AiProviders/openAi");
return new OpenAi();
case "azure":
const { AzureOpenAi } = require("../AiProviders/azureOpenAi");
return new AzureOpenAi();
default:
throw new Error("ENV: No LLM_PROVIDER value found in environment!");
}
}
function toChunks(arr, size) {
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
arr.slice(i * size, i * size + size)
@ -24,5 +37,6 @@ function toChunks(arr, size) {
module.exports = {
getVectorDbClass,
getLLMProvider,
toChunks,
};

View File

@ -1,4 +1,9 @@
const KEY_MAPPING = {
LLMProvider: {
envKey: "LLM_PROVIDER",
checks: [isNotEmpty, supportedLLM],
},
// OpenAI Settings
OpenAiKey: {
envKey: "OPEN_AI_KEY",
checks: [isNotEmpty, validOpenAIKey],
@ -7,6 +12,25 @@ const KEY_MAPPING = {
envKey: "OPEN_MODEL_PREF",
checks: [isNotEmpty, validOpenAIModel],
},
// Azure OpenAI Settings
AzureOpenAiEndpoint: {
envKey: "AZURE_OPENAI_ENDPOINT",
checks: [isNotEmpty, validAzureURL],
},
AzureOpenAiKey: {
envKey: "AZURE_OPENAI_KEY",
checks: [isNotEmpty],
},
AzureOpenAiModelPref: {
envKey: "OPEN_MODEL_PREF",
checks: [isNotEmpty],
},
AzureOpenAiEmbeddingModelPref: {
envKey: "EMBEDDING_MODEL_PREF",
checks: [isNotEmpty],
},
// Vector Database Selection Settings
VectorDB: {
envKey: "VECTOR_DB",
checks: [isNotEmpty, supportedVectorDB],
@ -27,6 +51,8 @@ const KEY_MAPPING = {
envKey: "PINECONE_INDEX",
checks: [],
},
// System Settings
AuthToken: {
envKey: "AUTH_TOKEN",
checks: [],
@ -56,6 +82,10 @@ function validOpenAIKey(input = "") {
return input.startsWith("sk-") ? null : "OpenAI Key must start with sk-";
}
function supportedLLM(input = "") {
return ["openai", "azure"].includes(input);
}
function validOpenAIModel(input = "") {
const validModels = [
"gpt-4",
@ -85,6 +115,17 @@ function validChromaURL(input = "") {
: null;
}
function validAzureURL(input = "") {
try {
new URL(input);
if (!input.includes("openai.azure.com"))
return "URL must include openai.azure.com";
return null;
} catch {
return "Not a valid URL";
}
}
// This will force update .env variables which for any which reason were not able to be parsed or
// read from an ENV file as this seems to be a complicating step for many so allowing people to write
// to the process will at least alleviate that issue. It does not perform comprehensive validity checks or sanity checks

View File

@ -1,14 +1,9 @@
const { ChromaClient, OpenAIEmbeddingFunction } = require("chromadb");
const { Chroma: ChromaStore } = require("langchain/vectorstores/chroma");
const { OpenAI } = require("langchain/llms/openai");
const { VectorDBQAChain } = require("langchain/chains");
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
const { ChromaClient } = require("chromadb");
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
const { storeVectorResult, cachedVectorInformation } = require("../../files");
const { v4: uuidv4 } = require("uuid");
const { toChunks } = require("../../helpers");
const { toChunks, getLLMProvider } = require("../../helpers");
const { chatPrompt } = require("../../chats");
const { OpenAi } = require("../../AiProviders/openAi");
const Chroma = {
name: "Chroma",
@ -49,22 +44,6 @@ const Chroma = {
const namespace = await this.namespace(client, _namespace);
return namespace?.vectorCount || 0;
},
embeddingFunc: function () {
return new OpenAIEmbeddingFunction({
openai_api_key: process.env.OPEN_AI_KEY,
});
},
embedder: function () {
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
},
llm: function ({ temperature = 0.7 }) {
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
return new OpenAI({
openAIApiKey: process.env.OPEN_AI_KEY,
modelName: model,
temperature,
});
},
similarityResponse: async function (client, namespace, queryVector) {
const collection = await client.getCollection({ name: namespace });
const result = {
@ -131,7 +110,6 @@ const Chroma = {
const collection = await client.getOrCreateCollection({
name: namespace,
metadata: { "hnsw:space": "cosine" },
embeddingFunction: this.embeddingFunc(),
});
const { chunks } = cacheResult;
const documentVectors = [];
@ -176,10 +154,10 @@ const Chroma = {
const textChunks = await textSplitter.splitText(pageContent);
console.log("Chunks created from document:", textChunks.length);
const openAiConnector = new OpenAi();
const LLMConnector = getLLMProvider();
const documentVectors = [];
const vectors = [];
const vectorValues = await openAiConnector.embedChunks(textChunks);
const vectorValues = await LLMConnector.embedChunks(textChunks);
const submission = {
ids: [],
embeddings: [],
@ -216,7 +194,6 @@ const Chroma = {
const collection = await client.getOrCreateCollection({
name: namespace,
metadata: { "hnsw:space": "cosine" },
embeddingFunction: this.embeddingFunc(),
});
if (vectors.length > 0) {
@ -245,7 +222,6 @@ const Chroma = {
if (!(await this.namespaceExists(client, namespace))) return;
const collection = await client.getCollection({
name: namespace,
embeddingFunction: this.embeddingFunc(),
});
const knownDocuments = await DocumentVectors.where(`docId = '${docId}'`);
@ -271,22 +247,36 @@ const Chroma = {
};
}
const vectorStore = await ChromaStore.fromExistingCollection(
this.embedder(),
{ collectionName: namespace, url: process.env.CHROMA_ENDPOINT }
const LLMConnector = getLLMProvider();
const queryVector = await LLMConnector.embedTextInput(input);
const { contextTexts, sourceDocuments } = await this.similarityResponse(
client,
namespace,
queryVector
);
const model = this.llm({
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
const chain = VectorDBQAChain.fromLLM(model, vectorStore, {
k: 5,
returnSourceDocuments: true,
// When we roll out own response we have separate metadata and texts,
// so for source collection we need to combine them.
const sources = sourceDocuments.map((metadata, i) => {
return { metadata: { ...metadata, text: contextTexts[i] } };
});
const response = await chain.call({ query: input });
return {
response: response.text,
sources: this.curateSources(response.sourceDocuments),
response: responseText,
sources: this.curateSources(sources),
message: false,
};
},
@ -312,8 +302,8 @@ const Chroma = {
};
}
const openAiConnector = new OpenAi();
const queryVector = await openAiConnector.embedTextInput(input);
const LLMConnector = getLLMProvider();
const queryVector = await LLMConnector.embedTextInput(input);
const { contextTexts, sourceDocuments } = await this.similarityResponse(
client,
namespace,
@ -330,7 +320,7 @@ const Chroma = {
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const responseText = await openAiConnector.getChatCompletion(memory, {
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -1,11 +1,10 @@
const lancedb = require("vectordb");
const { toChunks } = require("../../helpers");
const { toChunks, getLLMProvider } = require("../../helpers");
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
const { storeVectorResult, cachedVectorInformation } = require("../../files");
const { v4: uuidv4 } = require("uuid");
const { chatPrompt } = require("../../chats");
const { OpenAi } = require("../../AiProviders/openAi");
const LanceDb = {
uri: `${
@ -169,11 +168,11 @@ const LanceDb = {
const textChunks = await textSplitter.splitText(pageContent);
console.log("Chunks created from document:", textChunks.length);
const openAiConnector = new OpenAi();
const LLMConnector = getLLMProvider();
const documentVectors = [];
const vectors = [];
const submissions = [];
const vectorValues = await openAiConnector.embedChunks(textChunks);
const vectorValues = await LLMConnector.embedChunks(textChunks);
if (!!vectorValues && vectorValues.length > 0) {
for (const [i, vector] of vectorValues.entries()) {
@ -230,9 +229,8 @@ const LanceDb = {
};
}
// LanceDB does not have langchainJS support so we roll our own here.
const openAiConnector = new OpenAi();
const queryVector = await openAiConnector.embedTextInput(input);
const LLMConnector = getLLMProvider();
const queryVector = await LLMConnector.embedTextInput(input);
const { contextTexts, sourceDocuments } = await this.similarityResponse(
client,
namespace,
@ -249,7 +247,7 @@ const LanceDb = {
.join("")}`,
};
const memory = [prompt, { role: "user", content: input }];
const responseText = await openAiConnector.getChatCompletion(memory, {
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
@ -281,8 +279,8 @@ const LanceDb = {
};
}
const openAiConnector = new OpenAi();
const queryVector = await openAiConnector.embedTextInput(input);
const LLMConnector = getLLMProvider();
const queryVector = await LLMConnector.embedTextInput(input);
const { contextTexts, sourceDocuments } = await this.similarityResponse(
client,
namespace,
@ -299,7 +297,7 @@ const LanceDb = {
.join("")}`,
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const responseText = await openAiConnector.getChatCompletion(memory, {
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -1,14 +1,9 @@
const { PineconeClient } = require("@pinecone-database/pinecone");
const { PineconeStore } = require("langchain/vectorstores/pinecone");
const { OpenAI } = require("langchain/llms/openai");
const { VectorDBQAChain } = require("langchain/chains");
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
const { storeVectorResult, cachedVectorInformation } = require("../../files");
const { v4: uuidv4 } = require("uuid");
const { toChunks } = require("../../helpers");
const { toChunks, getLLMProvider } = require("../../helpers");
const { chatPrompt } = require("../../chats");
const { OpenAi } = require("../../AiProviders/openAi");
const Pinecone = {
name: "Pinecone",
@ -29,17 +24,6 @@ const Pinecone = {
if (!status.ready) throw new Error("Pinecode::Index not ready.");
return { client, pineconeIndex, indexName: process.env.PINECONE_INDEX };
},
embedder: function () {
return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY });
},
llm: function ({ temperature = 0.7 }) {
const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo";
return new OpenAI({
openAIApiKey: process.env.OPEN_AI_KEY,
modelName: model,
temperature,
});
},
totalIndicies: async function () {
const { pineconeIndex } = await this.connect();
const { namespaces } = await pineconeIndex.describeIndexStats1();
@ -144,10 +128,10 @@ const Pinecone = {
const textChunks = await textSplitter.splitText(pageContent);
console.log("Chunks created from document:", textChunks.length);
const openAiConnector = new OpenAi();
const LLMConnector = getLLMProvider();
const documentVectors = [];
const vectors = [];
const vectorValues = await openAiConnector.embedChunks(textChunks);
const vectorValues = await LLMConnector.embedChunks(textChunks);
if (!!vectorValues && vectorValues.length > 0) {
for (const [i, vector] of vectorValues.entries()) {
@ -246,22 +230,32 @@ const Pinecone = {
};
}
const vectorStore = await PineconeStore.fromExistingIndex(this.embedder(), {
const LLMConnector = getLLMProvider();
const queryVector = await LLMConnector.embedTextInput(input);
const { contextTexts, sourceDocuments } = await this.similarityResponse(
pineconeIndex,
namespace,
});
queryVector
);
const prompt = {
role: "system",
content: `${chatPrompt(workspace)}
Context:
${contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")}`,
};
const model = this.llm({
const memory = [prompt, { role: "user", content: input }];
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});
const chain = VectorDBQAChain.fromLLM(model, vectorStore, {
k: 5,
returnSourceDocuments: true,
});
const response = await chain.call({ query: input });
return {
response: response.text,
sources: this.curateSources(response.sourceDocuments),
response: responseText,
sources: this.curateSources(sourceDocuments),
message: false,
};
},
@ -284,8 +278,8 @@ const Pinecone = {
"Invalid namespace - has it been collected and seeded yet?"
);
const openAiConnector = new OpenAi();
const queryVector = await openAiConnector.embedTextInput(input);
const LLMConnector = getLLMProvider();
const queryVector = await LLMConnector.embedTextInput(input);
const { contextTexts, sourceDocuments } = await this.similarityResponse(
pineconeIndex,
namespace,
@ -303,7 +297,7 @@ const Pinecone = {
};
const memory = [prompt, ...chatHistory, { role: "user", content: input }];
const responseText = await openAiConnector.getChatCompletion(memory, {
const responseText = await LLMConnector.getChatCompletion(memory, {
temperature: workspace?.openAiTemp ?? 0.7,
});

View File

@ -26,6 +26,93 @@
pad-left "^2.1.0"
tslib "^2.5.0"
"@azure-rest/core-client@^1.1.3":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@azure-rest/core-client/-/core-client-1.1.4.tgz#628381c3653f6dbae584ca6f2ae5f74a5c015526"
integrity sha512-RUIQOA8T0WcbNlddr8hjl2MuC5GVRqmMwPXqBVsgvdKesLy+eg3y/6nf3qe2fvcJMI1gF6VtgU5U4hRaR4w4ag==
dependencies:
"@azure/abort-controller" "^1.1.0"
"@azure/core-auth" "^1.3.0"
"@azure/core-rest-pipeline" "^1.5.0"
"@azure/core-tracing" "^1.0.1"
"@azure/core-util" "^1.0.0"
tslib "^2.2.0"
"@azure/abort-controller@^1.0.0", "@azure/abort-controller@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249"
integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==
dependencies:
tslib "^2.2.0"
"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.5.0.tgz#a41848c5c31cb3b7c84c409885267d55a2c92e44"
integrity sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-util" "^1.1.0"
tslib "^2.2.0"
"@azure/core-lro@^2.5.3":
version "2.5.4"
resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.5.4.tgz#b21e2bcb8bd9a8a652ff85b61adeea51a8055f90"
integrity sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-util" "^1.2.0"
"@azure/logger" "^1.0.0"
tslib "^2.2.0"
"@azure/core-rest-pipeline@^1.10.2", "@azure/core-rest-pipeline@^1.5.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.0.tgz#a36dd361807494845522824532c076daa27c2786"
integrity sha512-+MnSB0vGZjszSzr5AW8z93/9fkDu2RLtWmAN8gskURq7EW2sSwqy8jZa0V26rjuBVkwhdA3Hw8z3VWoeBUOw+A==
dependencies:
"@azure/abort-controller" "^1.0.0"
"@azure/core-auth" "^1.4.0"
"@azure/core-tracing" "^1.0.1"
"@azure/core-util" "^1.3.0"
"@azure/logger" "^1.0.0"
form-data "^4.0.0"
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.0"
tslib "^2.2.0"
"@azure/core-tracing@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503"
integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==
dependencies:
tslib "^2.2.0"
"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.3.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.4.0.tgz#c120a56b3e48a9e4d20619a0b00268ae9de891c7"
integrity sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.2.0"
"@azure/logger@^1.0.0", "@azure/logger@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1"
integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==
dependencies:
tslib "^2.2.0"
"@azure/openai@^1.0.0-beta.3":
version "1.0.0-beta.3"
resolved "https://registry.yarnpkg.com/@azure/openai/-/openai-1.0.0-beta.3.tgz#bf4f5ec0a5644b3a9ce4372620856a65e7721e24"
integrity sha512-gW4odbuy/X/W34SdvXomj/JzR09MyMHCY5Kd2ZxJkQo3IUGqJXz1rEv6QER7IAGgBFgNawE97K6UuJfMmoT0rw==
dependencies:
"@azure-rest/core-client" "^1.1.3"
"@azure/core-auth" "^1.4.0"
"@azure/core-lro" "^2.5.3"
"@azure/core-rest-pipeline" "^1.10.2"
"@azure/logger" "^1.0.3"
tslib "^2.4.0"
"@fortaine/fetch-event-source@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz#b8552a2ca2c5202f5699b93a92be0188d422b06e"
@ -86,6 +173,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
"@types/command-line-args@5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.0.tgz#adbb77980a1cc376bb208e3f4142e907410430f6"
@ -1128,6 +1220,15 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
http-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
"@tootallnate/once" "2"
agent-base "6"
debug "4"
https-proxy-agent@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
@ -2301,6 +2402,11 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
tslib@^2.2.0, tslib@^2.4.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"
integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==
tslib@^2.5.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3"