Add function handling for OpenAI model

for now it's querying only time, but in the future there will be more commands
This commit is contained in:
Wroclaw 2023-07-23 05:50:16 +02:00
parent bebef021fb
commit 0df05e2f06
4 changed files with 169 additions and 33 deletions

View file

@ -4,6 +4,7 @@ import { database, openai } from "./index";
import Moderation from "./moderation";
import config from "./config";
import toOpenAIMessages from "./toOpenAIMessages";
import FunctionManager from "./funcitonManager";
type NonNullableInObject<T, V> = { [k in keyof T]: k extends V ? NonNullable<T[k]> : T[k] };
type apiRequest = DiscordApi.Message | DiscordApi.RepliableInteraction;
@ -12,6 +13,9 @@ export type request = apiRequest & NonNullableInObject<apiRequest, "channel" | "
/** Stores the queue requests on the channels. */
const channelsRunning: DiscordApi.Collection<string, request[]> = new DiscordApi.Collection();
type ChannelQueue = NonNullable<ReturnType<typeof channelsRunning.get>>;
type RequestMessage = NonNullable<ReturnType<ChannelQueue["at"]>>;
/**
* Gets the user that requested the execution
* @param request The request to get the user from
@ -158,13 +162,49 @@ export async function queueRequest(request: apiRequest) {
executeFromQueue(request.channelId);
}
/**
* Logs used tokens to the terminal and to the database
* @param answer the response that OpenAI returned
* @param message the message that initiated the execution
* @param functionRan counter of how many function have been ran
*/
function logUsedTokens(
answer: Awaited<ReturnType<typeof openai.createChatCompletion>>,
message: RequestMessage,
functionRan: number,
) {
const usage = answer.data.usage;
const functionName = answer.data.choices[0].message?.function_call?.name;
if (usage != undefined) {
const channelName: string = !message.channel.isDMBased() ? `${message.channel.name} (${message.guild?.name})` : `@${getAuthor(message).tag}`;
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in #${channelName}${functionName ? " [Function: " + functionName + "]" : ""}`);
database.usage.create({
data: {
timestamp: message.createdAt,
user: BigInt(getAuthor(message).id),
channel: BigInt(message.channelId),
guild: message.guildId ? BigInt(message.guildId) : null,
usageReguest: usage.prompt_tokens,
usageResponse: usage.completion_tokens,
functionName: functionName ?? null,
functionRan: functionName ? functionRan : 0,
}
}).catch((e => {
console.error("Failed to push to a database");
console.error(e);
}));
}
}
/**
* Executes the queue for the channel
* @param channel the channel to run the queue for
*/
async function executeFromQueue(channel: string) {
const channelQueue = channelsRunning.get(channel) as NonNullable<ReturnType<typeof channelsRunning.get>>;
const message = channelQueue.at(0) as NonNullable<ReturnType<typeof channelQueue.at>>;
const channelQueue = channelsRunning.get(channel) as ChannelQueue;
const message = channelQueue.at(0) as RequestMessage;
let functionRanCounter = 0;
try {
let messages: DiscordApi.Collection<string, DiscordApi.Message> = await message.channel.messages.fetch({ limit: config.limits.messages, cache: false });
@ -180,29 +220,39 @@ async function executeFromQueue(channel: string) {
message.deferReply();
}
const answer = await openai.createChatCompletion({
const OpenAImessages = toOpenAIMessages(messages);
let answer = await openai.createChatCompletion({
...config.chatCompletionConfig,
messages: toOpenAIMessages(messages),
messages: OpenAImessages,
// FIXME: don't use new instance of FunctionManager
functions: new FunctionManager().getFunctions(),
});
const usage = answer.data.usage;
if (usage != undefined) {
const channelName: string = !message.channel.isDMBased() ? `${message.channel.name} (${message.guild?.name})` : `@${getAuthor(message).tag}`;
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in #${channelName}`);
logUsedTokens(answer, message, ++functionRanCounter);
database.usage.create({
data: {
timestamp: message.createdAt,
user: BigInt(getAuthor(message).id),
channel: BigInt(message.channelId),
guild: message.guildId ? BigInt(message.guildId) : null,
usageReguest: usage.prompt_tokens,
usageResponse: usage.completion_tokens
}
}).catch((e => {
console.error("Failed to push to a database");
console.error(e);
}));
let generatedMessage = answer.data.choices[0].message;
if (!generatedMessage) throw new Error("empty message received");
// handle function calls
while (generatedMessage.function_call) {
OpenAImessages.push(generatedMessage);
OpenAImessages.push({
role: "function",
name: generatedMessage.function_call.name,
// FIXME: don't use new instance of FunctionManager
content: new FunctionManager().handleFunction(generatedMessage.function_call),
});
answer = await openai.createChatCompletion({
...config.chatCompletionConfig,
messages: OpenAImessages,
// FIXME: don't use new instance of FunctionManager
functions: new FunctionManager().getFunctions(),
});
logUsedTokens(answer, message, ++functionRanCounter);
generatedMessage = answer.data.choices[0].message;
if (!generatedMessage) throw new Error("empty message received");
}
const answerContent = answer.data.choices[0].message?.content;