원래는 Chat GPT4.0 API를 사용하려 하였으나 승인이 나는데 시간이 오래 걸려 GPT3.5를 사용하였다.
java11
springboot 2.7.12
RestTemplate
Jackson
build.gradle에 아래와 같은 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
@Service
public class GPTService {
private static final String API_URL = "https://api.openai.com/v1/chat/completions";
private static final String API_KEY = "Key 값"; // 본인의 API 키를 사용하세요.
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
@Autowired
public GPTService(RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper) {
this.restTemplate = restTemplateBuilder.build();
this.objectMapper = objectMapper;
}
//DB에서 수질 정보 가져오는 메소드
public String getWaterInfo(String id) {
String url = "http://localhost:8080/waterinfo/yearly/" + id;
//
...
}
//DB에서 급이 기록 가져오는 메소드
public String getFoodRecord() {
String url = "http://localhost:8080/fish-info/food-record";
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
return response.getBody();
}
//DB에서 일출,일몰 시간 가져오는 메소드
public String getSunriseSunset() {
String url = "http://localhost:8080/weather/sunrise-sunset";
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
return response.getBody();
}
//질문하기 전에 GPT에 데이터 Input
public String prepareMessage(String waterInfo, String foodRecord, String sunriseSunset) {
return "Here is the water info for the year: " + waterInfo + "\n"
+ "Here is the feeding record: " + foodRecord + "\n"
+ "Here is the sunrise and sunset info for today: " + sunriseSunset + "\n";
}
//일출, 일몰 시간 String -> LocalTime으로 파싱
private SunriseSunsetData parseSunriseSunset(String sunriseSunset) {
// ...
}
//질의응답 매뉴얼
public String askGPT(String question) {
try {
// Data Input
String waterInfo = getWaterInfo("1");
String foodRecord = getFoodRecord();
String sunriseSunset = getSunriseSunset();
String preMessage = prepareMessage(waterInfo, foodRecord, sunriseSunset);
String modifiedQuestion = question.toLowerCase();
// 다음과 같은 질문을 포함한 경우의 답변 매뉴얼 생성
if (modifiedQuestion.contains("밥을 언제 줄까?")) {
SunriseSunsetData sunriseSunsetData = parseSunriseSunset(sunriseSunset);
if (sunriseSunsetData != null) {
LocalDateTime sunriseDateTime = sunriseSunsetData.getSunriseDateTime();
LocalDateTime sunsetDateTime = sunriseSunsetData.getSunsetDateTime();
// 밥 주는 시간 계산
// 밥 주는 시간을 문자열로 포맷팅
// 응답에 밥 주는 시간 추가하기
preMessage += "광어에게 밥을 줄 시간은 아침: " + formattedFeedingTime1 + ", 저녁: " + formattedFeedingTime2 + "입니다.";
} else {
// 오류 처리 로직 추가
}
} else if( // 조건)
// 답변에 포함시킬 내용
// ...
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + API_KEY);
List<Map<String, String>> messages = new ArrayList<>();
// System message with preMessage
Map<String, String> systemMessage = new HashMap<>();
systemMessage.put("role", "system");
systemMessage.put("content", preMessage);
messages.add(systemMessage);
// User message with the question
Map<String, String> userMessage = new HashMap<>();
userMessage.put("role", "user");
userMessage.put("content", question);
messages.add(userMessage);
// 답변 가능한 최대 글자수, 사용 모델 설정
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("messages", messages);
requestBody.put("max_tokens", 500);
requestBody.put("model", "gpt-3.5-turbo");
HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers);
ResponseEntity<String> response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class);
if (response.getStatusCode() == HttpStatus.OK) {
System.out.println("Response: " + response.getBody());
Map<String, Object> responseBody = objectMapper.readValue(response.getBody(), Map.class);
if (responseBody.containsKey("choices")) {
List<Map<String, Object>> choices = (List<Map<String, Object>>) responseBody.get("choices");
if (choices != null && !choices.isEmpty() && choices.get(0).containsKey("message")) {
Map<String, Object> messageResponse = (Map<String, Object>) choices.get(0).get("message");
if (messageResponse.containsKey("content")) {
String answer = (String) messageResponse.get("content");
return answer.trim();
}
}
}
} else {
throw new RuntimeException("ChatGPT API 요청 실패: " + response.getStatusCode());
}
} catch (Exception e) {
throw new RuntimeException("ChatGPT API 호출 중 오류 발생", e);
}
throw new RuntimeException("ChatGPT API 호출에서 예상치 못한 상황 발생");
}
}
@CrossOrigin
@RestController
@RequestMapping("/gpt")
public class GPTController {
// Service의 class file 명 : GPTService
private final GPTService gptService;
@Autowired
public GPTController(GPTService gptService) {
this.gptService = gptService;
}
@PostMapping("/chat/completions")
public String askGPT(@RequestBody String question) {
return gptService.askGPT(question);
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GPT Chat</title>
<style>
// CSS 생략
</style>
</head>
<body>
<div class="container">
<div class="header">
<img class="logo" src="https://upload.wikimedia.org/wikipedia/commons/0/04/ChatGPT_logo.svg">
<h2 class="title">GPT 질문 내역</h2>
</div>
<div class="input-container">
<input type="text" id="question-input" class="input-field" placeholder="질문을 입력하세요">
<button id="question-button" class="button">질의문 버튼</button>
</div>
<div class="header">
<img class="logo" src="https://upload.wikimedia.org/wikipedia/commons/0/04/ChatGPT_logo.svg">
<h2 class="title">GPT 답변 내역</h2>
</div>
<div id="answer-container" class="answer-container"></div>
</div>
<script>
//...
</script>
</body>
</html>
<script>
// 질의문 버튼 클릭 이벤트 핸들러
document.getElementById('question-button').addEventListener('click', async () => {
const questionInput = document.getElementById('question-input');
const question = questionInput.value.trim();
questionInput.value = '';
if (question) {
const response = await fetch('http://localhost:8080/gpt/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
question: question
})
});
if (response.ok) {
const answer = await response.text();
const answerContainer = document.getElementById('answer-container');
answerContainer.innerHTML = answer;
} else {
console.error('Failed to fetch GPT answer');
}
}
});
</script>
내가 예상한 넙치의 먹이 활동이 활발한 시간은 일몰, 일출시간 30분 전인데 GPT는 일출 후 1~2시간 후와 일몰 직전을 먹이 활동이 활발하다고 판단하였다.
나는 GPT에게 내가 계획한 시간 대에 급이를 하라는 답변 매뉴얼을 설정하였으나 그와는 다른 답변을 도출한다.
내가 예상한 넙치의 먹이 활동이 활발한 시간은 넙치 양식장에서 급이를 하는 시간과 낚시 카페에서 넙치가 잘 잡힌다는 시간을 비교하여 추정한 수치이기에 정확하지 않을 수 있으나 GPT의 답변 기준은 무엇을 기준으로 한 것인지 알 수 없다.