SelfCheck_AI Troubleshooting_3 (26.01.26)

Meustar·2026년 1월 26일

Project

목록 보기
11/15

작성일: 2026. 01. 26
한 줄 요약: API 구조 변경 시 겪은 JSON 매핑 오류와, AI가 점수 키(Key)를 헷갈리는 문제를 프롬프트로 해결한 과정


🛑 Intro

오늘 SelfCheck AI 서비스의 핵심 기능을 '단일 항목 첨삭'에서 '다중 항목 일괄 첨삭'으로 업그레이드하는 작업을 진행했다.
코드만 고치면 금방 끝날 줄 알았지만, 예상치 못한 곳에서 500 에러(NPE)가 터졌고, AI는 내 의도와 다르게 동문서답을 했다.

오늘 겪은 두 가지의 굵직한 에러와 그 해결 과정을 기록으로 남긴다.


🚨 Issue 1. "DTO는 멀쩡한데 왜 Null이지?" (NPE)

1. 문제 상황

API 리팩토링 후 서버를 띄우고 Postman으로 테스트 요청을 보내자마자, 콘솔에 빨간색 에러 로그가 가득 찼다.

java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because the return value of "selfcheck_ai.resume.dto.ResumeRequest.getItems()" is null
    at selfcheck_ai.resume.service.ResumeServiceImpl.processResume(ResumeServiceImpl.java:25)
    at selfcheck_ai.resume.controller.ResumeController.createResume(ResumeController.java:28)

서비스 로직인 request.getItems().size() 부분에서 itemsnull이라며 죽어버린 것.

2. 원인 분석

가설 1: DTO에 Setter가 없나?
가장 먼저 의심한 것은 Lombok 설정이었다. items 필드에 값이 주입되지 않았으니, @Getter만 있고 @Setter@Data가 없어서 Jackson 라이브러리가 값을 못 넣은 게 아닐까?

// ResumeRequest.java 확인
@Data // @Getter, @Setter, @RequiredArgsConstructor 등 포함
public class ResumeRequest {
    private List<ResumeItem> items;
    ...
}

확인 결과 @Data가 잘 붙어 있었다. DTO 코드에는 문제가 없었다.

가설 2: 요청 데이터(Payload)가 잘못되었나?
로그를 다시 보니, 내가 보내고 있는 JSON 데이터의 모양이 문제였다.
백엔드 코드는 items라는 리스트를 받도록 수정했는데, 정작 요청은 구버전 API 스펙 그대로 보내고 있었던 것이다.

❌ 내가 보낸 요청 (Wrong)

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

백엔드는 items라는 키를 기다리고 있는데, 엉뚱한 question 키가 들어오니 매핑을 못 하고 itemsnull로 둔 채 넘어가 버린 것이다.

3. 해결

Step 1: 요청 JSON 수정
API 스펙에 맞춰 JSON 구조를 변경했다.

✅ 수정된 요청 (Correct)

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

Step 2: 예외 처리 강화
단순히 클라이언트가 잘 보내길 기대하기보다, JSON 변환 과정에서 문제가 생기면 명확한 에러 메시지를 뱉도록 ErrorCode를 추가했다.

// AiClient.java
try {
    userMessageText = objectMapper.writeValueAsString(items);
} catch (JsonProcessingException e) {
    throw new BusinessException(ErrorCode.JSON_PROCESSING_ERROR);
}

🚨 Issue 2. AI의 환각 (Hallucination) - "점수가 왜 2개뿐이야?"

1. 문제 상황 (Symptoms)

NPE를 해결하고 드디어 AI가 응답을 줬다. 그런데 응답 내용을 뜯어보니 이상했다.
분명히 평가 기준은 4가지(직무 적합성, 문제 해결력, 성장 가능성, 의사소통)로 정해두었는데, 질문을 2개(items: [Q1, Q2]) 보냈더니 점수도 2개만 왔다.

😱 AI의 이상한 응답

"score": {
    "1": 60,  // ??? 직무 적합성 점수여야 하는데...
    "2": 70,  // ??? 문제 해결력 점수여야 하는데...
    "3": 0,   // 0점?
    "4": 0    // 0점?
}

심지어 3번, 4번 항목은 아예 평가를 안 했다.

2. 원인 분석

"AI는 맥락(Context)을 오해했다."

프롬프트에서 "1": 점수, "2": 점수 라고 JSON 예시를 보여줬더니, AI는 이 숫자를 '평가 카테고리 번호'가 아니라 '입력된 질문의 순서(Index)'로 해석해버린 것이다.

  • 입력 질문 1개 → "1": 점수 생성
  • 입력 질문 2개 → "1": 점수, "2": 점수 생성

이것은 전형적인 Overfitting(과적합) 현상이다. 프롬프트가 모호하면 AI는 가장 쉬운 방법(입력 개수 = 출력 개수)으로 추론해버린다.

3. 해결 : Prompt Engineering

AI에게 "Strong Typing"을 적용했다. 개발자가 코드에 타입을 명시하듯, 프롬프트에도 각 숫자가 무엇을 의미하는지 '절대 규칙'을 박아넣었다.

수정된 프롬프트 (AiClient.java)

String systemMessageText = """
    ...
    [작업 1: 종합 평가]
    **주의: 입력된 항목의 개수와 상관없이, 반드시 아래 4가지 카테고리 각각에 대한 점수를 산출해야 합니다.**
    
    [출력 형식 가이드]
    JSON의 Key "1", "2", "3", "4"는 입력 순서가 아닙니다. 
    아래 정의된 **평가 카테고리의 고유 ID**입니다.
    - "1": 직무 적합성
    - "2": 문제 해결력
    ...
    
    [작업 2: 상세 첨삭]
    사용자가 입력한 질문이 N개라면, results 리스트도 정확히 N개를 반환하세요.
""";

✅ 결과 (Correct Response)
이제 질문을 2개 보내든 5개 보내든, score 객체는 항상 4개의 평가 기준을 꽉 채워서 보내주고, results 리스트는 입력 개수에 맞춰서 정확하게 반환하게 되었다.


💡 Lessons Learned (배운 점)

1. 에러 로그는 거짓말을 하지 않는다.

NPE가 났을 때 코드를 의심하기 전에 "내가 무엇을 입력했는가"를 먼저 확인했어야 했다. DTO 구조를 바꿨다면 Payload 구조도 당연히 바꿨어야 했는데, 관성적으로 테스트하다가 시간을 허비했다.

2. 프롬프트는 '개발 명세서'다.

AI에게 "적당히 잘 해줘"라고 하면 "적당히 엉망으로" 해준다.

  • "N개면 N개를 줘라"
  • "이 숫자는 인덱스가 아니라 ID다"

이렇게 유치원생에게 설명하듯(혹은 컴파일러에게 타입 힌트를 주듯) 아주 구체적이고 명확한 제약 조건을 걸어야 원하는 퀄리티의 결과를 얻을 수 있음을 깨달았다. Prompt Engineering은 이제 선택이 아니라 필수 역량이다.

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

0개의 댓글