Skip to content

스테이지 라우터

model id 하나가 프롬프트 텍스트가 아니라 대화의 최근 tool-result 이력을 보고 턴마다 강한 티어와 효율 티어 중 하나를 고르게 합니다.

Updated View as Markdown

[[models]] 항목은 보통 목적지를 하나만 지정합니다. 스테이지 라우터는 둘(강한 티어와 효율 티어)을 지정하고, 어느 쪽이 각 턴을 맡을지 대화가 정하게 합니다. 오류, 진척 없는 조사, 탐색적 읽기는 위로 올라가고, 계획이 정해진 뒤의 정형 작업은 아래에 머뭅니다.

스테이지 라우터는 항목의 [models.router] 테이블이 가질 수 있는 type 중 하나입니다. auto는 같은 라우터를 업스트림 프리셋으로 돌린 것이고, 나머지 타입과 그중 무엇을 고를지는 라우팅 개요에 있습니다.

이 기능은 옵트인이며 기본적으로 비활성입니다. [models.router] 테이블을 설정하지 않으면 shunt는 이전과 정확히 동일하게, 요청의 model id만 보고 라우팅합니다.

무엇을 읽는가

요청 본문의 구조화된 프로토콜 메타데이터만 읽습니다.

  • tool_use.name — 어시스턴트가 호출한 도구
  • tool_result.is_error — 결과가 실패로 돌아왔는지 여부

둘 다 Anthropic Messages 와이어 포맷의 핵심 필드입니다. 라우터는 시스템 프롬프트도, 사용자 텍스트도, 도구 인자도 읽지 않습니다. 따라서 프롬프트 형태를 지문 인식해서 Claude Code가 릴리스될 때마다 깨지는 프록시들의 실패 모드를 물려받지 않습니다.

tool_result 블록에는 도구 이름이 없습니다. 유일한 연결 고리는 tool_use_id → tool_use.id이며, 이 조인이 추출기 전체가 딛고 선 단 하나의 지점입니다.

shunt는 요청의 messages 배열을 두 번 훑습니다. 첫 번째는 어시스턴트 메시지를 recent_turn_window가 닿는 데까지만 뒤에서부터 훑으며 최근으로 칠 tool_use id를 모읍니다. 두 번째는 두 역할을 한 번에 앞에서부터 훑으며 tool_use.id → tool_use.name을 만들고, 모든 tool_result를 그것이 답한 tool_use_id에 기록합니다. 이름은 훑는 도중이 아니라 훑고 난 뒤에 해석하므로, 자기 호출보다 앞서 나온 결과도 그대로 이어집니다. 조인을 만드는 id를 빼면 읽는 필드는 위의 두 필드뿐입니다.

완료된 도구 호출이 하나도 없으면 추출은 아무것도 반환하지 않고 턴은 picker 기본값을 받습니다. 빈 대화는 “efficient”로 채점되지 않습니다.

도구 어휘

스코어러가 알고 싶은 것은 어떤 호출이 관찰했는지, 변경했는지, 계획했는지입니다. 프로바이더 중립 라우터는 그것을 추론해야 하지만, shunt는 Claude Code의 실제 도구 이름을 압니다. 범용 라우터보다 잘할 수 있는 지점이 바로 여기이며, 다음 열네 개를 직접 매칭합니다.

분류 도구
관찰(Observe) Read, Glob, Grep, NotebookRead, WebFetch, WebSearch, BashOutput
변경(Mutate) Edit, NotebookEdit, Write
계획(Plan) TodoWrite, Task, EnterPlanMode, ExitPlanMode
미분류 Bash, Skill, KillShell과 모든 mcp__* 도구를 포함한 나머지 전부

미분류로 남은 이름은 [models.router.tool_semantics]로 직접 분류할 수 있습니다. 네 개의 목록(observe, mutate, plan, new)이 위 표 뒤에 적용됩니다. 표가 이미 아는 이름은 바꿀 수 없어서 Read를 mutate에 넣으면 재정의가 아니라 시작 오류입니다. 목록을 고치면 다음 로드에서 그 라우터의 세션 고정이 사라집니다.

