도식화

Algorithm Enum에 있는 알고리즘 문제들을 매일 0시 0분에 초기화하여 선택 → 이 때, 선택에 따른 날짜를 기록해두어 최신순으로 하나씩 선택하도록 구현

알고리즘 서비스에서 해당 키워드를 가지고 Prompt 생성

Chat GPT LLM에 프롬프트 전달 후, 가져온 반환값을 가지고 또 다시 응답 파싱 수행

Client에게 응답값 반환

image.png

기능 구현

application.yaml

yaml 정의를 통해 openai gpt 4.5 모델 활용

...

openai:
  model: "gpt-4.5"
  api:
    key: "secrets"
    url: "<https://api.openai.com/v1/chat/completions>"
    
...

ChatGPT Request

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);
    }

}

ChatGPT Response

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;
    }

}

Challenge Service

@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);
    }

}