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] Chatbot ver.1 개발 #89

Merged
merged 7 commits into from
Aug 28, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.fav.daengnyang.domain.chatbot.controller;

import com.fav.daengnyang.domain.chatbot.service.ChatbotService;
import com.fav.daengnyang.domain.chatbot.service.dto.request.CreateMessageRequest;
import com.fav.daengnyang.domain.chatbot.service.dto.response.MessageResponse;
import com.fav.daengnyang.global.auth.dto.MemberPrincipal;
import com.fav.daengnyang.global.dto.response.ExceptionResponse;
import com.fav.daengnyang.global.web.dto.response.SuccessResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RequestMapping("/chatbots")
@RestController
public class ChatbotController {

private final ChatbotService chatbotService;

// 챗봇에게 메시지 보내고 응답 받기
@PostMapping
public SuccessResponse<?> createMessage(
@AuthenticationPrincipal MemberPrincipal memberPrincipal,
@Valid @RequestBody CreateMessageRequest messageRequest
) throws InterruptedException {
MessageResponse response = chatbotService.getMessage(memberPrincipal.getMemberId(), messageRequest);
return SuccessResponse.ok(response);
}

// 챗봇에게 처음 메시지 보내기(새로운 세션 만들기)
@PostMapping("/session")
public SuccessResponse<?> createSession(
@AuthenticationPrincipal MemberPrincipal memberPrincipal
){
chatbotService.createSession(memberPrincipal.getMemberId());
return SuccessResponse.created("세션 생성에 성공했습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.fav.daengnyang.domain.chatbot.entity;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(name = "chatbot")
@Getter
@Setter
@NoArgsConstructor
public class Chatbot {

@Id
@Column(name = "member_id")
private Long memberId;

@Column(name = "thread_id")
private String threadId;

@Builder
public Chatbot(Long memberId, String threadId) {
this.memberId = memberId;
this.threadId = threadId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fav.daengnyang.domain.chatbot.repository;

import com.fav.daengnyang.domain.chatbot.entity.Chatbot;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ChatbotRepository extends JpaRepository<Chatbot, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.fav.daengnyang.domain.chatbot.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fav.daengnyang.domain.chatbot.entity.Chatbot;
import com.fav.daengnyang.domain.chatbot.repository.ChatbotRepository;
import com.fav.daengnyang.domain.chatbot.service.dto.request.CreateMessageRequest;
import com.fav.daengnyang.domain.chatbot.service.dto.response.MessageResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class ChatbotService {

private final ChatbotRepository chatbotRepository;

// GPT API 키
@Value("${gpt-key}")
private String gptKey;

// assistant ID
@Value(("${assistant-id}"))
private String assistantId;

// 유저의 메시지에 대한 응답 받기
public MessageResponse getMessage(Long memberId, CreateMessageRequest messageRequest) throws InterruptedException {
String threadId = getThreadId(memberId);
sendMessage(threadId, messageRequest.getChat());
runMessages(threadId, assistantId);
Thread.sleep(1500);
List<String> messages = getMessageTextValues(threadId);
MessageResponse response = MessageResponse.builder()
.chat(messages.get(0))
.build();
return response;
}

// 세션 생성하면서 첫 메시지 보내는 메소드
public void createSession(Long memberId) {
chatbotRepository.deleteById(memberId);
getThreadId(memberId);
}

// 지금 스레드(세션) id 가져오기
private String getThreadId(Long memberId) {
try {
Chatbot chatbot = chatbotRepository.findById(memberId).orElseThrow(Exception::new);
return chatbot.getThreadId();
} catch (Exception e){
String thread = createThreadId();
Chatbot chatbot = new Chatbot();
chatbot.setThreadId(thread);
chatbot.setMemberId(memberId);
chatbotRepository.save(chatbot);
return thread;
}
}

// 새로운 스레드(세션 만들기)
private String createThreadId() {
String URL = "https://api.openai.com/v1/threads";
try {
// HTTP 요청 설정
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.set("Authorization", "Bearer " + gptKey);
headers.set("OpenAI-Beta", "assistants=v2");
HttpEntity<String> entity = new HttpEntity<>("", headers);

// API 호출
ResponseEntity<String> response = restTemplate.exchange(URL, HttpMethod.POST, entity, String.class);
String jsonResponse = response.getBody();

// JSON 응답에서 id 추출
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonResponse);
return rootNode.path("id").asText();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

// 메시지 전송하기
private void sendMessage(String threadId, String messageContent) {
String BASE_URL = "https://api.openai.com/v1/threads/";
try {
// HTTP 요청 설정
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.set("Authorization", "Bearer " + gptKey);
headers.set("OpenAI-Beta", "assistants=v2");

// 요청 바디 설정
String requestBody = String.format(
"{\"role\": \"user\", \"content\": \"%s\"}",
messageContent
);
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

// API 호출
String url = BASE_URL + threadId + "/messages";
restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
} catch (Exception e) {
e.printStackTrace();
return;
}
}

// 메시지를 GPT에서 동작시킴
private void runMessages(String threadId, String assistantId) {
String BASE_URL = "https://api.openai.com/v1/threads/";
try {
// HTTP 요청 설정
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + gptKey);
headers.set("Content-Type", "application/json");
headers.set("OpenAI-Beta", "assistants=v2");

// 요청 바디 설정
String requestBody = String.format(
"{\"assistant_id\": \"%s\"}",
assistantId
);
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);

// API 호출
String url = BASE_URL + threadId + "/runs";
restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
} catch (Exception e) {
e.printStackTrace();
return;
}
}

// Message의 Text를 받아서 반환하는 메소드
private List<String> getMessageTextValues(String threadId) {
String BASE_URL = "https://api.openai.com/v1/threads/";
try {
// HTTP 요청 설정
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.set("Authorization", "Bearer " + gptKey);
headers.set("OpenAI-Beta", "assistants=v2");

HttpEntity<String> entity = new HttpEntity<>(null, headers);

// API 호출
String url = BASE_URL + threadId + "/messages";
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);

// 응답 처리
String jsonResponse = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonResponse);
List<String> textValues = new ArrayList<>();

// 메시지에서 text value 추출
for (JsonNode messageNode : rootNode.path("data")) {
for (JsonNode contentNode : messageNode.path("content")) {
if (contentNode.path("type").asText().equals("text")) {
String textValue = contentNode.path("text").path("value").asText();
textValues.add(textValue);
}
}
}

return textValues;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.fav.daengnyang.domain.chatbot.service.dto.request;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class CreateMessageRequest {

@NotNull(message = "챗봇에게 보낼 메시지를 입력해 주세요.")
private String chat;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.fav.daengnyang.domain.chatbot.service.dto.response;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@Getter
@Setter
public class MessageResponse {
private String chat;

@Builder
public MessageResponse(String chat) {
this.chat = chat;
}
}
Loading