LLM과 JSON 출력
요새는 agentic LLM이니 tool calling이니 이런 것들이 프론티어이지만 LLM 초기에는 JSON(또는 모종의 구조화된 출력)을 내뱉게 하는 것도 힘들었다.
- 규칙성 없는 순수 next-token 샘플링이었음
- 잘못된 JSON이 나와도 기계적인 검증 절차 없이 그냥 출력됨
- 오타 하나로 전체가 파싱 오류가 나는 일이 빈번했음
- "질문 → JSON 답" 데이터가 많이 없었음
Sure! Here's your JSON:같은 서두를 붙이는 경우가 많아, 이를 제거하기도 어려웠음- 출력 JSON이 길어질수록 instruction following 능력이 저하됐었음
- BPE 토큰 경계가 JSON 문법 단위와 어긋났음
- 예를 들어
{",key,":와 같은 식으로 토큰이 쪼개져, 모델이 JSON 구조를 학습하지 못함 - 이 어긋남 자체는 지금도 존재하며, 현대 엔진이 토큰-문법 정렬을 별도로 처리하는 이유임
- 예를 들어
이를 우회하기 위한 여러 편법들이 존재했었다.
- 각 key 별로 value를 단답형으로 질문하게 해서 코드 레벨에서 JSON을 엮어냄
- 우선 답변을 하게 한 뒤, JSON을 regex로 감지해내거나, 최대한 살릴 수 있는 부분을 살려내서 사용함
- LLM이 답변하기 더 쉬운 YAML이나 TOML 형태로 답변하게 한 뒤 추출해서 변환함
- 잘 나올 때까지 통째로 재생성... (output-level rejection)
이를 근본적으로 해결하려는 연구는 두 갈래로 진행됐다.
- 모델 레벨(가중치). 스키마 예제와 tool calling 데이터를 포스트트레이닝(행동 교정)에 넣어 JSON을 더 잘 출력하게 만드는 것.
- 디코딩 레벨(인퍼런스 엔진). 애초에 잘못된 토큰이 나올 수 없게 샘플링을 막는 것.
디코딩 레벨의 시조는 사실 LLM 붐 이전인 PICARD(2021)로, SQL 생성용이었다. 토큰을 하나 뽑은 후 파서로 검사하고, 불합격이면 기각하고 다시 뽑는 token-level rejection 방식이다.