Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add OpenAI GPT model integration #39

Merged
merged 13 commits into from
Apr 14, 2023
Merged
4 changes: 2 additions & 2 deletions app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import express from "express";
import {api} from "./src/api";
import {errorHandler} from "./src/api/middlewares/errorHandler";
import { api } from "./src/api";
import { errorHandler } from "./src/api/middlewares/errorHandler";

export const app = express();

Expand Down
6 changes: 6 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ if (!process.env.GRAPHQL_API)
if (!process.env.GUILD_ID)
throw new Error(errMessage("GUILD_ID is required"))

if (!process.env.OPENAI_API_KEY) {
throw new Error(errMessage("OPENAI_API_KEY is required"))
}

interface Config {
prefix: string;
discordToken: string;
Expand All @@ -25,6 +29,7 @@ interface Config {
lessonChannels: { [key: string]: string };
channels: { [key: string]: string };
port: number;
openaiApiKey: string;
}

export const config: Config = {
Expand Down Expand Up @@ -66,4 +71,5 @@ export const config: Config = {
welcome: "831750041445203979",
},
port: parseInt(process.env.PORT ?? "") || 5623,
openaiApiKey: process.env.OPENAI_API_KEY,
} as const;
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"@discordjs/builders": "^0.12.0",
"@discordjs/rest": "^0.3.0",
"discord.js": "^13.6.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"zod": "^3.7.3",
"@discordjs/builders": "^0.12.0",
"@discordjs/rest": "^0.3.0",
"graphql": "^16.3.0",
"graphql-request": "^4.2.0"
"graphql-request": "^4.2.0",
"openai": "^3.2.1",
"zod": "^3.7.3"
},
"devDependencies": {
"@types/express": "^4.17.12",
Expand Down
31 changes: 21 additions & 10 deletions src/Bot/commands/commands.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { SlashCommandBuilder } from "@discordjs/builders";
import { SlashCommandBuilder } from '@discordjs/builders'

const userSlashCommand =
new SlashCommandBuilder()
.setName("lookup")
.setDescription('Get the user Discord username by their C0D3 username')
.addStringOption(option =>
option.setName('username')
.setDescription("The user's username on C0D3")
.setRequired(true))
new SlashCommandBuilder()
.setName("lookup")
flacial marked this conversation as resolved.
Show resolved Hide resolved
.setDescription('Get the user Discord username by their C0D3 username')
.addStringOption(option =>
option.setName('username')
.setDescription("The user's username on C0D3")
.setRequired(true))



export const commands = [userSlashCommand].map((command) => command.toJSON());

const gptSlashCommand = new SlashCommandBuilder()
.setName('ask')
.setDescription('Ask the assistant a question')
.addStringOption((option) =>
option
.setName('question')
.setDescription('The question you want to ask the assistant')
.setRequired(true)
)


export const commands = [userSlashCommand, gptSlashCommand].map((command) => command.toJSON())
22 changes: 22 additions & 0 deletions src/Bot/commands/commandsReplies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CommandInteraction } from "discord.js"
import { GraphQLClient } from "graphql-request"
import { USER_INFO } from "../../graphql"
import { config } from "../../../config"
import { sendPrompt } from "./externals/gpt"

type UserInfoQuery = {
userInfo: {
Expand Down Expand Up @@ -44,3 +45,24 @@ export const lookupReply = async (interaction: CommandInteraction) => {
await interaction.editReply({ content: 'We could not find the user.' })
}
}

export const assistantAskReply = async (interaction: CommandInteraction) => {
const questionArg = interaction.options.getString('question')

if (!questionArg) {
await interaction.reply({ content: 'You need to provide a question.', ephemeral: true })
return
}

try {
// Sometimes, the model takes more than 3 seconds to respond, so we need to defer the reply
// to let the user know that the bot is processing the request and it won't be rejected
await interaction.deferReply()
JasirZaeem marked this conversation as resolved.
Show resolved Hide resolved
const { completion } = await sendPrompt(questionArg)

await interaction.editReply({ content: completion })
return
} catch (e) {
await interaction.reply({ content: 'We could not reach the assistant.' })
}
}
28 changes: 28 additions & 0 deletions src/Bot/commands/externals/gpt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Configuration, OpenAIApi } from 'openai'
import { config } from '../../../../config'

const configuration = new Configuration({
apiKey: config.openaiApiKey,
})
const openai = new OpenAIApi(configuration)

export const sendPrompt = async (question: string) => {
const prompt = `You\'re a coding assistant. You help students and explain things to them in a simple, clear, and super-friendly way, and you only respond to questions related to coding or programming.\n\n###EXAMPLE\n\nStudent: What's const in JavaScript?\nCoding assistant: \n> What's const in JavaScript?\n\n"const" keyword in JavaScript is a way to declare variables with a value that'll never change.\n\n###CURRENT\n\nStudent: ${question}\nCoding assistant:`
JasirZaeem marked this conversation as resolved.
Show resolved Hide resolved
const response = await openai.createCompletion({
model: 'text-davinci-003',
JasirZaeem marked this conversation as resolved.
Show resolved Hide resolved
prompt,
temperature: 0.7,
max_tokens: 256,
JasirZaeem marked this conversation as resolved.
Show resolved Hide resolved
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
})

if (response.status !== 200) {
throw new Error('OpenAI API request failed')
}

return {
statusText: response.statusText, status: response.status, completion: response.data.choices[0].text
}
}
5 changes: 4 additions & 1 deletion src/Bot/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Awaitable, Interaction } from "discord.js";
import { lookupReply } from "./commandsReplies";
import { assistantAskReply, lookupReply } from "./commandsReplies";

export const onInteractionCreate = (interaction: Interaction): Awaitable<void> => {
if (!interaction.isCommand()) return;

const { commandName } = interaction;

if (commandName === "lookup") lookupReply(interaction)
if (commandName === 'ask') {
JasirZaeem marked this conversation as resolved.
Show resolved Hide resolved
assistantAskReply(interaction)
}
}
Loading