adds basic key management

This commit is contained in:
nai-degen 2023-04-08 04:12:15 -05:00 committed by nai-degen
parent 5d0184310c
commit e1606e3f66
1 changed files with 102 additions and 0 deletions

102
src/keys.ts Normal file
View File

@ -0,0 +1,102 @@
/* Manages OpenAI API keys. Tracks usage, disables expired keys, and provides
round-robin access to keys. Keys are stored in the OPENAI_KEY environment
variable, either as a single key, or a base64-encoded JSON array of keys.*/
import { logger } from "./logger";
import crypto from "crypto";
/** Represents a key stored in the OPENAI_KEY environment variable. */
type KeySchema = {
/** The OpenAI API key itself. */
key: string;
/** Whether this is a free trial key. These are prioritized over paid keys if they can fulfill the request. */
isTrial?: boolean;
/** Whether this key has been provisioned for GPT-4. */
isGpt4?: boolean;
};
/** Runtime information about a key. */
type Key = KeySchema & {
/** Whether this key is currently disabled. We set this if we get a 429 or 401 response from OpenAI. */
isDisabled?: boolean;
/** Threshold at which a warning email will be sent by OpenAI. */
softLimit?: number;
/** Threshold at which the key will be disabled because it has reached the user-defined limit. */
hardLimit?: number;
/** The maximum quota allocated to this key by OpenAI. */
systemHardLimit?: number;
/** The current usage of this key. */
usage?: number;
/** The time at which this key was last used. */
lastUsed: number;
/** Key hash for displaying usage in the dashboard. */
hash: string;
};
const keys: Key[] = [];
function init() {
const keyString = process.env.OPENAI_KEY;
if (!keyString) {
throw new Error("OPENAI_KEY environment variable is not set");
}
let keyList: KeySchema[];
try {
keyList = JSON.parse(Buffer.from(keyString, "base64").toString());
} catch (err) {
// We don't actually know if bare keys are paid/GPT-4 so we assume they are
keyList = [{ key: keyString, isTrial: false, isGpt4: true }];
}
for (const key of keyList) {
keys.push({
...key,
isDisabled: false,
softLimit: 0,
hardLimit: 0,
systemHardLimit: 0,
usage: 0,
lastUsed: 0,
hash: crypto
.createHash("sha256")
.update(key.key)
.digest("hex")
.slice(0, 6),
});
}
}
function list() {
return keys.map((key) => ({
...key,
key: undefined,
}));
}
function getKey(model: string) {
const needsGpt4Key = model.startsWith("gpt-4");
const availableKeys = keys.filter(
(key) => !key.isDisabled && (!needsGpt4Key || key.isGpt4)
);
if (availableKeys.length === 0) {
let message = "No keys available. Please add more keys.";
if (needsGpt4Key) {
message =
"No GPT-4 keys available. Please add more keys or use a non-GPT-4 model.";
}
logger.error(message);
throw new Error(message);
}
// Prioritize trial keys
const trialKeys = availableKeys.filter((key) => key.isTrial);
if (trialKeys.length > 0) {
logger.info("Using trial key", { key: trialKeys[0].hash });
return trialKeys[0];
}
// Otherwise, return the oldest key
const oldestKey = availableKeys.sort((a, b) => a.lastUsed - b.lastUsed)[0];
logger.info("Using key", { key: oldestKey.hash });
return oldestKey;
}
export { init, list, getKey };