Field guide · クロスプラットフォーム同期

同じ Tavern カードを Chub.ai RisuAI SillyTavern に 読み込ませても、まったく同じ振る舞いにはならない。挨拶の順番が 入れ替わり、lorebook のトリガーが沈黙し、カスタムプロンプトが 消える。なぜそうなるのか、フィールド単位で解きほぐしていく。

fol. i.r

同じカードの三つの人生

昨日書いたカードを、 SillyTavern から V2 PNG として書き出し、 Chub.ai にアップロードする。Chub からダウンロードし直したファイルを RisuAI に読み込ませる。見た目はどこでもほぼ同じ ── 同じ立ち絵、 同じ名前、同じ description ── なのに、実際の会話挙動は 少しずつずれていく。原因を突き止めるのに何日もかかる。

最初のメッセージが、本来の first_mes ではなく 3 番目の alternate_greeting になっている。SillyTavern では確実に発火していた lorebook エントリが、RisuAI では静かに無効になっている。Tavern で 設定したはずの extensions.depth_prompt が、三つ目のホストには存在しない。これらは普通の意味での バグではなく、同じフォーマットを共有しているだけの三つの プロジェクトの間で起きる実装ドリフト(implementation drift)だ。

fol. ii.r

3 プラットフォーム、3 つの哲学

どのフィールドがどこで生き残るかを予想するには、そもそも 各ホストが「何であるか」を覚えておくのが近道だ。

  • SillyTavern はセルフホストのクライアントだ。カード仕様を母語として 扱い、V1・V2・V3 の大部分はチャットループから直接読まれる。 拡張機能も、たいていは extensions に自分のキーを書き足す方を選び、スキーマ自体には触らない。 「正規の挙動」がどう見えるかを知りたいなら、おおむね SillyTavern が事実上のリファレンスになる。
  • Chub.ai はコミュニティ・ホストだ。仕事は保管と提示であって推論 ではない。カードを解析するのはギャラリー、バージョン管理、 検索 UI を回すためで、PNG 自体はそのままユーザーの クライアントに返す。Chub は独自の拡張名前空間( extensions の chub_ プレフィックスのキー)を持ち、アップロード時には 比較的保守的にペイロードを保つが、ギャラリーで前面に出す フィールドには明確な選好がある。
  • RisuAI はクロスプラットフォーム・クライアント(Web・デスクトップ・ モバイル)で、歴史的に Tavern フォーマットを基層として 扱い、その上に独自のモジュール体系を載せてきた。lorebook、 トリガー、モジュール束縛には Risu 固有の作法があり、 それらは extensions を通って行き来する。カード自体は他ホストでも開けるが、 Risu 固有の挙動は持って行けない。

どれが正しい設計ということではない。仕事の置き場所を、 クライアントに置くか、ホストに置くか、可搬な実行系に置くか、 という三通りの賭けの結果で、作者が見ているドリフトはその 影に過ぎない。

fol. iii.r

フィールド単位のドリフトはここに集中する

ドリフトはランダムではなく、仕様が曖昧なフィールドと、各 プラットフォームが拡張したフィールドに偏る。下表は作業用の 対照表で、契約書ではない ── 各バージョンで挙動は変わり 続けているし、これからも変わる。

FieldSillyTavernChub.aiRisuAI
character_bookネイティブ、V2 セマンティクス完備保持、chub_lorebook 拡張経由で提示読み込まれるが Risu 独自 lore モジュールに再投影されることが多い
alternate_greetings順序を保持、ユーザーが選択可順序保持、ギャラリーは表示用に並べ直すことあり順序はおおむね保持、UI ピッカーはビルド依存
extensions.*多くは保持、対応する拡張が消費chub_ キー含めてラウンドトリップ可Risu のキーは尊重、それ以外は再書き出し時に落ちることあり
mes_example「START」マーカーで解析、few-shot として投入逐語的に保管、ホストは解釈しない解釈する。空白やマーカーが正規化されることあり
system_prompt尊重、グローバル preset とマージされうる逐語的に保管尊重、Risu モジュールに再ラップされうる
assets(V3)対応は拡大中、解決規則はまだ収束途上保管されるが提示されるのは一部の種類のみ部分対応、表情アセットは別経路
creator_notes_multilingual(V3)あれば読むcreator_notes と並べて表示されることあり表示は一貫しない

