Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agents/skills/switchyard-coding-agent-launchers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ after.
| Look up upstream model catalogs | `fetch_model_ids(...)` in `switchyard/cli/model_catalog/model_discovery.py` |
| Add a plan-execute route | Add `type: plan_execute` in route YAML; `_plan_execute_switchyard` in `route_bundle.py` maps it to `PlanExecuteProfileConfig`. |
| Add a deterministic route | Add `type: deterministic` in route YAML; `_deterministic_switchyard` maps it to `DeterministicRoutingProfileConfig`. |
| Add a learned prefill-probe route | Add `type: prefill_probe` in route YAML; `_prefill_probe_switchyard` maps it to `PrefillProbeProfileConfig` while model discovery skips the internal probe. |
| Add a preset | Put it beside the profile config under `switchyard/lib/profiles/`; presets return typed config objects, not runnable apps. |
| Inspect saved defaults | `switchyard configure --show` reads `switchyard/cli/config/user_config.py` and renders via `switchyard/cli/status.py`. |

Expand Down Expand Up @@ -87,6 +88,8 @@ Route YAML and launchers share this model-dispatch path:
direct strong/weak passthrough entries.
- `type: deterministic`, `type: escalation_router`, and `type: stage_router`
register the route key plus direct strong/weak passthrough entries.
- `type: prefill_probe` registers the route key plus direct strong/weak
passthrough entries; its probe target remains internal.
- `type: plan_execute` registers the route key plus the executor as a direct
passthrough; the planner is internal routing logic.
- `type: latency_service` and `type: noop` register the
Expand Down
1 change: 1 addition & 0 deletions .agents/skills/switchyard-lib-core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ right validation set. If the change is driven by a launcher need, also read
| An OpenAI-compatible provider target such as NVIDIA Inference Hub or OpenRouter | Use the existing OpenAI-compatible backend/profile with `base_url`, `api_key`, and model id wiring. Add a new backend only when the provider has a real wire-format, auth, retry, or health contract that cannot fit that path. |
| Direct Rust component bindings | Add concrete PyO3 classes under `crates/switchyard-py/src/component_bindings/`, keep config bindings near the component binding that consumes them, and expose them lazily from `switchyard_rust/components.py`. Do not keep growing `core_bindings.rs` or `switchyard_rust/core.py` with concrete component classes. |
| Route YAML / model dispatch | Use `switchyard/cli/route_bundle.py` and `switchyard/lib/route_table_builders.py`. They build `RouteTable` entries from profile-backed runtimes and keep launchers plus `switchyard serve --routing-profiles` on one path. |
| A learned prefill-probe route | Use `type: prefill_probe` with `probe`, `strong`, and `weak` targets. The route table exposes and discovers only the completion tiers; the probe remains internal to `PrefillProbeProfileConfig`. |
| Route sub-agent traffic to a fixed worker target | Set `subagent_target: <target-id>` in any profile's common envelope (consumed like `type` in Rust `SerializedProfileConfig`, `crates/switchyard-components-v2/src/config/parsing.rs`). The Python loader (`switchyard/lib/profiles/loader.py`) wraps the built profile in `SubagentOverrideProfile`; detection is the Rust-bound `is_subagent_request(headers)` from `switchyard_rust.profiles`, a thin wrapper over `Metadata::from_headers` + `Metadata::is_subagent_work` in `crates/protocol/src/metadata.rs` (the canonical lineage fact and work-vs-maintenance policy). On the libsy `Algorithm` path, wrap with the `SubagentOverride` combinator (`crates/libsy/src/algorithms/subagent_override.rs`; Python: `switchyard.libsy.algorithms.subagent_override`). Do not re-implement header sniffing inside individual profiles or algorithms. |
| Shared/persistent session-affinity pins across workers or pod churn | Configure the latency route with `session_affinity: true` + `affinity_store: redis` + `affinity_store_url` (optional `affinity_store_ttl_seconds`, `affinity_key_prefix`); the escalation_router route takes the same `affinity_store*` keys (no `session_affinity` flag — its latch is always on; default prefix `swyd:esc:`). `SessionAffinity` keeps the Rust `SessionCache` as L1 and reads/writes through the `AffinityPinStore` L2 (`switchyard/lib/redis_pin_store.py`), fail-open behind a 0.1s socket timeout and a 3-failure/10s-cooldown circuit breaker (`switchyard_affinity_l2_breaker_open` gauge). Requires the `switchyard[affinity-redis]` extra. |
| Stats / telemetry | Reuse `StatsRequestProcessor`, `StatsResponseProcessor`, `StatsLlmBackend`, and `StatsAccumulator`. A profile config should thread one accumulator through all three when stats are enabled. Do not write a parallel collector. |
Expand Down
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions benchmark/routing-profiles/prefill-probe-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Learned prefill-complexity route with a local Qwen3.6 hidden-state probe.
# Start the probe as documented in docs/vllm-serve-hidden-state.md, then export:
# NVIDIA_API_KEY
# VLLM_BASE_URL=http://127.0.0.1:8000
# HIDDEN_STATES_DIR=/tmp/vllm-hidden-states
# PREFILL_ROUTER_CHECKPOINT_DIR=/absolute/path/to/inference_artifact
#
# Serve from the repository root:
# switchyard --routing-profiles \
# benchmark/routing-profiles/prefill-probe-local.yaml -- serve --port 4000
# Clients select the learned route by sending model: prefill-complexity-router.

