実践ガイド · アップロード前検証

Chub.ai や RisuAI にアップロードする前にキャラクターカードを検証する方法。

Tavern カードをアップロードする前の儀式 —— Chub.ai RisuAI SillyTavern への投稿を静かに壊す10個の失敗、それを潰す手動チェックリスト、そしてカード数が増えた時に自動化したい lint ワークフロー。

fol. iv.r

出航前の最後の確認

主要なカードプラットフォームのサポート板を開けば、同じ質問が繰り返されている。カードはアップできたのに挨拶が空ロアブックが反映されない画像プレビューが壊れる。スレッドの結末も似ている —— こっそり直して、再エクスポートし、再投稿。カード自体は「あと一歩」だったわけだ。

検証は、その手戻りを防ぐ安価な習慣である。Chub.ai に push する前、あるいは RisuAI へインポートする前に、フィールド一覧を2分で歩いておくこと。同じチェック表はスケールする —— 10枚なら目視で、100枚ならスクリプトで。本稿はその両面を扱う:人間が見るべきことと、linter に任せるべきこと。

fol. v.r

アップロードを壊す10の失敗

コミュニティの bug スレで遭遇する頻度のおよその順:

  1. first_mes の欠落、または空の mes_example ホストはカードを受け入れるが、チャット画面が空白になる。RisuAI は特に mes_example でトーンを揃えるので、空文字は「妥当なデフォルト」ではない。
  2. 長すぎる description がトークン予算を破裂させる。 投稿は通っても、最初の生成時にはペルソナで埋め尽くされて返答スペースがない。トークン上限はホストごとに違うが、原則として常駐ブロック(description + personality + scenario + system_prompt)は対象モデルの context window に余裕を残す。
  3. creator_notes system_prompt の混同。 前者は人間向け —— 使い方の注意、警告、クレジット。後者は毎ターンモデルに送られる。逆にすると、メタコメントが会話に漏れるか、jailbreak がモデルに届かないかのどちらか。
  4. PNG tEXt チャンクの破損、または誤ったエンコーディング。 慣例は keyword =chara の tEXt、中身は Base64 でエンコードした UTF-8 JSON。生 UTF-8 をそのまま書いたり、非 ASCII を剥ぎ落としたり、別 keyword を使うツールは、一部ホストでは開けるが次のホストでは静かに壊れる PNG を出す。
  5. character_book フィールドの型違反。 entry は所定の形を期待する: keys は文字列の配列、カンマ区切り文字列ではない。 insertion_order は数値、 enabled はブール。手で編集したロアブックほど数値を文字列にしていたり配列を単一文字列にしていたりして、インポータは警告なくその entry を捨てる。
  6. extensions にあるホスト未対応のカスタムキー。 SillyTavern が書き込んだ Regex pipeline の設定は、Chub のプレビューレンダラには無意味。壊れはしないが、ローカルで確認した挙動は移動した瞬間に消える。
  7. V3 専用フィールドが V2 カードに混入。 nickname group_only_greetings creator_notes_multilingual は V3 で追加。カードの spec が依然 chara_card_v2 のままだと、準拠クライアントはこれらを無視してよい。
  8. tags の形が違う。 これは「文字列の配列」。 "tags": "fantasy, royalty" は文字列であり、拒否されるか、奇妙な単一タグ「fantasy, royalty」として索引される。
  9. alternate_greetings に空文字列が混入。 たいていは消し忘れた下書きの残骸。空白の選択肢として描画するクライアントもあれば、例外を投げるものもある。どちらにせよノイズ系のバグ。
  10. ホストポリシー —— レートリミット、画像サイズ上限、NSFW フィルタ。 スキーマの問題ではなく、ローカル linter では拾い切れないが、上記と同じ頻度でアップロードを失敗させる。深夜の一括投稿前に、各ホストのルールを確認しておくこと。
fol. vi.r

手動チェックリスト

カード数が少ないうちは、自分で歩く方がどんなツールより早い。アップロード前に以下を埋めること:

  • [ ] spec spec_version が一致(V2 は 2.0、V3 は 3.0)。
  • [ ] name description first_mes が全て存在し非空。
  • [ ] 常駐プロンプトが目標 context window 内に余裕を残す。
  • [ ] creator_notes にモデル向け指示が混入していない。
  • [ ] PNG が開け、プレビューが正常、hex で keyword chara tEXt チャンクが見える。
  • [ ] 全ての character_book.entries[*].keys が文字列配列。
  • [ ] tags が配列; alternate_greetings に空文字列なし。
  • [ ] V2 カードに V3 専用キーが混入していない(逆も同様)。
  • [ ] extensions の各キーが文書化され、どのホスト向けか把握している。
  • [ ] 対象ホストのアップロードルール(画像サイズ、NSFW、レートリミット)を確認済み。
fol. vii.r

linter が代わりにやるべきこと

手動チェックリストは30枚あたりで楽しさが尽きる。その先は自動化。実用的なカード linter は3つの仕事をする:

  • スキーマ検証。 PNG をパース、 tEXt をデコード、宣言された spec に対して JSON を検査する。必須フィールド、型、列挙、V2 カードに V3 キーが混入していないか等。
  • ホスト互換性マトリクス。 カード中の各キーについて、どのホストが実際に消費するかを報告。SillyTavern しか読まないフィールドが「壊れている」わけではない —— ただ、移動できないと事前に分かるのが価値。
  • トークン予算の見積もり。 よく使う tokenizer で常駐プロンプトを集計し、対象モデルの context を圧迫する閾値を超えたら警告する。

まさに tavernai.cards が埋めようとしているニッチ:手動でやるはずのチェックを、実際に投稿する各ホストに対して走らせるアップロード前ワークベンチ。

アップロード履歴の中で壊れたカードを発見するのはもう終わりにしよう。投稿前に V2 / V3 カードを実ホストの癖に合わせて lint し、読める diff でスペック間を移行する。

別の言語で読む