SelfCheck_AI 8. 단일 항목에서 다중 항목으로: 자기소개서 첨삭 API의 진화 (26.01.26)

Meustar·2026년 1월 26일

Project

목록 보기
10/15

작성일: 2026. 01. 26
한 줄 요약: API 구조를 List 형태로 리팩토링하고, AI에게 '종합 평가'와 '개별 첨삭'을 동시에 수행시키는 로직 구현


📅 Today's Goal

기존의 API는 한 번에 하나의 질문과 답변({q, a})만 처리할 수 있었다. 하지만 실제 취업 현장에서 자기소개서는 보통 지원 동기, 성격의 장단점, 직무 경험, 입사 후 포부 등 3~5개의 항목으로 구성된다.

사용자가 한 항목씩 요청을 보내고 기다리는 것은 UX 관점에서 좋지 않다. 따라서 오늘은 여러 개의 자기소개서 항목을 한 번에 요청받아, AI가 전체를 관통하는 종합 점수를 매기고, 각 항목별로 디테일한 수정안을 제공하는 기능을 구현하는 것이 목표다.

이 작업은 기존 API의 Request/Response 스펙을 완전히 뜯어고치는 Breaking Change이므로, 설계부터 구현까지 꼼꼼한 접근이 필요했다.


1. API 설계 변경 (Breaking Change)

가장 먼저 클라이언트(프론트엔드)와 서버가 주고받을 데이터 규격(Protocol)을 재정의했다.

1-1. Request Body 구조 변경

기존에는 단순 객체였으나, 이제는 유동적인 개수의 항목을 받기 위해 items라는 리스트 래퍼(Wrapper) 구조를 도입했다. 무제한 입력을 막기 위해 최대 5개까지의 제약 조건을 걸었다.

Before (AS-IS)

{
  "question": "지원 동기는?",
  "answer": "..."
}

After (TO-BE)

{
  "items": [
    {
      "question": "지원 동기",
      "answer": "..."
    },
    {
      "question": "성격의 장단점",
      "answer": "..."
    }
  ]
}

1-2. Response Body 구조 변경

응답 역시 대폭 업그레이드했다. 단순히 "잘 썼네요"라는 코멘트보다는, "이렇게 고쳐보세요"라고 구체적인 대안을 제시하는 것이 사용자에게 훨씬 유용하다. 따라서 revisedAnswer(수정된 답변) 필드를 추가했다.

또한, 점수(score)는 개별 항목 점수가 아닌, 제출된 모든 항목을 종합하여 평가한 4가지 지표로 정의했다.

  • 직무 적합성 (Job Fit)
  • 문제 해결력 (Problem Solving)
  • 성장 가능성 (Growth Potential)
  • 의사소통 (Communication)

2. DTO 구현 (Data Transfer Object)

설계된 내용을 바탕으로 Java 코드를 작성했다. LombokJakarta Validation을 적극 활용하여 코드를 간결하게 유지했다.

ResumeRequest.java

리스트 처리를 위해 내부 정적 클래스(ResumeItem)를 정의하고, @Size 어노테이션으로 비즈니스 제약 사항(1~5개)을 검증하도록 했다.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResumeRequest {

    @Valid
    @Size(min = 1, max = 5, message = "질문 항목은 최소 1개에서 최대 5개까지 가능합니다.")
    private List<ResumeItem> items; // 리스트 구조로 변경

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ResumeItem {
        @NotBlank(message = "질문은 필수입니다.")
        private String question;

        @NotBlank(message = "답변 내용은 필수입니다.")
        private String answer;
    }
}

ResumeResponse.java

AI의 분석 결과는 크게 '점수(Map)''결과 리스트(List)'로 나뉜다. revisedAnswer 필드를 통해 AI가 직접 첨삭(Re-write)한 내용을 담는다.

@Getter
@Builder
public class ResumeResponse {
    // 4대 평가 항목 점수 (1:직무, 2:문제해결, 3:성장, 4:소통)
    private Map<String, Integer> score;

    // 항목별 상세 분석 및 수정안
    private List<ResumeResultItem> results;

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ResumeResultItem {
        private String question;
        private String revisedAnswer; // AI가 보완하여 재작성한 답변
        private String comment;       // 수정 이유 및 조언
    }
}

3. Service Layer 비즈니스 로직

Controller에서 받은 요청을 처리하는 processResume 메서드도 반복문 로직으로 변경되었다.

  1. DB 저장: 요청받은 모든 항목(items)을 순회하며 DB에 저장한다. (추후 히스토리 관리를 위함)
  2. AI 요청: 리스트 전체를 AiClient에 넘겨, AI가 문맥을 파악하고 일괄 처리하도록 한다.
