LLM이 계속해서 깨진 JSON을 반환하는 이유와 실제 해결 방법
요약
LLM이 JSON 형식을 완벽하게 준수하지 못해 발생하는 프로덕션 환경의 문제점과 이를 해결하기 위한 '제약된 디코딩(Constrained Decoding)' 기술을 설명합니다. 프롬프트 엔지니어링이나 후처리 방식의 한계를 지적하며, 토큰 샘플링 단계에서 문법을 적용해 유효한 출력만을 보장하는 원리를 다룹니다.
핵심 포인트
- LLM의 JSON 출력 성공률은 모델과 스키마에 따라 85~95% 수준에 머묾
- 프롬프트 엔지니어링과 후처리는 지연 시간 및 비용 문제를 야기함
- 제약된 디코딩은 샘플링 전 유효하지 않은 토큰을 마스킹하여 형식을 보장함
- 함수 호출(Function calling)은 도구 인자 경로를 통해 신뢰성을 높이는 방법임
실제 사용자 앞에 언어 모델(Language Model)을 배치하는 모든 애플리케이션은 결국 동일한 벽에 부딪힙니다. 모델은 텍스트를 반환하지만, 여러분의 애플리케이션은 데이터가 필요합니다. 이 둘은 서로 다르며, 그 사이의 간극이 놀라울 정도로 많은 프로덕션(Production) 코드들이 머무는 곳입니다.
여러분은 JSON을 요청합니다. 하지만 JSON처럼 보이는 무언가를 받게 됩니다. 대부분의 경우 파싱(Parsing)이 되지만, 때로는 마지막에 쉼표가 붙어 있거나, 필드 이름이 코드에서 예상하는 것과 약간 다르게 철자가 적혀 있거나, 숫자가 따옴표로 감싸져 있거나, 혹은 여는 중괄호 앞에 친절한 문장이 놓여 있기도 합니다. 데모 단계에서는 이를 수동으로 수정하면 되지만, 프로덕션 환경에서는 여러분에게 페이지(Page)를 보냅니다.
85%의 문제
모델에게 JSON으로 답변하도록 프롬프팅(Prompting)하는 것은 모델의 종류와 스키마(Schema)가 얼마나 까다로운지에 따라 85%에서 95% 사이의 성공률을 보입니다. 트래픽을 곱하기 전까지는 이 수치가 수용 가능한 것처럼 들립니다. 하루에 만 번의 호출이 발생한다면, 95%의 성공률은 500번의 실패를 의미합니다. 각각의 실패는 파이프라인(Pipeline)을 중단시키거나, 재시도(Retry) 비용을 소모하거나, 혹은 일주일 뒤에 누군가가 발견하게 될 잘못된 데이터를 조용히 기록하게 됩니다.
팀들은 이를 세 가지 방식으로 우회하며, 이 세 가지 방식 모두 비용이 발생합니다.
첫 번째는 프롬프트 엔지니어링 (Prompt Engineering)입니다. 스키마를 반복하고, 예시를 추가하고, 정중하게 요청하는 것입니다. 이는 적중률을 높여주지만 결코 확실성에 도달하지 못하므로, 여전히 폴백(Fallback) 경로가 필요합니다.
두 번째는 후처리 (Post processing)입니다. 정규 표현식(Regex) 추출, JSON 복구 라이브러리, 혹은 예산(Budget)을 정해둔 재시도 루프(Retry loop)를 사용하는 것입니다. 이는 지연 시간(Latency)을 추가하고, 실제 제품과는 아무런 관련이 없는 종류의 버그들을 만들어냅니다.
세 번째는 모든 것을 함수 호출 (Function calling)을 통해 라우팅하는 것입니다. 함수를 호출하고 싶지 않은 상황일지라도, 순수하게 도구 인자(Tool argument) 경로가 일반 텍스트 경로보다 더 신뢰할 수 있기 때문에 이 방식을 사용합니다.
제약된 디코딩 (Constrained Decoding)이 실제로 작동하는 방식
진정한 해결책은 프롬프트(prompt)보다 한 단계 아래 계층에서 일어납니다. 모델은 매 단계마다 전체 어휘 집합(vocabulary)에 대한 확률 분포(probability distribution)를 생성하며, 일반적으로는 해당 분포 내의 어떤 토큰(token)이라도 샘플링(sampling)될 수 있습니다. 제약된 디코딩 (Constrained Decoding)은 이 단계 앞에 문법(grammar)을 배치하여, 스키마(schema)와 이미 생성된 내용을 고려했을 때 다음에 올 수 없는 모든 토큰을 마스킹(masking) 처리합니다.
만약 스키마에서 다음 요소가 정해진 집합 중 하나의 필드 이름이어야 한다고 규정한다면, 해당 이름들로 시작하지 않는 모든 토큰은 0으로 처리됩니다. 만약 값이 정수(integer)여야 한다면, 따옴표(quote) 문자는 도달할 수 없는 상태가 됩니다. 모델은 여전히 선택을 하지만, 단지 정해진 형태(shape) 밖으로 선택할 수 없을 뿐입니다.
이것이 바로 이 방식이 제공하는 보장이 프롬프팅 (prompting)과는 본질적으로 다른 이유입니다. 모델이 특정 방식으로 행동하도록 설득된 것이 아닙니다. 샘플링(sampling)이 이루어지기 전에 출력 공간(output space)의 유효하지 않은 분기(branches)들을 제거해 버린 것입니다. 잘못된 형식의 JSON (Malformed JSON)은 발생할 가능성이 낮은 것이 아니라, 도달 자체가 불가능한 것입니다.
JSON 모드는 스키마 강제(Schema Enforcement)가 아닙니다
이러한 차이점은 많은 팀을 혼란에 빠뜨리는데, 두 기능 모두 동일한 용어로 마케팅되기 때문입니다.
JSON 모드 (JSON mode)는 출력이 구문론적으로 유효한(syntactically valid) JSON임을 의미합니다. 즉, 파싱(parse)이 가능하다는 뜻입니다. 하지만 이는 필드가 존재하는지, 이름이 예상과 일치하는지, 혹은 타입(type)이 일치하는지에 대해서는 아무것도 말해주지 않습니다. 여러분의 코드에는 전혀 쓸모없는 객체가 깔끔하게 파싱되어 나올 수도 있습니다.
스키마 강제 (Schema enforcement)는 출력이 여러분이 제공한 스키마를 준수함을 의미합니다. 필드가 존재하고, 이름이 정확하며, 타입이 올바르고, 열거형(enums)이 지켜집니다. 이것이 바로 실제로 연결해 둘 가치가 있는 기능이며, 여러분이 실제로 어떤 기능을 활성화했는지 확인하는 것이 매우 중요합니다. 잘못된 기능을 가정했을 때 발생하는 실패 모드는, 데이터가 파싱은 되지만 이후의 다운스트림(downstream) 단계에서 무언가를 망가뜨리는 형태이기 때문입니다.
지원 여부와 API의 정확한 형태는 제공자(provider)마다 다르며, 중첩된 객체(nested objects), 유니온(unions), 재귀적 스키마(recursive schemas)에서 이러한 차이가 가장 빠르게 나타납니다. 이 메커니즘이 어떻게 작동하는지, 각 제공자가 무엇을 지원하는지, 그리고 그에 따른 프로덕션 패턴(production patterns)에 대한 전체적인 분석은 여기에서 확인할 수 있습니다: https://www.adaptiverecall.com/structured-output/
코드에 여전히 남겨두어야 할 것들
제약된 디코딩 (Constrained decoding)은 특정 유형의 실패를 제거합니다. 하지만 판단(judgment)의 필요성까지 제거하지는 않습니다.
검증 레이어 (validation layer)를 유지하세요. 이는 마이크로초 단위의 비용만 발생할 뿐이며, 이제는 더 발생 가능성이 높은 버그인 '스키마 자체가 잘못된 경우'를 잡아낼 수 있습니다.
의미론적 체크 (semantic checks)를 유지하세요. 스키마는 confidence가 0과 1 사이의 부동 소수점(float)임을 보장할 수 있습니다. 하지만 그 숫자가 무엇을 의미하는지는 보장할 수 없습니다.
스키마 복잡도를 주의하세요. 깊게 중첩되거나 과도하게 재귀적인 스키마는 모델을 좁은 통로로 몰아넣으며, 그 안을 채울 추론(reasoning)의 품질을 떨어뜨릴 수 있습니다. 더 평탄한(flatter) 스키마가 일반적으로 필드 내부의 더 나은 콘텐츠를 생성합니다.
거절(refusals)을 별도로 처리하세요. 답변을 거부하는 모델은 어떻게든 그 사실을 말해야 합니다. 만약 스키마에 이를 위한 공간이 없다면, 당신은 아무 내용도 없는 잘 형성된 객체(well formed object)를 받게 될 것입니다.
요점 (The Takeaway)
대부분의 팀이 유지하고 있는 파싱(parsing) 코드는 모델이 신뢰할 수 없다는 신호가 아닙니다. 그것은 잘못된 레이어에 문제를 해결하도록 요청하고 있다는 신호입니다. 보증(guarantee)의 단계를 디코딩(decoding) 단계로 낮추면, 재시도 루프(retry loop), 복구 라이브러리(repair library), 그리고 새벽 3시에 울리는 호출(3 AM page)이 한꺼번에 사라지며, 검증(validation)은 원래 의도되었던 작은 역할만을 수행하게 될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기