adds basic key management
This commit is contained in:
parent
5d0184310c
commit
e1606e3f66
|
@ -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 };
|
Loading…
Reference in New Issue