2023-06-26 20:38:38 +02:00
|
|
|
const KEY_MAPPING = {
|
2023-08-04 23:56:27 +02:00
|
|
|
LLMProvider: {
|
|
|
|
envKey: "LLM_PROVIDER",
|
|
|
|
checks: [isNotEmpty, supportedLLM],
|
|
|
|
},
|
|
|
|
// OpenAI Settings
|
2023-06-26 20:38:38 +02:00
|
|
|
OpenAiKey: {
|
|
|
|
envKey: "OPEN_AI_KEY",
|
|
|
|
checks: [isNotEmpty, validOpenAIKey],
|
|
|
|
},
|
|
|
|
OpenAiModelPref: {
|
|
|
|
envKey: "OPEN_MODEL_PREF",
|
2023-10-31 19:38:28 +01:00
|
|
|
checks: [isNotEmpty],
|
2023-06-26 20:38:38 +02:00
|
|
|
},
|
2023-08-04 23:56:27 +02:00
|
|
|
// Azure OpenAI Settings
|
|
|
|
AzureOpenAiEndpoint: {
|
|
|
|
envKey: "AZURE_OPENAI_ENDPOINT",
|
|
|
|
checks: [isNotEmpty, validAzureURL],
|
|
|
|
},
|
2023-11-06 22:13:53 +01:00
|
|
|
AzureOpenAiTokenLimit: {
|
|
|
|
envKey: "AZURE_OPENAI_TOKEN_LIMIT",
|
|
|
|
checks: [validOpenAiTokenLimit],
|
|
|
|
},
|
2023-08-04 23:56:27 +02:00
|
|
|
AzureOpenAiKey: {
|
|
|
|
envKey: "AZURE_OPENAI_KEY",
|
|
|
|
checks: [isNotEmpty],
|
|
|
|
},
|
|
|
|
AzureOpenAiModelPref: {
|
|
|
|
envKey: "OPEN_MODEL_PREF",
|
|
|
|
checks: [isNotEmpty],
|
|
|
|
},
|
|
|
|
AzureOpenAiEmbeddingModelPref: {
|
|
|
|
envKey: "EMBEDDING_MODEL_PREF",
|
|
|
|
checks: [isNotEmpty],
|
|
|
|
},
|
|
|
|
|
2023-10-30 23:44:03 +01:00
|
|
|
// Anthropic Settings
|
|
|
|
AnthropicApiKey: {
|
|
|
|
envKey: "ANTHROPIC_API_KEY",
|
|
|
|
checks: [isNotEmpty, validAnthropicApiKey],
|
|
|
|
},
|
|
|
|
AnthropicModelPref: {
|
|
|
|
envKey: "ANTHROPIC_MODEL_PREF",
|
|
|
|
checks: [isNotEmpty, validAnthropicModel],
|
|
|
|
},
|
|
|
|
|
2023-11-09 21:33:21 +01:00
|
|
|
// LMStudio Settings
|
|
|
|
LMStudioBasePath: {
|
|
|
|
envKey: "LMSTUDIO_BASE_PATH",
|
2023-11-14 21:31:44 +01:00
|
|
|
checks: [isNotEmpty, validLLMExternalBasePath],
|
2023-11-09 21:33:21 +01:00
|
|
|
},
|
|
|
|
LMStudioTokenLimit: {
|
|
|
|
envKey: "LMSTUDIO_MODEL_TOKEN_LIMIT",
|
|
|
|
checks: [nonZero],
|
|
|
|
},
|
|
|
|
|
2023-11-14 21:31:44 +01:00
|
|
|
// LocalAI Settings
|
|
|
|
LocalAiBasePath: {
|
|
|
|
envKey: "LOCAL_AI_BASE_PATH",
|
|
|
|
checks: [isNotEmpty, validLLMExternalBasePath],
|
|
|
|
},
|
|
|
|
LocalAiModelPref: {
|
|
|
|
envKey: "LOCAL_AI_MODEL_PREF",
|
|
|
|
checks: [],
|
|
|
|
},
|
|
|
|
LocalAiTokenLimit: {
|
|
|
|
envKey: "LOCAL_AI_MODEL_TOKEN_LIMIT",
|
|
|
|
checks: [nonZero],
|
|
|
|
},
|
|
|
|
|
2023-10-30 23:44:03 +01:00
|
|
|
EmbeddingEngine: {
|
|
|
|
envKey: "EMBEDDING_ENGINE",
|
|
|
|
checks: [supportedEmbeddingModel],
|
|
|
|
},
|
|
|
|
|
2023-08-04 23:56:27 +02:00
|
|
|
// Vector Database Selection Settings
|
2023-06-26 20:38:38 +02:00
|
|
|
VectorDB: {
|
|
|
|
envKey: "VECTOR_DB",
|
|
|
|
checks: [isNotEmpty, supportedVectorDB],
|
|
|
|
},
|
2023-09-29 22:20:06 +02:00
|
|
|
|
|
|
|
// Chroma Options
|
2023-06-26 20:38:38 +02:00
|
|
|
ChromaEndpoint: {
|
|
|
|
envKey: "CHROMA_ENDPOINT",
|
|
|
|
checks: [isValidURL, validChromaURL],
|
|
|
|
},
|
2023-09-29 22:20:06 +02:00
|
|
|
ChromaApiHeader: {
|
|
|
|
envKey: "CHROMA_API_HEADER",
|
|
|
|
checks: [],
|
|
|
|
},
|
|
|
|
ChromaApiKey: {
|
|
|
|
envKey: "CHROMA_API_KEY",
|
|
|
|
checks: [],
|
|
|
|
},
|
|
|
|
|
|
|
|
// Weaviate Options
|
2023-08-09 03:02:30 +02:00
|
|
|
WeaviateEndpoint: {
|
|
|
|
envKey: "WEAVIATE_ENDPOINT",
|
|
|
|
checks: [isValidURL],
|
|
|
|
},
|
|
|
|
WeaviateApiKey: {
|
|
|
|
envKey: "WEAVIATE_API_KEY",
|
|
|
|
checks: [],
|
|
|
|
},
|
2023-09-29 22:20:06 +02:00
|
|
|
|
|
|
|
// QDrant Options
|
2023-08-16 00:26:44 +02:00
|
|
|
QdrantEndpoint: {
|
|
|
|
envKey: "QDRANT_ENDPOINT",
|
|
|
|
checks: [isValidURL],
|
|
|
|
},
|
|
|
|
QdrantApiKey: {
|
|
|
|
envKey: "QDRANT_API_KEY",
|
|
|
|
checks: [],
|
|
|
|
},
|
2023-08-09 03:02:30 +02:00
|
|
|
|
2023-06-26 20:38:38 +02:00
|
|
|
PineConeEnvironment: {
|
|
|
|
envKey: "PINECONE_ENVIRONMENT",
|
|
|
|
checks: [],
|
|
|
|
},
|
|
|
|
PineConeKey: {
|
|
|
|
envKey: "PINECONE_API_KEY",
|
|
|
|
checks: [],
|
|
|
|
},
|
|
|
|
PineConeIndex: {
|
|
|
|
envKey: "PINECONE_INDEX",
|
|
|
|
checks: [],
|
|
|
|
},
|
2023-08-04 23:56:27 +02:00
|
|
|
|
|
|
|
// System Settings
|
2023-07-21 00:25:47 +02:00
|
|
|
AuthToken: {
|
|
|
|
envKey: "AUTH_TOKEN",
|
2023-09-29 19:44:40 +02:00
|
|
|
checks: [requiresForceMode],
|
2023-07-21 00:25:47 +02:00
|
|
|
},
|
|
|
|
JWTSecret: {
|
|
|
|
envKey: "JWT_SECRET",
|
2023-09-29 19:44:40 +02:00
|
|
|
checks: [requiresForceMode],
|
2023-07-21 00:25:47 +02:00
|
|
|
},
|
2023-06-26 20:38:38 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
function isNotEmpty(input = "") {
|
|
|
|
return !input || input.length === 0 ? "Value cannot be empty" : null;
|
|
|
|
}
|
|
|
|
|
2023-11-09 21:33:21 +01:00
|
|
|
function nonZero(input = "") {
|
|
|
|
if (isNaN(Number(input))) return "Value must be a number";
|
|
|
|
return Number(input) <= 0 ? "Value must be greater than zero" : null;
|
|
|
|
}
|
|
|
|
|
2023-06-26 20:38:38 +02:00
|
|
|
function isValidURL(input = "") {
|
|
|
|
try {
|
|
|
|
new URL(input);
|
|
|
|
return null;
|
|
|
|
} catch (e) {
|
|
|
|
return "URL is not a valid URL.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function validOpenAIKey(input = "") {
|
|
|
|
return input.startsWith("sk-") ? null : "OpenAI Key must start with sk-";
|
|
|
|
}
|
|
|
|
|
2023-10-30 23:44:03 +01:00
|
|
|
function validAnthropicApiKey(input = "") {
|
|
|
|
return input.startsWith("sk-ant-")
|
|
|
|
? null
|
|
|
|
: "Anthropic Key must start with sk-ant-";
|
|
|
|
}
|
|
|
|
|
2023-11-14 21:31:44 +01:00
|
|
|
function validLLMExternalBasePath(input = "") {
|
2023-11-09 21:33:21 +01:00
|
|
|
try {
|
|
|
|
new URL(input);
|
|
|
|
if (!input.includes("v1")) return "URL must include /v1";
|
|
|
|
if (input.split("").slice(-1)?.[0] === "/")
|
|
|
|
return "URL cannot end with a slash";
|
|
|
|
return null;
|
|
|
|
} catch {
|
|
|
|
return "Not a valid URL";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-04 23:56:27 +02:00
|
|
|
function supportedLLM(input = "") {
|
2023-11-14 21:31:44 +01:00
|
|
|
return ["openai", "azure", "anthropic", "lmstudio", "localai"].includes(
|
|
|
|
input
|
|
|
|
);
|
2023-08-04 23:56:27 +02:00
|
|
|
}
|
|
|
|
|
2023-10-30 23:44:03 +01:00
|
|
|
function validAnthropicModel(input = "") {
|
2023-11-06 22:13:53 +01:00
|
|
|
const validModels = ["claude-2", "claude-instant-1"];
|
2023-10-30 23:44:03 +01:00
|
|
|
return validModels.includes(input)
|
|
|
|
? null
|
|
|
|
: `Invalid Model type. Must be one of ${validModels.join(", ")}.`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function supportedEmbeddingModel(input = "") {
|
|
|
|
const supported = ["openai", "azure"];
|
|
|
|
return supported.includes(input)
|
|
|
|
? null
|
|
|
|
: `Invalid Embedding model type. Must be one of ${supported.join(", ")}.`;
|
|
|
|
}
|
|
|
|
|
2023-06-26 20:38:38 +02:00
|
|
|
function supportedVectorDB(input = "") {
|
2023-08-16 00:26:44 +02:00
|
|
|
const supported = ["chroma", "pinecone", "lancedb", "weaviate", "qdrant"];
|
2023-06-26 20:38:38 +02:00
|
|
|
return supported.includes(input)
|
|
|
|
? null
|
|
|
|
: `Invalid VectorDB type. Must be one of ${supported.join(", ")}.`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function validChromaURL(input = "") {
|
|
|
|
return input.slice(-1) === "/"
|
|
|
|
? `Chroma Instance URL should not end in a trailing slash.`
|
|
|
|
: null;
|
|
|
|
}
|
|
|
|
|
2023-08-04 23:56:27 +02:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-06 22:13:53 +01:00
|
|
|
function validOpenAiTokenLimit(input = "") {
|
|
|
|
const tokenLimit = Number(input);
|
|
|
|
if (isNaN(tokenLimit)) return "Token limit is not a number";
|
|
|
|
if (![4_096, 16_384, 8_192, 32_768].includes(tokenLimit))
|
|
|
|
return "Invalid OpenAI token limit.";
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-09-29 19:44:40 +02:00
|
|
|
function requiresForceMode(_, forceModeEnabled = false) {
|
|
|
|
return forceModeEnabled === true ? null : "Cannot set this setting.";
|
|
|
|
}
|
|
|
|
|
2023-06-26 20:38:38 +02:00
|
|
|
// 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
|
|
|
|
// and is simply for debugging when the .env not found issue many come across.
|
2023-09-29 19:44:40 +02:00
|
|
|
function updateENV(newENVs = {}, force = false) {
|
2023-06-26 20:38:38 +02:00
|
|
|
let error = "";
|
|
|
|
const validKeys = Object.keys(KEY_MAPPING);
|
2023-08-04 00:59:51 +02:00
|
|
|
const ENV_KEYS = Object.keys(newENVs).filter(
|
|
|
|
(key) => validKeys.includes(key) && !newENVs[key].includes("******") // strip out answers where the value is all asterisks
|
2023-06-26 20:38:38 +02:00
|
|
|
);
|
|
|
|
const newValues = {};
|
|
|
|
|
|
|
|
ENV_KEYS.forEach((key) => {
|
|
|
|
const { envKey, checks } = KEY_MAPPING[key];
|
|
|
|
const value = newENVs[key];
|
|
|
|
const errors = checks
|
2023-09-29 19:44:40 +02:00
|
|
|
.map((validityCheck) => validityCheck(value, force))
|
2023-06-26 20:38:38 +02:00
|
|
|
.filter((err) => typeof err === "string");
|
|
|
|
|
|
|
|
if (errors.length > 0) {
|
|
|
|
error += errors.join("\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
newValues[key] = value;
|
|
|
|
process.env[envKey] = value;
|
|
|
|
});
|
|
|
|
|
|
|
|
return { newValues, error: error?.length > 0 ? error : false };
|
|
|
|
}
|
|
|
|
|
2023-08-16 01:00:27 +02:00
|
|
|
async function dumpENV() {
|
|
|
|
const fs = require("fs");
|
|
|
|
const path = require("path");
|
|
|
|
|
|
|
|
const frozenEnvs = {};
|
|
|
|
const protectedKeys = [
|
|
|
|
...Object.values(KEY_MAPPING).map((values) => values.envKey),
|
|
|
|
"CACHE_VECTORS",
|
|
|
|
"STORAGE_DIR",
|
|
|
|
"SERVER_PORT",
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const key of protectedKeys) {
|
|
|
|
const envValue = process.env?.[key] || null;
|
|
|
|
if (!envValue) continue;
|
|
|
|
frozenEnvs[key] = process.env?.[key] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var envResult = `# Auto-dump ENV from system call on ${new Date().toTimeString()}\n`;
|
|
|
|
envResult += Object.entries(frozenEnvs)
|
|
|
|
.map(([key, value]) => {
|
|
|
|
return `${key}='${value}'`;
|
|
|
|
})
|
|
|
|
.join("\n");
|
|
|
|
|
|
|
|
const envPath = path.join(__dirname, "../../.env");
|
|
|
|
fs.writeFileSync(envPath, envResult, { encoding: "utf8", flag: "w" });
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-06-26 20:38:38 +02:00
|
|
|
module.exports = {
|
2023-08-16 01:00:27 +02:00
|
|
|
dumpENV,
|
2023-06-26 20:38:38 +02:00
|
|
|
updateENV,
|
|
|
|
};
|