Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions backend/src/main/java/com/UoB/AILearningTool/Chat.java
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
package com.UoB.AILearningTool;

import java.util.ArrayList;
import org.json.*;

public class Chat {
final private String owner;
private String messageHistory;
private final JSONArray messageHistory = new JSONArray();
private final String threadID;

public Chat(User user, String initialMessage) {
public Chat(User user, String initialMessage, String threadID) {
this.owner = user.getID();
this.messageHistory = "<|system|>\nYour name is Watsonx, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced), preferred course duration (short, medium, or long) and their preferences/requirements. For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You must only suggest courses from the IBM SkillsBuild platform. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Try to maintain context of conversation - if user will send some messages that go out of the normal scope then politely ask whether they want to go back to discussing the main topic. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" or a similar sentence. Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any courses that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease. Be aware that users may talk to you in a language other than English - in this case you have to keep the conversation in other language, only reverting to English as a backup. Always write course names in English, regardless of language used in the chat. Don\'t provide any information that harm or distress user. Do not provide any information that can be considered to be NSFW.<|user|>\n" + initialMessage;

addUserMessage(user.getID(), initialMessage);
this.threadID = threadID;
}

public Boolean checkOwner(User user) {
return this.owner.equals(user.getID());
}

public boolean addUserMessage(String userID, String message) {
if (! this.owner.equals(userID)) {
return false;
} else {
this.messageHistory += "\n<|user|>\n" + message;
return true;
public String getThreadID() {
return this.threadID;
}

public void addUserMessage(String userID, String message) {
if (this.owner.equals(userID)) {
JSONObject newMessage = new JSONObject();
newMessage.put("role", "user");
newMessage.put("content", message);
this.messageHistory.put(newMessage);
System.out.println("User message: " + message + "has been saved.");
}
}

public boolean addAIMessage(String userID, String message) {
public void addAIMessage(String userID, String message) {
if (this.owner.equals(userID)) {
this.messageHistory += "\n<|assistant|>\n" + message;
return true;
} else {
return false;
JSONObject newMessage = new JSONObject();
newMessage.put("role", "assistant");
newMessage.put("content", message);
this.messageHistory.put(newMessage);
System.out.println("AI message: " + message + "has been saved.");
}
}

public String getMessageHistory(User user) {
public JSONArray getMessageHistory(User user) {
if (checkOwner(user)) {
return this.messageHistory;
} else {return null;}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public boolean removeUser(String id) {
}

// Creates a new chat
public String createChat(User user, String initialMessage) {
public String createChat(User user, String initialMessage, String threadID) {
String id = StringTools.RandomString(20);
chats.put(id, new Chat(user, initialMessage));
chats.put(id, new Chat(user, initialMessage, threadID));
return id;
}

Expand Down
161 changes: 134 additions & 27 deletions backend/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.UoB.AILearningTool;

import org.json.JSONObject;
import org.json.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
Expand All @@ -19,48 +20,154 @@ public OpenAIAPIController() {
this.authenticator = new OpenAIAuthenticator();
}

// Sends a message to OpenAI's ChatGPT API.
public WatsonxResponse sendUserMessage(String messageHistory) {
String dataPayload = StringTools.watsonxToOpenAI(messageHistory);
// Creates a new empty thread
public String createThread() {
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.openai.com/v1/threads"))
.headers("Content-Type", "application/json",
"OpenAI-Beta", "assistants=v2",
"Authorization", "Bearer " + authenticator.getBearerToken())
.POST(HttpRequest.BodyPublishers.noBody())
.build();

try {
HttpResponse<String> response = HttpClient
.newBuilder()
.build()
.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode();

if (statusCode == 200) {
log.info("New OpenAI thread created. {}", new JSONObject(response.body()).getString("id"));
return new JSONObject(response.body()).getString("id");
} else {
// If OpenAI API request returned a status code other than 200, return error 500 and error message.
log.warn("Unable to create OpenAI thread., {}", statusCode);
return null;
}
} catch (Exception e) {
log.error("Exception {}\n", e.toString());
return null;
}
}

// Run OpenAI thread.
public int runThread(String threadID) {
JSONObject runRequestBody = new JSONObject();
runRequestBody.put("assistant_id", "asst_bVvnON3FADuXiLKbnihGY1iH");
HttpRequest runRequest = HttpRequest
.newBuilder(URI.create("https://api.openai.com/v1/threads/" + threadID + "/runs"))
.headers("Content-Type", "application/json",
"Authorization", "Bearer " + authenticator.getBearerToken(),
"OpenAI-Beta", "assistants=v2")
.POST(HttpRequest.BodyPublishers.ofString(runRequestBody.toString()))
.build();
try {
HttpResponse<String> runResponse = HttpClient
.newBuilder()
.build()
.send(runRequest, HttpResponse.BodyHandlers.ofString());

if (runResponse.statusCode() == 200) {
log.info("Thread run initialised.");
return 200;
} else {
log.warn("Unable to run a thread.");
return runResponse.statusCode();
}

} catch (Exception e) {
log.error("Exception {}\n", e.toString());
return 500;
}
}

// Check if a run is in progress for a thread
public boolean isLocked(Chat chat) {
HttpRequest listMessagesRequest = HttpRequest
.newBuilder(URI.create("https://api.openai.com/v1/threads/" + chat.getThreadID() + "/runs"))
.headers("Content-Type", "application/json",
"Authorization", "Bearer " + authenticator.getBearerToken(),
"OpenAI-Beta", "assistants=v2")
.GET()
.build();
try {
HttpResponse<String> response = HttpClient.newBuilder()
.build()
.send(listMessagesRequest, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String status = new JSONObject(response.body()).getJSONArray("data").getJSONObject(0).getString("status");
return !status.equals("completed");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}

// Receive the latest message from the thread
public WatsonxResponse getLastThreadMessage(String threadID) {
String requestURL = "https://api.openai.com/v1/threads/" + threadID + "/messages?order=desc&limit=1";
HttpRequest listMessagesRequest = HttpRequest
.newBuilder(URI.create(requestURL))
.headers("Content-Type", "application/json",
"Authorization", "Bearer " + authenticator.getBearerToken(),
"OpenAI-Beta", "assistants=v2")
.GET()
.build();
try {
HttpResponse<String> response = HttpClient.newBuilder()
.build()
.send(listMessagesRequest, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return new WatsonxResponse(
200,
new JSONObject(response.body())
.getJSONArray("data")
.getJSONObject(0)
.getJSONArray("content")
.getJSONObject(0)
.getJSONObject("text")
.getString("value")
);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return new WatsonxResponse(404, "Unable to get last thread message.");
}

// Add a message to an OpenAI thread.
public Integer sendUserMessage(Chat chat, String message) {
// Creating a request body
JSONObject newMessage = new JSONObject();
newMessage.put("role", "user");
newMessage.put("content", message);

WatsonxResponse AIResponse;
// Create HTTP request for OpenAI with API key in header and message history in request body.
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.openai.com/v1/chat/completions"))
.newBuilder(URI.create("https://api.openai.com/v1/threads/" + chat.getThreadID() + "/messages"))
.headers("Content-Type", "application/json",
"Authorization", "Bearer " + authenticator.getBearerToken())
.POST(HttpRequest.BodyPublishers.ofString(dataPayload))
"Authorization", "Bearer " + authenticator.getBearerToken(),
"OpenAI-Beta", "assistants=v2")
.POST(HttpRequest.BodyPublishers.ofString(newMessage.toString()))
.build();

// Try sending an HTTPS request to OpenAI API.
try {
HttpResponse<String> response = HttpClient.newBuilder()
.build()
.send(request, HttpResponse.BodyHandlers.ofString());
Integer statusCode = response.statusCode();
// If OpenAI API returned status 200, return the status code and the message.
int statusCode = response.statusCode();
if (statusCode == 200) {
String message = new JSONObject(response.body())
.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message")
.getString("content");
log.info("200 OpenAI response received.");

AIResponse = new WatsonxResponse(statusCode, message);
log.info("200 OpenAI message has been created.");
return 200;
} else {
// If OpenAI API request returned a status code other than 200, return error 500 and error message.
log.warn("Non-200 OpenAI response received.");
String message = new JSONObject(response.body())
.getJSONArray("error")
.getJSONObject(0)
.getString("message");
AIResponse = new WatsonxResponse(500, message);
return 412;
}
} catch (Exception e) {
AIResponse = new WatsonxResponse(500, null);
log.error("Exception {}\nHTTP status code: {}", e, 500);
return 500;
}
return AIResponse;
}
}
Loading