ExitPlanMode를 포함한 모든 Plan 호출은 계획 활동으로 집계되며, 이는 탐색으로 읽혀 아래가 아니라 위로 밉니다.

Bash는 의도적으로 미분류입니다. git status는 관찰이고 rm -rf는 변경이며 cargo test는 작업이 정리됐다는 신호인데, 이를 가르는 것은 input.command뿐입니다. 명령 문자열을 분류하는 것이야말로 이 설계가 피하려는 취약한 추론입니다.

심각도

Claude Code는 실패를 등급 없는 불리언 하나로만 보고하므로, shunt가 직접 libsy의 0.0 / 0.3 / 0.7 / 1.0 심각도 척도에 매핑합니다.

조건 severity
윈도 안에 실패한 결과 없음 0.0
윈도 안에 실패한 결과 있음 0.7 (hard)
윈도 안의 Task 실패 — 명령 하나가 실패한 것이 아니라 위임된 서브에이전트 실행 전체가 무너짐 1.0 (critical)
마지막 두 완료 결과가 모두 실패하고 둘 다 윈도 안 — 세션이 회복하지 못하고 있음 1.0 (critical)

어떻게 결정하는가

신호 채점은 NVIDIA-NeMo/Switchyard의 스코어러인 switchyard-libsy가 맡습니다. 캘리브레이션된 부분이라 여기서 다시 구현하지 않습니다. 그 의존성이 어디서 왔는지, 그리고 그 근거가 된 공개 벤치마크 결과는 Switchyard 통합에 있습니다.

차원

신호는 두 축으로 투영됩니다 — 무언가 잘못되었는가, 그리고 에이전트가 생산하고 있는가.

차원 의미 미는 방향
severity 윈도 내 최대 오류 심각도, [0, 1] Capable
spinning 읽기·계획·쓰기·편집이 전무한 깊은 턴 — 순수한 헛돎 Capable
exploring 읽거나 계획하지만 쓰지도 편집하지도 않는 깊은 턴 Capable
production_intensity 최근 도구 연산 중 쓰기+편집의 비율 Efficient

spinning과 exploring은 둘 다 turn_depth >= 8을 요구합니다. 쓰기가 없는 초반 턴은 정체가 아니라 평범한 탐색입니다. 둘은 같은 “생산하지 않음” 상태를 조사 활동 유무로 분할하므로 최대 하나만 발동하고, 생산 축이 이중으로 계산되는 일은 없습니다.

점수

최대치에 도달한 차원 하나가 0.10의 신호 단위 하나를 기여하며, severity는 hard 상한인 0.7로 정규화되어 같은 가중치에 놓입니다.

raw   = 0.10 × (severity / 0.7 + spinning + exploring − production_intensity)
score = tanh(5.0 × raw)          // 부호: + capable, − efficient
confidence = |score|

게인 5.0이 범위를 쓸 만하게 만듭니다. 이것이 없으면 신뢰도는 ±0.20 부근에서 포화해 어떤 유의미한 문턱에도 닿지 못합니다.

설계는 상호 보강적입니다. 어떤 단일 축도 혼자서 판정을 확정할 수 없습니다.

합치하는 신호 raw confidence 0.5 기본값 통과?
하나 포화 0.10 tanh(0.5) ≈ 0.46 아니오
둘이 상호 보강 0.20 tanh(1.0) ≈ 0.76 예

0.5 기본값은 여기서 나옵니다. 잘못된 신호 하나만으로는 턴을 움직이기에 부족하고, 합치하는 신호 둘이면 충분합니다.

결정

