JSON을 안정적으로 출력하는 법 — 4층 출력 방어선
4층 출력 방어선: 프롬프트 Few-shot으로 기반 다지기 → 인터페이스 네이티브 능력(JSON Mode / Function Calling) → 밑바탕의 제약 디코딩(Token 단위 감시 + 문법 트리 제약) → 코드 검증과 에러 되먹임 재시도.
1층: 프롬프트로 기반 다지기
- 방식: 구호만 외치지 말 것(「JSON으로 출력해 주세요」). 완벽한 입력/출력 쌍 예시를 한두 개 직접 줄 것
- few-shot 예:
# 작업: 이름과 나이 추출
- 원리: 대모델의 모방 능력이 매우 강해서 구체적 샘플이 있으면 크게 벗어나지 않는다
- 한계: 이것만으로는 턱없이 부족하다 — Few-shot은 확률을 높일 뿐 어떤 보장도 제공하지 않는다
2층: 모델 네이티브 능력
{"response_format": {"type": "json_object"}}
3층: 밑바탕의 비법 — 제약 디코딩
- 전제: 반드시 모델을 로컬에서 직접 돌려야 한다(디코딩 과정에 개입할 수 있어야 함)
- 메커니즘: 모델이 글자를 뱉을 때(토큰 단위 생성) 코드로 지켜보다가, 문법에 맞지 않는 내용을 출력하려 하면 그 글자의 생성 확률을 즉시 0으로 만든다
- 예시:
{ "name" : John — 이미 합법적으로 생성된 key는 유지하고 비합법 후속은 차단
4층: 폴백 사고 — 검증과 재시도
- 철칙: 앞에서 아무리 잘했어도 반드시 폴백이 있어야 한다
- 방식: 결과를 받으면 즉시 코드로 검사·검증. 괄호 누락, 타입 오류 등은 에러 정보를 그대로 되먹여 모델이 스스로 고쳐 다시 출력하게 한다
핵심 통찰
- 「확률적 이어쓰기」가 모든 포맷 불안정 문제의 제1원인이다 — LLM에는 「구조를 출력한다」는 개념이 없고 다음 토큰을 샘플링할 뿐이다. 이 점을 이해하면 프롬프트에 쓴 어떤 요구도 확률을 높일 뿐 보장을 제공하지 못하는 이유를 알 수 있다.
- 모델의 「예의」가 포맷 킬러다 — RLHF로 훈련된 대화 습관(접두 인사말, 접미 설명, 마크다운 코드 펜스)이야말로 구조화 출력의 최대 오염원이다. 「정렬 목표」와 「공학 목표」가 충돌하는 전형적 사례다.
- 방어선은 「보장 강도」 순으로 정렬해야지 「구현 난이도」 순으로 정렬하면 안 된다 — 프롬프트는 확률급, 인터페이스 파라미터는 프로세스급, 제약 디코딩은 문법급, 코드 검증은 결과급이다. 강도가 점증하며 뒤로 갈수록 결정론에 가까워진다.
- 제약 디코딩은 유일하게 문법급 보장을 제공하는 수단이지만 배포 전제가 있다 — 샘플링 과정에 개입할 수 있어야 하므로 모델 가중치/추론 스택을 직접 보유해야 한다. 즉 클라우드 API를 쓸 때 3층 「밑바탕 차단」은 손댈 수 없다.
- 폴백 층의 가치는 「더 강해서」가 아니라 「무조건적이어서」다 — 앞의 세 층은 모델 버전, 파라미터 지원도, 배포 형태에 따라 무력화될 수 있고, 「코드 검증 + 에러 되먹임 재시도」만이 어떤 형태에서도 쓸 수 있다. 그래서 유일하게 생략할 수 없는 층이다.
- 에러 되먹임(error feedback loop)은 LLM을 자가 치유 가능한 컴포넌트로 만든다 — 파서의 에러 원문을 모델에게 되던지는 것이, 사람이 규칙을 짜서 JSON을 고치는 것보다 범용적이다. 에러 정보 자체가 고품질의 수정 힌트이기 때문이다.
- 다층 방어는 이식 가능한 공학 패러다임이다 — 「소프트 유도 → 하드 제약 → 밑바탕 차단 → 사후 폴백」이라는 계층 사고는 JSON뿐 아니라 도구 호출 파라미터, 콘텐츠 안전, 길이 통제 등 「모델을 말 듣게 하는」 모든 장면에 똑같이 적용된다.
엔지니어링 실전 Tips
- Few-shot 예시는 「그대로 복사 가능」해야 한다: 예시 출력은 최종적으로 기대하는 맨 JSON이어야 하며, 예시에 어떤 설명 문구도 넣지 말 것. 그렇지 않으면 모델이 그대로 따라 배운다.
- 순수 JSON Mode보다 Function Calling을 우선할 것: JSON Mode는 「합법적인 JSON임」만 보장하지만, Function Calling은 필드명과 필드 타입(
name: string / age: number)까지 제약하므로 후속 검증이 한 겹 줄어든다.
- 제약 디코딩은 자체 추론 스택에서만 가능하다: 클라우드 API 호출 시 이 층은 아예 부재하므로, 아키텍처 리뷰 때 배포 형태를 미리 확인하고 손에 넣을 수 없는 능력 위에 신뢰성 방안을 세우지 말 것.
- 검증 층은 두 종류의 에러를 구분해야 한다: 구조 오류(괄호 누락)는 바로 재시도해도 되지만, 의미 오류(값을 잘못 추출)는 재시도가 무효이므로 사람이나 규칙 폴백으로 가야 한다.
- 되먹임 시 원본 에러 텍스트를 함께 돌려보낼 것. 모델은
Expecting ',' delimiter: line 1 column 24 같은 정보에 대한 수정 성공률이 막연한 「포맷이 틀렸다」보다 확연히 높다.
- 재시도에는 상한과 강등이 있어야 한다: 무한 재시도는 비용과 지연을 폭발시킨다. N회를 넘으면 계속 토큰을 태우지 말고 구조화된 실패 객체를 반환할 것.
- 2층/3층을 넣었다고 4층을 지우지 말 것: 폴백 층은 모델 형태와 무관한 유일한 보험이다.