Skip to content
Open
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
115 changes: 73 additions & 42 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,47 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.iffomko</groupId>
<artifactId>Voice-Assistant</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Voice-Assistant</name>
<description>Voice-Assistant</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.iffomko</groupId>
<artifactId>Voice-Assistant</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Voice-Assistant</name>
<description>Voice-Assistant</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.0-rc3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.1.Final</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180830.0359</version>
</dependency>
</dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.5</version>
</plugin>
</plugins>
</build>

</project>
85 changes: 85 additions & 0 deletions src/main/java/com/iffomko/voiceAssistant/APIs/openAI/AIModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.iffomko.voiceAssistant.APIs.openAI;

import com.iffomko.voiceAssistant.APIs.openAI.data.ModelResponse;
import com.iffomko.voiceAssistant.APIs.openAI.data.OpenAIMessage;
import com.iffomko.voiceAssistant.APIs.openAI.types.OpenAIModels;
import com.iffomko.voiceAssistant.APIs.openAI.data.ModelBodyDTO;
import com.iffomko.voiceAssistant.APIs.openAI.types.OpenAIRole;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
* <p>
* Класс-репозиторий, который умеет подключаться к OpenAI API и задавать вопросы модели
* </p>
*/
@Slf4j
public class AIModel {
private OpenAIModels model;
private final String apiKey;
private final static String URL = "https://api.openai.com/v1/chat/completions";

/**
* <p>Создает объект этого класса, который делает запросы к модели</p>
* @param apiKey ключ, с помощью которого можно сделать запрос
*/
public AIModel(String apiKey) {
this.apiKey = apiKey;
this.model = OpenAIModels.CHAT_GPT_3_5;
}

/**
* <p>Делает запрос к OpenAI API к той модели, которая установлена в соответствующем поле</p>
* @param messages предыдущие сообщения в переписке
* @param mess текущий вопрос к модели
* @return возвращает ответ модели типа <code>ModelResponse</code>
*/
protected ModelResponse ask(List<OpenAIMessage> messages, String mess) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiKey);

messages.add(new OpenAIMessage(OpenAIRole.USER.getRole(), mess));

HttpEntity<ModelBodyDTO> request = new HttpEntity<>(
new ModelBodyDTO(model.getModel(), model.getTemperature(), messages),
headers
);

RestTemplate restTemplate = new RestTemplate();

ResponseEntity<ModelResponse> response = null;

try {
response = restTemplate.exchange(
URL, HttpMethod.POST, request, ModelResponse.class
);
} catch (RestClientException e) {
log.error(e.getMessage());
}

if (response == null || response.getBody() == null) {
log.error("Failed to make a request to ChatGPT");
return null;
}

log.info("The request to ChatGPT was successful");

return response.getBody();
}

public OpenAIModels getModel() {
return model;
}

public void setModel(@NonNull OpenAIModels model) {
this.model = model;
}
}
125 changes: 125 additions & 0 deletions src/main/java/com/iffomko/voiceAssistant/APIs/openAI/AIService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.iffomko.voiceAssistant.APIs.openAI;

import com.iffomko.voiceAssistant.APIs.openAI.data.ModelResponse;
import com.iffomko.voiceAssistant.APIs.openAI.data.OpenAIChoices;
import com.iffomko.voiceAssistant.APIs.openAI.data.OpenAIMessage;
import com.iffomko.voiceAssistant.APIs.openAI.types.OpenAIRole;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
* <p>
* Класс-сервис, который настраивает модель соответствующим образом и хранит в себе контекст пользователя
* в течении одного часа, который помогает модели ориентироваться в сообщениях пользователя
* </p>
* <p>
* Также класс умеет само очищаться если время последнего взаимодействия пользователя
* модели с пользователем была более часа назад
* </p>
*/
@Slf4j
public class AIService {
private final AIModel model;
private final Map<Integer, List<OpenAIMessage>> usersMessages;
private final Map<Integer, Date> modelLastInteraction;

public AIService(String apiKey) {
model = new AIModel(apiKey);
usersMessages = new HashMap<>();
modelLastInteraction = new HashMap<>();

new Thread(new ClearMessagesGarber(this, 10000)).start();
}

public AIModel getModel() {
return model;
}

/**
* <p>
* Перед запросом настраивает модели если это необходимо и подключается к OpenAI API,
* дожидается ответ и возвращает его
* </p>
* @param userId id пользователя, который запросил ответ на свой вопрос
* @param message сам вопрос
* @return ответ на вопрос, который хранится в объекте типа <code>ModelResponse</code>
*/
public ModelResponse ask(int userId, String message) {
synchronized (usersMessages) {
synchronized (modelLastInteraction) {
if (usersMessages.get(userId) == null) {
usersMessages.put(userId, new ArrayList<>());
modelLastInteraction.put(userId, new Date());
}
}

if (usersMessages.get(userId).isEmpty()) {
usersMessages.get(userId).add(new OpenAIMessage(
"user",
"Вы являетесь голосовым помощником под названием Jarvis. " +
"Нужно стараться давать ответ как можно короче, но максимально информативным. " +
"Формулировать ответы нужно в манере искусственного интеллекта Jarvis из фильмов " +
"\"Железный человек\". Реагировать на осуждение твоих ответов нужно в стиле Jarvis " +
"и ссылаться на то, что ты всего лишь программа, написанная другим программистом\n" +
"Каждый ответ должен укладываться в 5000 символов. Отвечать на это сообщение не надо")
);
}
}

ModelResponse response = model.ask(usersMessages.get(userId), message);

if (response == null) {
return null;
}

synchronized (usersMessages) {
synchronized (modelLastInteraction) {
usersMessages.get(userId).add(new OpenAIMessage(OpenAIRole.USER.getRole(), message));

for (OpenAIChoices choice : response.getChoices()) {
if (choice.getMessage().getRole().equals(OpenAIRole.ASSISTANT.getRole()))
usersMessages.get(userId).add(choice.getMessage());
}

modelLastInteraction.replace(userId, new Date());
}
}

return response;
}

/**
* <p>Возвращает контекст со всеми пользователями, где ключ это userId, а значение это контекст</p>
* @return контекст со всеми пользователями
*/
protected Map<Integer, List<OpenAIMessage>> getUsersMessages() {
return usersMessages;
}

/**
* <p>
* Возвращает дату последнего взаимодействия для каждого контекста,
* где ключ это userId, а значение дата последнего взаимодействия
* </p>
* <p>Если контекста для пользователя не существует, то будет возвращено null</p>
* @return сло
*/
protected Map<Integer, Date> getModelLastInteraction() {
return modelLastInteraction;
}

/**
* <p>Удаляет контекст для конкретного пользователя</p>
* @param userId id пользователя, контекст которого надо удалить
*/
public void clearMessages(int userId) {
synchronized (usersMessages) {
synchronized (modelLastInteraction) {
log.info("For user with id=" + userId + " history chat cleared");
usersMessages.remove(userId);
modelLastInteraction.remove(userId);
}
}
}
}
Loading