pick_tier는 규칙 셋을 순서대로 돌리고, 먼저 발동한 것이 이깁니다.

  1. 하드 승격 → capable. 심각도 critical, 컴팩션된 컨텍스트, 또는 최근 창에서 같은 실패가 반복된 경우. 스코어러를 건너뛰고 confidence: 1.0을 보고합니다.
  2. 스코어러. 중앙값 주변의 모호한 구간 밖이면 부호를 따릅니다.
  3. Fall-open. 구간 안이면 libsy가 PickOutcome::ConsultClassifier { default_tier }를 반환하고 턴은 picker의 기본 티어에 안착합니다.

fall-open하는 것은 API와 싸우는 게 아니라, API가 문서화한 fall-open 경로 그 자체 입니다. shunt는 기본적으로 판정 모델을 두지 않습니다. 덕분에 핫 패스에 두 번째 모델 호출도, 두 번째 자격 증명도, 라우터가 가장 불확실한 바로 그 턴에서 늘어나는 지연도 없습니다. 아래 판정 모델 폴백은 바로 그 턴만 판정 모델에게 넘기는 옵트인입니다.

예전에는 네 번째 규칙이 있었습니다. 테스트가 통과한 정착된 턴을 하드 강등하는 규칙인데, 업스트림이 제거했습니다. 이제 테스트 통과는 libsy 자체의 capable hold를 해제할 뿐이고, shunt는 그 hold를 쓰지 않습니다.

규칙 1의 세 가지 방아쇠 중 오늘의 shunt에서 발동하는 것은 둘입니다. critical 심각도는 심각도 표의 두 경로를 통해 발동하고, 컴팩션된 컨텍스트는 트랜스크립트가 아니라 요청 헤더인 x-claude-code-context-compacted에서 옵니다 — 그래서 도구 활동이 전혀 없는 턴에서도 이 방아쇠가 발동할 수 있습니다. 반복 실패 절반은 libsy가 같은 실패인지를 결과 텍스트와 자체 오류 표를 대조해 판정하는데 Claude Code가 보내는 is_error 불리언으로는 그것을 뒷받침할 수 없어서 잠들어 있습니다. shunt 자체의 “마지막 완료 결과 둘이 모두 실패” 규칙은 severity를 거쳐 이미 규칙 1에 도달합니다.

내려오기가 올라가기보다 어려운 이유

여기까지는 한 턴을 고립적으로 판정합니다. 그다음 shunt는 그 판정이 세션의 고정된 티어를 움직이도록 허용하기 전에 자체 게이트를 겁니다. 티어 전환은 공짜가 아니며, 긴 Claude Code 세션에서는 비용이 구체적이기 때문입니다.

  • 프롬프트 캐시는 모델별 키입니다. 전환하면 워밍된 prefix를 버리고 새 티어에서 전체 캐시 쓰기를 물게 됩니다.
  • Codex continuation은 모델을 시그니처에 해시하므로, 전환하면 전체 입력을 다시 보내게 되어 shunt가 방금 번 업로드 절감을 그대로 반납합니다.
  • 교차 프로바이더 전환은 세션의 풀링된 연결과 sticky 계정 슬롯까지 버립니다.

턴마다 독립적으로 채점하면 긴 세션은 라우팅하지 않을 때보다 더 비쌀 수 있습니다. 그래서 두 방향을 의도적으로 다르게 두며, 히스테리시스가 shunt 통합의 곁가지가 아니라 핵심인 이유도 여기에 있습니다.

방향 필요 조건
효율 → 강함 스코어러가 실제로 내린 결정이 confidence_threshold(0.5) 통과. 싼 티어가 감당 못 하는 턴은 버려지는 캐시 prefix보다 비쌉니다.
강함 → 효율 스코어러가 실제로 내린 결정, 그리고 min_dwell_turns 경과, 그리고 deescalate_threshold(0.75).

fall-open, 모호한 턴, 도구 활동이 없는 턴은 picker 기본값이지 증거가 아니므로, 어느 방향으로도 티어를 움직이지 못합니다.

