SelfCheck_AI Troubleshooting_5 - AI API 수정 (Socket Hang up, CORS, 429 Error, Data Parsing) (26.02.02)

Meustar·2026년 2월 2일

Project

목록 보기
15/15

🔥 Issue 1: Socket Hang up (Timeout)

⚠️ 문제 상황

웹 브라우저에서 '첨삭 실행' 버튼을 누르면 약 1분 뒤 500 Internal Server Error와 함께 Error: socket hang up 로그가 발생하며 요청이 실패했다.

🔍 원인 분석

Next.js의 API Routes(pages/api/...)를 Proxy로 사용하여 백엔드(Spring Boot)에 요청을 보내고 있었는데, Next.js 서버의 기본 타임아웃(약 30~60초)보다 AI가 심사숙고하는 시간(약 90초 이상)이 더 길어서 연결이 강제로 끊긴 것이다.

✅ 해결 방법

중개자(Next.js Proxy)를 제거하고, 클라이언트(브라우저)에서 백엔드로 직접 요청(Direct Connection)을 보내도록 아키텍처를 변경했다. 또한 Axios 타임아웃 설정을 대폭 늘렸다.

// src/api/resumeApi.ts
const baseApi = axios.create({
    baseURL: "http://localhost:8080/api", // 백엔드 직통 주소
    timeout: 300000, // 5분(300s)으로 설정하여 AI 생성 시간 확보
    headers: { "Content-Type": "application/json" },
});

🔥 Issue 2: CORS Policy 위반

⚠️ 문제 상황

직통 연결로 바꾸자마자 브라우저 콘솔에 빨간색 Access to XMLHttpRequest ... blocked by CORS policy 에러가 떴다.

🔍 원인 분석

프론트엔드(localhost:3000)와 백엔드(localhost:8080)의 포트(Origin)가 달라서 브라우저의 보안 정책(SOP)에 의해 요청이 차단되었다.

✅ 해결 방법

Spring Boot 설정에 CorsFilter를 빈으로 등록하여 프론트엔드 오리진을 명시적으로 허용했다.

// WebConfig.java
@Bean
public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("http://localhost:3000"); // 프론트엔드 허용
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    // ...
}

🔥 Issue 3: Gemini API 404 & 429 Error

⚠️ 문제 상황

서버 시작 시 404 Not Found가 뜨거나, 몇 번 테스트하면 429 Too Many Requests가 발생하며 AI 기능이 마비되었다.

🔍 원인 분석

  1. 404: gemini-1.5-flash-latest 같은 별칭(Alias)이 API 버전에 따라 지원되지 않는 경우가 있었다.
  2. 429: 최신 모델(gemini-2.5)의 무료 쿼터가 매우 적어(분당 20회 미만), 3회 반복 검증 로직을 돌리기에 부족했다.

✅ 해결 방법

  • Auto-Fix: 서버 기동 시 MODEL_CANDIDATES 리스트를 순회하며 현재 연결 가능한 모델을 자동으로 찾도록 구현.
  • Retry Logic: 429 에러 발생 시 즉시 실패하지 않고, Exponential Backoff (2s -> 4s -> 8s) 방식으로 대기 후 재시도하도록 수정.

🔥 Issue 4: 프론트엔드 점수 0점 표시 & 데이터 파싱 오류

⚠️ 문제 상황

백엔드 로그에는 [1회차] 검증 점수: 92라고 찍히는데, 웹 화면에는 계속 0점으로 표시되고 첨삭 내용도 비어있었다. 브라우저 콘솔에는 Cannot read properties of undefined (reading 'map') 에러가 발생했다.

🔍 원인 분석

백엔드가 보내주는 JSON 구조와 프론트엔드가 참조하는 경로가 달랐다.

  • 백엔드: { score: {...}, results: [...] } (Flat 구조)
  • 프론트엔드: response.data.data.score (Nested 구조를 기대함)

✅ 해결 방법

프론트엔드 API 호출부에서 데이터를 받아 UI 컴포넌트가 원하는 형태로 변환해주는 어댑터(Adapter) 로직을 추가했다.

// resumeApi.ts
const backendData = response.data; // 실제 응답 데이터 (data 필드 없음)
const realData = backendData.data || {}; // 혹시 모를 래핑 대응

// 데이터 변환 (Adapter)
const adaptedData = {
    success: true,
    data: {
        score: realData.score || { jobFit: 0, ... }, // Null-Safe 처리
        feedback: realData.results || [] // 배열 매핑
    }
};

🔥 Issue 5: Java String Format Exception

⚠️ 문제 상황

AI가 답변을 생성하다가 갑자기 백엔드에서 java.util.UnknownFormatConversionException: Conversion = ''' 에러가 발생했다.

🔍 원인 분석

AI가 답변 내용 중에 "성능을 85% 개선했습니다"와 같이 퍼센트(%) 기호를 사용했는데, Java의 String.formatted() 메서드가 이 %를 포맷 지정자(format specifier)로 오인하여 파싱 에러를 일으킨 것이다.

✅ 해결 방법

AI에게 데이터를 전달할 때 String.formatted()를 사용하지 않고, UserMessage 객체에 직접 문자열을 담아 전송하는 방식으로 변경하여 특수문자 충돌을 원천 차단했다.

// 수정 전 (위험)
// String prompt = "Analyze this: %s".formatted(userData); 

// 수정 후 (안전)
messages.add(new UserMessage("Analyze this:\n" + userData));
profile
유튜브 기술 영상을 보면서 잘 이해하기 위해... Lilys AI를 활용해 배경지식, 영상 전체 요약 및 핵심 내용 설명들을 블로깅 합니다. 작성한 내용들에 대해서 언제고 다시 "내가" 찾아 볼 수 있도록 기록으로 남깁니다!

0개의 댓글