Skip to content

chore(routing): remove public noop and passthrough route types - #148

Merged
elyasmnvidian merged 3 commits into
mainfrom
emehtabuddin/remove-noop-passthrough-route-types
Jul 28, 2026
Merged

chore(routing): remove public noop and passthrough route types#148
elyasmnvidian merged 3 commits into
mainfrom
emehtabuddin/remove-noop-passthrough-route-types

Conversation

@elyasmnvidian

@elyasmnvidian elyasmnvidian commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Background

This change removes type: noop and type: passthrough, leaving type: model as the only way to route to one upstream.

type: noop was originally an internal tool for measuring proxy overhead. It is a route that returns a fixed response with no upstream LLM call, so you can measure Switchyard's own request-handling cost, separate from backend latency. type: passthrough was a route that forwarded to a single upstream and could also auto-discover the upstream's catalog by listing its GET /v1/models. All three — type: model, type: passthrough, and type: noop — did the same thing: call exactly one target. noop and passthrough were only ever configured for tests and CI, never to serve real requests.

That redundancy makes the router harder to read: every reader has to reason about two route types the server never serves, and an empty {} route silently became a noop instead of raising an error.

We can remove them because type: model already forwards to exactly one upstream, and the only two callers of noop — the self-contained doc tests and the perf CI job — now use a local mock OpenAI upstream plus a type: model route (see "Rebuilt" below). Nothing depends on noop or passthrough after that.

