ルーティングは分類ではなく、制約付きのスケジューリング
まず、よく誤られている言い方から入る。マルチモデルを使い始めたチームが最初にやることとして、タスク分類器を訓練することが多い。ユーザー入力を「簡単」と「難しい」に分け、難しいほうは大きいモデル、簡単なほうは小さいモデルへ流す——という思路だ。この考え方は demo なら成り立つが、本番環境ではほぼ確実に破綻する。理由は3種類あり、どれも同じ結論に着地する——ルーティングの本質は分類ではなく、複数のハード制約のもとで行うスケジューリングである。
1つ目は、そもそも目標がズレていることだ。分類器が答えるのは「このリクエストはどのカテゴリに属するか」だけだが、本番環境で本当に問われるのは「今使えるこの一批のモデルのなかで、誰が、許容できるコストと許容できる latency で、このリクエストを及第点まで仕上げられるか」だ。前者はラベル空間の中での一点選択、後者は予算と SLA とベンダーの可用性制約をすべて乗せた制約付き組合せ最適化問題になる。小さい規模では両者の結論は一致するが、規模がのぼるとすぐに乖離する。
2つ目は、能力の非単調性を無視していることだ。ほとんどの人が「モデルが強いほどよい」というメンタルモデルを持っているので、ルーティングは能力順に並べた線形ルールに退化してしまう。しかし現実のタスクは多次元だ。中くらいのモデルは長い構造化抽出では flagship よりも安定するかもしれないし、小さいモデルの多言語性能は flagship を追い越すかもしれない。能力に制約をかけられたチャネルモデルは、簡単な分類では一切劣らない。能力マトリクス(capability matrix)は多次元かつ非単調だ。まさにこれが、ルーティングを専用のシステムとしてつくる価値のある理由でもある。
3つ目がもっとも致命的で、ルーティング判断の正しさは、それ自体からは観測できない。小さいモデルに流れたリクエストが、大きいモデルに流していたほうがよかったかどうかは、永遠に分からない。このせいで、「ルーティングを入れて40%コスト削減しました」みたいな主張はいずれも検証不可能になる。この問題は本稿の9節で特扱いをし、oracle 上界と regret カーブを使って、ルーティングの効果を測定できる形にする方法を説明する。
本稿の進み方は実際のエンジニアリング上の依存順で並んでいる。まず「いったい何を最適化しているのか」を定義し、次に分類器、そして能力マトリクスで事実を蓄積し、成本と latency をハード制約にし、デグレードチェーンと cascade を組み、最後に「どう定量化するか」「設定ドリフトをどう統制するか」を解く。各段ごとに適用境界も示す——ルーティングシステムのエンジニアリング工数の半分は、「やってはいけない場面」の判定に費やされている。
一、2026年にルーティングが必須になった理由:単一モデルの4つの天井
1.1 能力の天井:平均点が長い裾を隠す
フラッグシップモデルの評価スコアは、平均で平準化された一本の曲線にすぎない。順位表で見ているのは何百問もの平均的な成績だが、あなたの本番トラフィックはまったく異なる分布をしている。大量のリクエストはテンプレートの照合や抽出で、少数は複数ステップの推論が必要な複雑な判断で、さらにごく一部は長い文書に図表と段落をまたぐ関連付けが加わった複合タスクだ。この3種類がモデルに求めるものは方向が違う。平均点を取ると、「特定のカテゴリではフラッグシップが専用の小さなモデルに負ける」というケースを覆い隠してしまう。
より現実的な問題は失敗コストの非対称性だ。同じ構造化抽出の失敗でも、ルート選択を誤れば下流のデータが汚染され、手動ロールバックが走ってしまう。一方、雑談の回答が若干粗かったところでユーザーが気づくことはない。予算を全リクエストに均等に配分するのは、いちばん重要性の低い問題を、いちばん高額なモデルに投げつけるのと同じだ。ルーティングが最初にやるべきことは、このリクエストが失敗したときの被害の大きさを、決定ロジックにはっきり埋め込むことだ。
1.2 経済の天井:単価が下がっても請求書は下がらない
この3年間、モデルの単価は下がり続けてきたのに、多くのチームの推理請求書は比例して下がっていない。むしろ増えている。原因はほぼいつも同じで、値下がりによって呼び出しが安くなり、そのかわりに呼び出し量のほうが速く増えるからだ。モデル能力が向上すると、以前は「人がやる/ルールでやる/やらない」だった領域が新しく開く。社内環境ではこの傾向がさらに深刻で、かつては正規表現の照合で処理していた意図識別を小さなモデルに置き換えた途端に精度が上がり、業務側が曖昧なシーンを全部こちらへ流してきた。
よく見落とされるコスト項目がもう1つある。それはリトライと手戻りだ。大きいモデルの呼び出しが1回失敗して、さらにリトライが1回走ると、そのコストはすでに正しい小さなモデルの呼び出し3回分を超えていることがある。失敗率が8%なら、節約できた金額からまずこの損失を差し引いた分が純利益になる。「1回目の呼び出しの token 単価」だけを数えるコスト計算は、必ずルーティングの効果を過大評価してしまう。
1.3 レイテンシの天井:待ち行列と最初のトークンの遅延
エンドツーエンドのレイテンシは、おおよそネットワークと待ち行列、TTFT(最初のトークンまでの時間)、そして出力トークン数に応じて積み上がるデコード時間の3つに分解できる。ルーティングはこの3つのすべてに影響する。大きいモデルに切り替えると TTFT と1トークンあたりのデコード時間の両方が上昇し、小さいモデルなら逆になる。もう少し微妙なのは待ち行列の項だ。同じモデルでも低負荷時の P99 が800ミリ秒なのに、ピーク負荷時は待ち行列だけで十几秒に達する。ルーティングシステムが平均レイテンシしか見ていないと、すでに過負荷になっているチャネルへトラフィックを流し続けことになる。
したがってルーティングは、平均値ではなく分位数を意思決定の根拠にしなければならず、さらにチャネルの現在の負荷をリアルタイムの入力として扱わなければならない。オフラインでまとめた能力表の上だけで選択して、リアルタイムの混雑をまったく見ない router は、トラフィックのピーク時にレイテンシとエラー率の両方を同時に踏むことになる。
1.4 供給の天井:レート制限、リージョン可用性、チャネルの差
2026年のモデル供給の構図はこうだ。単一のベンダーでは全ニーズをカバーできなくなり、各ベンダーは厳しいレート制限とリージョンごとのクォータを持っている。つまり「マルチモデル」は最適化オプションから可用性インフラへ変わった。能力的に互いに代替できるチャネルを2つ以上持っておらないと、1度のレート制限やリージョン障害がそのままユーザーに見える事故になる。
そしてまさにこのことが、多くのチームが初めて本気でルーティングを始めるきっかけになっている。節約のためではなく、先月あるベンダーが主力リージョンで2週間連続で品質を落としたからだ。由此可见、供給の冗長化がルーティングのもっとも硬い要求であり、コスト最適化はそれに付随する副産物にすぎない。順番を逆にすると、節約はできても保険としては役立たない脆いシステムを作り上げる。
1.5 4つの天井のうち、どれが先にあなたを噛むか
4つの天井が発動する順番は互いに異なるので、おおよその判定基準で並べられる。供給の天井は通常いちばん先に発動する。ベンダー側のレート制限、リージョン品質低下、突発的な容量逼迫は予測不能であり、しかも一度発動するとハード障害になる。多くのチームがレート制限を2度食らってはじめてルーティングを決断するが、その時点ではアーキテクチャの中に大量のハードコードされたモデル名がすでに残っており、改修コストは最初からエイリアスとして設計した場合とは比較にならない。
レイテンシの天井がそれに続きます。表れ方は比較的隠蔽的で、平均レイテンシが悪化するのではなく、ある時間帯・ある種類のリクエスト・あるリージョンの P99 が突然跳ね上がるのに、監視画面上は「全体として正常」に見える。この種の問題を追いにくいのは、それが総量ではなくトラフィックの構成に依存しているからだ。ルーティング判断をタスク族ごとに切り分けて表示して初めて見える。
能力の天井はゆっくりと姿を見せる。ある日突然爆発するのではなく、業務機能の迭代とともに少しずつ侵食していく。新しいシーンのローンチ時に「フラッグシップにしかできないから」という理由でフラッグシップを既定にしてしまい、いつのまにかフラッグシップの呼び出し量が10倍になり、その大半のトラフィックは最初からと同じくらい簡単だったという状態になる。
経済の天井は多くの場合いちばん最後に、しかももっとも隠れている。「請求書は増えたのに理由が説明できない」という形で現れる。請求書の増加はたいてい3方向からの重なりで生じる——新機能が招いた新しいトラフィック、リクエストあたりの出力長の増加、そしてリトライ率の上昇。この3つを別の要因として切り分けて初めて、コスト最適化をパッチ当てではなく体系として作れる。
この順番には実践上そのまま使える含意がある。4つの天井がすべて現れてからルーティングシステムを構築し始めてはならない。先にエイリアス抽象と意思決定ログ(コストはきわめて低い)を作っておくことで、最初の3つの天井を観測可能なイベントに変えられる。能力マトリクスやルーティング戦略は、データが溜まったあとで導入すればいい。初期コストが極端に低く、効果がすぐ得られるステップは、単独でやっておく価値がある。
二、先に問題を定義する:いったいどの量を最適化しているのか
コードを1行でも書く前に、明確な最適化目標が必要だ。1リクエストあたりの価値は、複数の異なる物差しを混ぜ合わせるのではなく、比較可能な1つの量として定義するのがよい。もっとも実用的なのは「品質」を通貨に換算することだ。
1リクエストあたりの純価値 = 業務収益 −(token コスト + レイテンシペナルティ + 失敗リトライコスト + 人手介入コスト)
この式のどの項も測定でき、要因分解でき、しかも OpenTelemetry の GenAI セマンティック規約で統一された span に集められる(gen_ai.request.model、gen_ai.usage.input_tokens、gen_ai.usage.output_tokens はいずれも標準フィールド)。この物差しがそろって初めて、「節約する」と「品質を上げる」が初めて同じことの両面になる。これがないと、コスト最適化と効果最適化がレビュー会議で互いに衝突する。
ルーティング判断に具体的に落とし込むなら、単一スコアで決めるのではなく、制約付きの最大化問題として扱うことになる。
- ハード制約:レイテンシの P99 上限、予算上限、特定リージョンのデータコンプライアンス要件、満たすべき構造化出力の契約;
- ソフト目標:ハード制約を満たす範囲で、品質スコアを最大化する、あるいは成功リクエストあたりのコストを最小化する;
- 副次目標:品質が拮抗しているときは、負荷が低く、キャッシュヒット率が高く、レイテンシが安定したチャネルを優先する。
この定義のしかたには直接的な利点がある。「デグレード」が自然に導かれるのだ。デグレードはシステム障害時の応急措置ではなく、最適化問題が予算を締めたときの最適解にほかならない。予算を0.02ドルから0.005ドルに下げれば、最適戦略は自動的にフラッグシップから小さなモデルへ切り替わる。独立したデグレードスイッチは要らない。逆に、「平常時は小さなモデル、障害時は大きいモデル」という設計は多くの人の直感に反する。障害時は品質を守るためによりこではなく、可用性を守るためにまずデグレードすべきであり、この点はあとで詳しく触れる。
三、タスク分類器:3つのアプローチとそれぞれの失敗モード
3.1 ルールとキーワード:コストゼロだが、先頭部分しかカバーできない
もっとも素朴なアプローチはルールだ。長さのしきい値、コードブロックを含むか、業務キーワードにヒットするか、画像添付があるか、高価値顧客からのリクエストか。この種のルールの利点は推論コストがゼロ、完全に説明可能、冷スタート不要だという点で、多くのシーンでトラフィックの30%から50%をカバーでき、しかもこの部分のルーティング判断はほぼ誤らない。
このルールの天井もはっきりしている。扱えるのは「表面的な特徴」と「難易度」が結びついているタスクだけだ。難易度と表面特徴が切り離された途端にルールは機能しなくなる。典型的な反例は、短いが複数ステップの推論が必要なリクエスト(「この3案ならどれがよいか」で、3案は添付ファイルにある)や、冗長だが機械的なタスク(100万語のログから特定のパターンを探す)だ。長さはもっとも誤用されやすいヒューリスティックである。長いから難しい、短いから易しい、とは限らない。
実践上の助言は、ルールをルーティングチェーンの第1層に置くこと。ただしそれを分類器の本体として扱ってはいけない。ルールの真の価値は、後ろにある学習ベースの分類器にスケジューリング不要の直通経路を提供すること、そして学習ベースの分類器の信頼度が低いときに保険として働くことだ。
3.2 embedding 類似度ルーティング:もっとも実用的なデフォルトの出発点
分類器を1つだけ実装するとしたら embedding similarity を選べ。「このリクエストは、これまでに特定のモデルが要ったリクエストに似ているか」という問いを、ベクトル検索1回に置き換えるだけだ。小さな embedding モデル(数百MB、CPU でミリ秒級)あれば動く。この方式の強みはラベル体系の管理が要らないこと。ラベルが過去のサンプルそのもの、つまり「このリクエストはどのモデルが正しく処理したか」から得られるからだ。
# kNN 风格路由:用历史请求的“正确模型”作为弱标签,
# 把新请求投到最近的若干条历史上做加权投票。
# 关键前提:向量已 L2 归一化,内积等价于余弦相似度。
import numpy as np
VECTOR_INDEX = "hnsw" # 服务端用 HNSW,客户端持有同一份 embedding 模型
TOP_K = 12 # 投票邻居数:太小方差大,太大抹平难度边界
def route(query: str, ctx: dict) -> str:
q = embed_client.encode(query, normalize_embeddings=True)
# 只在“同任务族”的候选集内检索,避免跨域相似度污染
candidates = index.search(
q, k=TOP_K,
filter={"tenant_id": ctx["tenant"], "locale": ctx["locale"]},
)
votes = {}
for sim, item in zip(candidates.scores, candidates.items):
if sim < 0.55: # 相似度太低:交给下游回退逻辑
continue
w = float(sim) ** 3 # 锐化:让最相似的邻居主导投票
votes[item.model_id] = votes.get(item.model_id, 0.0) + w
if not votes:
return ALIAS_DEFAULT # 明确回退,而不是随机挑一个
return max(votes.items(), key=lambda kv: kv[1])[0]
このアプローチで重要な3つのエンジニアリングディテールが往々無視される。1つ目はフィルタ付き検索が必須だ。テナントも言語も事業ラインも違う相似度は比較できない。混ぜたまま投票すると、中国語のサポートリクエストが英語のコードモデルに流れてしまう。2つ目は類似度のしきい値を省けない。kNN は近傍が何であっても必ず何かを返す性質がある。しきい値がなければ「すべてのリクエストが過去に最も多く現れたモデルに流される」という退化した挙動になり、分布に長い裾のあるシステムではほぼ確実に起きる。3つ目は embedding モデル自体がバージョン管理された資産だ。バージョンを変えればインデックスを再構築する必要があり、履歴ラベルの比較性もそれとともに失われる。この点は第10節でさらに掘り下げる。
3.3 LLM-as-a-router:判断をより賢いモデルに任せる
このルートの論理は単純だ。モデルを選ぶこと自体が推論タスクなら、安価だが十分に賢いモデルで判断させればいい。実践では通常、数百億パラメータ以下の小さなモデルに構造化されたルーティングのプロンプトを与え、難易度レベルか能力要求タグを出させ、それを決定論的なコードで具体的なモデルにマッピングする。
最大の利点は説明可能で、セマンティクスも扱えること。「このリクエストには文書横断の推論が必要だ」と先に述べさせてからタグを出させれば、判断理由を取り出して監査できる。最大の欠点も同じくらい明確で、これ自体が1回の推論呼び出しなので、レイテンシもコストも総計に計上される。対象の小さなモデルより高ければ、あるいはこのルーティングによる分類の効果がそれ自身のコストを上回ってしまうなら、このルートは成り立たない。経験的には意思決定価値が高く、リクエスト量が中程度のシーンに向いている。契約レビューや複雑なチケットのトリアージがその例で、高QPSの分類APIではない。
もう1つ、直感に反するけれども重要な発見がある。LLM ルーターの判断は、小さなモデルと大きいモデルの出力が明確に異なるサンプルでのみ正しい。2つのモデルが同種のタスクで拮抗しているなら、ルーターがどれだけ「賢く」あってもどちらを使うべきかは判別できない。そのリクエストは確かにどちらでも問題ないので、この場合のルーティングの利得そのものが存在しない。したがって分類精度を高めることに労力を投じるのではなく、まず「この種のタスクでモデル間の差が十分大きいか」を確認すべきだ。過小評価されがち的前提条件である。
3.4 学習型ルーター:選好データから意思決定境界を学ぶ
このルートの代表的な発想は、人間の選好データを使って軽量な判別器を訓練し、「強いモデルのほうが適切か」を直接予測することだ。発想の源はペア単位の選好比較にある。同じプロンプトに対する大きいモデルと小さいモデルの出力を用意し、アノテーターか強いモデルの判断でどちらがよいかを決める。強いモデルと弱いモデルの選好分布に安定した分界点が存在するなら、その分界点をそのまま分類器として学び、「アップグレード確率」を直接出力する。ルールや kNN に比べて、離散的なラベルよりはるかに豊かな連続信号を吸収できる点が強みだ。
ただし、この種の方法には必ず成立しなければならない前提が3つあり、1つ欠ければ失敗する。1つ目は、2つの候補モデルが同じバッチのプロンプトに対して成対の出力を用意する必要がある。これは初期段階で無視できない2倍の呼び出しコストを払うことを意味する。2つ目は、選好データが実際のトラフィック分布を覆っていなければならない。そうでなければ学ばれる境界は、汎用的な質問にはよく効くが自分の縦型タスクでは大きく偏ったものになる。3つ目は、アップグレード確率のしきい値をコストモデルと連動させる必要がある。アップグレードの期待利得は P(アップグレードが正しい) × Δ品質 − 追加コスト − 追加レイテンシ なので、確率のしきい値は固定の0.5ではなく、この不等式の解になる。
学習型ルーターのもっとも美しい副産物は、それが返す連続的な信頼度だ。この確率は自然に cascade 戦略(第8節)、予算制約(第6節)、段階的リリース(第10節)を駆動できるので、たとえ最初はシンプルな kNN で始めていたとしても、硬いラベルではなく信頼度付きの分布としてルーティング出力を設計しておくべきだ。これは最も先送りしやすく、あとから最も補いにくいインターフェース判断でもある。
3.5 本番で信号が1つではすまない:階層化とフォールバック
最後は階層化だ。成熟したルーターはいずれも、費用のかからない信号を先に、費用のかかる信号を後ろに置くカスケード構造になっている。ルールによる直行(コストゼロ)→ embedding 類似度(ミリ秒級)→ 学習型ルーター(サブミリ秒)→ LLM 判別(小さなモデルの呼び出し1回)。信頼度の低いリクエストは1段ずつ上位へ浮上していく。最初からもっとも高額の1発を撃つのではない。
フォールバック経路は、暗黙的な受け皿ではなく明示的に宣言しなければならない。よくある事故はこうだ。分類器が例外を投げる → クライアントライブラリが既定でリトライする → リトライが既定のフラッグシップモデルに飛ぶ → その夜に理由の説明できないコストのスパイクが生まれる。フォールバックを設定ファイル上の明示的なルールとして書き、それ専用のコストアラートを用意しておくことほど重要なことはない。
3.6 マルチラベルルーティング:1つのリクエストが複数の能力を同時に必要とする
これまでの議論はすべて「単一ラベル・単一モデル」という形態を前提にしていた。1つのリクエストがどの種類に属するかを見分けて、モデルを1つ選ぶ。現実の業務では、この前提はしばしば成り立たない。典型的な例が「添付された契約書を読んで修正案を作る」というリクエストだ。これは長いコンテキスト(契約書は長い)、構造化された出力(修正案をフィールドに落とす必要がある)、領域に関する精度(法律用語を間違えてはいけない)、そして一定の推論深度を同時に要求する。この4つの要件の最適解が1つのモデルに揃うとは限らない。長いコンテキストには別のモデル、構造化の制約には別のモデル、最終生成には3つ目、という使い分けが自然になる。
このような複合的な要件の扱い方には2つの道がある。道1は能力スコアを付けて Pareto 最適な単一モデルを選ぶもので、1つのモデルが複数の能力を同時に満たすことを要求する。実装は簡単だが、そのようなモデルが存在しない可能性があり、その結果毎回どれか1つの項目で妥協が必要になる。道2はリクエストをパイプラインに分解するもので、抽出工程には長いコンテキストと構造化出力ができるモデル、推論工程には推論の強いモデル、検証工程には安価な小さなモデルを使う。道2の利得の上限は高いが、複雑さも1桁ぶん増える。中間結果の受け渡し(抽出段階の誤りが後工程のすべてを汚染する)と誤りの蓄積という問題が生まれるからだ。
現実的な折衷案は1〜2工程だけ分解することである。リクエストの中で「形式的な要求が最も強い」部分(たとえば厳密な JSON を出力しなければならない抽出)を見つけ、それだけを専用に最適化されたモデルで処理し、残りは1回の呼び出しのままにしておく。この妥協で得られる利得は通常、複雑さの増加分をカバーできる。もっとも確実な部分だけを切り出しており、そしてもっとも確実な切れ目は、評価も帰属も最もしやすい部分でもあるからだ。
ただし、マルチラベルルーティングは評価を難しくする。ラベルが1次元から多次元になり、ルーターの誤り率が単一の数字で表せなくなる。タスク族の境界も組み合わせによって曖昧になる。実践的な対処法はマルチラベルの完全な表現を目指さないことで、「何か特別な硬い要件が存在するか」という疎なシグナルだけを残すことだ。たとえば「長いコンテキストが必要」「画像を見る必要がある」「JSON を出力しなければならない」「2万 token を超える」などだ。これらのシグナルは検出精度が高く、説明しやすく、そしてまさに「モデルの選択を誤らせる」原因になりやすい次元でもある。
四、冷スタート:200件のサンプルから本番に載せられる分類器へ
4.1 先に結果からラベルを逆算する。分類体系を先に設計しない
冷スタートでもっとも多い誤った進め方はこうだ。まず業務側と向き合って5層のタスク分類体系を設計し、その体系に沿ってデータをラベル付けする。問題は、その体系は紙の上では完璧でも、ラベル付けの段階で大量のサンプルが分類できないことに気づき、最終的に強いて押し込むか捨てるかになることだ。現実的な道筋はその逆だ。まず既存のシステムでしばらく動かして、結果の中からモデル間の性能差が最大になるサンプルを観察し、差がはっきりした領域だけをクラスタリングして、データ駆動のタスク区分を得る。
具体的な手順はこうだ。実リクエストを数百から数千件サンプルして、候補モデルを2〜3個ずつ一度ずつ走らせ、品質スコアとコストを記録する。この1ステップで3つのものが同時に得られる。タスククラスタ、クラスタ内のモデル間、そして再利用しつづけるリグレッションベースラインだ。コストは一度きりの2倍呼び出しで、通常は正式リリース後の節約で回収できる。
このステップには見落とされがちな価値がもう1つある。ルーティングという行為そのものに価値があるのかどうかを教えてくれるからだ。あなたのトラフィック分布で、頭部モデルと尾部モデルの単位コスト差が1.5倍にしかならず、品質差の源泉が主にプロンプト側にあるなら、ルーティングの利得の上限は最初から固定されている。この場合はプロンプトエンジニアリングと制約付きデコードに労力を注ぐべきだ。この判断に最も適したタイミングは冷スタートのときであって、リリース3か月後ではない。
4.2 shadow traffic と active learning:評価とラベル付けを紐づける
冷スタート期のラベル付け予算はきわめて限られているので、1回のラベル付けを無駄にせずに使う必要がある。shadow trafficがもっとも効果的だ。本番リクエストは従来どおりの戦略で実行しつつ、同時に「対照モデル」をもう1つ呼び出して、両者の差を記録する。差が大きいサンプルは自動的にラベル付け待ちキューに入り、差が小さいものはそのままスキップする。本質的には、active learning の不確実性サンプリングを本番システムの中で回している。
# 影子评估 + 主动学习队列:把标注预算集中在决策边界附近
def on_request(req, primary_result):
# 1) 只在“可能有信息量”的请求上付出对照调用的成本
if request_value(req) < SHADOW_MIN_VALUE: # 低价值请求不值得花对照成本
return primary_result
alt = call(alias=SHADOW_ALIAS, request=req) # 通常是比 primary 更强的模型
# 2) 用便宜的判据估计两者差距,而不是每次都人工确认
gap = judge(alt.text, primary_result.text)
# 3) 只有边界附近的样本才消耗人工标注预算
if UNCERTAIN_LO <= gap <= UNCERTAIN_HI:
labeling_queue.push(req, primary_result, alt, gap)
# 4) 但无论是否标注,决策日志必须落盘(第十节要用)
decision_log.append(req, alias=SHADOW_ALIAS, gap=gap)
return primary_result
この経路には重要な性質がある。ラベル付けデータとルーティングの効果の証拠を同時に生み出す。第4ステップで記録される意思決定ログに、対照呼び出しの結果を加えたものは、オフラインで再生できる評価セットになる。3か月後に分類器を交換しても、モデルを交換しても、しきい値を調整しても、このログ上で直接再生して比較できる。再サンプリングは不要だ。ルーティングシステムのうち投資対効果がいちばん高い土台であり、他にない。
4.3 冷スタート期にもっとも犯しやすい3つの誤り
- 開発セットの分布で学習し、本番の分布でリリースする。もっともよくある兆候は、オフラインの分類精度が90%でも、本番では大量に誤る。原因は過学習ではなく、分布そのものの違いであることが多い。本番にはより長い入力、より奇妙な複数ターンの履歴、そして上流で切断されたコンテキストが含まれる。
- 「正しい」の定義を無視する。このリクエストを「よく処理した」とはどういう意味でいうのか。形式が正しいだけで十分なのか、事実が正しいだけで十分なのか、業務の受け入れを通すのか。冷スタート期に定義を確定させないと、その後のすべての定量化が意味を失う(第9節でも述べる)。この定義は直感ではなく、業務側の受け入れ基準であるべきだ。
- 複雑なルーターを早く投入する。200件のサンプルで訓練した3分類器の複雑さは、3000件のサンプルで作った kNN とは比較にならない。サンプル数が足りない段階では、kNN とルールの組み合わせがほぼ最良のコストパフォーマンスになる。
五、能力マトリクス:腐らせないための保守のしかた
5.1 マトリクスに含めるべき次元
能力マトリクスは「どれを選ぶか」という問題を、データ検索で答えられる状態にするための前提になる。腐る根本原因は、能力を記録するだけで適用範囲、証拠の強さ、有効期間を記録しないことにある。実用的なマトリクスは少なくとも以下の次元を必要とし、そのすべてにメタ情報を伴う。
| 次元 | 役割 | メタ情報の要件 |
|---|---|---|
| タスク族 | マトリクスの行。通常8〜20個で、4.1のデータ駆動クラスタリングから得る | 定義、前回の検証時刻、サンプル数 |
| 品質スコア | そのタスク族における相対的な性能。信頼区間を伴ってよい | 評価セットのバージョン、評価時刻、有意差の有無 |
| コストプロファイル | 入力/出力 token の単価、典型的な長さの分布、キャッシュヒット率の想定 | 価格のバージョン、有効開始時刻(値下げにはタイムスタンプを付ける) |
| レイテンシプロファイル | TTFT と TPOT の分位数。典型的な入力長さでの値を含む | 測定リージョン、測定時刻、負荷条件 |
| 制約能力 | コンテキストウィンドウ、最大出力、構造化出力、ツール呼び出し、マルチモーダル、バッチ処理のサポート | ベンダーのドキュメントバージョン、または実測による検証日 |
| 供給属性 | レート制限、リージョン可用性、モデルエイリアスの安定性、デグレード用チャネル | クォータのティア、SLA 条項、有効開始時刻 |
| コンプライアンス属性 | データ保持方針、訓練利用条項、リージョンのコンプライアンス等級 | 契約のバージョン、再確認日 |
5.2 点数表から能力フィンガープリントへ
従来の手法は、人が点数表を保守し、オペレーターが四半期ごとに更新するものだった。この表の問題は不正確さではなく、更新周期が現実より1桁遅いことだ。モデルの小バージョン更新、価格調整、リージョンの障害、ベンダーのポリシー変更は、発生頻度が週単位、ときどき日単位なのだから。
より持続可能なのは、マトリクスを能力プローブ(capability probe)に分解することだ。自動化された、ごく小さいプローブ要求の集合で、マトリクスの各属性に1票ずつ投げる。コンテキストウィンドウは極長の入力1回で切り詰めを誘発して検証し、構造化出力は厳格な JSON Schema の契約で検証し、マルチモーダルは画像を1枚送って引用できるかを確かめ、レート制限は429の出現頻度から推測する。これらのプローブはスケジュールジョブに載せられる。得られるのはタイムスタンプ付きの能力スナップショットであり、静的な表ではない。
重要な設計はプローブと評価の分離にある。プローブが検証するのは「できるかどうか」(能力の境界。硬い事実で、自動化しやすい)で、評価が測るのは「好不好」(品質スコア。業務側のラベルが必要で、更新もより遅い)。多くのチームが両者を1つの表に混ぜてしまい、価格の変更のたびに品質評価までやり直すことになり、そのうちマトリクスは更新されなくなる。二種類のデータを分けて保存することが、マトリクスを長生きさせる前提である。
5.3 腐化の4つの形態と検出手段
マトリクスの腐りは「明白な誤り」として現れるのではない。4つの隠れた形態がある。
- ベンダーの静かな変更:小バージョン更新、リージョン展開の設定調整、デフォルトのサンプリングパラメータの変化。対策は主要なタスク族にゴールド標準リグレッションセット(数十本の固定サンプル、変更のたびに自動実行)を残し、品質スコアがしきい値を超えて落ちたらアラートを鳴らすこと。
- 価格と実効使用量の乖離:マトリクスの token 単価は正しいが、実際の平均出力長は変化している(プロンプトの反復、few-shot の増加)。対策はコストプロファイルに理論値ではなく実測の分位数を保存し、コストの帰属を業務機能に紐づけること。
- タスク分布のドリフト:新機能のローンチが新しいリクエスト種別を生み、マトリクスにはその行すら存在しない。症状はルーティング判断の分布に、マトリクスの範囲外の新しいクラスタが現れること。対策は意思決定ログのクラスタリング監視で、新しいクラスタが出て規模がしきい値を超えたらマトリクスの追記を発動させる(これも第10節のドリフト統制の一部である)。
- 期限切れの未マーク:3か月間どの判断にもヒットしなかった能力の記録は、昨日の検証済みのものと同じくらい信頼できるように見える。対策は各行に TTL を付け、期限切れの行は判断時にそのままスキップして再評価を発動させること。使い続けるのではない。
5.4 マトリクスの実行時の実体:エイリアスと能力セット
マトリクスは最終的に、実行時に参照できる構造に着地しなければならない。推奨するのは2段の抽象だ。上層が能力エイリアス(ビジネス側はそれしか認識しない)、下層が具体的なモデルとチャネル(設定で決まる)。ビジネス側のコードが要求するのは「長い文書を読めて、かつ JSON を安定して出力できる中程度のコストのモデル1台」であって、「特定の型番」ではない。
# 能力别名 -> 能力集 -> 具体渠道。别名是路由的寻址单位,不是模型名。
CAPABILITY = {
'fast_chat': {'max_in': 128_000, 'structured': True, 'tier': 'cheap'},
'deep_reason': {'max_in': 200_000, 'structured': True, 'tier': 'frontier'},
'vision_doc': {'max_in': 32_000, 'vision': True, 'tier': 'mid'},
'long_extract': {'max_in': 400_000, 'structured': True, 'tier': 'mid'},
}
# 一个别名可绑定多个渠道:能力相同,供给与价格不同,由路由实时挑选
ALIAS_BINDING = {
'fast_chat': ['vendor-a/mini-2', 'vendor-b/flash-2', 'vendor-c/turbo'],
'deep_reason': ['vendor-a/frontier', 'vendor-b/reasoner'],
}
def candidates(alias, ctx):
"""查能力集 -> 过滤硬约束 -> 再交给策略层按质量/成本/负载打分排序。"""
cap = CAPABILITY[alias]
for c in ALIAS_BINDING[alias]:
if c.max_input_tokens < ctx.tokens: # 上下文窗口不够,直接出局
continue
if not ctx.allowed_region(c.region): # 合规硬约束
continue
yield c
この抽象がもたらす直接的な利点は3つある。ベンダーを交換してもビジネス側のコードは変わらない。同じ能力に対して故障移送できるチャネルを持てる。コストとレイテンシの戦略層が具体的なモデルから分離され、独立に進化できる。代償は1段ぶんの追加の解決オーバーヘッドと、「ビジネス側が実際にどのモデルを使ったか知らない」ことで生じる調査の手간의低下である。後者は意思決定ログの完全な経路で補える。第10節で扱う。
5.5 手動オーバーライド:必要だが、有効期限を必ず持たせること
完璧な自動化にも、即座に人手介入が必要な瞬間は必ず来る。あるモデルに一時的な障害が出たので一時的にブロックする、今回のキャンペーンのためにあるエイリアス全体を最高構成に切り替えたい、あるベンダーでセキュリティ問題が出たので即座に取り下げる。能力マトリクスはこの種の手動オーバーライドをサポートしていなければ、選び道はコードを書き直してリリースし直すことだけで、数時間から数日かかる。
ただし手動オーバーライドはルーティングシステムの腐化の第1の原因だ。すべての自動検証を迂回してしまうからだ。オーバーライド項目は3つの規律を守らなければならない。すべてのオーバーライドに責任者と理由をつける(さもないとなぜこのモデルがブロックされたのか誰も知らないし、解除する者もいない)。すべてのオーバーライドに期限をつける(一時ブロックは障害回復とともに自動的に失効させるべきで、設定に永遠に残してはいけない)。すべてのオーバーライドを意思決定ログで追跡できるようにする(さもないと問題が生じたときに策略判断と人手介入のどちらによるのか区別がつかない)。
もう1つ経験がある。手動オーバーライドの回数自体が健全性の指標になる。あるエイリアスが毎月何度も人手介入されているなら、自動の決定戦略が現実と大きく乖離している。オーバーライド項目を足し続けるのではなく、戦略に戻って直すべきだ。オーバーライド項目が増え続けるマトリクスは、運用手段でモデリングの問題を隠している。
六、コストとレイテンシの予算をハード制約にする
前述のとおり、デグレードは最適化フレームの中で自然に導かれる。これを実際に起こすには、予算が第一級の市民でなければならない。各業務先の設定に散らばったコメントでは足りない。予算には少なくとも3層ある。
def resolve_plan(request, budgets, matrix):
# 1) 先按硬约束过滤:上下文窗口、合规、速率限制、实时负载
pool = [c for c in matrix.candidates(request)
if c.region_allowed(request)
and not c.rate_limited(now)
and c.load_p95() < budgets.max_queue_ms]
# 2) 在候选里按“净价值”排序,而不是按单价排序
def net_value(c):
expected_cost = estimate_cost(c, request) # 含缓存命中与典型长度
p_success = matrix.success_rate(c, request.task_family)
retry_cost = (1 - p_success) * expected_cost * RETRY_FACTOR
latency_penalty = c.p99_ms(request) / budgets.ms_value_ratio
return (p_success * request.value) - (expected_cost + retry_cost + latency_penalty)
ordered = sorted(pool, key=net_value, reverse=True)
# 3) 预算不够时,不是硬切,而是从次优解里挑“质量损失最小”的那个
for c in ordered:
est = estimate_cost(c, request)
if est <= budgets.per_request:
return Plan(model=c, mode='primary', est_cost=est)
return Plan(model=ordered[-1], mode='degraded',
est_cost=estimate_cost(ordered[-1], request))
このコードでもっとも注目すべきは並べ替えの関数が単価ではなく純価値を使っているところだ。単価順に並べるシステムは、安いモデルに一貫して有利になり、成功率の違いが生むリトライコストを無視する。純価値順に並べると、「安いけど成功率は6割のチャネル」と「少し高いけど成功率は9割5のチャネル」の間で正しい選択をする。この1箇所の変更で、通常はルーティングの実際上の利得を設計上の期待値の近くまで引き戻せる。
6.1 予算の3層:リクエスト単位、セッション単位、日/月単位
リクエスト単位の予算がいちばん実装しやすい。1回の呼び出しにいくら使ってよいかだけで、max_tokens とモデル選択にそのままマップできる。ステートレスな API に向いている。セッション単位の予算は複数ターンや Agent のシーンを扱う。1つのタスクでモデルを20回ほど呼べるが総予算は固定、という状況だとルーターがすべきことは配分だ。予算を重要な工程に集中させる(たとえば検索と計画に60%を先に使い、最終生成に40%を残す)のであって、1工程ずつ均等に分けるのではない。この考え方はテスト時の計算拡張における予算配分と同じ問題である。
日/月単位の予算は組織の制約であり、クォータの割り当てと超過時の処分戦略が必要になる。実践におけるよくある誤りは、日予算をリクエスト数に応じて各業務へ線形に配分してしまうことだ。結果として低価値のトラフィックと高価値のトラフィックが同じ枠を得る。より良いのは業務ごとに独立した日ごとの上限額を与え、余った分は繰り越せるようにすることだ。そうすれば予算は実際に使われている場所へ自然に流れていく。
6.2 予算を超えたら先に何を削るか
予算が尽きたときに単純にサービスを拒否するのでも、単純に最も安いモデルにデグレードするのでもない。正しい順序は、まず出力長を削り、次に推論の深さを削り、ようやくモデルを替える。理由はこうだ。出力長が品質に与える限界的な損失はもっとも緩やかで(ユーザーが1文少なく読む損失は、答えが1文間違っている損失より小さい)、推論の深さ(思考ステップ数、サンプリング回数)は中〜高価値のリクエストで損失がはっきり現れるのに対し、モデルの変更はもっとも粗い手段だから。
見落とされがちな予算項目がもう1つある。コンテキスト予算だ。長い会話のシーンでは、入力 token の増加は2次的になる。各ターンで完全な履歴をまるごと再送するためだ。コンテキストの切り詰めや prefix caching の再利用をしないと、請求書はターン数に対して非線形に増えていくが、このとき「もっと安いモデルに替える」はまったく効かない。入力側のコスト最適化とモデルルーティングは別の話だが、必ず同じ予算ダッシュボードで見なければならない。
6.3 業務の価値を予算に換算する:プロダクトが必ず関与しなければならないただ1つのステップ
これまでのすべての予算パラメータは、最終的に1つの問題に収束する。正しい回答1回分の価値はいくらか。これは技術的な問題ではないが、ルーティングシステム全体の最適化方向を決めており、避けられない。答えが「サポート応答1回の価値はおよそ人手処理1回のコストの100分の1」なら、予算上限はきわめて明確になり、ルーティング戦略は自然に低コスト案へ傾く。もし1件の誤判定が数百万規模の契約損失に直結するなら、予算は事実上の制約にならないほど緩く、戦略は品質優先に傾く。
実務では正確な数値よりも段階分けのほうが現実的だ。リクエストを業務の価値で3〜5段階に分け、段階ごとに予算の範囲とレイテンシの範囲を与える。段階分けがよいのは、それが十分に粗いので安定していて、プロダクト担当者がそのまま決められ、同時に十分に有用だからだ。ルーティングシステムは段階の間の選択だけ扱えばよく、連続的な価値のシグナルを扱う必要がない。ほとんどのチームはここから始めるべきで、精密な価値回帰モデルの構築に着手すべきではない。そのモデルに必要なデータ量と保守コストは、たいてい利得を远远に超える。
プロダクトの関与が必須な2つめは、デグレードの可視化要件を明示することだ。同じ「答えが短くなった」でも、雑談のシーンではどうでもいいデグレードでも、法律文書のシーンでは受け入れられないかもしれない。この要件は業務側しか出せない。実装コストはきわめて低い。レスポンスに1つのフラグを載せ、UI で表示するかどうかを決めるだけだ。ただ、事前に言っておかないと、リリース後に「デグレードされた答えが短すぎる」というユーザー苦情として爆発することが多い。
3つめは「不確実」に値段をつけることだ。ルーティングシステムの信頼度が、アップグレードすべきかどうかを判断するには足りないとき、システムの既定の挙動はどうすべきか。保守的な選択はアップグレードする(品質を守る、成本を犠牲にする)で、攻撃的な選択はそのまま通す(コストを守る、品質を犠牲にする)だ。この選択に技術的な正解はない。業務が2種類の誤りをどれだけ許容できるかによって決まり、実装者に既定の分岐を残すのではなく、明示的に宣言して設定すべきだ。後者は通常たまたまもっとも保守的な分岐なので、その結果システムはプロダクトの決定なしに自動的に高コストの経路を選ぶことになる。
七、デグレードチェーンとフォールバック:順序が成否を分ける
7.1 デグレードは「もっと悪いモデルに替える」ことではない
デグレードチェーンでもっともよくある誤った設計は、能力の弱い順に並べたモデルのリストで、問題が起きたら順番にたどっていくというものである。この設計は実験室では成り立つが、本番では3つの形で破綻する。
- 意味的に等価でない:「40万 token を読める」から「12万 token を読める」へデグレードすると、ユーザーが得るのは暗黙に切り詰められた答えになり、明示的にエラーにするより悪い。デグレードの各段階は意味的に等価でなければならない。そうでなければそれはデグレードではなく欺瞞だ。
- レイテンシが積み上がる:3段のデグレードがすべて失敗した場合、総レイテンシは3つの合計にリトライのバックオフを加えたものになり、クライアントのタイムアウトを確実に突破しうる。デグレードチェーンには各段が自分の時計で計算するのではなく、独立した全体の期限が必要だ。
- カスケード的な雪崩:主チャネルが揺れる → 全量がバックアップチャネルに流入 → バックアップチャネルが倒れる。デグレードチェーン自体が故障の増幅器になる。
もっと安定する設計は、障害の種類ごとに経路を分けることで、汎用のチェーンを1本用意することではない。
# 降级路径按“请求形态 + 故障类型”分别声明,而不是一条通用瀑布。
PATHS = {
# 首选语义等价替换:换渠道、换区域,能力不变
'soft': [('channel', 'next_best_channel'), ('region', 'other_region')],
# 能力收缩:必须同时收紧任务范围,避免静默截断
'shrink': [('reduce_scope', 'drop_optional_refs'),
('reduce_output', 'cap_tokens_to_512')],
# 最后才降级到规则/模板,这类答案必须在 UI 上明确标记
'terminal': [('deterministic', 'rule_based_extract'),
('user_visible', 'ask_user_clarify')],
}
HARD_DEADLINE = {'total_ms': 3000, 'per_hop_ms': 700}
def run(req, node):
path = PATHS[node.kind]
for hop in path:
if elapsed() > HARD_DEADLINE['total_ms']:
break # 整体时限优先于“按计划走完降级链”
try:
return invoke(hop, req) # 每个 hop 自身也有超时与取消
except Retryable as e:
observe(hop, e) # 失败计入该 hop 的健康度
return Terminal(user_visible_fallback(req))
7.2 フォールバック:再試行可能なものと不可能なものを分ける
フォールバックの第一の規律はまずエラーの分類を行い、それから行動を決めることだ。次の3分類にそのまま落とせる。どの分類も対処はまったく異なる。
- 再試行可能かつ冪等:429 のレート制限、接続タイムアウト、5xx。対処はチャネルを切り替えることと指数バックオフ。ただしバックオフはチャネル単位でなければならず、全体単位であってはいけない。さもないと1つのチャネルのレート制限が全トラフィックを引きずる。
- 再試行不可能:コンテキスト長超過、内容ポリシーによる拒否、不正なパラメータ。対処は即座にデグレードへ移ることで、再試行は絶対に行わない。この種のエラーは100回再試行しても結果は同じで、再試行のたびにコストを燃やすだけだ。
- 再試行可能だが冪等ではない:ツール呼び出し、書き込み操作、外部サイド効果を伴う Agent のステップ。対処はベンダーを替えて実行することで、必ず冪等キーを添えなければならない。さもないと「タイムアウトしたはずが実際には成功していた」という呼び出しが重複したサイド効果を生む。
3番目がもっとも見落とされやすい。ベンダー横断のフォールバックは、1回の業務操作が2つの異なるモデルによって実行されることを意味する。ツール呼び出しの規約もパラメータの書式も出力フォーマットも、すべて違う。解決策はツール呼び出しのプロトコルをゲートウェイ層に固定することで、すべての候補モデルに同じ契約に従わせることだ。各モデルのツール定義がネイティブに互換だと期待するのではなく、共通層を別に用意することになる。統一ゲートウェイ(LiteLLM、Portkey、Envoy AI Gateway のようなもの)の真の価値もここにある。その中核的な貢献は数行のコードを節約することではなく、ベンダー横断の切替にセマンティックに整列した共通層を提供することにある。
7.3 順序設計の4つの原則
- 等価をデグレードより優先する:どの段階でも、デグレードする前に能力が等価なチャネルをすべて試す。チャネルの切り替えは品質に影響しないので、デグレードとして扱うべきではない。
- キャッシュをモデルより優先する:セマンティックキャッシュや結果キャッシュのヒットは、コストもレイテンシもゼロの「最適なモデル」だ。チェーンの先頭に置くべきである。ただし、キャッシュの類似度しきい値が十分に保守的で、「完全に同一」と「単に類似」を区別できることが前提になる。あるタスクでは、誤ったキャッシュヒットはデグレードより危険だ。
- 速く失敗するほうが、遅く失敗するよりよい:ヘルスチェックとサーキットブレーカーは、スライディングウィンドウのエラー率と P99 レイテンシに基づくべきで、サーキットブレーカーの復帰は固定のクールダウンではなく半開きのプローブで行うべきである。固定のクールダウン時間は、トラフィックの変動の中で破綻する。早すぎれば繰り返し叩き落とし、遅すぎれば回復が遅すぎて長時間利用不可になる。
- デグレードは見えるようにしろ:すべてのデグレードはレスポンスにフラグを載せ、意思決定ログに落とす。このルールはプロダクトの潔癖のように見えるが、実際には命を守る。ユーザーが「今日は答えの質が悪い」と苦情を言ってきたとき、「デグレードが起きたのか、どれだけ続いたか、誰に影響したか」に答えられる唯一の手段がこの経路にあるからだ。
7.4 演習:デグレードチェーンは実際に切れたときにしか検証できない
デグレードチェーンには周知の性質がある。すべてが正常なときは完全にデッドコードになるので、その正しさは通常の利用では検証できない。それなのに、これがシステムにとって最も難しい瞬間に頼る唯一の支えになっている。より悪いのは、チェーン上の多くの分岐がコードを書いてから一度も実行されたことがないことで、本当に必要になったときに初めて、あるデグレードモデルのタイムアウト設定が主モデルと同じ(つまりこちらもタイムアウトする)とか、あるデグレード経路が依存するキャッシュがコールドパスで一度も埋められていなかったことに気づく。
したがってデグレードチェーンには定期的な強制演習が必要であり、チェーン全体ではなく1ホップずつ行わなければならない。1ホップずつとは、ある1つのホップだけをエラーを返させ、後続の各ホップの挙動が期待どおりか(次の経路に進んだか、誤ってリトライしたか、エラーをそのまま黙ってユーザーに返したか)を確認することだ。チェーン全体の演習では「全部失敗したらどうなるか」しか検証できず、「2番目のホップが失敗したらどうなるか」は特定できない。
演習で人為的に注入する障害は、少なくとも4分類をカバーする必要がある。接続層の失敗(接続タイムアウト、接続拒否)、サービス層の失敗(エラーステータスコードを返す)、挙動層の失敗(成功を返すが中身が空、あるいは書式が間違っている)、そして遅い応答(返さない。ただレイテンシを超えているだけ)。最後の1種類がもっとも抜けやすく、もっとも本番事故を起こしやすい。サーキットブレーカーはエラー率しか集計せず、「遅くなったがエラーではない」状況に鈍感だからだ。遅い応答ほどシステム全体を倒す種類でもある。
演習の結論は1つの表にまとめておくべきだ。各ホップの期待される挙動、実測された挙動、そしてその差分。この表には副作用もある。「デグレードチェーン」がコードの塊からレビューできる仕様書へと姿を変えるのだ。この表があればコードと仕様が一対一で対応する。実際には、新人がデグレードのロジックを追えないことが、もっとも頻出する引き継ぎの障壁になっている。
八、Cascade:安いモデルで先に試して、必要なら上げる
8.1 仕組み:2段階+1つの判定器
cascade 戦略の考え方は単純だ。まず安いモデルで素早く試して、成功したらそこで終わり、失敗したらそれまでの結果を携えて強いモデルへ上げる。この仕組みは学術上でも多くの分布に対して有効だと示されている。多くのタスクの難易度分布が双峰的だからだ。リクエストの大半はどのモデルにとっても簡単で、残る一小部分だけが本当に能力を必要とする。cascade はこの2つの場合のコストを分けている。
async def cascade(req, budgets):
# 阶段一:便宜模型 + 收紧的输出上限(cascade 的收益来源之一)
draft = await call(alias='fast_chat',
request=req,
max_tokens=budgets.draft_tokens, # 通常是完整预算的 20~40%
reasoning_effort='low')
# 判定器:这是整个策略的成败所在,三种实现可靠性递减、成本递增
verdict = verify(draft, req) # 1) 确定性判据:schema 校验 / 规则 / 引用校验
# 2) 自检:让同一模型回答“此题是否已被完整回答”
# 3) 独立小判别器:另一次小模型调用做二分类
if verdict.ok and verdict.confidence >= budgeted_threshold(req):
return Outcome(text=draft.text, tier=0, cost=budgets.draft_cost)
# 阶段二:升级,并携带阶段一结果作为上下文(这一步会额外花 prefill,
# 是 cascade 最常被忽略的隐性成本)
final = await call(alias='deep_reason',
request=req,
context=verdict.carry_over(draft))
return Outcome(text=final.text, tier=1, cost=budgets.draft_cost + final.cost)
この構造のなかで最も重要で、しかももっとも忘れられがちな詳細がある。第2段階の入力には第1段階の出力も含まれる。つまり「節約できたリクエスト1件につき強いモデルの prefill を1回分節約する」と「アップグレードしたリクエスト1件につき長いコンテキストの prefill を1回分余計に払う」という2項目を同時に収支として計算する必要がある。ドラフトが長いのにアップグレード率も高いなら、cascade のコストは直接強いモデルを呼ぶより高くなることがある。だからこそ、ドラフトの上限を制御することは選択肢ではない。
8.2 どんなときに有効で、どんなときに無効か
| 条件 | 理由 | 推奨 |
|---|---|---|
| 難易度の分布が双峰で、簡単な側の山が広い | 大半のリクエストが1回で当たり、アップグレードは長い裾だけ | もっとも理想的。アップグレード率は通常2割以下に抑え込める |
| 難易度の分布が連続的で、はっきりした山がない | ほぼ全てのリクエストがしきい値の付近にあり、アップグレード率は100%に近づく | 利得はゼロ、負になることもある。直接ルーティングに切り替えろ |
| 判定器そのものをタスクより難しくしている | 判定器は誤判定で通す(品質崩壊)か誤判定で上げる(利得ゼロ)のどちらか | まず判定器を正しく、単純に保つ。しきい値の調整で覆い隠すな |
| レイテンシ予算が極端に厳しい | 第1段階+判定+第2段階は直列なので、P99 が明確に悪化する | cascade はリアルタイムでないバッチ処理やオフラインタスクにだけ使え |
| 出力長を制御できない | ドラフトが長すぎると、アップグレード時の prefill コストが節約額をすべて食う | max_tokens の上限を強制し、ドラフトは切り詰めて引き渡す |
| 強いモデルにベンダーリスクがある | cascade の最大の売りは強いモデルが少数のリクエストでしか呼ばれないことで、リスク耐性が高い | cascade でもっとも過小評価されている利得がこれだ |
表の2行目は立ち止まって考える価値がある。アップグレード率は cascade が実施に値するかどうかを決める最重要の単体指標である。オフライン再生でアップグレード率が50%を超えたなら、この戦略は数学的に儲からない。全リクエストに2回分のモデルコストを払いながら、強化モデルの呼び出しを節約できるのは半分にも満たないリクエストだけだからだ。平均コストを見るより信頼できるのは、これをリリース前のゲート条件にすることだ。平均コストは「簡単な山」の比率に隠されるからだ。
8.3 落とし穴のチェックリスト
- 偽陽性のアップグレード:判定器が過敏で、アップグレード率が80%まで跳ね上がり、利得がすべて消える。症状はコストが下がらないばかりか高まることだ。診断はアップグレード率を日常ダッシュボードの最重要指標にすること、そして判定器に混同行列の監視をかけること。
- 偽陰性の通過:判定器が問題を通したままにして、下流に届けられ、下流には誤ったことを教えてくれるシグナルが一切ない。cascade でもっとも危険な失敗で、アラームが鳴らず、ユーザーの苦情の中にしか現れない。唯一の防御は判定器の見逃し率をリグレッションテストに組み込み、プロンプトやモデルが変わるたびに再実行すること。
- 選択バイアスの評価:「小さいモデルがすでに失敗した」サンプルだけでアップグレードの利得を評価すると、効果を系統的に過大評価する。評価は実際の分布の全量で走らせなければならない。
- ルーティングのフラつき:cascade の判定結果はランダムサンプリングに依存するので、同じリクエストが2回で違う階層を辿ることがある。すると A/B テストがノイズになる。「同じモデル」が「同じ出力」を意味しなくなるからだ。サンプリングシードを固定するか、実験設計の中でこの事実を認めるかのどちらかだ。
- cascade をデグレードチェーンと混同する:両者は方向が逆だ。cascade は日常的なコスト最適化(安い優先)で、デグレードチェーンは異常時の可用性の保険(可用性優先)だ。混ぜると、正常時のトラフィックが障害時に誤ってデグレードされてしまう。
8.4 強化学習・テスト時計算との境界
cascade の考え方とテスト時の計算拡張、そしてオンライン強化学習は、隣り合いながら別の問題を解決している。境界を引いておく価値がある。実践ではよく混同されるからだ。テスト時の計算拡張は、単一モデルの内部で思考ステップ数を増やし、並列サンプリングを行い、検証器による再順位付けによって品質を稼ぐものだ。コストの単位は「同じモデルのより多くの token」になる。cascadeはモデルの間で選択する。コストの単位は「安いモデルの少しの token と、たまに起きる高価なモデル呼び出し」だ。両者は組み合わせられるが、最適化の目標は違う。前者が調整するのは品質と計算量の曲線、後者が調整するのは品質と通貨の曲線である。
オンライン強化学習でルーティングするとの境界はさらに微妙だ。ルーティング自体を最適化対象の戦略として扱い、フィードバック(ユーザーが受け入れたか、タスクが成功したか)で学習すれば理論上は最適だが、実践ではほとんど成功しない。理由は3つある。フィードバックの信号が遅延してノイズが大きく、ルーティング判断の良否は数週間後に事業指標として初めて現れることがある。行動空間は離散で結果を取り消せず、1回選択を誤ればコストは即座に発生し利得は将来にある。探索コストが高すぎて、本番でのランダム探索は一部のユーザーにわざと劣悪なサービスを提供することを意味し、多くの業務で許容できない。
そこで現実的なのは、オフラインのペア選好データでルーターを学習し、オンラインでは制約付きの貪欲選択だけを行うことで、探索を完全にオフライン環境の中に閉じ込めることである。本質的には contextual bandit のオフライン解法だ。学習段階では全候補のログを使える(IPS あたりの重み補正で分布の不一致を扱う)。オンライン段階では戦略を実行するだけで探索しない。これは3.4節で触れた学習型ルーターと同じ手法で、学習目標とデータの出所を少しはっきりとさせただけのものだ。
注目に値する新しい方向として、ルーター自身の信頼度をリクエスト単位の推論予算の配分に使うということがある。ルーティングの確率が高ければタスクは簡単なので、思考ステップ数を少なくする。確率が低ければタスクは難しいので、より多くの思考ステップ数を渡す。これによって「モデルを選ぶ」と「計算量を選ぶ」という2つの判断が1つになる。cascade の考え方を単一モデルの中で一般化したものと見なせる。前提はあくまで古い問題だが、ルーターが十分に信頼されていなければ、予算配分は本来もっと増やすべきでないリクエストに計算量を足してしまう。
九、ルーティングの効果の定量化:勘に頼らずにやるには
冒頭の問いに戻る。ルーティングの正しさは、それ自体からは観測できない。これを定量化するには外部の基準系を導入する必要がある。もっとも実用的な基準は oracle だ。すべての評価サンプルについて、すべての候補モデルをオフラインで走らせているなら、「このリクエストの最高の結果」はすでに分かっている。ルーティングの価値とは、その上界にどれだけ近いかにほかならない。
import numpy as np
def evaluate_router(log, candidates):
# log 里每条样本有:每个候选模型的质量分 q[m]、成本 c[m]、时延 t[m]
# oracle = 成本达标约束下质量最高的那个候选
gain, regret, cost = [], [], []
for row in log:
feasible = {m: r for m, r in row.runs.items() if r.cost <= row.budget}
if not feasible:
continue
best = max(feasible.values(), key=lambda r: r.quality)
chosen = row.decision
# 质量 regret:相对上界掉了多少
regret.append(best.quality - chosen.quality)
# 成本节省:相对“全用最强模型”省了多少(必须扣掉重试)
gain.append((row.strongest_cost - chosen.total_cost) / row.strongest_cost)
cost.append(chosen.total_cost)
return {
'quality_regret_mean': float(np.mean(regret)), # 越小越好
'quality_regret_p95': float(np.percentile(regret, 95)),
'cost_saving_vs_best': float(np.mean(gain)),
'abs_spend_per_success': float(np.sum(cost) / len(log)), # 比均值更诚实
}
この指標のグループには、紛らわしい点がいくつかある。ルーティング精度はもっとも悪用され、しかも情報量がもっとも乏しい指標だ。「本当に最適なモデルを選べた」ときにしか成り立たないが、マルチモデルのシーンでは並列した最適치가複数存在することがある。もっと悪いのは、これが利得と単調な関係にないことだ。精度が80%から95%に上がっても、コストは何も下がらないことがある。残った5%がいちばん利得の大きいリクエストだからだ。したがって主指標は regret である。「自分のほうがどれだけ損をしたか」に直接対応する。
9.1 oracle で上界を定義し、oracle で対照群も定義する
oracle の価値は regret を計算することにとどまらない。実践でもっとも有用的な3つの対照群がある。すべて最強モデルを使う(品質の上界、コストの上界)、すべて最安モデルを使う(コストの下界、品質の下界)、現在の本番戦略(ベースライン)。あなたのルーターが証明すべきなのは、上界に十分近いこと、そして現状より大幅に節約できていること。3本の線を1枚のグラフに描けば利得は一目でわかるし、このグラフは技術に詳しくない業務担当者にもそのまま読める。
さらにコスト-品質の Pareto カーブを別に描いておきたい。アップグレード確率のしきい値を少しずつ上げて、ルーターを1本の曲線に沿って滑らせ、「コスト対品質」の連続的なトレードオフを出力する。この曲線の存在がルーティングの議論全体の核心である。どこかの1点で運任せにしたのではなく、連続した空間の中で適切な動作点を選んだことを示す。業務側が本当に議論したいのは「その点」であって、「そのルーター」ではない。
9.2 実行可能な評価フロー
- 評価セットを構築する:意思決定ログからサンプルし、すべてのタスク族をカバーし、さらに境界サンプルを意図的にオーバーサンプリングする(ルーターの判断信頼度が低い部分)。一様サンプリングでは指標はよく見えるが、判別力がない。
- 全候補を走らせる:各サンプルを各候補モデルで1回ずつ走らせ、品質、コスト、レイテンシ、成功率を記録する。これが oracle データの出どころになる。コストは一度きりのもので、支払う価値がある。
- タスク族ごとに報告する:全体だけを見ない。ルーティングの利得はタスク族によって大きく異なり、総合指標は「ある族であなたは負け続ける決定をしている」という事実を覆い隠してしまう。
- Pareto カーブを描いて点を選ぶ:業務側と一緒に曲線上の動作点を選び、それを設定ファイルのしきい値として固定する。
- 本番後は監視し続ける:指標には成功リクエストあたりの単位コスト(リクエストあたりの単位コストではない)、regret の移動平均、アップグレード率、タスク族ごとのトラフィック分布のずれを含める。どれか1つでもしきい値を超えたら、手順1に戻る。
9.3 オフライン指標が嘘をつく理由
- 評価セットの分布が本番の分布と異なる。もっともよくあるのは評価セットで長い裾が過小評価されていることで、それこそがルーティング判断がもっとも不確実な場所である。
- 出力長の差を無視する。同じ「正解」でも、1つのモデルが50文字、別のモデルが800文字を出力すれば、コストは16倍も違う。品質評価では長さを制約として扱うか、長さ正規化をしなければならない。
- 失敗のコストを無視する。失敗率が3%から6%に上がると、全体の事業指標が明らかに悪化するのに、「平均品質スコア」だけを見るとほとんど気づけない。
- レイテンシの裾を無視する。平均レイテンシは改善しているのに P99 が悪化しているのは、対話型プロダクトでは純損失である。
- 評価自体の不確実性を無視する。LLM-as-judge には位置バイアスと長さへの偏りがあり、同じ一批の出力でも評価を1回変えるだけで結論が変わる。したがってすべての品質指標は信頼区間を伴わせ、2つの方策の差が有意でないときは結論を出さない。「改善して見える」だけの変更が、もっともよくある偽の利得の源である。
9.4 オフライン評価とオンライン評価の組み合わせ方
オフライン評価もオンライン評価も相手方の代替にはならない。失敗のしかたが異なる。オフライン評価の主な問題はサンプル選択バイアスだ。評価セットはある時刻、あるトラフィック構成のもとでサンプルされたもので、本番のトラフィック構成は常に変わり続ける。3月のデータから評価した最適なしきい値は、10月には新機能のローンチでまったく合わなくなっているかもしれない。ルーターが悪化したからではなく、最適化の対象が別のものになっただけだ。
オンライン評価の主な問題はノイズが大きく、周期が長く、コストもかかることだ。事業指標(コンバージョン率、投诉率、チケット解決率)の変化が現れるには、相応しいサンプル量と相応しい時間が必要だが、段階展開の期間中に別のものも直しており、指標の変化を要因分解できない。このため多くのチームが A/B テストを立ち上げて2週間動かしても有意差が出ず、「ルーティングには価値がない」と軽やかに判断してしまう。
したがって健全の組み合わせは、オフラインが高速な反復を担い、オンラインが最終確認を担うことである。オフラインは全候補ログで再生評価を行い、1日に何十組ものしきい値組み合わせを数秒で回せるので、日常的なパラメータ調整に使う。オンラインは shadow traffic で無リスクの検証を行い(決定はせず対照を記録するだけ)、継続的に動かし、コストは抑えられたうえで分布がずれたかどうかの信号を得られる。本当の動作点の変更にだけ、完全な A/B を通す。
実験設計について、別に記しておく価値のある経験がある。ルーティングシステムは A/B テストをやりにくくする。リクエストが異なるモデルにルーティングされる結果、「同じ実験群」のリクエストが実際には異なる処理を経由しており、効果が希釈されるからだ。緩和策はルーティング判断で層別化することで、まず「小さいモデルが処理したリクエストの部分集合で2群に差があるか」を見る、次に全体の差を見る。そうすれば実験の観測結果を説明できると同時に、「ルーティングはこの種のリクエストで本当に正しい判断をしているか」にも答えられる。A/B の層別化の次元とルーティングの判断の次元を揃えるのは、実験品質とルーティング品質を同時に高める設計である。
十、マルチモデルが併存する時代のバージョン管理と設定ドリフト
10.1 コードにモデル名を書かない
マルチモデルのシステムでもっとも高くつく技術的負債は、コード、設定、プロンプト、ドキュメント、フロントエンドの選択肢に散らばったモデル名である。さまざまな形で存在する。ハードコードされた文字列、変数の既定値、環境変数、プロンプトの中でたまたま書かれた「高推理強度で」、ある同僚のローカル開発設定まで。モデルをアップグレードするときにそれらすべてを見つけ出すのは容易くない。
# 路由配置是数据,不散落在代码里;配置本身要可版本化、可回滚、可审计。
@dataclass
class RoutingPolicy:
version: str # 语义化版本,改动必升
alias_budgets: dict # 别名 -> 每日美元额度
cascade: dict # 别名 -> {threshold, draft_tokens}
capability_overrides: dict # 人工覆盖,优先级高于自动指纹
fallback_order: list # 与故障类型绑定,不是一条通用瀑布
ab_buckets: dict # 灰度分桶,显式声明
def fingerprint(self) -> str:
# 配置指纹用于:变更检测、灰度一致性校验、回滚判定
return sha256(canonical_json(self).encode()).hexdigest()[:16]
設定即コードがもたらす利点はロールバックできることだけではない。もっとも重要なのは比較できることだ。フィンガープリントが同じなら2つのインスタンスの挙動は一致しており、これは段階リリースと設定ドリフト検出のどちらにも必須である。shadow インスタンス、再生システム、本番インスタンスの3つが同じフィンガープリントを共有していなければ、比較した差分はすべてノイズになる。
10.2 ルーティングの意思決定ログ唯一の事後依拠資料
意思決定ログに残すのは「どのモデルを選んだか」よりはるかに多い。最小限のフィールド集合はこれだ。リクエストフィンガープリント(マスクした入力の要約。重複排除と再生に使う)、そのときのルーティング判断と信頼度、候補プールと各候補の主要属性(当時の品質推定、コスト、レイテンシ、負荷、ハード制約で除外されたか)、実際に呼び出したモデルと実使用量と実レイテンシ、当たったデグレード段、そして戦略バージョンのフィンガープリント。
このログがあってはじめて、その後に必ず聞かれる問いに答えられる。先週の回答の品質が悪くなったのはなぜか。あるテナントのデグレード率が他の4倍になるのはなぜか。分類器を交換したあと本当によくなったのか、それとも問題を別のタスク族へ移しただけなのか。意思決定ログがなければ、第9節のすべての定量化はリリース前の一度きりの結論にすぎず、継続的に検証できない。コンプライアンス監査(あるデータが禁じられたリージョンに送られていないことの証明)でも同じくらい硬い要件である。
実践上の細部として1点触れておきたい。意思決定ログは、モデルを再呼び出しせずに再生できるようにしておかなければならない。全候補の出力を保存すると体積が巨大になるので、通常は判断と候補の属性だけを保存し(安価)、厳密な原因特定が必要なときにだけ出力を遡って埋める(高価)。この2層構造があれば、「再生のためにもう10倍ストレージを保存すべきか」という問いに対して理屈のある答えが得られる。
10.3 ドリフト検出とリリースのリズム
設定ドリフトには観測可能な信号が4つあり、すべてダッシュボードにしておくべきだ。
- 判断分布のずれ:各エイリアスが選ばれた比率、タスク族ごとのトラフィック構成。KL 散度か、単純なバケット比率の変化で前の時間窓と比較する。この信号がいちばん早く警告を出す。品質低下より通常1〜2週間早い。
- 実使用量とマトリクス予測の乖離:各エイリアスの実 token 使用量とコストプロファイルからのずれ。乖離が拡大し続けるなら、マトリクスの価格か長さの分布が陳腐化している。
- 品質のドリフト:ゴールド標準リグレッションセットを変更のたびに自動実行し、本番サンプリングの judge スコアの傾向と突き合わせる。
- デグレード率とアップグレード率:この2つの率はもっとも反応の速い健全性の指標だ。アップグレード率の急上昇は通常、分類器の失準か上位モデルの退行を意味する。デグレード率の上昇は供給側の問題か容量不足を意味する。
リリースのリズムについては、モデルの小バージョン更新の影響範囲は、多くの人の期待よりもはるかに大きい。インターフェースを変えずに、告知なしに、スタイルを変え、ツール呼び出しの形式を変え、特定言語の扱い方を変えることがある。だから「モデルをアップグレードする」と「ルーティング戦略をアップグレードする」は同じリリースプロセスを通すべきだ。まず shadow インスタンスで固定のリグレッションセットを回し、次に少量のトラフィックを段階的に出し、最後に全量にする。このプロセスは通常のコードのリリースと本質的に違いはない。違うのは、shadow インスタンスが走るのは実トラフィックのミラーだという一点だけで、それは統一ゲートウェイのルーティング機能にとても向いている。ゲートウェイは同時にリリースの経路と観測点だからだ。
見落とされがた最重要の規律がもう1つある。ルーティング戦略を複数の階層で変更できるようにしてはいけない。多くのシステムにはコードの既定ルーティングがあり、サーバ側の動的設定があり、運営バックエンドのスイッチがある。この3つが同時に存在すると、どれが有効になるかは優先順位ルールで決まるが、優先順位ルール自体にはテストのカバレッジがない。リリース事故の多くは「バックエンドで誰かがスイッチを1つ変えた」ことで起きる。単一の設定ソース(10節最初のコードにあるあの)に収束することが、この種の事故を根絶する根本的な手段である。
10.4 ゲートウェイ層の責務の境界:何を一元化し、何を業務側に残すか
マルチモデルを運用するときには、反対方向の行き過ぎも現れる。すべてをゲートウェイに押し込んで、最後には誰も触りたがらないブラックボックスになる。実行可能な分割の軸は「業務の意味と関係があるか」だ。ゲートウェイ層に置くべきなのは、業務と無関係で、かつベンダー横断で一致していなければならない仕組み、つまり統一の認証と鍵管理、リクエストのリトライとバックオフ、レート制限、セマンティックキャッシュ、テレメトリの計測点、構造化出力の契約検証、そしてチャネル横断の等価置換。これらの仕組みを業務側でそれぞれ書いても、一致させることも、集約的に観測することも不可能だ。
業務側に残さなければならないのは、業務判断が必要な決定すべて、すなわちこのリクエストはいくらの価値があるか、失敗の被害はどれほど大きいか、デグレード後の回答は受け入れてもらえるか、この種のタスクにおける2つのモデルの品質差は有意か。これらをゲートウェイに入れると、ゲートウェイが業務のセマンティクスを理解しなければならず、最後には業務も分からず、保守も難しい巨大な化け物になる。ゲートウェイが出すべきなのはシグナル(コスト、レイテンシ、成功率、現在の負荷、候補の可用性)であり、決定は業務側の戦略層が行う。
ゲートウェイの選定について現実的な助言を1つ。まず必要としている種類のゲートウェイを選べ。コストの帰属が必要なら、テレメトリと使用量の集約がうまくできているものを選ぶ。ベンダー横断のフォールバックが必要なら、リトライとデグレードの意味論がはっきり定義されているものを選ぶ。自前の推論クラスタと外部 API を併用しているなら、両方に接続できるものを選ぶ。「機能がもっとも全部入り」なゲートウェイを過度に追いかけると、どれも特定の軸で弱く、最後は回避で補うことになり、むしろ統一層の意味を失う。
自前の環境で特に重要な境界の問いがもう1つある。自前クラスタのモデルをルーティングに組み込むべきか。答え 通常は「YES」であり、利得も過小評価されている。自前クラスタのコスト構造は外部 API とまったく違い(限界コストは低いが、遊休のリスクがある)、量が多く、難易度が比較的単純で、待ち行列を許容できるトラフィックに自然に適している。一方、量が多くても難しいトラフィックを外部 API に残すことができる。この分業は自前クラスタの利用率と外部 API のコスト効率を同時に高め、マルチベンダー体系でもっとも率直な利得の1つになる。
10.5 ベンダーの廃止とモデルの廃棄移行:必ず起きる運用イベント
どれだけのベンダーを使っていても、モデルの廃止や型番の廃棄は、意外ではなく必ず起きるイベントである。ほとんどのベンダーは事前通知の期間を与えるが、実践にはこの期間をきわめて短くしてしまうケースがいくつかある。地域的な緊急廃止、コンプライアンス起因の強制撤去、そしてメジャーバージョンリリースで旧ミニバージョンをそのまま利用不可にすることなどだ。
この件がルーティングシステムに求める要件は、見た目より高い。難しい点はモデル名を替えることではなく、能力マトリクスのその1行が空になることだ。廃止済みモデルの評価データも、代替モデルの同じタスク族での評価も持っていない。昨夜まで候補リストに入っていなかったからだ。だから本当に効く準備は「あらかじめ代替モデルを準備しておく」ことではなく、実際に使っているリストより広い候補プールを常に整備しておくことである。現在使われている各エイリアスについて、評価済みの候補チャネルを最低2つ用意し、そのうちの1つは今は使っていないものにしていい。
この「より広い候補プール」には、目立たないが有益な利点がもう1つある。能力マトリクスを常に新鮮な状態に保てる。予備チャネルが3か月使われなかったなら、その評価データはすでに陳腐化していて、実際に必要になったときに初めて基準を満たしていないと気づくと、バックアップがなかったのと同じになる。だから候補プールの項目には、実際の使用頻度とは別の独立した評価リフレッシュのリズムが必要である。
移行そのものは、通常の変更と同じプロセスを通すべきだ。shadow インスタンスでの比較 → 小割合の段階展開 → 判断分布と品質指標の観測 → 全量。特殊なのは、新旧2つのモデルをしばらく並行させることであり、その場で置き換えないことである。並行の価値は、新しいモデルに品質問題が出たときに即座に切り戻せることにある。その場で置き換えて、しかも状態がロールバックできないなら、この移行は不可逆な賭けになってしまう。
最後の規律は、エイリアスを具体的なモデルのバージョンに結び付けないということだ。業務側のコードに deep_reason_v3 と書くのではなく deep_reason と書いているなら、モデルのアップグレードのたびにチーム横断の全面的なコード改修になり、そしてその改修は無限期に延期されて、最後は慌ただしいリリースになる。バージョン変更を設定層に収束させることだけが、移行コストを「週」から「時間」まで下げる方法である。
十一、やってはいけないとき:ルーティング導入を避けるべきケース
ルーティングシステムの複雑度は小さくない。以下のいくつかは明確に推奨しない。この予算を別の場所に使うほうがリターンは大きい。
- トラフィックが小さい、あるいはタスクが単一。1日数百リクエスト、あるいは全部が同一種類のタスクなら、ルーティングの利得は構築と保守のコストをカバーできない。まず能力評価とプロンプトの最適化に手を入れる。
- レイテンシ予算が1回の呼び出ししか許さないほど厳しい。cascade と階層ルーティングはいずれも直列の区間を増やすので、このときの正しいやり方はモデルを1つに固定し、エンジニアリング資源をキャッシュと推論の最適化に投入することだけである。
- チームに評価能力がない。ルーティングシステムの価値の90%は評価能力から来る。「どのモデルがどのタスクで適切か」に答えられないなら、ルーターがどれだけ賢くても、言えない対象を最適化しているだけになる。この前提が満たされていないとき、ルーティングは反証不可能なシステムを作ることになる。
- 使えるベンダーが1社だけ。等価なチャネルがなければ「ルーティング」は「1つのモデルを選ぶ」に退化し、フォールバックの議論自体が成り立たない。このときにもっとも価値があるのは、統一ゲートウェイの抽象層を先に作っておき、将来2社目を接続する準備をしておくことだ。
十二、確実に走り切る落地順序
- まず単一モデルのベースラインを作る:モデルを1つ固定し、プロンプト、キャッシュ、リトライ戦略を突き詰め、品質、成功あたりの単位コスト、P99 レイテンシを測る。ベースラインがなければ比較対象がない。
- まず意思決定ログと要因分解を作る:まだルーティングがない段階で
gen_ai.*のようなフィールドを仕込み、コストを業務機能に帰属させる。このステップはモデル判断のロジックをまったく必要としないが、以降のすべての定量化の前提になる。 - 能力エイリアスと2つ目のチャネルを追加する:業務コードをモデル名から切り離し、2つ目のベンダーを入れる。ここで解けるのは可用性であり、ついでにゲートウェイ抽象が成立しているかも検証できる。
- 全候補でのオフライン評価を1回やる:oracle データを取得し、コスト-品質の Pareto カーブを描く。このステップが、ルーティングの上限がどこにあるか、そしてどのタスク族はそもそもルーティングに値しないかを教えてくれる。
- ルール+kNN 版を立ち上げる:もっとも単純で、説明可能で、冷スタートコストがゼロの版を先に入れる。目標は最適ではなく、意思決定ログを発生させ始めることで、以降のすべての作業にデータの基盤を与えることだ。
- タスク族ごとに段階的に学習型ルーティングを入れる:評価が「モデル間の差が大きく、サンプル量も十分」と示した族にのみ、より複雑なルーターを入れる。他の族は飛ばし、全サイト統一にはしない。
- 条件を満たすタスク族に cascade を有効にする:アップグレード率をゲート条件にし、しきい値を超えたら止める。並行して、レイテンシの影響をリアルタイムでないタスクの選別に組み込む。
- 品質リグレッションと設定統制を日常化する:ゴールド標準リグレッションセットを変更のたびに自動実行し、設定は単一ソース、フィンガープリント検証、能力マトリクスの TTL と再評価のパイプラインを整備する。ここまできて、ルーティングは「機能」ではなく「システム」になる。
もし1つだけやるなら、私は手順2を選ぶ。ほとんどのチームはこれを飛ばして直接ルーターの学習を始め、最後には「効果が言えない」という袋小路に陥り、勘と気合いで反復を続けることになる。要因分解とログは「今やっても損がなくて、あとからやると代償が極端に強い」基盤の唯一の piece である。
おわりに
モデルルーティングがうまくできているかどうかは、最後には分類器の精度には現れない。3つの素朴な数値に現れる。成功1リクエストあたりの単位コスト、P99 レイテンシ、そして oracle に対する品質の regret だ。この3つの数値は継続的に測定でき、業務側の前で説明でき、モデル供給の変化に合わせて再計算できる——この3点ができればルーティングは本物のエンジニアリングシステムであり、できなければ if-else を包んだコストの幻想にすぎない。
2026年のモデル供給はどの時代よりも豊富で、「どれを選ぶか」が時々起こる技術的決定から、リクエストごとに答えるべき常態的な問題になった。変わったのは選択肢であって、方法論の骨組みはごく安定している。問題を制約付きの最適化として定義し、能力マトリクスで事実を蓄積し、予算とデグレードチェーンで不確実性を枠で囲い、最後に感覚ではなく regret で検証する。選択肢が増えるほど、この方法論の価値は大きくなる。
このシステムの本質を1文でまとめると、こうなる。あるモデルを賢くするのではなく、「どのモデルを使うか」という決定を、個人のその場しのぎの判断から、リグレッション可能で、監査可能で、説明可能な意思決定プロセスへ変えることだ。前者は採用でしか解けないが、後者はエンジニアリングで解ける。そして毎日新しいモデルがあり、毎日新しい価格があり、毎日新しいレート制限がある世界では、後者の複利ははるかに大きい。