Configuration: recactor how it is written and handled

This commit is contained in:
Wroclaw 2023-09-18 11:06:15 +02:00
parent 13e993b964
commit 7ff4abc3c0
10 changed files with 130 additions and 61 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
dist dist
node_modules node_modules
src/config.* config.*
.env .env

View file

@ -1,3 +1,10 @@
# GPTcord # GPTcord
Connect ChatGPT to your Discord chat! This is a direct bridge to bring ChatGPT to discord. Connect ChatGPT to your Discord chat! This is a direct bridge to bring ChatGPT to discord.
## Configuration
Project can be configured in two ways:
1. `config.ts` in the project root folder, that exports an `IConfig`. [That](./src/configDefault.ts) interface can be imported from `./src/configDefault`.
2. Using environment variables, as defined in [defaultConfig.ts](./src/configDefault.ts).

91
src/configDefault.ts Normal file
View file

@ -0,0 +1,91 @@
import { Message } from "discord.js";
import {
ChatCompletionRequestMessage as OpenAIMessage,
CreateChatCompletionRequest as ChatCompletionRequestData,
} from "openai";
export interface IConfigRequired {
readonly calendarParams: Intl.DateTimeFormatOptions;
/** Tokens to authentiate with */
readonly tokens: {
readonly Discord: string;
readonly OpenAI: string;
};
/** Messages to append at the start of every chat history when sending to API */
systemPrompt(context: Message): OpenAIMessage[];
/** OpenAI model config */
readonly chatCompletionParams: Omit<ChatCompletionRequestData, "messages" | "function_call" | "functions" | "n">;
/** Limits for message selection */
readonly readLimits: {
/** Maximum message age to include (in miliseconds) */
readonly time: number;
/** Maximum number of messages to select (maximum 100) */
readonly messages: number;
/** Maximum total token usage for messages (counted locally) */
readonly tokens: number;
};
/** Default limits for user inteacting with the bot */
readonly userLimits: {
/** How much requests can an user make if it's not overriden in a database entry */
readonly requests: number;
};
}
export type IConfig = Partial<IConfigRequired>;
export default function newConfig(config?: IConfig): IConfigRequired {
return { ...defaultConfig, ...config };
}
function envAsString(key: string): string | undefined {
key = key.toLocaleUpperCase();
return process.env[key];
}
function envAsNumber(key: string): number | undefined {
key = key.toLocaleUpperCase();
const value = Number(process.env[key]);
return !Number.isNaN(value) ? value : undefined;
}
const defaultConfig: IConfigRequired = {
calendarParams: {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
},
tokens: {
Discord: envAsString("TOKENS__DISCORD") ?? "",
OpenAI: envAsString("TOKENS__OPENAI") ?? "",
},
systemPrompt(): OpenAIMessage[] {
return [
{
role: "system",
content:
envAsString("SYSTEM_PROMPT") ??
`You are GPTcord, an AI built on top of ChatGPT (a large language model trained by OpenAI) for Discord. Answer as concisely as possible.`,
},
];
},
chatCompletionParams: {
model: envAsString("CHAT_COMPLETION_PARAMS__MODEL") ?? "gpt-3.5-turbo",
max_tokens: envAsNumber("CHAT_COMPLETION_PARAMS__MAX_TOKENS") ?? 384,
frequency_penalty: envAsNumber("CHAT_COMPLETION_PARAMS__FREQUENCY_PENALTY"),
presence_penalty: envAsNumber("CHAT_COMPLETION_PARAMS__PRESENCE_PENALTY"),
temperature: envAsNumber("CHAT_COMPLETION_PARAMS__TEMPERATURE"),
top_p: envAsNumber("CHAT_COMPLETION_PARAMS__TOP_P"),
},
readLimits: {
time: envAsNumber("READ_LIMITS__TIME") ?? 60 * 60 * 1000,
messages: envAsNumber("READ_LIMITS__MESSAGES") ?? 50,
tokens: envAsNumber("READ_LIMITS__TOKENS") ?? 2048,
},
userLimits: {
requests: envAsNumber("USER_LIMITS__REQUESTS") ?? 25,
},
};

View file

@ -1,49 +0,0 @@
import { ChatCompletionRequestMessage as OpenAIMessage , CreateChatCompletionRequest as ChatCompletionRequestData } from "openai";
// Don't forget to rename the file to config.ts
export default class config {
static readonly calendarConfig: Intl.DateTimeFormatOptions = {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
};
/** Tokens to authenticate with */
static readonly tokens = {
Discord: "Discord token here",
OpenAI: "OpenAI token here",
};
/** Messages to append at the start of every chat when sending to API */
static systemPrompt(): OpenAIMessage[] {
return [
{ role: "system", content: `You are GPTcord, an AI built on top of ChatGPT (a large language model trained by OpenAI) for Discord. Answer as concisely as possible.` }
];
}
/** OpenAI model config */
static readonly chatCompletionConfig: Omit<ChatCompletionRequestData, "messages"> = {
model: "gpt-3.5-turbo",
max_tokens: 384,
};
/** limits for message selection */
static readonly readLimits = {
/** maximum time in the past for messages (in miliseconds) */
time: 60*60*1000,
/** maximum number of messages to select (maximum 100) */
messages: 50,
/** maximum total token usage for messages */
tokens: 2048,
};
/** default user limits */
static readonly userLimits = {
/** how much requests can an user make if it's not overriden in database entry */
requests: 25,
};
}

View file