defaults:
timeout_secs: 600.0

routes:
prefill-complexity-router:
type: prefill_probe
enable_stats: true
fallback_target_on_evict: strong
tier_timeout_s: 600.0

probe:
id: probe
model: Qwen/Qwen3.6-35B-A3B
base_url: ${VLLM_BASE_URL}/v1
format: openai

strong:
id: strong
model: aws/anthropic/bedrock-claude-opus-4-7
api_key: ${NVIDIA_API_KEY}
base_url: https://inference-api.nvidia.com/v1
format: anthropic

weak:
id: weak
model: nvidia/nvidia/nemotron-3-super-120b-long-ctx
api_key: ${NVIDIA_API_KEY}
base_url: https://inference-api.nvidia.com/v1
format: openai

strong_checkpoint_head: opus-4.7
weak_checkpoint_head: nemotron-3-super
hidden_states_dir: ${HIDDEN_STATES_DIR}
checkpoint_dir: ${PREFILL_ROUTER_CHECKPOINT_DIR}

routing_policy:
type: cost_aware
lambda: 0.5
weak_cost: 0.0
strong_cost: 1.0
4 changes: 3 additions & 1 deletion crates/switchyard-components/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ rust-version.workspace = true
async-stream = "0.3"
async-trait = "0.1"
futures-util = "0.3"
half = "2"
parking_lot = "0.12"
rand = "0.8"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots", "stream"] }
safetensors = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
switchyard-core = { path = "../switchyard-core" }
switchyard-libsy = { path = "../libsy" }
switchyard-protocol = { path = "../protocol" }
switchyard-translation = { path = "../switchyard-translation" }
tokio = { version = "1", features = ["rt", "sync"] }
tokio = { version = "1", features = ["rt", "sync", "time"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }

[dev-dependencies]
Expand Down
4 changes: 3 additions & 1 deletion crates/switchyard-components/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
pub mod backends;
pub mod dimension_collector;
pub mod intake;
mod prefill_probe;
pub mod request_processors;
pub mod response_processors;
pub mod stage_router;
Expand All @@ -29,7 +30,8 @@ pub use intake::{
RequestMetadata, SubModelCall, SubModelCalls,
};
pub use request_processors::{
DimensionCollector, IntakeRequestProcessor, RandomRoutingDecision, RandomRoutingEngine,
DimensionCollector, IntakeRequestProcessor, PrefillProbeProcessorConfig,
PrefillProbeRequestProcessor, RandomRoutingDecision, RandomRoutingEngine,
RandomRoutingProcessorConfig, RandomRoutingTier, StatsRequestProcessor,
};
pub use response_processors::{
Expand Down
Loading
Loading