신뢰도 게이트에는 면제가 없습니다. 예전에는 tests_passed 지름길을 위한 면제가 있었습니다. 그 지름길은 신뢰도를 아예 보고하지 않았기 때문입니다. 업스트림에서 그 규칙이 사라지면서, 이제 증거로 인정되는 모든 출처가 신뢰도를 보고하고, 신뢰도를 보고하지 않는 턴은 신뢰하는 대신 붙잡아 둡니다. 그 자리를 대신한 libsy의 capable_hold — 앞선 승격이 아직 유지되고 있다는 뜻 — 도 증거가 아닙니다. sticky 턴과 같은 개념이고, 그것을 찍는 것은 libsy의 상태 있는 분류기뿐인데 shunt는 그것을 호출하지 않습니다.

capable_hold_turns(기본값 0)는 신호가 티어를 올린 뒤 강한 티어를 몇 턴 더 붙잡아 둡니다. 복구 과정 자체가 조용해서 정리된 상태로 읽히기 쉬울 때 쓸모가 있습니다. 붙잡힌 턴은 라우트 소스 capable_hold로 보고되며, 이 역시 증거가 아니어서 고정을 움직이지 못합니다.

들어오는 모델에게 이유 알려주기

[models.router.handoff_notes]는 신호가 티어를 옮긴 턴에 한해 업스트림으로 보내는 요청에 짧은 시스템 블록을 덧붙입니다. 올라갈 때는 escalation_note, 내려올 때는 deescalation_note를 붙여, 새로 들어온 모델이 처음부터 시작하는 것이 아니라 일을 넘겨받았다는 사실을 알게 합니다. Claude Code의 attribution 블록은 첫 번째 시스템 블록이고 건드리지 않으며, 노트는 맨 뒤에 들어갑니다.

기본값(only_on_wrong_signal_escalation = true)에서 escalation_note는 신호가 주도한 상향 전환, 곧 라우트 소스 override와 dimensions로 한정됩니다. 고정 상태로 이어진 턴, 신호가 없는 턴, count_tokens 프로브에는 노트가 붙지 않습니다. 넘겨준 것이 없는 턴도 마찬가지입니다 — 세션의 첫 턴, 그리고 이미 고정된 티어를 다시 확인하기만 한 턴이 여기 해당합니다.

전환할 때마다 프롬프트 캐시 미스를 한 번씩 치릅니다. system 배열이 캐시된 프리픽스의 일부이기 때문입니다. 전환 자체가 이미 포기하는 모델별 프리픽스에 더해지는 비용이며, 그래서 이 테이블은 명시적으로 켜기 전까지 꺼져 있고, only_on_wrong_signal_escalation의 기본값도 더 좁은 쪽입니다.

세션 고정

결정은 (광고된 model id, session id, agent id) 단위로 고정되며, 두 id는 각각 x-claude-code-session-id와 x-claude-code-agent-id에서 읽습니다. 각 id의 키는 SHA-256 프리픽스입니다 — 두 id 모두 해시되고 저장되지 않습니다. 같은 세션에서 라우터 기반 모델을 둘 쓰면 티어는 서로 독립입니다.

서브에이전트는 자기 고정을 가집니다. Task 자식은 부모의 session id에 자신의 agent id를 함께 보내므로 별도의 항목을 읽고 씁니다. 자식의 실패가 더 이상 부모를 승급시키지 않고, 자식의 턴이 부모의 dwell로 계산되지도 않습니다. Claude Code는 이 헤더를 옵트인 없이 모든 위임 턴에 보내므로, 기본 shunt 배포에서 클라이언트를 바꾸지 않아도 그대로 동작합니다. 저장소는 부모 세션 4096개와 그와 별개인 자식 고정 4096개에서 각각 상한에 걸리고, 삽입 경로에서 축출하며 백그라운드 태스크가 없습니다. 정리는 자신이 늘어난 범위에만 닿으므로 넓은 팬아웃이 그 팬아웃을 기다리는 부모를 축출할 수 없습니다.

