Jsiphon - デルタ追跡と曖昧性検知をサポートするLLMストリーミング向けJSONパーサー
(github.com/webtoon-today)こんにちは、Jsiphon を紹介します。LLMストリーミングで構造化された応答を使う際によく発生する問題を解決します。
LLMで構造化された応答(JSONモード)をストリーミングとあわせて使うと、部分応答のパースに悩まされることがよくあります。この場合、単純に JSON.parse() することはできず、不完全なJSONを繰り返し復元する方式を使いがちです。
しかし、LLMの応答が持つ 「順序が逆転せず、追加されるだけの(append-only)」 という特性を活用すれば、この問題をもっときれいに解決できます。Jsiphon は次の3つの機能を提供します。
-
Append-only パース —
{"msg": "Helのような部分的な応答が入ってきたとき、即座に{msg: "Hel"}のような完全な応答を返します。例のmsgフィールド以前のスキーマについては、すでに完成済みと判定します。 -
デルタ追跡 — 毎回出力される応答では、全体スナップショットに加えて、直近で追加された内容を別途提供します。たとえばチャットボットの吹き出しを複数描画しているなら、全体を描き直さず最後の吹き出しの増分だけを新たに描けるようになります。前の例で LLM が続けて
"lo, World!"を出力したなら、{msg: "lo, World!"}を応答のdeltaの下からそのまま見つけられます。スナップショットを受け取るたびにJSON復元パースや diff をする必要がなくなります。 -
曖昧性検知 — 応答と完全に同じツリー構造を持つ曖昧性ツリー(ambiguity tree)を返します。スナップショットに含まれるデータが確定済みか、それともまだ応答を読み続けている途中かを、複数の深さで把握できます。たとえば次のデータをストリーミングしているとき
{"header":{"title": "abcdefghijk","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デザインについてフィードバックをいただけるとありがたいです。
まだコメントはありません。