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

Chat Gpt 로그인 연동 #42

Merged
merged 16 commits into from
Nov 8, 2024
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
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.security:spring-security-oauth2-jose'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'

/*
캐싱 관련
*/
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.server.computerscience.chatbot.config;

import java.util.concurrent.TimeUnit;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.benmanes.caffeine.cache.Caffeine;

@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS) // 1시간 후 만료
.maximumSize(1000)); // 최대 1000개의 항목 유지
return cacheManager;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.server.computerscience.chatbot.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -20,8 +22,9 @@ public class ChatbotController {

@PostMapping("/chat/text")
public ResponseEntity<ChatBotResponseDto> chat(
@RequestBody ChatBotRequestDto chatBotRequestDto
@RequestBody ChatBotRequestDto chatBotRequestDto,
@AuthenticationPrincipal OAuth2User user
) {
return ResponseEntity.ok(ChatBotResponseDto.from(chatbotService.chat(chatBotRequestDto)));
return ResponseEntity.ok(ChatBotResponseDto.from(chatbotService.talkToAssistant(chatBotRequestDto, user)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import lombok.Getter;

@Getter
public enum ChatGptContentType {
public enum ChatContentType {
TEXT("text");
private final String lower;

ChatGptContentType(String lower) {
ChatContentType(String lower) {
this.lower = lower;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import lombok.Getter;

@Getter
public enum ChatGptRole {
SYSTEM("system"), USER("user");
public enum ChatRole {
SYSTEM("system"), USER("user"), ASSISTANT("assistant");
private final String lower;

ChatGptRole(String lower) {
ChatRole(String lower) {
this.lower = lower;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.server.computerscience.chatbot.dto.request;

import com.server.computerscience.chatbot.domain.ChatGptContentType;
import com.server.computerscience.chatbot.domain.ChatContentType;

import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -13,12 +13,12 @@
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChatGptContentDto {
public class ChatContentDto {
private String type;
private String text;

public static ChatGptContentDto from(ChatGptContentType type, String text) {
return ChatGptContentDto.builder()
public static ChatContentDto from(ChatContentType type, String text) {
return ChatContentDto.builder()
.text(text)
.type(type.getLower())
.build();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.server.computerscience.chatbot.dto.request;

import java.util.List;
import java.util.stream.Collectors;

import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -16,14 +15,12 @@
@Builder
public class ChatGptRestRequestDto {
private String model;
private List<ChatGptMessageDto> messages;
private List<ChatMessageDto> messages;

public static ChatGptRestRequestDto from(String model, List<String> prompts) {
public static ChatGptRestRequestDto from(String model, List<ChatMessageDto> chatMessages) {
return ChatGptRestRequestDto.builder()
.model(model)
.messages(prompts.stream()
.map(ChatGptMessageDto::fromUserText)
.collect(Collectors.toList()))
.messages(chatMessages)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.server.computerscience.chatbot.dto.request;

import java.util.Collections;
import java.util.List;

import com.server.computerscience.chatbot.domain.ChatContentType;
import com.server.computerscience.chatbot.domain.ChatRole;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChatMessageDto {
private String role;
private List<ChatContentDto> content;

public static ChatMessageDto from(String text, ChatRole role) {
return ChatMessageDto.builder()
.role(role.getLower())
.content(Collections.singletonList(ChatContentDto.from(ChatContentType.TEXT, text)))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.server.computerscience.chatbot.service;

import org.springframework.security.oauth2.core.user.OAuth2User;

import com.server.computerscience.chatbot.dto.request.ChatBotRequestDto;

public interface ChatbotService {
String chat(ChatBotRequestDto chatBotRequestDto);
String talkToAssistant(ChatBotRequestDto chatBotRequestDto, OAuth2User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.server.computerscience.chatbot.service.implement;

import java.util.LinkedList;
import java.util.List;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import com.server.computerscience.chatbot.dto.request.ChatMessageDto;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatCacheService {
private static final String CHAT_MESSAGE = "chatMessages";
private static final String CHAT_USED_CHANCE = "chatRemainChance";

private final CacheManager cacheManager;

/**
* 이미 저장되어있는 대화 목록이 있을 경우, 아니라면 새로운 목록을 반환
*/
public List<ChatMessageDto> getChatMessages(String userId) {
Cache cache = cacheManager.getCache(CHAT_MESSAGE);
if (cache != null) {
List<ChatMessageDto> chatMessages = cache.get(userId, List.class);
return chatMessages != null ? chatMessages : new LinkedList<>();
}
return new LinkedList<>();
}

@CachePut(value = CHAT_MESSAGE, key = "#userId")
public List<ChatMessageDto> saveChatMessage(String userId, ChatMessageDto chatMessageDto, int maxMessageSize) {
List<ChatMessageDto> chatMessages = getChatMessages(userId); // 캐시에서 가져오기
chatMessages.add(chatMessageDto);
if (chatMessages.size() > maxMessageSize) {
chatMessages.remove(0);
}
return chatMessages;
}

// 남은 이용 횟수를 가져오는 메서드
public Integer getUsedChance(String userId) {
Cache cache = cacheManager.getCache(CHAT_USED_CHANCE);
if (cache != null) {
Integer usedChance = cache.get(userId, Integer.class);
return usedChance != null ? usedChance : 0; // 캐시에서 가져온 값이 없으면 0 반환
}
return 0; // 기본값
}

// 사용 횟수를 증가시키는 메서드
@CachePut(value = CHAT_USED_CHANCE, key = "#userId")
public int increaseUsedChance(String userId) {
int currentUsedChance = getUsedChance(userId);
currentUsedChance = currentUsedChance + 1;
return currentUsedChance;
}

/**
* 매 시간마다 모든 사용자에 대한 사용 횟수 캐시를 삭제하는 메서드
*/
@Scheduled(cron = "0 0 * * * *") // 매 시간 정각에 실행
@CacheEvict(value = CHAT_USED_CHANCE, allEntries = true)
public void clearAllUsedChances() {
// 모든 사용자에 대한 사용 횟수 캐시를 삭제합니다.
System.out.println("모든 사용자에 대한 사용 횟수 캐시를 삭제했습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
package com.server.computerscience.chatbot.service.implement;

import java.util.Collections;
import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.server.computerscience.chatbot.dto.request.ChatBotRequestDto;
import com.server.computerscience.chatbot.dto.request.ChatGptRestRequestDto;
import com.server.computerscience.chatbot.dto.request.ChatMessageDto;
import com.server.computerscience.chatbot.dto.response.ChatGptResponseDto;
import com.server.computerscience.chatbot.service.ChatbotService;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatGptService implements ChatbotService {
public class ChatGptService {
private final String model = "gpt-4o-mini";
private final String payingApiUrl = "https://api.openai.com/v1/chat/completions";
private final RestTemplate restTemplate;

@Override
public String chat(ChatBotRequestDto chatBotRequestDto) {
public String chat(List<ChatMessageDto> chatMessages) {
ChatGptRestRequestDto chatGptRestRequestDto = ChatGptRestRequestDto.from(model,
Collections.singletonList(chatBotRequestDto.getPrompt()));
chatMessages);
ChatGptResponseDto chatGptResponseDto = restTemplate.postForObject(
payingApiUrl,
chatGptRestRequestDto,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.server.computerscience.chatbot.service.implement;

import java.util.List;

import org.springframework.stereotype.Service;

import com.server.computerscience.chatbot.domain.ChatRole;
import com.server.computerscience.chatbot.dto.request.ChatBotRequestDto;
import com.server.computerscience.chatbot.dto.request.ChatMessageDto;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatManageService {
private static final int MAX_CHAT_CHANCE = 30;
private static final int MAX_MESSAGES_SIZE = 15;
private final String NO_MORE_CHANCE = "채팅 기회를 모두 소모하셨습니다. 1시간마다 초기화됩니다.";
private final ChatCacheService chatCacheService;
private final ChatGptService chatGptService;

public String respond(String userId, ChatBotRequestDto chatBotRequestDto) {
if (chatCacheService.getUsedChance(userId) >= MAX_CHAT_CHANCE) {
return NO_MORE_CHANCE;
}
/**
* User가 가지고 있는 이전 대화 기록을 가져온다.
*/
List<ChatMessageDto> chatMessages = beforeRespond(userId,
chatBotRequestDto);
/**
* 챗봇에게 받은 답변 또한 이전 대화 기록에 넣는다.
*/
String answer = chatGptService.chat(chatMessages);
afterRespond(userId, answer, chatMessages);
return answer;
}

private List<ChatMessageDto> beforeRespond(String userId, ChatBotRequestDto chatBotRequestDto) {
ChatMessageDto chatMessageFromUser = ChatMessageDto.from(chatBotRequestDto.getPrompt(), ChatRole.USER);
return chatCacheService.saveChatMessage(userId, chatMessageFromUser, MAX_MESSAGES_SIZE);
}

private void afterRespond(String userId, String answer, List<ChatMessageDto> chatMessages) {
ChatMessageDto chatMessageFromAssistant = ChatMessageDto.from(answer, ChatRole.ASSISTANT);
chatCacheService.saveChatMessage(userId, chatMessageFromAssistant, MAX_MESSAGES_SIZE);
chatCacheService.increaseUsedChance(userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.server.computerscience.chatbot.service.implement;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import com.server.computerscience.chatbot.dto.request.ChatBotRequestDto;
import com.server.computerscience.chatbot.service.ChatbotService;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class LoginChatBotService implements ChatbotService {
private final ChatManageService chatManageService;
private final String NOT_LOGIN = "로그인이 필요합니다.";
@Value("${spring.security.oauth2.client.provider.cognito.user-name-attribute}")
private String userIdentifier;

@Override
public String talkToAssistant(ChatBotRequestDto chatBotRequestDto, OAuth2User user) {
if (user == null) {
return NOT_LOGIN;
}
String userId = (String)user.getAttributes().get(userIdentifier);
return chatManageService.respond(userId, chatBotRequestDto);
}
}
Loading
Loading