Show GN: Jsiphon - 델타 추적과 중의성 감지를 지원하는 LLM 스트리밍용 JSON 파서
(github.com/webtoon-today)안녕하세요, Jsiphon을 소개합니다. LLM 스트리밍에 구조화된 응답을 사용할 때 흔히 발생하는 문제를 해결합니다.
LLM에 구조화된 응답(JSON모드)을 스트리밍과 같이 사용할 때 흔히 부분 응답 파싱하는 문제에 부딪히는데요. 이런 경우에 그냥 JSON.parse()할 수는 없고 반복적으로 불완전한 JSON을 복구하는 방식을 사용하곤 합니다.
그런데 LLM 응답이 가진 '순서 역전없이 추가되기만 하는(append-only)' 특성을 활용하면 이 문제를 더 깔끔하게 풀 수 있고, Jsiphon 이 다음과 같이 세가지 기능을 제공합니다.
-
Append-only 파싱 — {"msg": "Hel 같이 부분적 응답이 들어왔을 때 즉시 {msg: "Hel"} 와 같이 완전한 응답을 반환합니다. 예시의 msg 필드 이전의 스키마에 대해서는 이미 완성된 것으로 판정합니다.
-
델타 추적 — 매번 출력되는 응답에는 전체 스냅샷에 더해 최신 추가된 내용을 따로 제공합니다. 예를 들어 챗봇 말풍선을 여러개 그리는 중이라면, 전체를 새로 그리지 않고 마지막 말풍선의 증가분만 새로 그릴수있게 도와줍니다. 앞선 예제에서 LLM이 이어서 "lo, World!"을 출력한다면, 바로 {msg: "lo, World!"} 를 응답의 delta 아래에서 찾을 수 있습니다. 스냅샷을 받을 때마다 JSON 복원 파싱과 diff를 할 필요가 없어집니다.
-
중의성 감지 — 응답과 완전히 동일한 트리구조를 가지는 중의성 트리(ambiguity tree)를 반환합니다. 스냅샷에 포함된 데이터가 확정되었는지, 아니면 계속 응답을 읽고 있는 중인지를 여러 뎁스에서 알려줍니다. 예를들어 다음 데이터를 스트리밍 중일 때
- {"header":
- {"title": "abcd
- efghijk",
- "date": "..."
- },
- "body": "..."}
isAmbiguous(ambiguous.header.title) 를 사용하면 title이 완성된 시점(응답번호 3)부터 title을 안전하게 사용할 수 있게 됩니다(후속 필드의 완성여부를 기다리지 않음). 전체 완성이 아니라 부분완성을 모든 층위에서 제공하므로, isAmbiguous(ambiguous.header) 는 header의 모든 자손이 완성되는 경우에 isAmbiguous = false를 반환합니다.
이미 부분 JSON 복원/파싱 라이브러리가 많이 있고(partial-json, gjp-4-gpt 등) 근본적인 파싱 문제를 잘 해결하는데요, Jsiphon 은 LLM이 append only로 스트리밍된다는 특성을 활용하여 단순히 스냅샷을 제공할 뿐만 아니라, 필드별로 증분(delta)를 제공하고, 매 반복마다 완성된 필드를 판별할 수 있게 합니다.
이미 비슷한 문제를 풀고계시다면 공감하실만한 부분일 것 같습니다. 저는 Jsiphon에 멀티타입 SSE를 결합해 챗봇이 텍스트를 스트리밍하면서 동시에 여러 플래그들(is_adult, need_admin 등)을 실시간으로 판별하도록 하고 있습니다.
추가로 실용적인 면에서 제로디펜던시, 잘못된 응답에도 에러를 던지지 않음, JSON 앞뒤의 무의미한 텍스트 제거 등 편의기능이 추가되어 있습니다.
GitHub: https://github.com/webtoon-today/jsiphon
npm install jsiphon
만약 도움이 되신다면 사용해봐주시고 의견 부탁드려요.
ambiguity tree는 나름 도전적인 설계인 것 같은데요, 더 좋은 방법이 있는지도 궁금합니다. API 디자인에 대해 피드백 주시면 감사하겠습니다.