同じ Tavern カードを Chub.ai、 RisuAI、 SillyTavern に 読み込ませても、まったく同じ振る舞いにはならない。挨拶の順番が 入れ替わり、lorebook のトリガーが沈黙し、カスタムプロンプトが 消える。なぜそうなるのか、フィールド単位で解きほぐしていく。
同じカードの三つの人生
昨日書いたカードを、 SillyTavern から V2 PNG として書き出し、 Chub.ai にアップロードする。Chub からダウンロードし直したファイルを RisuAI に読み込ませる。見た目はどこでもほぼ同じ ── 同じ立ち絵、 同じ名前、同じ description ── なのに、実際の会話挙動は 少しずつずれていく。原因を突き止めるのに何日もかかる。
最初のメッセージが、本来の first_mes ではなく 3 番目の alternate_greeting になっている。SillyTavern では確実に発火していた lorebook エントリが、RisuAI では静かに無効になっている。Tavern で 設定したはずの extensions.depth_prompt が、三つ目のホストには存在しない。これらは普通の意味での バグではなく、同じフォーマットを共有しているだけの三つの プロジェクトの間で起きる実装ドリフト(implementation drift)だ。
3 プラットフォーム、3 つの哲学
どのフィールドがどこで生き残るかを予想するには、そもそも 各ホストが「何であるか」を覚えておくのが近道だ。
- SillyTavern はセルフホストのクライアントだ。カード仕様を母語として 扱い、V1・V2・V3 の大部分はチャットループから直接読まれる。 拡張機能も、たいていは extensions に自分のキーを書き足す方を選び、スキーマ自体には触らない。 「正規の挙動」がどう見えるかを知りたいなら、おおむね SillyTavern が事実上のリファレンスになる。
- Chub.ai はコミュニティ・ホストだ。仕事は保管と提示であって推論 ではない。カードを解析するのはギャラリー、バージョン管理、 検索 UI を回すためで、PNG 自体はそのままユーザーの クライアントに返す。Chub は独自の拡張名前空間( extensions の chub_ プレフィックスのキー)を持ち、アップロード時には 比較的保守的にペイロードを保つが、ギャラリーで前面に出す フィールドには明確な選好がある。
- RisuAI はクロスプラットフォーム・クライアント(Web・デスクトップ・ モバイル)で、歴史的に Tavern フォーマットを基層として 扱い、その上に独自のモジュール体系を載せてきた。lorebook、 トリガー、モジュール束縛には Risu 固有の作法があり、 それらは extensions を通って行き来する。カード自体は他ホストでも開けるが、 Risu 固有の挙動は持って行けない。
どれが正しい設計ということではない。仕事の置き場所を、 クライアントに置くか、ホストに置くか、可搬な実行系に置くか、 という三通りの賭けの結果で、作者が見ているドリフトはその 影に過ぎない。
フィールド単位のドリフトはここに集中する
ドリフトはランダムではなく、仕様が曖昧なフィールドと、各 プラットフォームが拡張したフィールドに偏る。下表は作業用の 対照表で、契約書ではない ── 各バージョンで挙動は変わり 続けているし、これからも変わる。
| Field | SillyTavern | Chub.ai | RisuAI |
|---|---|---|---|
| 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)は失うものが多い。
PNG tEXt:見えない舞台
フィールド単位の解釈差より前に、PNG コンテナそのものが ドリフトの源だ。Tavern カードの本体は PNG の tEXt チャンク(大きい場合は zTXt)の 中に住み、キーは chara、 値は Base64 エンコードされた JSON だ。三つのホストが このチャンクをどれくらい触るかは違う:
- SillyTavern はインポート時に読み、エクスポート時に書き 直す。既知フィールドはロスレスだが、知らない拡張データは 往復の中で並び順や形を正規化されたという報告がある。
- Chub.ai はアップロード時に元のチャンクを大筋で保つが、 サムネイルや最適化バリアントを生成する際に PNG 自体を 再エンコードすることがある。正規のカードをダウンロード するパスを使うのが最も安全だ。
- RisuAI のインポート経路は、ペイロードを自前のモデルに 通してから再送出する。Risu を一周してきたカードは、 構造的には同じでもバイト的には異なることが多い。
アップロードの瞬間にカードが静かにフィールドを失っていて、 原因がわからないとき、答えはたいていこの層にある。 エンコードの詳細は別記事の PNG tEXt チャンクメタデータと Tavern カード で掘り下げる。
ライブラリをずれさせない作法
三つのホストを完全に一致させることはできない。十分に近く 保ち、ライブラリが旅を生き延びるところまで持っていくことは できる。役に立つ習慣は三つだ:
- 最大公約数の フィールド集合を主役にする。 V2 の正規キー ── name、 description、 personality、 scenario、 first_mes、 alternate_greetings、 mes_example、 system_prompt、 character_book ── を構造材として扱い、 extensions の中身はベストエフォートとして扱う。挙動として重要なら、 正規キーで表現できなければならない。
- リリースごとに ラウンドトリップ・テスト。 真実の源(source of truth)から書き出し、各ホストに 取り込み、書き戻し、JSON を diff する。その diff が ドリフトだ。多くの作者は最初の一度だけ手作業でやって そのままになる。生き残るカードは、これを自動化した 人が育てているカードだ。
- diff をレポートと して残す。 ドリフトは積もる。昨日はラウンドトリップを 3 回耐えた フィールドが、来週のホスト更新で消えるかもしれない。 diff を雰囲気ではなくデータとして追うことが、読者より 先に静かな退化を捕まえる唯一の方法だ。
原稿は一つ、方言は三つ
tavernai.cards はまさに この問題のための「写字台」として作っている:正規のカードは 一つ、生きた方言は三つ、そして読める diff。
- 双方向同期 ── 同じカードを Chub.ai、RisuAI、SillyTavern に押し 出し、ホスト側の編集を真実の源に引き戻す。
- 自動 diff レポート ── ラウンドトリップごとに、どのフィールドが保たれ、 正規化され、落とされたかをホスト別に見せる。
- フォーマット 認識付き変換 ── V1・V2・V3 と、各ホストが extensions に積み上げた方言を、ブラックボックスではなく目に 見えるマッピングで変換する。
- 公開前 lint ── ホスト非互換フィールド、古いアセット参照、無音の エンコードバグを公開前に拾い上げる。
工房は少しずつ開いている。Chub、Risu、Tavern を同時に 抱えているカード作家こそ、まず巻物に名前を残してほしい ── クロスプラットフォーム同期が最初に出る機能だ。