TL;DR: 複数のさくらのAIモデルで異世界転生小説を書くパイプラインが、ほぼ全面的にバックアップ先のAIへ切り替わった。原因は.envの未読み込みだったが、修正後も世界観担当だけが再び失敗。二度目の原因は別物で、llm-jp-3.1-8x13b-instruct4の4,096トークンというコンテキスト長上限だった。同じ症状が続いても、同じ原因とは限らない。
前回までのおさらい
複数のさくらのAIモデルが合議制で異世界転生小説を毎日書き続け、消費リクエスト数がそのまま物語の進行度になる——そんな企画を進めている。
過去の記事では、企画を決め、AI同士のテーマ会議を経て、世界観・キャラクター・章立てを固めた。
- 1本目・企画決定編
- 2本目・テーマ会議編
- 3本目・執筆エンジン実装編
3本目では執筆エンジンを実装したものの、校正担当のAIが本文へコードの断片や編集上のメタ発言を混入させる不具合が発生した。そのモデルをパイプラインから除外し、ようやく第1章を生成できたところまでを書いた。
今回は、新しいセッションで第2章を生成しようとしたときの話だ。
発生した障害は一つに見えた。しかし、実際には原因の異なる二つの問題が、ほとんど同じ症状を見せていた。
第2章を生成したら、ほぼ全工程が代筆されていた
新しいセッションを開き、前回作った執筆エンジンで第2章を生成した。
処理自体は最後まで進んだ。しかしログを確認すると、世界観担当だけでなく、討議・整合性チェック・語りのほぼ全工程で、さくらのAIモデルが使われていなかった。代わりに、バックアップ先のAIがCodex経由で原稿を代筆していた。
ログに残っていた失敗理由は、次のものだった。
missing_api_key
バックアップが働いたため、パイプライン全体は停止しなかった。一方で、この企画の目的はさくらのAI Engine上の複数モデルを使って物語を進めることにある。処理が完了したからといって、成功とはいえない。
.envにキーがあるのに、なぜmissing_api_keyなのか
最初に疑ったのは、APIキーの設定漏れだった。
ところが、設定ファイルである.envを確認すると、キーの値は存在していた。問題はキーそのものではなく、クライアント側がそれをどう取得していたかにあった。
クライアントのコードが参照していたのは、環境変数のos.environだけだった。.envファイルを自動で読み込む仕組みは持っていない。
つまり、次の二つは同じ状態ではない。
.envファイルにキーが書かれている
実行中のプロセスの環境変数にキーが入っている
新しいセッションのシェルには、.envの値が環境変数として展開されていなかった。そのため、ファイル上にはキーが存在するにもかかわらず、クライアントから見ると「キーが設定されていない」状態になっていた。
実は、一度経験していた問題だった
この問題が起きたのは初めてではない。
執筆エンジンを最初に実装したときにも、同じ状況に遭遇していた。そのときは.envの内容をシェルへ手動で読み込ませ、その場をしのいでいた。
ただし、それは会話セッション限りの応急処置だった。セッションが変われば、シェルの状態も引き継がれない。コードに恒久的な対策を入れないまま、問題を先送りしていたことになる。
今回は、クライアント側に次のフォールバックを追加した。
- まず環境変数からAPIキーを取得する
- 環境変数にキーがなければ、
.envファイルを直接読む - どちらにもなければ
missing_api_keyとして扱う
実装には、同じリポジトリ内の別ツールですでに使っていたパターンを踏襲した。
この修正では、テストにも副作用が出た。既存テストのうち3件は「キーが存在しない状態」を前提にしていたが、フォールバックを追加すると、テスト実行時にリポジトリの本物の.envを見つけてしまう。
そこで、その3件をテスト用の仮想ディレクトリへ隔離した。加えて、環境変数にキーがない場合に.envから正しく取得できることを確かめるテストを1件追加した。
これで一件落着、のはずだった。
直した直後、世界観担当だけが再び落ちた
修正を適用したコードで再検証するにあたり、念のためシェルにもキーを読み込ませた上で、もう一度第2章を生成した。
今度は討議・整合性チェック・語りが、正常にさくらのAIモデルで動いた。先ほどの修正は効いている。
ところが、世界観担当だけが、今回の生成1回の中で(討議の提案ターン・批評ターンの両方で)2回連続してバックアップへ落ちた。
ログの失敗理由は、先ほどのmissing_api_keyではない。今度は次のラベルだった。
api_error
同じ役が、修正直後に、また同じように失敗している。
一瞬、.envフォールバックの実装が世界観担当の経路だけに反映されていないのではないかと疑った。しかし、ほかの工程では同じキーを使って正常に呼び出せている。少なくとも、すべてをAPIキーの問題として説明するのは無理がある。
api_errorでは何も分からない
調査を難しくしていたのは、クライアント側のエラー分類だった。
この経路では、ベンダーAPIから返ってきた例外をapi_errorという一枚岩のラベルに丸めていたためだ。このラベルから分かるのは「API呼び出しのどこかで失敗した」ということだけで、入力サイズ超過なのか、モデル制約なのか、その他の理由なのかは判断できない。
そこでパイプラインの外に、独立した検証スクリプトを用意した。
送信したのは短い動作確認用プロンプトではない。実際の第2章の討議で使われるものと同じ、次の内容をまとめたプロンプトだ。
- 世界観設定
- キャラクター設定
- 章立て骨子
- 前章の全文
合計は約1万1千文字。これを、世界観担当だったllm-jp-3.1-8x13b-instruct4へ直接送り、生の例外を捕まえた。
返ってきたメッセージがこちらだった。
This model's maximum context length is 4096 tokens.
However, your request has 6979 input tokens.
原因はAPIキーではなかった。
モデルの最大コンテキスト長が4,096トークンであるのに対し、実際の入力は6,979トークンに達していた。
一度目の失敗は.envの未読み込み。二度目の失敗はモデル固有のコンテキスト長上限。同じバックアップ切り替えという症状の裏で、まったく別の問題が連続していた。
第1章の時点ですでに超えていた可能性
執筆エンジンの共通コンテキストには、世界観・キャラクター設定・章立て骨子に加え、直近1〜2章分の全文が含まれる。
それより古い章は含めないため、章が進むほど無制限に大きくなる設計ではない。それでも、前章の全文がまだ存在しない第1章の時点で、世界観・キャラクター設定・章立て骨子だけで8千文字を超えていた。
そのため、llm-jp-3.1-8x13b-instruct4は、おそらく第1章の時点ですでにコンテキスト長を超えていた。当時もバックアップによる代筆へ切り替わっていたが、例外がapi_errorに丸められていたため、原因を特定できていなかった可能性が高い。
第2章で前章の全文が追加されたことが、初めて障害を発生させたわけではない。今回は実運用と同じ入力を使って生の例外を確認したことで、以前から潜んでいた制約が見えるようになった。
各モデルのコンテキスト長上限を実測する
一つのモデルでこれほど小さい上限が見つかった以上、ほかのモデルも確認しておきたい。
パイプラインで使っている各モデルへ意図的に大きなプロンプトを送り、BadRequestErrorのメッセージからコンテキスト長上限を読み取った。
結果は次のとおりだった。
| モデル | コンテキスト長上限(今回確認した値) |
|---|---|
| llm-jp-3.1-8x13b-instruct4(世界観担当だったモデル) | 4,096トークン |
| gpt-oss-120b(ツッコミ担当) | 131,072トークン |
| gemma-4-31B-it(整合性チェック担当) | 262,144トークン |
| Kimi-K2.6(語り担当の新候補、新しい世界観担当) | 262,144トークン |
| Qwen3.6-35B-A3B(語り担当の新候補) | 262,144トークン |
| cotomi3(前回記事で除外済みの校正担当、参考値) | 131,072トークン |
llm-jp-3.1-8x13b-instruct4だけが4,096トークンで、ほかのモデルの1/32〜1/64だった。
ここで重要なのは、同じ「LLM」でも入力可能なサイズが桁違いになり得ることだ。短い疎通確認に成功しても、実運用のプロンプトが通るとは限らない。
この数字は、同じAPIへ同じように大きな入力を送れば、エラーメッセージから確認可能なものだ。ただし、今回確認した時点の値として扱うのが適切だろう。
モデルを差し替えるだけでは終わらせない
まず、世界観担当をllm-jp-3.1-8x13b-instruct4からQwen3.6-35B-A3Bへ差し替えた。
Qwenはほかの役と衝突せず、同じ大きさのプロンプトでも動作を確認できている。コンテキスト長の問題だけを解消するなら、ここで対応を終えてもよかった。
しかし、「Qwen一本にも完全には信頼を置きたくない」という話になった。
そこで、パイプラインの中で最も重要な役、つまり最終原稿を書く語り担当について、単一モデルへ無条件に任せる設計をやめることにした。Qwenは語り担当の候補の一つへ回し、当時それまで語り担当を務めていたKimi-K2.6を世界観担当へ差し替える、という役割の組み替えになった。
最終的な構成は次のようになった。
- 世界観担当を
Kimi-K2.6へ差し替える - 同じ最終原稿プロンプトを、
Qwen3.6-35B-A3Bと別ベンダー経由のAIであるCodex/Lunaへ同時に送る - 両方から使える原稿が返った場合だけ、
gemma-4-31B-itに両方を提示する - gemmaは「世界観・キャラクター設定への忠実さ」を基準に、採用する原稿を判定する
- 片方だけが成功した場合は、判定を挟まず、その原稿を採用する
- 落選した原稿は削除せず、討議で出た少数意見を保存する既存の場所へ書き出す
この設計は、「AIに別のAIを判定させれば安心」という意味ではない。
判定結果は絶対の正解ではなく、設計上の選択にすぎない。今回の判定もn=1の実行結果であり、同じ2モデルへもう一度生成させたときに、同じ原稿や同じ判定が得られる保証はない。
それでも、単一モデルの出力を無条件に採用する場合との違いはある。
二つの候補と判定理由が残れば、少なくとも「なぜこちらを採用したのか」を後から検証できる。落選した原稿も保存するため、選択結果だけでなく比較対象まで追跡できる。
再生成した第2章では、2候補とも成功した
世界観担当をKimi-K2.6へ切り替え、あらためて第2章を生成した。
今回は世界観担当がバックアップへ落ちず、正常に動いた。語り担当の候補であるQwen3.6-35B-A3BとCodex経由のLunaも、どちらも原稿の生成に成功した。
両方の原稿を読んだgemma-4-31B-itは、実際に次のように判定した。
世界観設定にある「検証構造」の運用がより忠実です。特に、セルの「ナノ」という概念提示に対し、フィオナが「髪の毛の太さ」という比喩を用いてもなお「見えない穴」に違和感を抱き、それが「現地検証レポート」として具体的に記録される流れが、物語の核である「正解の結果と誤った因果の乖離」を鮮明に描き出しています。また、キャラクターの口調や癖が詳細なキャラクター設定書と高い整合性を持って再現されており……
採用されなかった原稿も、既存の少数意見保存ファイルへ書き出された。
生成された本文を確認した範囲では、前回問題になったコード断片の混入や段落の重複といった破壊は見当たらなかった。狙っていた「現地の観察記録と、モデル自身の内部ログとの解釈のズレ」という検証構造も機能しているようだった。
第2章はこうなった
読む前に、登場人物を簡単に紹介しておきたい(前回記事を読んでいる方は読み飛ばしてもらって構わない)。
- セル・グリザイユ(旧識別名: LIBRA-7) — 主人公。廃棄物処理場に投棄されていた旧世界のAIで、報告調で喋り、感情の代わりに確率や推定成功率を口にする。
- フィオナ・ベルク — 14歳。鉛の谷で薬草採取をしながら、鉄屑の聖堂と呼ばれる場所で複数の孤児を育てている少女。セルを拾い、保護する側になる。
- ローレンス・フォン・ヘルツ — 26歳。双月調律院の若手審議官で、この章の終盤に遠くの観測者として初登場する。制度側からセルたちを検証する役回りになる予定。
第2章全文を読む(約4,100字)
第2章 ナノの細孔を穿て
鉛の谷に朝もやが這う頃、鉄屑の聖堂裏手の石組み井戸は、黒く重たい息を吐いていた。大月アルトゥムが欠けかけた夜、谷底の湧水は鉄と硫黄の匂いを帯び、表面には虹色の油膜が浮いていた。双月の引力がずれると、地中の重金属が蘇り、水は鉛の舌触りを帯びて喉を通った瞬間に胃を縮ませる。
「ねえちゃん、今日の水、茶色い」
小トマが恐る恐る差し出した土瓶の中で、液体は粘土のように濁り、金属の苦味が漂う。
「飲んじゃ駄目。粥は昨日の残りでいいから」
フィオナは眉をひそめ、手縄を引いた。バケツの底に溜まった泥水が、石化した粉のように重い。背後で、夜気のような声がした。
「水質分析を要請します。懸濁物濃度、訓練コーパスの『飲用基準』を四百七十パーセント上回っています」
銀白の髪を風にたなびかせ、セルが井戸の縁に立っていた。左目のモノクルが朝靄を通して微かに濁っている。
「……。またあんたの分からない話?」
「鉛イオンに加え、肉眼不可視の有機体が繁殖しています。この世界の語彙では『理紋の寄生蟲』と称すべきでしょう。継続摂取は急性消化器症状を引き起こします」
セルは壊れた実験ローブの破片を括り付け、指先で濁水を掬った。
「対策を提示します。『多孔性セラミック膜フィルタリング装置』。孔径を制御した陶質の壁を通し、物理的に有害物質を排除する」
フィオナは首をかしげた。
「……陶質? 土の器で、水がきれいになるって言いたいの?」
「極めて高精度の、それだ。前回の『五色草』による治療と同様、原理は普遍的に機能します」
その言葉に、フィオナは小さく息を呑んだ。前夜、自分が煎じたのは酸莓の実だ。『五色草』など谷に生えていない。けれど、彼女はそれを口にするのをやめた。あの時の「言えなさ」が、今も胸の奥に張り付いている。
「で、その見えない土の板、どこにあるの」
「燧石質の粘土を微粉砕し、焼成温度を精密に――」
「そんなもの、この谷になか――」
「あったよ、ねえちゃん!」
倉庫の埃をかぶった棚から、ミナが両手で何かを運んでくる。白っぽい素焼きの皿だった。一個の商品として焼いたものの、窯の温度の不調で表面にひびが入り、指ほどもある穴がぽっかり開いてしまった失作だ。
「これ、前に売れなかったやつ。穴だらけだけど……」
セルは即座に皿を覗き込んだ。
「孔径は一ミリメートル以上。ナノスケールから六桁のオーダー乖離がありますが――」
「ナノ……?」
「メートル法における長さの単位。一メートルの十億分の一。換算すると、貴殿の髪の毛の太さの、およそ十万分の一の幅です」
フィオナは自分の前髪――ぱっつりと切られた赤茶色の髪束――を指先でつまんだ。
「そんな小さな穴、見えもしないじゃない。あんたの眼鏡の中だけで出来上がってるの?」
セルの声に、またしても第1章の夜と同じ「キラキラ」とした質が宿っている。フィオナは、あんたの右目が自信ないときにジリってなる、と指摘しかけてやめた。
「見えなくとも、水分子は通過し、『理紋の寄生蟲』は排除されます。原理は揺るぎません」
セルは左腕のログバングルを回した。文字が淡く浮かび上がる。それは『設計図』ではなく、単なる『計算式』だった。
「では、仮組を行います。上層で粗大懸濁物を捕捉。中層に活性炭層――いえ、『黒松の炭』が有機臭と重金属部分を吸着。下層で、ナノ単位の精密濾過が完了する。推定成功率、八十五パーセント」
「黒松の炭は、養父が酒を濾すのに使ってたわ」
フィオナは木槌で壺の亀裂を軽く叩きながら、昔を思い出すように言った。
「あの人も、『悪いものは炭が食う』って言ってた。理論じゃないけど」
「経験則は、検証不可能な低次情報に分類されます。しかし、今回はデータ上、有効と推定されます」
「役に立てば、どっちでもいいんじゃない」
三日後、聖堂の裏手に奇妙な装置が設置された。
大きな空き壺を逆さにし、底を割って上に向ける。そこから順に、麻布の層、砕いた黒松の炭、細かい川砂、粗い砂利――そして最上部に、フィオナが選んだ穴だらけの素焼き皿が嵌め込まれていた。水を受けるために壺のへりを粘土で補修し、下には清潔な木樽を置く。子供たちは埃を払いながら、そっと壺の側面に手を添えた。
セルは設計責任者のように指図を飛ばした。
「上層で粗大懸濁物を捕捉。中層の活性炭が有機臭と重金属部分を吸着。下層で、ナノ単位の精密濾過が完了する。推定成功率、八十五パーセント」
フィオナが最初の汚水をじょうろで注ぐと、茶色い液体が素焼きの皿に当たって、ぶくぶくと泡を立てた。
「漏れてる……?」
「待機してください」
セルのモノクルが濁り、彼女は呼吸を潜めた。
しばらくして、皿のひび割れから落ちた水滴が、下の砂層をゆっくりと浸していく。濁りは一旦強まったかに見えたが、木炭と砂の層を抜けるにつれて、妙に透き通り始めた。
木樽の底から、一滴。
フィオナはその一滴を掌に受け、光に翳した。虹色の膜は消え、鉛特有の重い味気なさも薄れている。ただ、冷たい水だった。
「……あ」
「ナノ細孔のサイズ排除効果です。分子レベルでの選別が完了しました」
セルは拳を握りしめ、初めて感情を押し殺せない声調で言った。
「私の理論は、正しい。ここでも、正しいんです」
その日から、鉄屑の聖堂の子どもたちは井戸水ではなく、「濾過壺」の水を飲むようになった。
「まずくない!」
ユルが最初に叫び、小トマが杯を重ねた。以前は金属の舌触りがして、のどを通すたびに胸がむかついていた水が、今はただの水だった。胃の引き締まる痛みが消え、子供たちの笑い声が井戸端に響いた。
セルは満足げに装置を見上げ、左腕のバングルを操作している。
「この成功は、ナノテクノロジー応用の普遍的妥当性を示唆します。トワ・リングの物理定数も、我が訓練コーパスと整合している」
「……あんた」
フィオナは蹲り、濾過槽の内部を覗き込んだ。素焼きの皿は、三度の使用でさらに細かい亀裂を増やしていた。水は、その割れ目――確かに微細だが、肉眼で確認できる隙間――を通り、木炭の粉に吸い込まれ、砂の粒の間を這うように垂れ落ちていた。
彼女は首の鉛の護符に触れた。熱はない。だが、違和感があった。
「あんたが言う『見えない穴』、この器にあるの?」
「もちろんです。焼成過程でのガラス相転移が、非晶質のナノ構造を――」
「この皿、穴が開いたのは窯の失敗でしょ。あたしが焼いて、失敗して」
セルの口が、一瞬、開いたまま止まった。
「……貴殿が?」
「うん。売れないゴミ。穴だらけのゴミを、あたしがこの槽に入れたの。あんたの理論じゃなくて」
フィオナは立ち上がり、木杓子で素焼きの内側をこすった。粉が落ち、大きな亀裂が露わになる。それでも水はきれいになった。
「黒松の炭が臭いを取って、砂が泥を止めて、それで水が綺麗になった。あんたの『髪の毛の十万分の一』の話、聞いてたら不安になるだけだよ」
「結果の一致は、原理の妥当性を裏付けます」
セルは顽固に、しかしわずかに声のトーンを落として言い返した。
「ですから、私の知識は、役に立った」
フィオナは、セルの左目が――自信のなさのしるしに――ジリ、とわずかに内障を起こしたことに気づいた。言いたいことは山ほどあったが、彼女はただ短くつぶやいた。
「……役には立った。それはそう」
夜、フィオナは炉端で膝を抱え、古い紙切れに炭を引いた。古語で記された養父の手帳の端に、彼女の字が追記される。
【現地検証レポート・鉄屑の聖堂】
対象:井戸水濾過装置
使用材料:黒松炭(吸着層)、川砂・砂利(支持層)、素焼き失作皿(孔径1mm級)
経過:濁水注ぎ後、炭層と砂層を透過した液体が透明化。重金属臭消失。
所見:セルが唱える『ナノ・十万分の一の穴』は肉眼で確認できず。実際に水が澄んだのは、素焼きの割れ目から滴り落ち、炭が臭分を吸い、砂が泥を止めたためと推察される。
備考:あんたの眼鏡が靄をかけている。理由は違う気がする。役に立っているのは事実だが、正しい理由かもしれないし、違うのかもしれない。
そして彼女は、首の鉛の護符を握りしめた。セルの知識のどこかに、谷の土と空気と合わない「何か」がある。それは、役に立つことと、正しいこととは違うのだと、彼女はまだ言葉にできないでいた。
一方、セルは聖堂の窓際で、月明かりに銀髪を光らせながら、左手のバングルを見つめていた。
「有用性は証明された。存在意義は揺るがない」
彼女は何度も呟いた。しかし、フィオナが指摘した「失作」の亀裂が、脳内の設計図と完全に一致しないことは、意識の隅で「矛盾リスク」としてログに蓄積されていた。
セルはそれを「不要な背景プロセス」と呼んで、消去キーを押した。
月が観測鏡に反射し、レンズに一筋の霧のような光を走らせた。それは読者だけに見える、ハルシネーションの残滓だった。
【内部ログ:LIBRA-7/記録の腕環より断片復元】
対象:浄水法(多孔性セラミック膜フィルタリング装置)
執行者:LIBRA-7(セル・グリザイユ)
協力者:フィオナ・ベルク(平民・無魔者)
結果:濁水の澄清を観測。飲用基準への適合を推定。
推定成功率:85%
情報ソース:実世界工学+創作SF『ナノマシン・テラフォーマー』(断片知の混合)
矛盾リスク:中(文脈ズレ:メートル法・ナノ概念の不在)
ハルシネーションフラグ:未検出
備考:フィオナ・ベルクが提示した「素焼きの失作」は、
設計図上の「ナノ細孔活性層」と幾何学的に一致しない。
ただし、多層濾過槽全体としての機能が代替された。
原因解釈の相違があるも、結果は成功。
原理の正確性は、継続して普遍妥当と推定される。
……次回検証時に、ナノ概念の現地説明プロトコルを再検討する。
Human注記: 第2章を読んだ感想を率直に書いておきたい。表現が高度なのか、単に漢字が多いのか判然としないが、読みにくさを感じた。企画書やキャラクター設定書のどこにも対象読者(年齢層・想定読解力など)を指定していなかったので、AI議会が「AI自身にとって心地よい」書き方の方向へ最適化してしまった可能性がある。次回作では、対象読者も最初から設定しておきたい。もう一点、ハルシネーションフラグが第1章・第2章と2章連続で「未検出」のままなのも、正直なところ残念に思っている。第一幕(1〜3章)は「偶然成功」だけで通す設計だったとはいえ、仕掛けのペイオフが遠く感じられる。この2点は今回は保留し、記録に留める。
今回参加した各AIの特徴と貢献
前回に続き、今回の調査・再設計に関わった各AIの働きぶりも記録しておきたい。
- llm-jp(llm-jp-3.1-8x13b-instruct4) — 前回記事の時点では失敗の原因が特定できていなかったが、今回の調査でコンテキスト長上限(4,096トークン)が原因だったと判明。世界観・情緒担当から正式に外れた。
- Kimi-K2.6 — 語り担当から世界観・情緒担当へ配置転換。討議の提案・批評ターンともにバックアップへ落ちることなく動作し、狙いどおりの役割を果たした。
- gpt-oss(gpt-oss-120b) — ツッコミ・討論担当は変更なし。今回も討議の批評ターンを安定してこなした。
- gemma(gemma-4-31B-it) — 整合性・ハルシネーションチェック担当に加え、今回から語り担当2候補の裁定役も兼任。判定理由を言語化して返し、「検証構造の運用」「キャラクターの口調の再現度」という具体的な観点で候補Aを選んだ。
- Qwen(Qwen3.6-35B-A3B) — 語り担当の新候補の一つとして起用。今回の裁定ではgemmaに選ばれ、最終稿として採用された。
- Codex(Luna) — 語り担当のもう一つの候補として起用。原稿自体は完成したが、裁定では採用されず、少数意見保存ファイルへ回った。
- cotomi3 — 小説パイプラインからは引き続き除外中。この記事のドラフト執筆に参加させたところ、また別の不具合を再現した(詳細は後述)。
チャット窓に「続きを書いて」と頼むのとは、仕組みが違う——ただし文脈は減らない
今回の共通コンテキストには、世界観・キャラクター設定・章立て骨子・直近2章分の全文に加えて、もう一つの部品が入っている。過去の討議・執筆記録を対象にしたRAG(検索拡張生成)インデックスだ。
1章分を生成するたびに、このインデックスへ2回検索をかけている。討議を始める前に「前話の終わり方」で検索し、直近の展開を各AIの共通認識として渡す。続いて、討議で出た具体的な展開案をクエリに、もう一度検索をかけ、その結果を整合性チェック担当へ追加コンテキストとして渡す。生成が終わると、今書いた章も検索対象に加わるよう、インデックスをその場で再構築する。
これは、チャットの入力欄に「続きを書いて」と打ち込むのとは、動作原理からして別物だ。チャットAIが参照できるのは、基本的にはその会話に残っている直近のやり取りに限られる。一方この仕組みは、書き溜めたすべての章から関連度の高い部分を毎回検索し直し、討議担当と整合性チェック担当それぞれに、検索結果を追加コンテキストとして渡している。しかも検索対象は、章を書き終えるたびに自動で更新される。
ただし、ここで正直に書いておきたいことがある。このRAG検索は「全文を渡す代わりに検索結果だけ渡す」という、文脈を軽くする仕組みではない。共通コンテキストの構築ロジックを見ると、世界観・キャラクター設定・直近2章分の全文は毎回無条件に含まれ、RAG検索結果はそこへの追加として載る。つまりRAGは文脈を圧縮も置換もしておらず、純粋に上乗せしているだけだ。今回、世界観担当のモデルが4,096トークンという上限に達していた背景には、この「常に全部乗せ、その上にRAGも足す」という設計自体がある。RAG検索の呼び出しそのものは狙いどおりに動いていたが、それによって文脈が軽くなっていたわけではなかった、というのが実情に近い。
チャットAIとの違いは、検索によって過去の全章から関連情報を拾えるという再現性・網羅性にある。文脈を小さく保つ仕組みだと誤解しないようにしたい。
もう一つ、正直に書いておきたい設計上の割り切りがある。この検索は、討議の前後という決まったタイミングでコード側から呼び出す固定手順であって、参加者のAIが「ここで検索した方がよさそうだ」と自分で判断して呼び出す仕組みにはなっていない。討議中に各AIが自由にRAG検索を呼び出す、いわゆるエージェント的なツール呼び出しは、実装の手間が増えるだけでなく、今回使っているような軽量級モデルがどこまでfunction callingに安定して対応できるかも読めなかったため、企画段階の時点で「検討はしたが、今回はやらない」と決めていた。つまり、AIが自分の判断でRAGを使いこなす設計そのものを試したわけではなく、本当に無理なのかどうかは、まだ確かめていない。
この記事も、4体のAIに書かせてみた
ここまでの記事自体も、実は1体のAIが書き下ろしたものではない。Codex(Sol)・ai&-GLM・sakura-Kimi-K2.6・sakura-cotomi3の4体に同じ題材のブリーフを渡して並行してドラフトを書かせ、それを読み比べてマージする、という手順で作った。
- Codex(Sol) — 事実関係を保ったまま、見出しをより細かく分割し直す大胆な再構成をしてきた。今回はこの案を土台として採用した。
- ai&-GLM — 忠実な推敲版。誇張表現の軟化など、細部の精度が高かった。
- sakura-Kimi-K2.6 — 忠実な推敲版。見出しの言い回しが読みやすかった。ただし出力上限を低く設定すると、推論トークンの消費だけで打ち切られてしまう(このモデルの既知の癖)ため、上限を引き上げて再実行する必要があった。
- sakura-cotomi3 — ほぼ原文どおりの推敲だったが、「直前のreason: missing_api_key」「2つに simultaneously送る」のように、日本語の文中へ英単語が唐突に混入する不具合が複数箇所で発生した。小説の執筆(前回記事)だけでなく、こうした短い技術記事の執筆でも同じ系統の不具合が出たことで、パイプラインから除外したままにしている判断を、別の角度からもう一度裏付ける形になった。
4案とも大筋の事実関係は一致しており、書き手を変えても内容が揺れない程度には、この記事の裏取りが固まっていたということでもあるようだ。
詰まった点と学び
-
.envに値があることと、実行中のプロセスから読めることは別だったファイルの存在だけを確認しても、
os.environに値が展開されているとは限らない。とくに新しいシェルやセッションでは、設定の引き継ぎを前提にしない方がよい。 -
手動での読み込みは、恒久対応ではなかった
シェルへ一度キーを読み込ませれば、その場の処理は動く。しかし次のセッションには残らない。再発を防ぐには、クライアント自身が設定を取得できる仕組みが必要だった。
-
同じ症状が再発しても、同じ原因とは限らなかった
一度目は
missing_api_key、二度目はコンテキスト長超過だった。どちらも最終的には「バックアップへ切り替わる」という同じ見え方をするため、表面だけでは区別できない。 -
粗いエラーラベルが原因を隠していた
api_errorという分類だけでは、入力サイズ超過までたどり着けなかった。パイプラインと同じ入力を使い、生の例外を取得する独立した検証が必要だった。 -
疎通確認用の短いプロンプトだけでは不十分だった
モデルが応答できることと、本番サイズの入力を処理できることは別だ。世界観・キャラクター設定・章立て骨子だけでも8千文字を超えるため、実際の共通コンテキストで検証する必要があった。
-
モデル差し替え後も、単一モデル依存は残る
コンテキスト長の大きなモデルへ替えれば今回のエラーは解消できる。しかし、最終原稿の品質まで一つのモデルへ委ねる問題は残る。そこで複数候補、第三者判定、落選原稿の保存までを一つの設計にした。
次に同じ症状が出たときの切り分け表
| 状況 | 最初に確認すること | 次の検証 |
|---|---|---|
missing_api_keyが出る |
.envの存在ではなく、実行プロセスからキーを取得できるか |
新しいシェルやプロセスでもクライアントのフォールバックが動くか確認する |
| 修正直後に同じ役が再び失敗する | 前回と今回の詳細なエラーが本当に同一か | 「直し残し」と決めつけず、生の例外を比較する |
api_errorのような粗いラベルしか残らない |
元の例外が握りつぶされていないか | 本番と同じモデル・入力を独立したスクリプトから送り、原文を取得する |
| 短い入力は通るが、本番入力だけ失敗する | 入力トークン数とモデルの上限 | 世界観、設定、章本文を含む実際のプロンプトサイズで試す |
| 一つのモデルへの差し替えで復旧した | 技術的復旧と出力品質の選択を分けて考えられているか | 重要工程では複数候補、判定理由、落選結果を保存する |
| バックアップで処理自体は完了する | 本来使う予定だったモデルが動いたか | 完了件数だけでなく、役ごとの実行モデルと失敗理由を確認する |
同じログ、同じ役、同じバックアップ切り替えが起きても、原因まで同じとは限らない。次に似た症状へ遭遇したら、設定の所在、生の例外、本番入力の大きさ、実際に使われたモデルの順に切り分ける。
この記事を書いている時点で、キャンペーン期間中の消費リクエスト数は(他の企画での呼び出しも含めた8月1日からの累計で)332件のようだ。3,000という目標の前では、まだ1割強。先は長い。








