Skip to content

Commit

Permalink
[FEAT] Chatbot ver.1 개발 (#89)
Browse files Browse the repository at this point in the history
* [FEATURE] 챗봇 엔티티 생성

* [FEATURE] 메시지 전송 dto 구현

* [FEATURE] 메시지 응답 dto 구현

* [FEATURE] repository 구현

* [FEATURE] 세션 만드는 createSession, 메시지 전송하고 응답 받는 createMessage 메소드 구현

* [FEATURE] OpenAI API와 통신하면서 세션을 만들거나, 메시지에 대한 응답을 받는 메소드들 구현

* [STYLE] 코드 포맷팅
  • Loading branch information
sootudio authored Aug 28, 2024
1 parent 88b7991 commit 6d7dc1b
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 0 deletions.
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;
}
}

0 comments on commit 6d7dc1b

Please sign in to comment.