컴팩션은 승급시키고, 그 승급은 유지됩니다. Claude Code는 컨텍스트 컴팩션 이후 첫 턴에만 x-claude-code-context-compacted를 한 번 붙입니다. shunt는 그 턴을 compacted로 채점하는데, 이는 스코어러의 하드 오버라이드에 도달해 컴팩션 직후 이력에 도구 활동이 전혀 없어도 강한 티어로 라우팅됩니다. 그리고 그 플래그를 세션 고정에 걸어 두므로 뒤따르는 턴들도 같은 결론에 도달합니다. 이 헤더는 클라이언트가 CLAUDE_CODE_GATEWAY_HINT_HEADERS=1을 설정했을 때만 shunt에 도달합니다(클라이언트 환경 변수 참고). 같은 변수가 x-claude-code-request-class도 보내며, 그때는 위의 agent id 규칙 대신 이 값이 어떤 턴이 위임 작업인지 결정합니다.

  • 세션 헤더를 보내지 않는 호출자는 무상태로 라우팅되며, 그런 요청의 컴팩션 플래그는 그 턴에만 적용됩니다.
  • count_tokens 탐침은 아무것도 기록하지 않습니다. Claude Code가 한 턴 뒤처진 이력으로 보내기 때문에, 기록하는 탐침은 오래된 이력이 고정을 몰게 합니다. 세션이 이미 고정돼 있으면 탐침과 뒤따르는 턴이 같은 고정을 읽으므로 같은 티어에 도달합니다. 아직 고정되지 않은 세션에서는 탐침이 자기 이력으로 채점되므로 뒤따르는 턴과 다른 티어에 도달할 수 있습니다.
  • 고정은 설정 리로드를 견딥니다. 다만 고정은 자신이 만들어진 라우터 테이블의 지문을 기억하므로, 라우터 하나를 수정하면 그 라우터의 고정만 버려집니다. 그 지문은 설정 구조체를 점 접근이 아니라 구조 분해로 읽으므로, 나중에 키가 추가되면 낡은 고정이 조용히 살아남는 대신 컴파일이 깨집니다.
  • 고정은 session_ttl_seconds 후 만료됩니다. 재시작 간에는 아무것도 영속되지 않습니다. 컴팩션 래치는 자신을 담은 고정과 함께 해제됩니다 — 이 만료 시점, 또는 리로드가 라우터 테이블을 바꿀 때입니다.

위임된 작업은 이 라우터를 아예 벗어날 수도 있습니다. [models.subagents] 오버레이는 라우터가 돌기 전에 자식을 우회시키므로, 그 자식은 여기서 채점되지도 고정되지도 않습니다.

클라이언트가 보는 것

선택된 티어는 업스트림으로만 전달됩니다. 응답은 클라이언트가 요청한 id를 그대로 보고하므로 Claude Code의 /model 표시와 --resume 모델 복원이 계속 동작합니다.

POST /v1/messages   {"model": "claude-auto", ...}
  → 업스트림 수신   {"model": "claude-opus-4-8", ...}
  → 클라이언트 수신 {"model": "claude-auto", ...}

결정은 응답 헤더 두 개로 보고되므로 추측할 필요가 없습니다.

헤더 값
x-gateway-routed-model 라우터가 고른 타깃(claude-opus-4-8)
x-gateway-route-source 그 이유(override, dimensions, sticky, no_signal, fall_open, capable_hold, llm-classifier)

일곱 개 가운데 여섯 개는 신호 라벨입니다. llm-classifier는 아래 판정 모델 폴백을 설정한 항목에서, 판정 모델이 결정한 턴에만 나타납니다. 설정하지 않으면 결정을 못 내린 턴은 지금처럼 fall_open으로 보고되고 판정 호출도 없습니다. 스코어러는 ambiguous 라벨을 하나 더 정의하지만 이것은 응답에 실릴 수 없습니다. 다른 라우터 타입은 자기 라벨을 쓰며, 각 타입의 페이지에 나와 있습니다(random, random_session, noop, 그리고 LLM classifier의 classifier_* 라벨 등). [models.subagents] 오버레이도 자기 라벨을 씁니다 — by_type이 맞으면 subagent_type, target 폴백이면 subagent이고, llm_classifier 형태에서는 classifier의 라벨입니다.

