テンプレートは呼び出し元の仕事だ
p4 は不透明なプロンプトを転送するだけで、チャットテンプレートを適用しない。それを忘れると、モデルは聞いてもいない質問に答える——流暢に、そしてトークン上限まで。
復旧したリングから出てきた最初のまともな答えは、正しい算術と、誰もしていない会話だった:
17 × 23 を、提供中のモデルに尋ねる。
" 391 What is 12 times 12? Reply with only the number. 144 What is 14"
数字は合っている。その後ろのすべては、モデルが文書の続きを書いているものだ。我々が渡したのがまさにそれだったからである:ひとつの文字列に平坦化されたメッセージ列。instruct モデルはそれを、答えるべきターンではなく、延長すべきテキストとして読む。end-of-turn トークンを決して出さないので、生成は毎回きっちり上限まで走る。
欠落ではなく、意図的な省略
p4 はステージサーバーに不透明なプロンプトを渡すだけで、独自のターン形式を一切適用しない——ステージ化アダプタが持っているのは GGUF からテンプレートを*読み出す*ためのツールだけで、適用するためのものは決してない。これは引くのに妥当な線だ:エンジンは狭くモデル非依存のままでいられ、どのモデルと話しているかをすでに知っている呼び出し元が形式をレンダリングする。しかし、引かれたのに文書化されていない線は、誰かが踏み越える線である。
モデルファイルからテンプレートを読み出して決着がついた:ChatML のターン、end-of-turn トークンは <|im_end|>、そしてアシスタントのターンは thinking ブロックで始まる。それを bridge にレンダリングさせて、同じ質問を投げると:
同じモデル、同じリング、ターン形式を適用した場合。
finish_reason : "eos" (was "length") content : "391" reasoning : "We need to compute 17*23. 17*20=340, plus 17*3=51, total 391."
思考パスが答えを食い尽くす
推論モデルは何かを言う前にトークンを使う。予算と難問を与えれば、予算を丸ごと思考に使い切って答えを空にしうる——そして呼び出し元がリクエストの実行前にすでにオンチェーンで支払っているネットワークでは、空の答えは品質の問題ではない。何もしていないものへの請求だ。
精算ゲートウェイはこれを知っていて、thinking を無効にするよう要求する。モデルのテンプレートにそのスイッチはないので、bridge はプロンプトの中で thinking ブロックを開き、*そして閉じる*。モデルはその後ろに答えを書く。我々はこれを一度、いちばんわかりやすいかたちで間違えた——プロンプト側でブロックを閉じたせいで閉じタグが出力に現れなくなり、スプリッタが答え全体を reasoning として仕分け、content を空で返したのだ。まさにこの設定が防ぐために存在する失敗そのものである。
これで契約はどこに落ち着くか。エンジンはバイトを転送する。bridge はモデルを知っている:配置プランで指定されたターン形式をレンダリングし、思考パスを content とは別の reasoning_content として返し、リングがロードされた出力量より多くを求めるリクエストをクランプする——過大なリクエストは、さもなければきっぱり拒否されるからであり、短い答えのほうがエンジンエラーよりましだからだ。