Skip to content

LLM 分类器

让 LLM 裁判决定整个回合 —— 按解决概率在强目标与弱目标之间挑选,或挑选由你自己的提示词和 schema 指名的模型分组。

Updated View as Markdown

阶段路由器只在信号用尽的地方才咨询裁判。 type = "llm_classifier" 则把整个回合交给 LLM 裁判。条目里写明裁判、它可以挑选的 目的地,以及 mode —— 裁判产出三种判定形态中的哪一种:

mode 裁判返回 回合去往
capability 该任务的解决概率 达到或超过 base_threshold 时去 weak_target,否则去 strong_target
custom 符合你自己 schema 的 JSON,由 JSON Pointer 从中取出一个模型分组 该分组的第一个模型
escalation 对已完成的弱目标回合的裁决 见升级路由

本页介绍 capability 和 custom,它们在回合运行之前做决定,从不扣住回合。escalation 裁决的是已经存在的回合,放在单独一页。

mode 是必填的,这是对上游 schema 的一处有意偏离:上游把它默认成 capability。三种 模式的路由原理完全不同,而其中一种(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 为裁判不那么有把握的判定抬高门槛:对不确定或未匹配的判定加一次,对不受 支持的判定加两次。prompt 替换内置的 capability 提示词;schema 作为结构化输出配置单独 发送,所以提示词里不能含 {{RESPONSE_SCHEMA}}。

mode = "custom"

提示词和 JSON Schema 由你提供,再用一个 JSON Pointer 从判定里取出模型分组的名字,该 分组的第一个模型来处理这一回合。any 和 judge 是保留且必填的分组,其余名字都归你 —— 这正是一个条目能在两个以上模型之间做选择的原因。

[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 = "为这一回合恰好挑选一个目标。只返回符合响应 schema 的 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 是一个或多个有序的裁判候选,只被询问,不会被提供。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 为判定设定 token 上限。

会话状态存活在算法内部。 classify_trigger 的保留是上游的状态,存在路由器实例里, 而 shunt 每加载一次配置就构建一次该实例。热重载会重新构建它,所以重载之后会忘记每个会话 当时持有的目标 —— 这和 prefill_router 的性质相同。

max_judge_calls 是 shunt 自己的,按 (会话, agent) 计数,因此被委派的子任务花的是 自己的预算而不是父会话的;不带会话 id 的请求根本不被跟踪,对这类调用方该上限就是按请求 生效。预算在裁判调用发出时扣减,所以不需要裁判的回合 —— 重放已保留分配的 new_session 或 user_turn 回合 —— 在预算用完之后仍按该分配处理。裁判调用被拒绝的回合按裁判失败的方式 收尾:取下文的默认目标,route source 为 classifier_fail_open,裁判调用结果记为 budget_exhausted。classifier 形式的 [models.subagents] 覆盖层行为相同。

裁判没给出答案时

裁判调用以任何方式失败 —— 超时、响应过大、上游错误、400、无法解析的判定 —— 都算作没有 判定,该回合改走算法自己的默认目标:capability 模式是 strong_target,custom 模式是 default_target 分组的第一个模型。每个需要判定的回合恰好发出一次裁判调用,只打给第一个 裁判候选,因此失败不会沿着 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 或被委派的请求没有 agent id 时(message_hash_fallback 不适用于探测)。没有请求体的那些接口 —— GET /routes、 /v1/models 发现、shunt check —— 以路由来源 classifier_default 报告 fail-open 目标。

客户端看到什么

本节针对 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 目标

目标与裁判

每一个目标和每一个裁判都是普通的公开 model id,受与阶段路由器相同的一跳规则约束;裁判也 不能解析到 passthrough 路由:裁判调用不携带调用方的任何凭证,passthrough 路由便无凭 可用。每个裁判都遵循的通用规则 —— 凭证、准入和每次调用的上限 —— 见 裁判调用。

每个键、它的默认值和约束都在配置参考中。

Navigation

Type to search…

↑↓ navigate↵ selectEsc close