Skip to content

Commit

Permalink
Merge pull request #15 from langx:xuelink/issue14
Browse files Browse the repository at this point in the history
Migrate to ChatGPT from Gemini API
  • Loading branch information
xuelink authored Jun 5, 2024
2 parents 2aa5256 + 67b7ca0 commit 77d1233
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 290 deletions.
4 changes: 3 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
DISCORD_BOT_TOKEN=your_discord_token
DISCORD_CLIENT_ID=your_discord_client_id
GEMINI_API_KEY=your_gemini_api_key

OPENAI_API_KEY=your_openai_api_key
OPENAI_ASSISTANT_ID=your_openai_assistant_id
20 changes: 6 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,14 @@ You can try it out in the [#copilot channel](https://discord.langx.io) on Discor
3. **Fill in the environment variables** in the `.env` file:

```sh
GEMINI_API_KEY=your_gemini_api_key
```

4. **Copy instructions.js.sample to instructions.js** and fill in your instructions:
DISCORD_BOT_TOKEN=your_discord_token
DISCORD_CLIENT_ID=your_discord_client_id

```sh
cp instructions.js.sample instructions.js
```

5. **Prepare the environment** by running the prebuild script:

```sh
npm run prebuild
OPENAI_API_KEY=your_openai_api_key
OPENAI_ASSISTANT_ID=your_openai_assistant_id
```

6. **Deploy Appwrite Functions**:
4. **Deploy Appwrite Functions**:

1. Open the `appwrite.json` file in a text editor.
2. Update the `projectName` field with your `projectId`.
Expand All @@ -72,7 +64,7 @@ You can try it out in the [#copilot channel](https://discord.langx.io) on Discor
npm install -g appwrite-cli
```

6. Deploy the Appwrite functions by running the following command:
5. Deploy the Appwrite functions by running the following command:

```sh
appwrite deploy --functionId copilot
Expand Down
45 changes: 22 additions & 23 deletions appwrite.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
{
"projectId": "650750d21e4a6a589be3",
"projectName": "LangX",
"functions": [
{
"$id": "copilot",
"name": "copilot",
"runtime": "node-20.0",
"execute": [],
"events": [],
"schedule": "",
"timeout": 15,
"enabled": true,
"logging": true,
"entrypoint": "index.js",
"commands": "npm install --omit=dev",
"ignore": [
"node_modules",
".npm"
],
"path": "./"
}
]
}
"projectId": "650750d21e4a6a589be3",
"projectName": "LangX",
"functions": [
{
"$id": "copilot",
"name": "copilot",
"runtime": "node-20.0",
"execute": [],
"events": [
"databases.650750f16cd0c482bb83.collections.65075108a4025a4f5bd7.documents.*.create"
],
"schedule": "",
"timeout": 15,
"enabled": true,
"logging": true,
"entrypoint": "index.js",
"commands": "npm install --omit=dev",
"ignore": ["node_modules", ".npm"],
"path": "./"
}
]
}
93 changes: 52 additions & 41 deletions copilot/aiHandler.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,66 @@
import OpenAI from "openai";
import dotenv from "dotenv";

import { genAI, safetySettings } from "../utils/common.js";

// Load environment variables from .env file
dotenv.config();

const systemInstruction = decodeURIComponent(process.env.SYSTEM_INSTRUCTION);
const chatHistory = JSON.parse(process.env.CHAT_HISTORY) || [];
// Verify that the environment variable is loaded correctly
if (!process.env.OPENAI_API_KEY) {
throw new Error(
"The OPENAI_API_KEY environment variable is missing or empty."
);
}

const model = genAI.getGenerativeModel({
model: "gemini-1.5-pro-latest",
systemInstruction: systemInstruction,
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 64,
maxOutputTokens: 512,
responseMimeType: "application/json",
};
const assistant_id = process.env.OPENAI_ASSISTANT_ID;

async function handleInteraction(userMessage) {
try {
if (
!userMessage ||
typeof userMessage !== "string" ||
userMessage.trim() === ""
) {
console.error("Invalid user message:", userMessage);
throw new Error("Invalid user message");
}
return new Promise(async (resolve, reject) => {
try {
// Create the thread
const thread = await openai.beta.threads.create({
messages: [
{
role: "user",
content: userMessage,
},
],
});

const chatSession = model.startChat({
generationConfig,
safetySettings,
history: chatHistory,
});

// Send user message
const result = await chatSession.sendMessage(userMessage);
if (!result || !result.response) {
console.error("Invalid response from generative model:", result);
throw new Error("Invalid response from generative model");
}
if (!thread || !thread.id) {
throw new Error("Thread creation failed");
}

// Return the corrected message
return result.response;
} catch (error) {
console.error("Error in handleInteraction:", error);
throw error;
}
console.log("Thread created:", thread);

let output = "";

openai.beta.threads.runs
.stream(thread.id, {
assistant_id: assistant_id,
})
.on("textDelta", (textDelta) => {
output += textDelta.value;
})
.on("end", async () => {
console.log(output);

// Delete Thread
const response = await openai.beta.threads.del(thread.id);
if (response.deleted) {
console.log("Thread deleted");
}
// You can now use the 'output' variable for further processing
resolve(output);
});
} catch (error) {
console.error("Error creating thread or run:", error);
reject(error);
}
});
}

export { handleInteraction };
30 changes: 15 additions & 15 deletions copilot/aiHandler.spec.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import { handleInteraction } from "./aiHandler.js"; // Ensure this path is correct
import { chatHistory } from "../instructions.js"; // Ensure this path is correct

describe("handleInteraction function", () => {
it("should return a valid response object", async () => {
// Sample user message
const userMessage = "I have an apple.";
let response = await handleInteraction(userMessage);

// Call the handleInteraction function with the user message and chat history
let response = await handleInteraction(userMessage, chatHistory);
response = JSON.parse(response.text());
// If the response is a stringified JSON, parse it
if (typeof response === "string") {
response = JSON.parse(response);
}

// Check that the response object has 'correction' and 'explanation' properties
expect(response).toHaveProperty("correction");
expect(response).toHaveProperty("explanation");

// Check that 'correction' and 'explanation' are of type string or null
expect(response.correction).toBeNull(); // Or check for type string if not null
expect(response.explanation).toBeNull(); // Or check for type string if not null
// Check that 'correction' and 'explanation' are either null or of type string
const isValidType = (value) => value === null || typeof value === "string";
expect(isValidType(response.correction)).toBeTruthy();
expect(isValidType(response.explanation)).toBeTruthy();
});

it("should return a correction for a grammar fault", async () => {
// User message with a grammar fault
const userMessage = "I have a apple";
let response = await handleInteraction(userMessage);

// Call the handleInteraction function with the user message and chat history
let response = await handleInteraction(userMessage, chatHistory);
response = JSON.parse(response.text());
// If the response is a stringified JSON, parse it
if (typeof response === "string") {
response = JSON.parse(response);
}

// Check that the response object has 'correction' and 'explanation' properties
expect(response).toHaveProperty("correction");
expect(response).toHaveProperty("explanation");

// Check that 'correction' is not null and is a string
// Check that 'correction' and 'explanation' are of type string
expect(typeof response.correction).toBe("string");
expect(typeof response.explanation).toBe("string");
});
Expand Down
10 changes: 7 additions & 3 deletions discord/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,16 @@ client.on("interactionCreate", async (interaction) => {
// Acknowledge the interaction to avoid timing out
await interaction.deferReply();

let response = await handleInteraction(userMessage);
response = JSON.parse(response.text());
let aiResponse = await handleInteraction(userMessage);
aiResponse = JSON.parse(aiResponse);

if (response.correction) {
await interaction.editReply(
`📝 **Your message:** ${userMessage}\n✅ **Correction:** ${response.correction}\nℹ️ **Explanation:** ${response.explanation}`
`
📝 **Your message:** ${userMessage}\n
🤖️ ${aiResponse.explanation}
${aiResponse.correction}\n
`
);
} else {
await interaction.editReply(
Expand Down
20 changes: 9 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,22 @@ export default async ({ req, res, log, error }) => {
log(`userMessage: ${userMessage}`);
log(`userId: ${req.body.to}`);

const aiResponse = await handleInteraction(userMessage);
log(aiResponse.text());
let aiResponse = await handleInteraction(userMessage);
aiResponse = JSON.parse(aiResponse);

const correction = aiResponse.text();
const correctionObj = JSON.parse(correction);
log(`sender: ${req.body.sender}`);
log(`roomId: ${roomId}`);
log(`aiResponse ${aiResponse}`);

if (correctionObj.correction && correctionObj.explanation) {
if (aiResponse.correction && aiResponse.explanation) {
// Store the results in the copilot collection
const copilotDoc = await db.createDocument(
process.env.APP_DATABASE,
process.env.COPILOT_COLLECTION,
"unique()",
{
correction: correctionObj.correction,
explanation: correctionObj.explanation,
promptTokenCount: aiResponse.usageMetadata.promptTokenCount,
candidatesTokenCount: aiResponse.usageMetadata.candidatesTokenCount,
totalTokenCount: aiResponse.usageMetadata.totalTokenCount,
correction: aiResponse.correction,
explanation: aiResponse.explanation,
sender: req.body.sender,
roomId: roomId,
messageId: req.body.$id,
Expand All @@ -97,7 +95,7 @@ export default async ({ req, res, log, error }) => {
log(`Successfully created copilot document: ${copilotDoc.$id}`);
}

return res.json({ response: correctionObj });
return res.json({ response: aiResponse });
} catch (err) {
error(err.message);
return res.send("An error occurred", 500);
Expand Down
Loading

0 comments on commit 77d1233

Please sign in to comment.