Algorithm Enum에 있는 알고리즘 문제들을 매일 0시 0분에 초기화하여 선택 → 이 때, 선택에 따른 날짜를 기록해두어 최신순으로 하나씩 선택하도록 구현
알고리즘 서비스에서 해당 키워드를 가지고 Prompt 생성
Chat GPT LLM에 프롬프트 전달 후, 가져온 반환값을 가지고 또 다시 응답 파싱 수행
Client에게 응답값 반환
yaml 정의를 통해 openai gpt
4.5
모델 활용
...
openai:
model: "gpt-4.5"
api:
key: "secrets"
url: "<https://api.openai.com/v1/chat/completions>"
...
Prompt를 생성하여 알맞은 응답 요청
@Data
public class ChatGPTRequest {
private String model;
private List<Message> messages;
public ChatGPTRequest(String model, String prompt) {
this.model = model;
this.messages = new ArrayList<>();
this.messages.add(new Message("user", prompt));
}
public static ChatGPTRequest createCodingTestPrompt(String algorithm, String model) {
String prompt = String.format("Please provide 3 coding test questions that fit the subject of the %s on the public platform. Each problem should have a title, difficulty (Easy, Medium, Hard), kind, and a link to the problem. Pick one question for each level of difficulty. kind는 문제 사이트의 이름을 의미 해. Choose a random question from the sites on <https://programmers.co.kr/>, <https://www.acmicpc.net/> . description은 주지마. Title 줄 때 따옴표는 제거해 줘, Easy/Medium/Hard 순서대로 문제 출력해, *표시는 출력하지 않도록 한다. kind는 주소 형식으로 주지 말고 사이트 이름으로 줘 (예를 들면, 'Baekjoon' 이렇게)", algorithm);
return new ChatGPTRequest(model, prompt);
}
public static ChatGPTRequest createAIAnaliztionTestPrompt(TilAlgorithmDto tilAlgorithmDto, String model) {
String prompt = String.format("다음은 \\"Algorithm 이름\\"과 그에 해당하는 성공 횟수입니다. 이 데이터를 바탕으로 각 알고리즘 카테고리에서 강점과 개선이 필요한 부분을 분석해주세요. 부족한 향상시키기 위한 학습 권장 알고리즘도 함께 제시해주세요.%s\\n" +
"두 줄로 요약해서 출력해 줘", tilAlgorithmDto.toString());
return new ChatGPTRequest(model, prompt);
}
public static ChatGPTRequest createAIRecommendTestPrompt(TilAlgorithmDto tilAlgorithmDto, String model) {
String prompt = String.format("Algorithm 이름과 성공 횟수 데이터를 바탕으로 각 알고리즘 카테고리에서 부족한 알고리즘 관련 문제를 추천해 주세요. " +
"%s 주제에 맞는 코딩 테스트 문제를 공개 플랫폼에서 1개 추천해 주세요. 문제는 제목, 사이트 종류, 링크만 제공해 주세요. " +
"프로그래머스(<https://programmers.co.kr/>) 또는 백준(<https://www.acmicpc.net/>)에서 임의의 문제를 선택해 주세요. " +
"Title, Kind, Link 외에는 모든 출력값을 제거해 주세요. " +
"* 표시도 제거해 주세요.", tilAlgorithmDto.toString());
return new ChatGPTRequest(model, prompt);
}
}
String Split등 문자열 처리를 통해 원하는 형식으로 알맞게 정규화
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatGPTResponse {
private List<Choice> choices;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Choice {
private Message message;
}
public String getMessage(){
return choices.get(0).getMessage().getContent();
}
public List<ChallengeTilGPTDto> extractCodingTestProblems() {
List<ChallengeTilGPTDto> results = new ArrayList<>();
for (Choice choice : choices) {
String content = choice.getMessage().getContent();
System.out.println("content : \\n " + content + "\\n-------");
// 개행을 기준으로 잘라
String[] lines = content.split("\\n");
// 각 문제를 배열로 만듬
String[] first = {lines[0].substring(3), lines[1], lines[2], lines[3]};
String[] second = {lines[5].substring(3), lines[6], lines[7], lines[8]};
String[] third = {lines[10].substring(3), lines[11], lines[12], lines[13]};
List<String[]> parts = new ArrayList<>();
parts.add(first);
parts.add(second);
parts.add(third);
System.out.println("배열 출력:");
System.out.println(Arrays.toString(first));
System.out.println(Arrays.toString(second));
System.out.println(Arrays.toString(third));
for (String[] part : parts) {
Map<String, String> valueMap = new HashMap<>();
for (String partValue : part) {
int splitIndex = partValue.indexOf(":");
// ':'이 없으면 해당 줄을 건너뜀
if (splitIndex == -1) {
continue;
}
String value1 = partValue.substring(0, splitIndex).trim();
String value2 = partValue.substring(splitIndex + 1).trim();
valueMap.put(value1, value2);
System.out.println(String.format("value1 : %s , value2 : %s", value1, value2));
}
ChallengeTilGPTDto dto = new ChallengeTilGPTDto();
dto.setLevel(valueMap.get("Difficulty"));
dto.setSite(valueMap.get("Link"));
dto.setSiteKinds(valueMap.get("Kind"));
dto.setTitle(valueMap.get("Title"));
results.add(dto);
}
}
System.out.println("추출된 결과 크기 : " + results.size());
return results;
}
}
@Scheduled
어노테이션을 통해 매일 정각에 challenge가 발행되도록 구현
// 매일 정각에 createChallenge 실행
@Scheduled(cron = "0 0 0 * * *")
public void scheduleDailyChallengeCreation() {
createChallenge(); // 매일 자정에 createChallenge 메서드 실행
}
@Transactional
public void createChallenge() {
// 1) 알고리즘 DB에서 오래된 순으로 정렬 후, 제일 오래된 값을 가져온다.
Algorithm algorithm = algorithmRepository.findFirstByOrderByUsedAtDesc();
// 2) ChatGPT API 요청 생성 ( algorithm + model )
ChatGPTRequest request = ChatGPTRequest.createCodingTestPrompt(
algorithm.getEngClassification(), model);
// 3) ChatGPT API 호출하여 응답 받기
ChatGPTResponse chatGPTResponse = template.postForObject(apiURL, request,
ChatGPTResponse.class);
// 4) algorithm userAt 업데이트
algorithm.updateUsedAt();
// 5) Challenge entity
Challenge challenge = Challenge.builder()
.title(String.format("%s 알고리즘", algorithm.getEngClassification()))
.createdAt(LocalDateTime.now())
.algorithm(algorithm.getEngClassification())
.views(0L)
.build();
// 6) Challenge entity를 save
Challenge savedChallenge = challengeRepository.save(challenge);
// 6) 3개의 ChallengeTil을 만들어야 해.
List<ChallengeTilGPTDto> problems = chatGPTResponse.extractCodingTestProblems();
for (ChallengeTilGPTDto problem : problems) {
// 7) Challenge Til entity 를 만들어줬다.
Problem challengeTil = Problem.builder()
.title(problem.getTitle())
.challengeId(savedChallenge.getId())
.level(problem.getLevel())
.site(problem.getSite())
.siteKinds(problem.getSiteKinds())
.createdAt(LocalDateTime.now())
.build();
// 8) challenge til repository에 저장
problemRepository.save(challengeTil);
}
}