@ -2,9 +2,8 @@ import DiscordApi, { GuildTextBasedChannel, TextBasedChannel } from "discord.js"
import { ChatCompletionRequestMessage, ChatCompletionResponseMessage } from "openai"; import { ChatCompletionRequestMessage, ChatCompletionResponseMessage } from "openai";
import Axios from "axios"; import Axios from "axios";
import { database, openai } from "./index"; import { database, openai, config } from "./index";
import Moderation from "./moderation"; import Moderation from "./moderation";
import config from "./config";
import toOpenAIMessages from "./toOpenAIMessages"; import toOpenAIMessages from "./toOpenAIMessages";
import FunctionManager from "./funcitonManager"; import FunctionManager from "./funcitonManager";
@ -287,7 +286,7 @@ async function executeFromQueue(channel: string) {
do { do {
answer = await openai.createChatCompletion({ answer = await openai.createChatCompletion({
...config.chatCompletionConfig, ...config.chatCompletionParams,
messages: OpenAImessages, messages: OpenAImessages,
// FIXME: don't use new instance of FunctionManager // FIXME: don't use new instance of FunctionManager
functions: new FunctionManager().getFunctions(), functions: new FunctionManager().getFunctions(),

View file

@ -1,6 +1,6 @@
import { ChatCompletionFunctions, ChatCompletionRequestMessage, ChatCompletionRequestMessageFunctionCall } from "openai"; import { ChatCompletionFunctions, ChatCompletionRequestMessage, ChatCompletionRequestMessageFunctionCall } from "openai";
import config from "./config"; import { config } from "./index";
type parameterMap = { type parameterMap = {
string: string, string: string,
@ -104,6 +104,6 @@ class GetTime extends OpenAIFunction<Record<string, never>> {
}; };
execute(): string { execute(): string {
return `${Intl.DateTimeFormat().resolvedOptions().timeZone}): ${new Date().toLocaleString("en-US", config.calendarConfig)}`; return `${Intl.DateTimeFormat().resolvedOptions().timeZone}): ${new Date().toLocaleString("en-US", config.calendarParams)}`;
} }
} }

View file

@ -1,11 +1,12 @@
import DiscordApi from "discord.js"; import DiscordApi from "discord.js";
import { Configuration as OpenAIApiConfiguration, OpenAIApi } from "openai"; import { Configuration as OpenAIApiConfiguration, OpenAIApi } from "openai";
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from "@prisma/client";
import Typescript from "typescript";
import fs from "node:fs";
import config from "./config";
import { queueRequest } from "./execution"; import { queueRequest } from "./execution";
import InteractionManager from "./interactionManager"; import InteractionManager from "./interactionManager";
import newConfig, { IConfig, IConfigRequired } from "./configDefault";
const discord = new DiscordApi.Client({ const discord = new DiscordApi.Client({
intents: [ intents: [
DiscordApi.GatewayIntentBits.Guilds, DiscordApi.GatewayIntentBits.Guilds,
@ -14,6 +15,26 @@ const discord = new DiscordApi.Client({
] ]
}); });
function getConfig() {
let fileConfig: IConfig | undefined = undefined;
try {
fs.statSync("./config.ts");
const program = Typescript.createProgram(
["./config.ts"],
{outDir: "./dist"}
);
console.log(program.emit(program.getSourceFile("./config.ts")));
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access
fileConfig = require("./config").default as IConfig;
} catch (e) {
//FIXME: make errors more descriptive to the enduser
console.log(e);
}
return fileConfig !== undefined ? newConfig(fileConfig) : newConfig();
}
export const config: IConfigRequired = getConfig();
export const openai = new OpenAIApi(new OpenAIApiConfiguration({ export const openai = new OpenAIApi(new OpenAIApiConfiguration({
apiKey: config.tokens.OpenAI apiKey: config.tokens.OpenAI
})); }));

View file

@ -2,7 +2,7 @@ import { Collection, InteractionResponse, Message, SnowflakeUtil } from "discord
import { openai } from "./index"; import { openai } from "./index";
import { formatRequestOrResponse } from "./toOpenAIMessages"; import { formatRequestOrResponse } from "./toOpenAIMessages";
import config from "./config"; import { config } from "./index";
export default class Moderation { export default class Moderation {
/** Represents cache of messages that have been checked aganist OpenAI moderation API. */ /** Represents cache of messages that have been checked aganist OpenAI moderation API. */

View file

@ -1,7 +1,7 @@
// https://discordjs.guide/creating-your-bot/command-deployment.html#guild-commands // https://discordjs.guide/creating-your-bot/command-deployment.html#guild-commands
import { REST, RESTGetAPIOAuth2CurrentApplicationResult, RESTPostAPIApplicationCommandsJSONBody, Routes } from "discord.js"; import { REST, RESTGetAPIOAuth2CurrentApplicationResult, RESTPostAPIApplicationCommandsJSONBody, Routes } from "discord.js";
import config from "../config"; import { config } from "../index";
import requireDirectory from "require-directory"; import requireDirectory from "require-directory";
import Command from "../command"; import Command from "../command";

View file

@ -2,7 +2,7 @@ import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js"; import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js";
import FoldToAscii from "fold-to-ascii"; import FoldToAscii from "fold-to-ascii";
import config from "./config"; import { config } from "./index";
import countTokens from "./tokenCounter"; import countTokens from "./tokenCounter";
import { RequestMessage } from "./execution"; import { RequestMessage } from "./execution";
@ -115,7 +115,7 @@ export default function toOpenAIMessages(
}); });
} }
rvalue.push(...config.systemPrompt().reverse()); rvalue.push(...config.systemPrompt([...messages][0]).reverse());
return rvalue.reverse(); return rvalue.reverse();
} }