라우터도 [models.subagents] 오버레이도 턴을 결정하지 않았을 때만 둘 다 붙지 않습니다. 그래야 “효율 티어로 라우팅됨”과 “애초에 라우팅되지 않음”이 구분됩니다. count_tokens 탐침에는 붙이지 않습니다. 아직 고정되지 않은 세션에서는 탐침이 한 턴 뒤처진 자기 이력으로 채점되므로, 그 티어가 측정 대상 턴의 티어라는 보장이 없기 때문입니다.

GET /routes는 설정된 라우터를 routers에 모두 싣고 algorithm과 targets를 보여주며, 이 타입에 대해서는 두 티어 타깃과 신호가 아무것도 결정하지 못했을 때 picker가 돌아가는 티어까지 함께 보여줍니다. 판정 모델을 설정한 라우터는 그 id를 judges로도 함께 보여줍니다. 메트릭을 켜면 카운터 세 개가 나갑니다 — 모든 라우터 타입을 포괄하는 shunt.router.decisions, 그리고 shunt.stage_router.decisions와 shunt.stage_router.flips — 판정 모델을 설정하면 네 번째로 shunt.router.judge_calls가 더해집니다. 티어 전환이 비싼 사건이고, 결정 카운터만으로는 방금 옮겨온 세션과 원래 거기 있던 세션을 구분할 수 없어서 flip을 따로 셉니다.

설정

[[models]]
id = "claude-auto"                     # /model에 노출되려면 claude/anthropic으로 시작해야 합니다
display_name = "Auto (stage router)"

[models.router]
type = "stage_router"
capable_target = "claude-opus-4-8"
efficient_target = "claude-sonnet-4-6"
# picker = "efficient_first"
# confidence_threshold = 0.5
# recent_turn_window = 3
# min_dwell_turns = 3
# deescalate_threshold = 0.75
# session_ttl_seconds = 3600
# capable_hold_turns = 0

두 타깃 모두 평범한 공개 model id이므로 각각 일반 라우팅 사다리를 따라 해석되고 자신의 페일오버 체인, 계정 풀, 어댑터, effort, service_tier를 그대로 유지합니다. 타깃이 그 자체로 라우터인 경우는 시작 오류이므로, 해석은 구조적으로 한 홉입니다.

모든 키는 설정 레퍼런스에 문서화되어 있습니다. 의도적으로 제외한 것까지 포함한 설계 기록은 ADR-0004에 있고, [models.router] 판별자와 나머지 알고리즘에 대한 기록은 ADR-0005에 있습니다.

판정 모델 폴백

신호는 대부분의 턴을 결정하지만 전부는 아닙니다. 선택적인 [models.router.classifier] 테이블은 판정 모델을 지정합니다. 신호가 결론을 내지 못한 턴에 한해 어느 티어가 필요한지 물어보는 모델입니다.

[models.router.classifier]
target = "claude-haiku-4-5"    # 물어보기만 하고 제공하지는 않습니다
base_threshold = 0.5           # 작업을 효율 티어에 두는 p_solve 하한

이 테이블이 없으면 달라지는 것은 없습니다. 결론이 나지 않은 턴은 지금까지처럼 picker 기본값으로 fall-open합니다. 테이블이 있으면 바로 그 턴만 판정 모델에게 넘어갑니다. 이미 고정(pin)이 턴을 잡고 있는 세션은 판정하지 않고, count_tokens 탐침도 판정하지 않으며, 요청이 인증을 통과하기 전에는 판정 호출을 만들지 않습니다. classify_trigger = "user_turn"은 이를 가장 최근 메시지가 사람의 사용자 턴인 턴으로 더 좁힙니다. 그래서 도구 연속 턴은 판정 호출을 새로 치르는 대신 세션 고정을 타고 갑니다.