二つのパターンが繰り返される。第一に、extensions は誠実な 名前空間だ。各ホストが入れたいものはすべてここに 入り、ラウンドトリップは次のホストがその鍵を知っているか に完全に依存する。第二に、解釈されるフィールド ほどよくドリフトする。JSON を単に保管するホスト (Chub)は失うものが少なく、解析して再描画するホスト (SillyTavern、RisuAI)は失うものが多い。

fol. iv.r

PNG tEXt:見えない舞台

フィールド単位の解釈差より前に、PNG コンテナそのものが ドリフトの源だ。Tavern カードの本体は PNG の tEXt チャンク(大きい場合は zTXt)の 中に住み、キーは chara、 値は Base64 エンコードされた JSON だ。三つのホストが このチャンクをどれくらい触るかは違う:

  • SillyTavern はインポート時に読み、エクスポート時に書き 直す。既知フィールドはロスレスだが、知らない拡張データは 往復の中で並び順や形を正規化されたという報告がある。
  • Chub.ai はアップロード時に元のチャンクを大筋で保つが、 サムネイルや最適化バリアントを生成する際に PNG 自体を 再エンコードすることがある。正規のカードをダウンロード するパスを使うのが最も安全だ。
  • RisuAI のインポート経路は、ペイロードを自前のモデルに 通してから再送出する。Risu を一周してきたカードは、 構造的には同じでもバイト的には異なることが多い。

アップロードの瞬間にカードが静かにフィールドを失っていて、 原因がわからないとき、答えはたいていこの層にある。 エンコードの詳細は別記事の PNG tEXt チャンクメタデータと Tavern カード で掘り下げる。

fol. vi.r

ライブラリをずれさせない作法

三つのホストを完全に一致させることはできない。十分に近く 保ち、ライブラリが旅を生き延びるところまで持っていくことは できる。役に立つ習慣は三つだ:

  1. 最大公約数の フィールド集合を主役にする。 V2 の正規キー ── name description personality scenario first_mes alternate_greetings mes_example system_prompt character_book ── を構造材として扱い、 extensions の中身はベストエフォートとして扱う。挙動として重要なら、 正規キーで表現できなければならない。
  2. リリースごとに ラウンドトリップ・テスト。 真実の源(source of truth)から書き出し、各ホストに 取り込み、書き戻し、JSON を diff する。その diff が ドリフトだ。多くの作者は最初の一度だけ手作業でやって そのままになる。生き残るカードは、これを自動化した 人が育てているカードだ。
  3. diff をレポートと して残す。 ドリフトは積もる。昨日はラウンドトリップを 3 回耐えた フィールドが、来週のホスト更新で消えるかもしれない。 diff を雰囲気ではなくデータとして追うことが、読者より 先に静かな退化を捕まえる唯一の方法だ。
fol. vii.r

原稿は一つ、方言は三つ

tavernai.cards はまさに この問題のための「写字台」として作っている:正規のカードは 一つ、生きた方言は三つ、そして読める diff。

  • 双方向同期 ── 同じカードを Chub.ai、RisuAI、SillyTavern に押し 出し、ホスト側の編集を真実の源に引き戻す。
  • 自動 diff レポート ── ラウンドトリップごとに、どのフィールドが保たれ、 正規化され、落とされたかをホスト別に見せる。
  • フォーマット 認識付き変換 ── V1・V2・V3 と、各ホストが extensions に積み上げた方言を、ブラックボックスではなく目に 見えるマッピングで変換する。
  • 公開前 lint ── ホスト非互換フィールド、古いアセット参照、無音の エンコードバグを公開前に拾い上げる。

工房は少しずつ開いている。Chub、Risu、Tavern を同時に 抱えているカード作家こそ、まず巻物に名前を残してほしい ── クロスプラットフォーム同期が最初に出る機能だ。

他の言語