@Override
public ResumeResponse processResume(ResumeRequest request) {
    log.info("요청 수신 - 항목 수: {}", request.getItems().size());

    // 1. DB 저장 (Loop 처리)
    // 실제 운영 환경에서는 Batch Insert로 최적화 고려 필요
    try {
        for (ResumeRequest.ResumeItem item : request.getItems()) {
            Map<String, Object> params = new HashMap<>();
            params.put("userId", 1L); // 임시 User ID
            params.put("question", item.getQuestion());
            params.put("content", item.getAnswer());
            resumeMapper.saveResume(params);
        }
    } catch (Exception e) {
        log.error("DB 저장 실패 (계속 진행): ", e);
    }

    // 2. AI 분석 요청 (리스트 전체 전달)
    return aiClient.analyzeResume(request.getItems());
}

4. AI Prompt Engineering (핵심 로직) 🔥

오늘 개발의 하이라이트는 프롬프트 설계였다.
단순히 "이거 분석해줘"라고 하면, AI는 입력된 질문 개수만큼만 점수를 주거나, 형식을 멋대로 바꿀 위험이 있다.

따라서 System Prompt에 매우 강력한 제약 사항(Constraint)을 걸었다.

주요 프롬프트 전략

  1. 역할 부여 (Persona): "20년 경력의 IT 인사 담당자"
  2. 입력 처리: List<ResumeItem>을 JSON 문자열로 변환하여 주입.
  3. 점수 산정 로직 (Strong Typing):
  • 입력된 질문이 1개든 5개든 상관없이, 점수는 무조건 미리 정의된 4가지 카테고리로만 평가하도록 강제했다.
  • JSON Key 값("1", "2", ...)이 입력 순서가 아니라 카테고리 ID임을 명시했다.
  1. 출력 일관성:
  • results 리스트는 입력된 질문의 개수(N)와 정확히 일치하도록 지시했다.

[실제 적용된 프롬프트 일부]

[작업 1: 종합 평가 점수 산정]
제출된 모든 항목을 통틀어, 지원자의 역량을 아래 4가지 기준으로 평가하여 100점 만점으로 점수를 매기세요.
**주의: 입력된 항목의 개수와 상관없이, 반드시 아래 4가지 카테고리 각각에 대한 점수를 산출해야 합니다.**

- 카테고리 1: 직무 적합성 (Java 백엔드 개발자 기준)
- 카테고리 2: 문제 해결력
- 카테고리 3: 성장 가능성
- 카테고리 4: 의사소통

[작업 2: 항목별 상세 첨삭]
**사용자가 입력한 질문/답변이 N개라면, results 리스트에도 정확히 N개의 첨삭 결과가 있어야 합니다.**
순서는 입력된 순서를 유지하세요.

이 프롬프트 덕분에 AI는 질문이 몇 개가 들어오든 헷갈리지 않고 '종합 점수 4개''개별 첨삭 N개'를 정확한 JSON 포맷으로 반환하게 되었다.


5. Git 전략 및 협업

이번 변경은 API 스펙이 변하는 중요한 작업이었기 때문에 develop 브랜치에서 직접 작업하지 않고 Feature Branch 전략을 사용했다.

  1. feature/resume-api-update 브랜치 생성
  2. DTO 변경, Service 로직 수정, 프롬프트 개선 작업 진행
  3. 기능 단위로 명확하게 커밋 (Feat, Fix)
  4. 테스트 완료 후 develop 브랜치로 Merge

이 과정을 통해 운영 중인 코드에 영향을 주지 않고 안전하게 대규모 리팩토링을 마칠 수 있었다.


🏁 마무리

오늘 작업을 통해 SelfCheck AI는 단순한 '검사기'를 넘어, 실제 입사 지원 시나리오에 맞는 '종합 컨설턴트'의 모습을 갖추게 되었다.

특히 List 형태의 데이터를 다루면서 AI가 문맥(Context)을 놓치지 않도록 프롬프트를 정교하게 다듬는 과정에서 많은 것을 배웠다. 다음 단계는 이 프롬프트를 더 고도화하여, 지원자의 답변 스타일(톤앤매너)까지 분석하는 기능을 고민해봐야겠다.

profile
유튜브 기술 영상을 보면서 잘 이해하기 위해... Lilys AI를 활용해 배경지식, 영상 전체 요약 및 핵심 내용 설명들을 블로깅 합니다. 작성한 내용들에 대해서 언제고 다시 "내가" 찾아 볼 수 있도록 기록으로 남깁니다!

0개의 댓글