ステージルーターがジャッジに尋ねるのは、シグナルが尽きた
ところだけです。type = "llm_classifier" は代わりにターン全体を LLM ジャッジに委ねます。
エントリにはジャッジと、ジャッジが選べる宛先と、3 つある判定の形のどれかを決める mode を
書きます。
mode |
ジャッジが返すもの | ターンの行き先 |
|---|---|---|
capability |
タスクの解決確率 | base_threshold 以上なら weak_target、下回れば strong_target |
custom |
自分で書いたスキーマに一致する JSON。そこから JSON Pointer がモデルグループを選びます | そのグループの最初のモデル |
escalation |
完成した効率側のターンに対する判断 | エスカレーションを参照 |
このページでは capability と custom を扱います。どちらもターンが実行される前に決め、
ターンを留め置くことはありません。escalation はすでに存在するターンを判定するので、
別のページで扱います。
mode は必須です。上流のスキーマは capability を既定値にしていますが、ここでは
そうしません。3 つのモードはまったく別の原理でルーティングし、そのうち escalation は
提供するターンをバッファリングするため、mode を省いた設定が黙ってどれかのモードとして
読まれてはなりません。
mode = "capability"
パッケージのジャッジがタスクの解決確率を返します。その値が base_threshold 以上なら
ターンは weak_target に留まり、下回れば strong_target へ行きます。
[[models]]
id = "claude-judged"
[models.router]
type = "llm_classifier"
mode = "capability"
classifier_target = "claude-haiku-4-5"
strong_target = "claude-opus-4-8"
weak_target = "claude-sonnet-4-6"
base_threshold = 0.5
# threshold_step = 0.0
# classify_trigger = "every_request"threshold_step は、ジャッジの確信が弱い判定ほど基準を引き上げます。不確実または一致しない
判定には 1 回、サポート外の判定には 2 回加算されます。prompt はパッケージの capability
プロンプトを置き換えます。スキーマは構造化出力の設定として別に送られるため、プロンプトに
{{RESPONSE_SCHEMA}} を含めてはなりません。
mode = "custom"
プロンプトと JSON Schema を自分で与え、JSON Pointer が判定から モデルグループ名 を
取り出します。そのグループの最初のモデルがターンを処理します。any と judge は予約された
必須のグループで、それ以外の名前はすべてあなたのものです。1 つのエントリが 3 つ以上の
モデルから選べるのはこのためです。
[models.router]
type = "llm_classifier"
mode = "custom"
models = { judge = ["claude-haiku-4-5"], capable = ["claude-opus-4-8"], efficient = ["claude-sonnet-4-6"], any = ["claude-sonnet-4-6", "claude-opus-4-8"] }
default_target = "efficient"
prompt = "このターンのターゲットをちょうど 1 つ選んでください。レスポンススキーマに一致する JSON だけを返してください。"
response_schema = '''
{"type": "object",
"properties": {"target": {"type": "string", "enum": ["capable", "efficient"]}},
"required": ["target"],
"additionalProperties": false}
'''
policy = { type = "target_selector", selector = "/target" }models.any には選択されうるすべての宛先を並べ、他の回答グループのターゲットもすべてここに
現れる必要があります。models.judge は順序付きのジャッジ候補を 1 つ以上持ち、尋ねるだけで
提供はしません。default_target は、使える判定が得られなかったときのグループです。
ジャッジが走るタイミング
どちらのモードでも、ジャッジ呼び出しをどれだけ頻繁に払うかは classify_trigger が
決めます。
classify_trigger |
ジャッジが走るとき |
|---|---|
every_request(既定値) |
ツールの継続ターンも含めて毎ターン |
user_turn |
新しい人間のユーザーターンごと。その間のツール呼び出しではそのターゲットを保持します |
new_session |
一度だけ。セッション中はそのターゲットを使い続けます |
message_hash_fallback = true は、セッション id を送らないクライアント向けに、最初の
ユーザーメッセージで保持のキーを取ります。classify_trigger = "new_session" が必要です。
recent_turn_window はジャッジが追加で見る直近のターンを足し、max_output_tokens は判定の
上限を決めます。
セッションはアルゴリズムの中にあります。 classify_trigger の保持は上流の状態で、
ルーターのインスタンスの中にあり、shunt はそれを設定を読み込むたびに一度だけ構築します。
ホットリロードは作り直すので、リロードすると各セッションが持っていたターゲットを忘れます —
prefill_router と同じ性質です。
max_judge_calls は shunt 自身のもので、(session, agent) ごとに数えます。だから
委譲された子は親ではなく自分の予算を使います。セッション id を送らないリクエストは追跡
されないので、その呼び出し元にはこの上限がリクエスト単位で効きます。予算はジャッジ呼び出しを
送るときに差し引くので、ジャッジを必要としないターン — 保持された割り当てを再生する
new_session や user_turn のターン — は予算を使い切ったあとも、その割り当てのまま処理
されます。ジャッジ呼び出しを拒否されたターンは、ジャッジが失敗したターンと同じように閉じられ
ます。下のデフォルトへ行き、ルートソースは classifier_fail_open、ジャッジ呼び出しの結果は
budget_exhausted として記録されます。classifier 形式の
[models.subagents] オーバーレイも同じ動きをします。
ジャッジが答えないとき
ジャッジ呼び出しがどのように失敗しても — タイムアウト、サイズ超過、アップストリームエラー、
400、解釈できない判定 — 判定なしとみなされ、ターンはアルゴリズム自身の既定値へ行きます。
capability モードなら strong_target、custom モードなら default_target グループの
最初のモデルです。判定するターン 1 回につき、ジャッジ呼び出しはちょうど 1 回、最初の
ジャッジ候補に対してだけ行われます。したがって失敗しても models.judge をたどって再試行は
しません。ターンはそのまま応答され、ルートソースは classifier_fail_open で、クライアントは
変わらず 200 を受け取ります。
プローブはジャッジなしで解決します
count_tokens リクエストがジャッジを呼ぶことはなく、max_judge_calls の予算も使わず、
セッションの状態も変えません。new_session か user_turn では、セッションの直前のターンを
提供したターゲットで応答されます — user_turn では、プローブの最後のメッセージが新しい
ユーザーターンであってもそうです。それ以外は fail-open のターゲットで応答されます。
every_request のとき、まだ分類されていないセッションのとき、x-claude-code-session-id の
ないリクエストやエージェント id のない委譲リクエストのときがそうです
(message_hash_fallback はプローブには適用されません)。リクエストボディのない面 —
GET /routes、/v1/models ディスカバリー、shunt check — は fail-open のターゲットを
ルートソース classifier_default として報告します。
クライアントから見えるもの
この節は capability と custom モードを扱います。mode = "escalation" は独自の
ソースを報告し、それは Escalation のページに載っています。
ルーティングされたターンには x-gateway-routed-model(ターゲット)と
x-gateway-route-source が付きます。後者はターンがどのようにそのターゲットに至ったかを
示します。count_tokens プローブ以外の受け付けたリクエストでは同じラベルが
shunt.router.decisions の source になるため、classifier_default がそこに現れることは
ありません。
| ソース | 意味 |
|---|---|
llm-classifier |
このターンでジャッジが答え、その判定がターゲットを選びました |
classifier_retained |
user_turn か new_session で、以前のターンがすでに得たターゲットを再生したため、ジャッジ呼び出しはありませんでした |
classifier_fail_open |
判定がターンを決めませんでした。ジャッジが失敗した、答えが使えなかった、または max_judge_calls が呼び出しを拒否したため、既定のターゲットが応答しました |
classifier_default |
リクエストボディのない面が fail-open のターゲットを報告しました |
ターゲットとジャッジ
ターゲットもジャッジも、ステージルーターと同じ 1 ホップ規則に従う通常の公開モデル id で、 ジャッジは passthrough ルートに解決されてはいけません。ジャッジ呼び出しは呼び出し元の 資格情報をひとつも運ばないので、passthrough ルートには動かすものが残らないからです。どの ジャッジも従う一般的な規則 — 資格情報、アドミッション、呼び出しごとの上限 — は ジャッジ呼び出しにあります。
すべてのキー、その既定値、制約は 設定リファレンスにあります。