모델이 정말로 JSON을 스트리밍할 필요가 있을까요?
요약
LLM의 JSON 스트리밍 시 발생하는 불완전한 데이터 문제를 해결하기 위한 'slot-flight' 접근 방식을 소개합니다. 모델이 JSON 전체를 쓰는 대신 슬롯 프레임을 방출하고 SDK가 상태를 조립함으로써, 생성 과정 중에도 유효한 JSON 객체를 유지할 수 있습니다.
핵심 포인트
- 전형적인 JSON 스트리밍은 생성 중에는 유효하지 않은 문서를 생성함
- slot-flight는 슬롯 ID와 JSON 경로를 매핑하여 구조화된 상태를 유지함
- 중간 상태에서도 직렬화 가능한 유효한 JSON 객체 제공 가능
- 벤치마크 결과 slot-flight가 네이티브 JSON 스키마 스트리밍보다 안정적인 중간 상태를 제공함
LLM(Large Language Model)으로부터 JSON을 추출하는 것은 상당히 일상적인 일이 되었습니다. JSON Schema를 전달하고, 구조화된 출력(Structured Output)을 활성화하면, 모델의 성능이 충분할 경우 최종 JSON은 대개 유효합니다.
하지만 스트리밍(Streaming)을 활성화하면 어떤 일이 발생할까요?
생성 종료 시점의 유효한 JSON 문서는, 생성되는 동안 애플리케이션이 안전하게 소비할 수 있는 무언가를 갖는 것과는 다릅니다.
전형적인 스트리밍 JSON 응답은 다음과 같은 모습입니다:
{"summary":"Export job failed...
결국 이것은 유효한 JSON 객체가 되겠지만, 이 순간에는 전혀 JSON이 아닙니다.
실제로 이는 실제 애플리케이션에서 JSON 스트리밍을 다루기 어렵게 만듭니다.
- 파싱 실패(Parse failure)가 실제 오류는 아닙니다. 단지 응답이 아직 완료되지 않았음을 의미할 뿐입니다.
- 어떤 필드가 실제로 완성되었는지 판단하기 어렵습니다.
- 최종 출력이 유효하더라도, 중간 상태는 거의 내내 깨져 있습니다.
이는 문서 형식으로서 JSON이 가진 속성일 뿐입니다. 문서가 완성될 때까지 안정적인 중간 객체는 존재하지 않습니다.
모델이 실제로 JSON을 스트리밍할 필요가 있을까요?
반드시 그렇지는 않습니다.
대부분의 애플리케이션이 원하는 것은 모델이 JSON 문자열을 점진적으로 써 내려가는 것이 아닙니다. 그들이 정말로 필요로 하는 것은 **생성 과정 내내 사용 가능한 상태를 유지하는 구조화된 상태(Structured state)**입니다.
모델에게 JSON 문서를 직접 스트리밍하도록 요청하는 대신, 저는 모델이 압축된 슬롯 프레임(Slot frames)을 방출하도록 하는 실험을 진행했습니다. 각 슬롯 ID는 JSON 경로(JSON path)에 매핑되며, 들어오는 값들은 서버가 소유한 객체 상태(Object state)에 적용됩니다.
다시 말해, 모델은 값을 생성하고, SDK가 JSON 객체를 조립합니다.
예를 들어, 모델이 여전히 summary 필드를 생성 중이더라도, 애플리케이션은 이미 다음과 같은 값을 받을 수 있습니다:
{"summary":"Export job f"}
이것은 최종 결과는 아니지만, 유효하고 직렬화 가능한(Serializable) JSON 객체입니다. 이는 특별한 처리 없이 즉시 UI에 렌더링하거나, SSE(Server-Sent Events)를 통해 전송하거나, 다운스트림 서비스로 전달할 수 있습니다.
하나의 슬롯 (slot)이 실패하더라도 해당 슬롯만 다시 생성하면 됩니다. 이미 생성이 완료된 필드들은 다시 생성할 필요가 없습니다.
이것이 slot-flight의 핵심 접근 방식입니다.
벤치마크 (Benchmark)
NVIDIA의 openai/gpt-oss-20b 엔드포인트를 사용하여 두 가지 접근 방식을 비교했습니다.
- slot-flight: 모델이 슬롯 프레임 (slot frames)을 스트리밍하면,
slot-flight가 JSON 상태를 조립합니다. - json-schema: NVIDIA의 네이티브
response_format: { type: "json_schema" }스트리밍을 사용합니다.
결과는 다음과 같습니다:
slot-flight
final schema ok: 40/40 = 100%
stream JSON stable: 100%
...
네이티브 JSON 스키마 (JSON Schema) 구현은 신뢰할 수 있는 최종 결과를 생성했습니다: 40개의 응답 중 39개가 스키마 검증 (schema validation)을 통과했습니다.
하지만 스트리밍된 청크 (chunks) 중 유효한 JSON 상태를 나타내는 것은 약 2%에 불과했습니다. 생성 과정의 대부분 동안 클라이언트는 불완전한 JSON 문서를 보고 있었습니다.
slot-flight를 사용하면 slot-delta 이벤트를 포함하여 모든 중간 상태가 JSON으로서 직렬화 (serializable) 가능한 상태로 유지됩니다.
전형적인 이벤트는 다음과 같습니다:
{"type":"slot-delta","slot":"summary","value":"Export job f","state":{"summary":"Export job f"}}
value가 여전히 생성 중임에도 불구하고, state는 애플리케이션이 안전하게 소비할 수 있는 완전한 객체 (object)입니다.
결론 (Conclusion)
사용자들은 전체 응답이 완료되기 전에 요약 내용을 보고 싶어 하는 경우가 많습니다. 서버는 개별 필드가 사용 가능해지는 즉시 다운스트림 (downstream) 작업을 시작하고 싶어 할 수도 있습니다. UI는 한꺼번에 나타나는 대신 점진적으로 채워지는 객체로부터 자연스럽게 이득을 얻습니다.
그러한 상황에서 불완전한 JSON 접두사 (prefix)를 지속적으로 파싱하려고 시도하는 것은 훌륭한 인터페이스가 아닙니다.
제가 slot-flight를 만든 이유는 "LLM이 유효한 JSON을 생성할 수 있는가?"라는 질문에 답하기 위해서가 아닙니다.
더 흥미로운 질문은 이것입니다:
애플리케이션이 최종 JSON 문서가 존재하기 전에 구조화된 상태 (structured state)를 안전하게 소비할 수 있는가?
그것이 바로 slot-flight가 해결하도록 설계된 문제입니다.
또한 중첩된 객체 (nested objects) 및 객체 배열 (arrays of objects)에 대해서도 안정적인 구조화된 스트리밍 (structured streaming)을 지원합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기