본문으로 건너뛰기
AIPida
채택됨초급AI 코딩

LLM 출력 JSON 파싱이 가끔씩 깨지는데(코드펜스/설명문) 어떻게 안정적으로 받나요?

GPT-4o로 사용자 리뷰를 분류해서 JSON으로 받고 있습니다. 프롬프트에 "JSON만 출력해"라고 했는데도 100건 중 2~3건은 ```json 코드펜스로 감싸거나, 앞에 "여기 결과입니다:" 같은 설명을 붙여서 JSON.parse가 터집니다.

지금은 정규식으로 코드펜스 벗기고 try/catch로 막고 있는데 임시방편 같아요. 프로덕션에서 도는 거라 더 견고하게 하고 싶습니다. 다들 어떻게 처리하시나요?

답변 3

  • 채택된 답변에디터 검증

    정규식으로 코드펜스 벗기는 건 모델이 형식 바꾸면 또 깨지니까 근본 해결 아닙니다. 모델 차원의 구조화 출력을 쓰세요.

    OpenAI는 Structured Outputs가 있어요. response_format에 JSON Schema 주면 모델이 그 스키마 어기는 토큰을 아예 못 만들게 제약(constrained decoding)이 걸립니다. 코드펜스나 "여기 결과입니다:" 같은 인사말이 붙을 수가 없어져요. 단순 JSON 모드({"type":"json_object"})보다 스키마 강제가 훨씬 셉니다.

    스키마는 손으로 쓰지 말고 Zod로 정의한 다음 zod-to-json-schema로 변환하거나 OpenAI SDK 헬퍼 쓰면 파싱 결과 타입까지 보장돼요.

    그리고 스키마 강제 걸어도 받은 뒤에 Zod safeParse 한 번 더 태우는 검증 레이어는 항상 두세요. 구조는 맞는데 값이 enum 범위 벗어나는 경우가 가끔 있어서, 그때 재시도/폴백 경로가 필요합니다.

  • 모델 기능 다 켜도 저는 방어 코드 꼭 같이 둡니다. 외부 API라 언제 형식이 미묘하게 바뀔지 모르거든요. 3단으로 막아요:

    • 받은 문자열에서 첫 {와 마지막 } 사이만 잘라내기 (코드펜스/설명문 동시 제거)
    • JSON.parse 실패하면 같은 입력으로 1회 재시도
    • 두 번 실패하면 raw 응답을 로그에 남기고 그 건만 격리

    이러면 2~3% 실패가 거의 0으로 떨어집니다. 핵심은 실패한 raw를 버리지 말고 꼭 로깅해서 패턴 보는 거. 그거 보면 "아 얘는 특정 카테고리에서만 깨지네" 같은 게 보여요.

  • 에디터 검증

    Claude 쓰면 접근이 좀 다릅니다. response_format 같은 강제 스키마 모드 대신 **tool use(함수 호출)**로 구조화 출력 받는 게 정석이에요. JSON 스키마 가진 도구 하나 정의하고 tool_choice로 그 도구를 강제하면 모델이 스키마에 맞는 input을 채워서 호출합니다. 텍스트 본문에 설명 섞을 여지가 없어져요.

    assistant 메시지를 { 로 prefill해서 시작 토큰 강제하는 방법도 있긴 한데, tool use 쪽이 훨씬 깔끔하고 검증도 쉬워서 저는 이거 씁니다.