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 ...코드블록 패턴을 정규식으로 찾아 내부 텍스트만 다시 파싱한다.
- [방법 1]:
- 분석 및 해결 프로세스 (Analysis Flow):
- 도구/기술: Python 표준
json,re모듈만 사용. 외부 파싱 라이브러리 없이 구현. - 주요 단계: 직접 파싱 $\rightarrow$ 코드블록 추출 후 재파싱 $\rightarrow$ 첫
{부터 중괄호 깊이를 세어 깊이가 0이 되는 지점까지 슬라이싱 후 재파싱 $\rightarrow$ 셋 다 실패하면None반환(재프롬프트는 호출자 몫). - 결과 도출 및 검증: 순수 JSON·코드블록 감쌈·앞뒤 텍스트 혼재 세 케이스는 모두 정상 파싱됐고, 완전히 파싱 불가한 거부 응답만
None을 반환했다.
- 도구/기술: Python 표준
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.