Breaking change

  • A route bundle with type: passthrough or type: noop now fails to load with unsupported route type.
  • An empty route mapping {} now raises route '<id>': missing 'type' (it used to default to noop).
  • Catalog auto-discovery from a bare passthrough route (no target, list the upstream's whole GET /v1/models) is gone. type: model needs an explicit target.

Migrate type: passthrough to type: model and name the target:

routes:
  gpt-4o:
    type: model
    target: openai/gpt-4o

Kept unchanged

  • All internal passthrough plumbing: build_passthrough_table, build_tier_passthrough_switchyard, _passthrough_target. random_routing, stage_router, deterministic, and the launchers still register their tier targets as direct passthroughs.
  • PassthroughProfileConfig and OpenAiPassthroughBackend (the library single-upstream config and backend).
  • The --model single-model launcher label, the --inbound no-op flag, and translation passthrough.
  • The Rust switchyard-server type = "noop" config and the libsy Noop algorithm (separate subsystems).

Rebuilt the two callers that depended on noop

  • perf CI (.github/workflows/perf.yml): the "proxy overhead" job served a noop route. It now starts a local zero-latency mock OpenAI stub and serves a type: model route at it. The loopback hop adds a small constant overhead, noted in the workflow.
  • hermetic doc-test fixtures (tests/getting_started, tests/readme): they used noop to run guide snippets with no backend. They now start an in-process mock OpenAI server and point PassthroughProfileConfig at it. Both fixtures share tests/_mock_openai_server.py — the mock server plus one redirect_passthrough_to_local_mock helper.

Validation

  • ruff and mypy (strict) clean.
  • pytest -m "not integration": 1720 passed, 9 skipped, 28 deselected (isolated HOME). The count is lower than earlier runs because main removed the latency-aware router and its tests (refactor(routing)!: remove latency-aware router #162). One machine-specific test, test_verify.py::test_secrets_file_wins_when_no_cli_or_env, fails only when a real ~/.config/switchyard/secrets file is present; it passes with an isolated HOME and is unrelated to this change.
  • Guide snippets run against the mock upstream (--markdown-docs, 15 passed).
  • Rebased onto current main after the latency-aware router removal (refactor(routing)!: remove latency-aware router #162): the CHANGELOG keeps both Removed notes and import switchyard succeeds (no deleted latency modules resurrected).
  • Live checks: type: noop / type: passthrough / empty {} are rejected; a type: model route registers and serves.

@elyasmnvidian
elyasmnvidian requested a review from a team as a code owner July 27, 2026 17:15
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://NVIDIA-NeMo.github.io/Switchyard/pr-preview/pr-148/

Built to branch gh-pages at 2026-07-28 17:48 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change removes noop and passthrough route-bundle support, deletes no-op profile exports and implementation, replaces benchmark and test routes with model routes, adds local mock upstreams for hermetic execution, and updates documentation and examples.

Changes

Legacy route removal

Layer / File(s) Summary
Route bundle validation and dispatch
switchyard/cli/route_bundle.py
Rejects legacy route types, removes passthrough catalog discovery and noop construction, and updates route-key validation and missing-type errors.
Runtime exports and CLI route presentation
switchyard/__init__.py, switchyard/lib/profiles/__init__.py, switchyard/cli/launchers/launcher_runtime.py, switchyard/cli/switchyard_cli.py, examples/route.yaml
Removes no-op profile exports and updates route summaries, CLI examples, and bundled routes.
Local mock upstream and benchmark execution
.github/workflows/perf.yml, tests/getting_started/*, tests/readme/*, .github/workflows/getting-started.yml, .github/workflows/readme.yml
Uses local OpenAI-compatible mock servers for performance, getting-started, and README execution paths.
Documentation and regression coverage
docs/*, CHANGELOG.md, .agents/skills/..., tests/test_route_bundle.py, tests/test_*.py
Documents the removed route types and migrates route fixtures and assertions to explicit model routes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Poem

I’m a rabbit with routes to spare,
No noop hops through the meadow air.
Model paths now carry the load,
Mock-upstreams guard the testing road.
Documentation blooms where old routes lay.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: removing the public noop and passthrough route types.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/readme/conftest.py (1)

40-86: 📐 Maintainability & Code Quality | 🔵 Trivial

Duplicate of tests/getting_started/conftest.py's _MockOpenAIServer.

Identical implementation to tests/getting_started/conftest.py lines 39-92.

See consolidated comment for the full cross-file fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/readme/conftest.py` around lines 40 - 86, Remove the duplicate
_MockOpenAIServer implementation from tests/readme/conftest.py and reuse the
existing fixture or shared definition from tests/getting_started/conftest.py,
preserving the README tests’ current server behavior and base_url interface.
tests/getting_started/test_getting_started.py (1)

161-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a real type hint over object + # type: ignore.

Import _MockOpenAIServer under TYPE_CHECKING and type the fixture param directly instead of object/# type: ignore[attr-defined].

♻️ Proposed fix
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from tests.getting_started.conftest import _MockOpenAIServer
+
-def model_routes_yaml(tmp_path: Path, local_mock_openai_server: object) -> Path:
+def model_routes_yaml(tmp_path: Path, local_mock_openai_server: "_MockOpenAIServer") -> Path:
     # Same shape as the guide's Step 3 YAML (defaults + a single named route),
     # but a `type: model` route pointed at a local in-process mock OpenAI
     # server so the lifecycle test serves a real chain fully offline.
-    base_url = local_mock_openai_server.base_url  # type: ignore[attr-defined]
+    base_url = local_mock_openai_server.base_url
As per coding guidelines: "Use Python 3.12 syntax and conventions, including `X | Y` union types, type hints throughout, sorted imports, and `TYPE_CHECKING` guards for circular imports."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/getting_started/test_getting_started.py` around lines 161 - 180, Update
model_routes_yaml to type local_mock_openai_server as _MockOpenAIServer,
importing that symbol under a TYPE_CHECKING guard with the existing imports kept
sorted. Remove the object annotation and the # type: ignore[attr-defined]
comment.

Source: Coding guidelines

tests/getting_started/conftest.py (2)

39-92: 📐 Maintainability & Code Quality | 🔵 Trivial

Mock OpenAI server implementation duplicated verbatim in tests/readme/conftest.py.

_MockOpenAIServer and _CANNED_COMPLETION here are byte-for-byte identical to the ones in tests/readme/conftest.py (lines 40-86). Consider extracting to a shared test helper module.

See consolidated comment for the full cross-file fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/getting_started/conftest.py` around lines 39 - 92, Extract the
duplicated _MockOpenAIServer and _CANNED_COMPLETION definitions from
tests/getting_started/conftest.py and tests/readme/conftest.py into a shared
test helper module. Update both conftest files to import and reuse those shared
symbols, preserving the existing local_mock_openai_server fixture behavior.

39-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated hermetic mock-upstream fixture into a shared helper.

tests/getting_started/conftest.py and tests/readme/conftest.py implement byte-for-byte identical _MockOpenAIServer/_CANNED_COMPLETION classes and an identical _hermetic_build monkeypatch pattern. The shared _hermetic_build also reconstructs PassthroughProfileConfig(api_key="test-key", base_url=upstream.base_url) from scratch rather than overriding fields on the real _self instance, silently dropping any other fields (e.g. model) the guide/README snippet may set on its passthrough config.

  • tests/getting_started/conftest.py#L39-L131: move _MockOpenAIServer/_CANNED_COMPLETION to a shared test helper module (mirroring tests/_chain_test_helpers.py's pattern) and, in _hermetic_build, override fields on _self (e.g. via dataclasses.replace if PassthroughProfileConfig is a dataclass) instead of constructing a brand-new instance.
  • tests/readme/conftest.py#L40-L120: import the same shared helper and apply the same _self-preserving fix to _hermetic_build.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/getting_started/conftest.py` around lines 39 - 131, Extract the
duplicated _CANNED_COMPLETION and _MockOpenAIServer from
tests/getting_started/conftest.py#L39-131 and tests/readme/conftest.py#L40-120
into a shared test helper, then import and reuse them in both fixtures. Update
_hermetic_build at both sites to preserve each _self PassthroughProfileConfig
while overriding only api_key and base_url, using dataclasses.replace or the
appropriate equivalent; do not reconstruct the config so fields such as model
remain intact.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_route_bundle.py`:
- Around line 173-176: Update test_empty_route_mapping_is_rejected to assert
that build_route_bundle_table raises RouteBundleConfigError with match="missing
'type'", preserving the existing input and documenting the required diagnostic
rather than accepting any configuration error.

---

Nitpick comments:
In `@tests/getting_started/conftest.py`:
- Around line 39-92: Extract the duplicated _MockOpenAIServer and
_CANNED_COMPLETION definitions from tests/getting_started/conftest.py and
tests/readme/conftest.py into a shared test helper module. Update both conftest
files to import and reuse those shared symbols, preserving the existing
local_mock_openai_server fixture behavior.
- Around line 39-131: Extract the duplicated _CANNED_COMPLETION and
_MockOpenAIServer from tests/getting_started/conftest.py#L39-131 and
tests/readme/conftest.py#L40-120 into a shared test helper, then import and
reuse them in both fixtures. Update _hermetic_build at both sites to preserve
each _self PassthroughProfileConfig while overriding only api_key and base_url,
using dataclasses.replace or the appropriate equivalent; do not reconstruct the
config so fields such as model remain intact.

In `@tests/getting_started/test_getting_started.py`:
- Around line 161-180: Update model_routes_yaml to type local_mock_openai_server
as _MockOpenAIServer, importing that symbol under a TYPE_CHECKING guard with the
existing imports kept sorted. Remove the object annotation and the # type:
ignore[attr-defined] comment.

In `@tests/readme/conftest.py`:
- Around line 40-86: Remove the duplicate _MockOpenAIServer implementation from
tests/readme/conftest.py and reuse the existing fixture or shared definition
from tests/getting_started/conftest.py, preserving the README tests’ current
server behavior and base_url interface.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 882520ba-3771-4faf-8930-1d1991960fb9

📥 Commits

Reviewing files that changed from the base of the PR and between 6c0a622 and a22f2cf.

📒 Files selected for processing (30)
  • .agents/skills/switchyard-coding-agent-launchers/SKILL.md
  • .github/workflows/getting-started.yml
  • .github/workflows/perf.yml
  • .github/workflows/readme.yml
  • CHANGELOG.md
  • docs/cli_reference.md
  • docs/core_concepts.md
  • docs/internal/latency_service_routing.md
  • docs/operations/context_window.md
  • docs/routing_algorithms/overview.md
  • docs/routing_algorithms/stage_router_routing.md
  • examples/route.yaml
  • switchyard/__init__.py
  • switchyard/cli/launchers/launcher_runtime.py
  • switchyard/cli/route_bundle.py
  • switchyard/cli/switchyard_cli.py
  • switchyard/lib/profiles/__init__.py
  • switchyard/lib/profiles/noop.py
  • tests/getting_started/conftest.py
  • tests/getting_started/test_getting_started.py
  • tests/readme/conftest.py
  • tests/readme/test_readme.py
  • tests/test_launch_claude.py
  • tests/test_launch_openclaw_deterministic.py
  • tests/test_noop_llm_backend.py
  • tests/test_resolve_classifier_prompts.py
  • tests/test_route_bundle.py
  • tests/test_routing_banner.py
  • tests/test_run_manifest.py
  • tests/test_user_config.py
💤 Files with no reviewable changes (5)
  • examples/route.yaml
  • switchyard/lib/profiles/init.py
  • tests/test_noop_llm_backend.py
  • switchyard/lib/profiles/noop.py
  • switchyard/init.py

Comment thread tests/test_route_bundle.py
@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/remove-noop-passthrough-route-types branch from a22f2cf to d8bff54 Compare July 27, 2026 17:46
@elyasmnvidian
elyasmnvidian requested a review from sabhatinas July 28, 2026 06:22
@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/remove-noop-passthrough-route-types branch from 6ef82eb to 3b81f4f Compare July 28, 2026 06:41
@elyasmnvidian

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main (the two commits now sit on origin/main; the earlier conflict is gone) and addressed the review nitpicks:

  • One shared mock upstream. Moved _MockOpenAIServer and _CANNED_COMPLETION into tests/_mock_openai_server.py. tests/getting_started/conftest.py and tests/readme/conftest.py both import them, so there is one copy instead of two identical ones.
  • _hermetic_build keeps the snippet's other config fields. Both conftests now call dataclasses.replace(_self, api_key=..., base_url=...) instead of building a fresh PassthroughProfileConfig, so any other field the snippet set (for example model) is kept rather than dropped.
  • Real type hint instead of object. tests/getting_started/test_getting_started.py imports _MockOpenAIServer under TYPE_CHECKING and types local_mock_openai_server with it, so the object annotation and the # type: ignore[attr-defined] on .base_url are gone.

Validation on the rebased branch: ruff and mypy --strict clean; the affected unit tests pass (208), the --markdown-docs guide snippets pass (15), and the full not integration suite is 1897 passed / 9 skipped. The one remaining failure (test_verify.py::...test_secrets_file_wins_when_no_cli_or_env) is the pre-existing machine-specific one noted in the PR description — it passes with an isolated HOME.

@elyasmnvidian

Copy link
Copy Markdown
Contributor Author

Follow-up commit a729bf7b from a local audit pass (fail-closed / docs-match-runtime / plain-English):

  • docs/routing_algorithms/overview.md and docs/core_concepts.md: dropped two lines that still named the removed passthrough route type — the subagent_target route list and the /v1/models "discover" wording. Copying either now raises unsupported route type.
  • tests/test_route_bundle.py: added test_model_route_requires_target. type: model with no target/model fails closed (route 'm' requires target or model), and nothing covered that branch after the passthrough fallback was removed.
  • Wording: cannedfixed in the mock-upstream docstring/comment and perf.yml (kept the _CANNED_COMPLETION identifier).

Re-validated: ruff, mypy --strict, the route-bundle tests, and the --markdown-docs guide flows all pass.

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
…g-target test

Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
@elyasmnvidian
elyasmnvidian force-pushed the emehtabuddin/remove-noop-passthrough-route-types branch from a729bf7 to 8e1de47 Compare July 28, 2026 17:48
@elyasmnvidian
elyasmnvidian merged commit 9762a07 into main Jul 28, 2026
21 checks passed
@elyasmnvidian
elyasmnvidian deleted the emehtabuddin/remove-noop-passthrough-route-types branch July 28, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants