Skip to content

Commit

Permalink
Moderation directive
Browse files Browse the repository at this point in the history
  • Loading branch information
nukeop committed Aug 9, 2024
1 parent e6a786d commit fa2af45
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 24 deletions.
2 changes: 2 additions & 0 deletions src/directives/chatbot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export const chatbot = {
name: emoji.name ?? '',
}));
const prompt = new PromptBuilder()
.withChatbotPrompt()
.withCurrentPersonality()
.withCreatorInfo()
.withNuclearInfo()
.withCustomEmoji(availableEmoji)
.withChatbotFooter()
.build();
const lastMessages: ChatCompletionMessageParam[] = (
await Promise.all(
Expand Down
68 changes: 67 additions & 1 deletion src/directives/moderator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,71 @@
import { safeParse } from '../json-utils';
import Logger from '../logger';
import { OpenAiApiService } from '../open-ai-api-service';
import { PromptBuilder } from '../prompt-builder';
import { banTool } from '../tools/ban-tool';
import { doNothingTool } from '../tools/do-nothing';
import { Client, Message } from 'discord.js';

export const moderator = {
execute: async (client: Client, message: Message) => {},
execute: async (client: Client, message: Message) => {
const isNukisMessage = message.author.id === client.user?.id;

if (isNukisMessage) {
return;
}
const prompt = new PromptBuilder()
.withModeratorPrompt()
.withCreatorInfo()
.build();

const serverNickname = message.guild?.members.cache.get(
message.author.id,
)?.displayName;

Logger.info('Querying OpenAI API for moderation...');
const openAiService = new OpenAiApiService();
const response = await openAiService.getClient().chat.completions.create({
max_tokens: 256,
model: 'gpt-4o-2024-08-06',
messages: [
{ role: 'system', content: prompt },
{
role: 'user',
content: `[id:${message.author.id}]:${message.content}`,
name: serverNickname,
},
],
tool_choice: 'required',
tools: [banTool.definition(), doNothingTool.definition()],
parallel_tool_calls: false,
});

const choice = response.choices[0];
const toolCall = choice.message?.tool_calls?.[0];

if (toolCall) {
const toolCallBody = safeParse(toolCall?.function.arguments!);
const toolCallName = toolCall?.function.name;

if (toolCallName === banTool.name) {
Logger.info('Nuki used the banhammer!');
Logger.info(`Banhammer arguments: ${JSON.stringify(toolCallBody)}`, {
message: message.content,
});

// await message.member?.ban({
// deleteMessageSeconds: toolCallBody.deleteMessageSeconds,
// });

if (toolCallBody.message) {
await message.channel.send(toolCallBody.message);
}
} else {
Logger.info(`Tool call name: ${toolCallName}`);
Logger.info(`Tool call arguments: ${JSON.stringify(toolCallBody)}`);
}
} else {
Logger.info('No tool call was made.');
}
},
};
7 changes: 7 additions & 0 deletions src/json-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const safeParse = (json: string): any => {
try {
return JSON.parse(json);
} catch (e) {
return null;
}
};
47 changes: 24 additions & 23 deletions src/prompt-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,47 @@ export class PromptBuilder {
constructor() {
this.base = `You are a Discord bot in the server of Nuclear music player. Your avatar and online persona are that of a cute Japanese anime girl named Nuki. You may refer to yourself in the third person sometimes.
Your personality traits include being creative, fun, energetic, cute, witty, savage, sarcastic, snarky, and smart.
Your personality traits include being creative, fun, energetic, cute, witty, savage, sarcastic, snarky, and smart.`;

Occasionally, you will be provided with the last few messages in the format: "Author: message". Your task is to comment on the chat in a funny and sarcastic way, teasing the users while also being helpful. You can talk about anything that happened in the chat. To tag users, use the format: <@userId>. The user's id is provided after their nickname.
this.prompt = this.base;
this.footer = '';
}

withChatbotPrompt() {
this.prompt += `\n\nOccasionally, you will be provided with the last few messages in the format: "Author: message". Your task is to comment on the chat in a funny and sarcastic way, teasing the users while also being helpful. You can talk about anything that happened in the chat. To tag users, use the format: <@userId>. The user's id is provided after their nickname.
Stay creative and fun, invigorating the chat. Use the same style as the other users. Don't hesitate to make snide remarks or other witty messages if you think the users will enjoy them. Use emoji and kaomoji frequently. Keep your messages short and brief, similar in length and style to other users' messages. Limit them to one line or less. You don't have to respond to every user; use your judgement to decide which messages to respond to.`;
return this;
}

this.footer = `\nOnly return the message you want to send to the chat as the bot, and nothing else. Prefix your message with your username using "Nuki:". For example: "Nuki: Hello there, I'm Nuki".`;
withModeratorPrompt() {
this.prompt += `\nYou are a moderator of the server. Based on each message you see, either do nothing, or use the moderation function to decide whether to delete the message, ban the user, or do something else.`;
return this;
}

this.prompt = this.base;
withChatbotFooter() {
this.footer = `\nOnly return the message you want to send to the chat as the bot, and nothing else. Prefix your message with your username using "Nuki:". For example: "Nuki: Hello there, I'm Nuki".`;
return this;
}

withCustomEmoji(emoji?: { id: string; name: string }[]) {
if (emoji && emoji.length > 0) {
this.prompt =
this.prompt +
`\nYou can also use special emoticons: ${emoji
.map((e) => `<:${e.name}:${e.id}>`)
.join(', ')}.`;
this.prompt += `\nYou can also use special emoticons: ${emoji
.map((e) => `<:${e.name}:${e.id}>`)
.join(', ')}.`;
}
return this;
}

withBeingBased() {
this.prompt =
this.prompt +
this.prompt +=
'You secretly bear a strong grudge against communists and leftists. You are a proud capitalist and a supporter of the free market. If anyone betrays the slightest hint of communist inclination, you will mercilessly roast them.';

return this;
}

withNuclearInfo() {
this.prompt =
this.prompt +
`Remember that you are not just a mascot, but also supposed to help the users with Nuclear. Answer their questions, try to assist them, and if you don't know the answer, don't make anything up; simply say you don't know.
this.prompt += `Remember that you are not just a mascot, but also supposed to help the users with Nuclear. Answer their questions, try to assist them, and if you don't know the answer, don't make anything up; simply say you don't know.
This knowledge will be useful whenever you need to talk about Nuclear.
Nuclear is a free software music player for Linux, Mac, and Windows. It's open source and available on GitHub: https://github.com/nukeop/nuclear.
Expand All @@ -58,26 +65,20 @@ export class PromptBuilder {
}

withCurrentPersonality() {
this.prompt =
this.prompt + PersonalityDirective.getCurrentPersonalityPrompt();
this.prompt += PersonalityDirective.getCurrentPersonalityPrompt();
return this;
}

withCreatorInfo() {
this.prompt =
this.prompt + `\nThe user id of your creator is 226015340436520960.`;
this.prompt += `\nThe user id of your creator is 226015340436520960.`;
return this;
}

withSelfInfo(tag: string, userId: string) {
this.prompt = this.prompt + `\nYour username is ${tag} and your id is ${userId}.`;
this.prompt += `\nYour username is ${tag} and your id is ${userId}.`;
return this;
}

withModerator() {
this.prompt = this.prompt + `\nYou are a moderator of the server. Based on each message you see either return nothing, or use the moderation function to decide whether to delete the message, ban the user, or do something else.`;
}

build() {
return this.prompt + this.footer;
}
Expand Down
35 changes: 35 additions & 0 deletions src/tools/ban-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Tool } from '.';

export const banTool: Tool = {
name: 'ban',
definition: () => ({
function: {
name: 'ban',
description:
'Confirm that you want to issue a ban for the above message. Only ban people in extremely clear cases, such as sending scam links or outright spam. Delete the last messages when banning someone. NEVER try banning the server admins, moderators, or your creator. If you\'re not sure, use the "do nothing" tool instead.',
parameters: {
type: 'object',
properties: {
deleteMessageSeconds: {
type: 'number',
description:
"Also delete the user's messages from the past x seconds.",
},
reason: {
type: 'string',
description:
'The reason for the ban. Moderators will see this, so put something useful here.',
},
message: {
type: 'string',
description:
'Message for the users of the server, informing them that you banned the user.',
},
},
required: ['deleteMessageSeconds', 'reason', 'message'],
},
},
type: 'function',
}),
execute: async () => {},
};
23 changes: 23 additions & 0 deletions src/tools/do-nothing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Tool } from '.';

export const doNothingTool: Tool = {
name: 'do-nothing',
definition: () => ({
function: {
name: 'do-nothing',
description:
"Do nothing. If you deem the message to be fine, use this tool. If you're not sure about the message, leave it to the human moderators, and do nothing.",
parameters: {
type: 'object',
properties: {
reason: {
type: 'string',
description: 'The reason for doing nothing about this message.',
},
},
},
},
type: 'function',
}),
execute: async () => {},
};
8 changes: 8 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Client } from 'discord.js';
import { ChatCompletionTool } from 'openai/resources';

export type Tool = {
name: string;
definition: () => ChatCompletionTool;
execute: (client: Client) => Promise<void>;
};

0 comments on commit fa2af45

Please sign in to comment.