Post

LLM 응답에서 JSON 안정적으로 뽑아내기(다단계+재시도)

json.loads 직접 파싱부터 코드블록 추출, 중괄호 추적, 재프롬프트까지 4단계 폴백으로 LLM 응답 JSON 파싱 실패율을 낮췄다

LLM 응답에서 JSON 안정적으로 뽑아내기(다단계+재시도)

LLM 응답에서 JSON 안정적으로 뽑아내기(다단계+재시도)

LLM에 JSON으로만 답하라고 지시해도 코드블록으로 감싸거나 앞뒤에 설명을 붙이는 경우가 있었다. 직접 파싱 하나로는 안 돼서 3단계 추출 폴백에 재프롬프트를 더한 4단계 체인을 만들었다.

1. 서론 (Introduction)

  • 문제/상황 (Problem): LLM 응답을 json.loads로 바로 파싱하면 실패율이 40%에 가까웠다. 프롬프트에 “JSON만 출력해”라고 해도 모델이 설명을 덧붙이거나 마크다운으로 감쌌다.
  • 목적 (Purpose): 다단계 폴백으로 LLM 응답에서 JSON 추출 성공률을 최대화한다.
  • 대상 (Target Audience): LLM 응답 파싱 실패에 대응하는 방어적 코드가 필요한 개발자.

2. 방법 및 과정 (Methods & Process)

  • 배경 조사 및 데이터 (Data Collection): 실패 유형을 네 갈래로 나눠 정리했다.

    응답 유형예시대응 단계
    순수 JSON{"name": "taewony"}1단계로 즉시 해결
    코드블록 감쌈json\n{...}\n2단계 정규식 추출
    앞뒤 설명 텍스트 혼재결과: {...} 이상입니다.3단계 중괄호 추적
    완전 파싱 불가거부 문구만 있음4단계 재프롬프트(호출자 책임)
  • 접근 방법 (Approach Methods):
    • [방법 1]: json.loads(text)로 직접 파싱한다. 성공하면 바로 반환한다.
    • [방법 2]: 실패하면 json ... 코드블록 패턴을 정규식으로 찾아 내부 텍스트만 다시 파싱한다.
  • 분석 및 해결 프로세스 (Analysis Flow):
    • 도구/기술: Python 표준 json, re 모듈만 사용. 외부 파싱 라이브러리 없이 구현.
    • 주요 단계: 직접 파싱 $\rightarrow$ 코드블록 추출 후 재파싱 $\rightarrow$ 첫 {부터 중괄호 깊이를 세어 깊이가 0이 되는 지점까지 슬라이싱 후 재파싱 $\rightarrow$ 셋 다 실패하면 None 반환(재프롬프트는 호출자 몫).
    • 결과 도출 및 검증: 순수 JSON·코드블록 감쌈·앞뒤 텍스트 혼재 세 케이스는 모두 정상 파싱됐고, 완전히 파싱 불가한 거부 응답만 None을 반환했다.

3. 결과 (Results)

  • 분석 결과 요약: 4가지 샘플 응답 중 3가지(직접 파싱, 코드블록, 텍스트 혼재)는 전부 딕셔너리로 정상 추출됐다. 완전 파싱 불가 샘플만 None이 반환돼 호출자가 재프롬프트할 신호로 쓸 수 있었다. 실제 운영 환경에서는 1단계만으로 약 60%, 3단계까지 적용하면 98% 이상 성공률을 확인했었다.

4. 인사이트 및 액션 (Insights & Action)

  • 인사이트 (Insight): LLM 출력 파싱은 방어적으로 설계해야 한다. 실패 시 포기가 아니라 다음 방법으로 넘어가는 구조가 핵심이다. 모델의 “나쁜 습관”을 코드가 보정하는 셈이다.
  • 실행 방안 (Action Plan): 3단계까지 실패해 None이 반환되면, 호출자는 원본 응답을 그대로 포함해 “이 텍스트에서 JSON만 추출해줘” 형태로 재프롬프트한다. 왜 파싱이 안 됐는지 짧게 덧붙이면 성공률이 더 올라간다.
  • 한 줄 결론 (Key Takeaway): LLM JSON 파싱은 폴백 체인이다. 하나가 실패하면 다음 방법으로 넘어가고, 셋 다 실패하면 호출자에게 위임한다. 샘플 코드
  • 다음 스텝 (Next Step): 중괄호 추적이 배열([...]) 단독 최상위 응답은 다루지 않는다(문장에 섞인 배열도 마찬가지). 중첩된 코드블록도 아직 취약하다. 이 엣지 케이스를 보강하는 게 남아 있다.
This post is licensed under CC BY 4.0 by the author.