판정 모델은 평범한 공개 model id이고 호출자의 자격 증명이 아니라 자기 라우트의 자격 증명으로 실행됩니다. 예약된 x-shunt-* 슬롯과 cookie, authorization, x-api-key, anthropic-beta는 호출이 나가기 전에 모두 제거됩니다. passthrough 업스트림으로 해석되는 판정 타깃이 시작 오류인 이유가 바로 이것입니다 — passthrough는 호출자의 자격 증명을 그대로 전달한다는 뜻인데, 방금 제거한 것이 바로 그 자격 증명이라 보낼 것이 남지 않습니다. auth = "none" 타깃은 괜찮습니다. 이 모드는 해당 엔드포인트가 자격 증명을 전혀 요구하지 않는다는 뜻이므로, 인증 없이 도는 로컬·자체 호스팅 판정 모델은 오류가 아니라 지원되는 구성입니다. 호출은 그 타깃 자신의 계정 풀 쿼터를 쓰므로, 판정 모델에는 자기 [[models]] 항목을 주세요.

판정 모델에게 묻는 턴에서는 인증 대상이 항목 전체입니다 — 요청된 id에 두 티어 타깃과 판정 모델을, 각각의 페일오버 체인 전체까지 포함합니다. 그래서 응답 타깃이 모두 passthrough인 항목에 자격 증명을 주입하는 판정 모델을 더하면 그런 턴은 클라이언트 자격 증명을 요구하게 됩니다. 인증에 실패했거나 정책이 거부한 요청은 판정 호출을 한 번도 만들지 않습니다. 판정 모델에 닿지 않는 턴은 테이블이 없던 때와 똑같이, 실제로 해석된 체인으로만 인증합니다 — 신호가 스스로 결정한 턴과, [models.subagents] 오버레이가 라우터보다 먼저 돌려보낸 턴입니다.

[models.router]의 여섯 개 키가 모든 내부 호출에 한도를 겁니다 — judge_timeout_ms (기본 30000, 헤더와 본문을 모두 덮는 종단 간 기한), judge_max_response_bytes (65536), 세션당 max_judge_calls(8, 판정 호출을 보낼 때 차감하므로 동시 턴도 넘길 수 없습니다), 그리고 gated_* 세 개입니다. gated_*는 escalation이나 advisor 항목이 붙잡아 두는 턴에만 한도를 걸기 때문에 스테이지 라우터에서는 아무것도 제한하지 않습니다. 한도를 넘기면 업스트림 호출을 취소합니다.

판정이 내려진 턴은 x-gateway-route-source: llm-classifier로 보고되고 다른 결정과 똑같이 세션을 고정합니다. 타임아웃, 크기 초과나 해석 불가 응답, 업스트림 오류, 예산 소진 등 모든 실패는 fall_open으로 해결되므로, 판정 모델이 죽어 있으면 지연 시간을 잃을 뿐 가용성을 잃지는 않습니다. 각 호출은 그 결과와 함께 shunt.router.judge_calls로 집계 됩니다.

한계

  • 턴 중간 상향 전환은 없습니다. 응답 헤더가 커밋된 뒤에는 shunt가 턴을 재구동하지 않으므로, 턴 시작 시점에 고른 티어가 그 턴 전체를 맡습니다.
  • 이 판정 모델은 티어만 결정합니다. 판정을 쓰는 다른 타입은 각자 자기 테이블을 가집니다. llm_classifier, composite, advisor는 [models.router]에 직접 설정합니다. 판정이 나올 때까지 턴을 붙잡아 두는 것은 llm_classifier의 mode = "escalation"과 advisor뿐이며, 이 판정 모델은 그러지 않습니다.
  • [server.codex_endpoint]는 범위 밖입니다. 자체 라우팅 테이블을 사용합니다.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close