Skip to content

Commit

Permalink
Feature: 챗봇 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Youthhing committed Jan 17, 2025
1 parent 8fed240 commit 6ae6562
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 3 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-mail'

implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/cotato/team2/team2/config/OpenAiConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.cotato.team2.team2.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class OpenAiConfig {

@Value("${openai.api.key}")
private String openAiKey;

@Bean
public RestTemplate template(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
request.getHeaders().add("Authorization", "Bearer " + openAiKey);
return execution.execute(request, body);
});
return restTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.cotato.team2.team2.controller;

import com.cotato.team2.team2.controller.dto.ChatRequest;
import com.cotato.team2.team2.controller.dto.ChatResponse;
import com.cotato.team2.team2.service.ChatService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/chat")
public class ChatController {

private final ChatService chatService;

@GetMapping
public ResponseEntity<ChatResponse> chat(@RequestParam("sessionKey") String sessionKey,
@RequestParam("message") String message) {
return ResponseEntity.ok(chatService.chat(sessionKey, message));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import com.cotato.team2.team2.controller.dto.UserResponse;
import com.cotato.team2.team2.controller.dto.UsersResponse;
import com.cotato.team2.team2.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -26,7 +26,7 @@ public class UserController {
private final UserService userService;

@PatchMapping("/nickname")
public ResponseEntity<Void> updateNickname(@RequestBody UserNickNameUpdateRequest request) {
public ResponseEntity<Void> updateNickname(@RequestBody @Valid UserNickNameUpdateRequest request) {
userService.updateNickname(request.email(), request.nickname());
return ResponseEntity.ok().build();
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/cotato/team2/team2/controller/dto/AiMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.cotato.team2.team2.controller.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class AiMessage {
private String role;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.cotato.team2.team2.controller.dto;

import java.util.ArrayList;
import java.util.List;
import lombok.Data;

@Data
public class ChatGPTRequest {

private String model;
private List<AiMessage> messages;

public ChatGPTRequest(String model, String prompt) {
this.model = model;
this.messages = new ArrayList<>();
this.messages.add(new AiMessage("user", prompt));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.cotato.team2.team2.controller.dto;

import java.util.List;
import lombok.Data;

@Data
public class ChatGPTResponse {

private String id;
private String object;
private Long created;
private String model;
private List<Choice> choices;
private Usage usage;

@Data
public static class Choice {
private int index;
private Message message;
private String finishReason;
}

@Data
public static class Message {
private String role;
private String content;
private String refusal;
}

@Data
public static class Usage {
private int promptTokens;
private int completionTokens;
private int totalTokens;
private PromptTokensDetails promptTokensDetails;
private CompletionTokensDetails completionTokensDetails;

@Data
public static class PromptTokensDetails {
private int cachedTokens;
private int audioTokens;
}

@Data
public static class CompletionTokensDetails {
private int reasoningTokens;
private int audioTokens;
private int acceptedPredictionTokens;
private int rejectedPredictionTokens;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.cotato.team2.team2.controller.dto;

public record ChatRequest(
String sessionKey,
String message
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.cotato.team2.team2.controller.dto;

public record ChatResponse(
String response
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.cotato.team2.team2.controller.dto;

public record GPTResponse(
String message
) {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.cotato.team2.team2.controller.dto;

import jakarta.validation.constraints.NotNull;

public record UserNickNameUpdateRequest(
@NotNull
String email,
@NotNull
String nickname
) {
}
67 changes: 67 additions & 0 deletions src/main/java/com/cotato/team2/team2/service/ChatService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.cotato.team2.team2.service;

import com.cotato.team2.team2.controller.dto.ChatGPTRequest;
import com.cotato.team2.team2.controller.dto.ChatGPTResponse;
import com.cotato.team2.team2.controller.dto.ChatResponse;
import com.cotato.team2.team2.controller.dto.GPTResponse;
import com.cotato.team2.team2.domain.entity.User;
import com.cotato.team2.team2.service.component.UserCommonService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Service
@RequiredArgsConstructor
public class ChatService {

@Value("${openai.model}")
private String model;

@Value("${openai.api.url}")
private String apiURL;

private final UserCommonService userCommonService;

private final RestTemplate restTemplate;

public ChatResponse chat(String sessionKey, String message) {
User user = userCommonService.findBySessionKey(sessionKey);
GPTResponse response = chat(message);
return new ChatResponse(response.message());
}

public GPTResponse chat(String prompt) {
String processedPrompt = """
You're a chatbot talking to the user. You should answer all their questions and conversations in a slightly sarcastic and rude manner, but still be tsundere.
Here is the user message:
""" + prompt + """
Respond should be in korean.
Respond in the following JSON format:
{\s
"message": "Okay, what's bothering you? Go ahead and talk. I might help if there's something I can do, but don't get your hopes up."\s
}"
""";

ChatGPTRequest request = new ChatGPTRequest(model, processedPrompt);

ChatGPTResponse gptResponse = restTemplate.postForObject(apiURL, request, ChatGPTResponse.class);
ObjectMapper objectMapper = new ObjectMapper();
log.info("GPT Response: {}", gptResponse);
try {
return objectMapper.readValue(gptResponse.getChoices().get(0).getMessage().getContent(),
GPTResponse.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to parse GPT response", e);
}
}
}
8 changes: 7 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ spring:
connection-timeout: 5000
timeout: 5000
write timeout: 5000
auth-code-expiration-millis: 1800000
auth-code-expiration-millis: 1800000

openai:
api:
url: https://api.openai.com/v1/chat/completions
key: ${OPENAI_API_KEY}
model: gpt-3.5-turbo

0 comments on commit 6ae6562

Please sign in to comment.