Skip to content

refactor(routing)!: remove latency-aware router - #162

Merged
eric-liu-nvidia merged 1 commit into
mainfrom
eric-liu/switch-1111-delete-latency-aware-router-code-for-switchyard-02
Jul 28, 2026
Merged

refactor(routing)!: remove latency-aware router#162
eric-liu-nvidia merged 1 commit into
mainfrom
eric-liu/switch-1111-delete-latency-aware-router-code-for-switchyard-02

Conversation

@eric-liu-nvidia

@eric-liu-nvidia eric-liu-nvidia commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Removes the latency-aware router ("latency service") from Switchyard for the 0.2+ line, per SWITCH-1111.

The router depended on NVIDIA Inference Hub's latency endpoint and schema, which is specific to Inference Hub. Per the team decision, Switchyard should not carry this: Inference Hub can pin the current commit indefinitely, and deployments needing multi-endpoint / load- or latency-aware routing should move to Dynamo (backend-load / KV-cache-aware routing with failover) or an external load balancer such as Traefik / HAProxy.

Breaking change — removes the latency_service route type and the public LatencyServiceLLMBackend / LatencyServiceBackendConfig / LatencyServiceEndpoint / LatencyServiceProfileConfig / HealthPoller / EndpointHealth* exports.

What changed (49 files, +275 / −7,274)

  • Deleted the 4 Python source files (latency_service_llm_backend.py, health_poller.py, latency_service_backend_config.py, latency_service.py), 5 latency-only test files, the internal routing doc, and the example YAML.
  • Rewired the public API (__init__.py re-exports across backends / config / profiles / top-level) and stripped the latency_service route type out of cli/route_bundle.py (dispatch, key-sets, builders, type inference, aliases, plus an orphaned helper).
  • Re-vehicled test_redis_pin_store.py's shared-infra coverage onto the escalation router's _build_affinity_l2, and trimmed 4 mixed test files down to their non-latency tests.
  • libsy / Rust: the router was never in Rust — only 3 cosmetic references (two doc comments, one test label) were cleaned up.
  • Docs / observability: added a Keep-a-Changelog [Unreleased] → Removed entry (0.1.0 history preserved), trimmed metrics_reference.md, the three examples/prometheus/* files, and the AGENTS.md source-tree map; scrubbed stale docstrings/comments naming the deleted class across shared source.

Test coverage

A few generic behaviors were previously covered only through latency-vehicled tests; these were re-vehicled onto in-process passthrough-style backends:

  • Client-response middleware — LLM routes feed switchyard_client_responses_total, operational routes (/metrics, /health, /v1/models) do not.
  • Post-dispatch serialization exception → JSON 500 envelope.
  • Unsupported inbound role⚠️ behavior change: the removed latency backend special-cased a translation invalid_value into a provider-compatible 400; no surviving backend reproduces that, so an invalid role now surfaces as a 500 internal_error. The re-vehicled test pins this current 500 behavior and documents where a 400 could be restored (follow-up if desired).

Verification

ruff ✅ · mypy --strict ✅ · pytest 1735 passed, 35 skipped ✅ · mkdocs build --strict ✅ · cargo fmt --check / clippy -D warnings / test --workspace

One deselected test (test_verify.py::…::test_secrets_file_wins_when_no_cli_or_env) is a pre-existing, environment-specific config-leak failure — it reproduces on a clean tree and is unrelated to this change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Removed

    • Removed latency-aware routing and its associated configuration, endpoint selection, health polling, session-affinity behavior, and public API exports.
    • Removed related example configurations, monitoring alerts, documentation, and legacy tests.
  • Documentation

    • Updated metrics guidance, routing terminology, Prometheus setup instructions, and backend documentation to reflect the current routing options.
  • Bug Fixes

    • Added coverage ensuring post-dispatch and invalid-request failures return consistent JSON 500 error responses.
    • Refined routing statistics tests for escalation-based routing.

Signed-off-by: Eric Liu <zengyuanl@nvidia.com>
@eric-liu-nvidia
eric-liu-nvidia requested a review from a team as a code owner July 28, 2026 06:23
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1

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

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

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The pull request removes the latency-aware router and its backend, configuration, profile, route-bundle support, health metrics, alerts, documentation, exports, and dedicated tests. Remaining routing and endpoint error-contract tests are updated accordingly.

Changes

Latency Service Removal

Layer / File(s) Summary
Public API and repository surface
AGENTS.md, CHANGELOG.md, switchyard/__init__.py, switchyard/lib/backends/*, switchyard/lib/config/*, switchyard/lib/profiles/*
Removes Latency Service exports, configuration/profile references, health-poller references, and documents the removal.
Route-bundle removal
switchyard/cli/route_bundle.py, tests/test_route_bundle.py, tests/test_redis_pin_store.py
Removes latency route inference, aliases, validation, construction helpers, and related tests; Redis affinity coverage now uses escalation-router configuration.
Metrics and alert updates
docs/internal/metrics_reference.md, examples/prometheus/*, crates/switchyard-components/tests/stats_accumulator.rs, tests/test_outcome_metrics.py
Removes Latency Service metrics and alerts and updates metric guidance and outcome-counter tests.
Endpoint error-contract coverage
tests/test_inference_e2e.py, tests/test_upstream_error_passthrough.py, tests/test_route_selection_headers.py
Adds translation-based JSON 500 contract tests and removes Latency Service-specific passthrough and header-flow tests.
Documentation and terminology cleanup
crates/switchyard-py/src/core_bindings/*, switchyard/cli/*, switchyard/lib/*, tests/*
Generalizes comments and docstrings that referenced the removed backend or router.

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

Poem

I’m a bunny with a tidy route,
Latency hops away in a cloudless chute.
Metrics shed old ears and alerts grow light,
New tests sparkle in the moonlit night,
While exports curl up, clean and right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.50% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: removal of the latency-aware router and related routing surface.
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.

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: 2

🧹 Nitpick comments (3)
tests/test_outcome_metrics.py (1)

274-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate _canned_app() as FastAPI.

build_switchyard_app returns FastAPI; -> object discards that under strict mypy for no benefit.

♻️ Proposed typing tightening
-def _canned_app() -> object:
+def _canned_app() -> FastAPI:
     return build_switchyard_app(
         Switchyard(backend=_CannedBackend(), translator=TranslationEngine())
     )

Requires from fastapi import FastAPI in the import block.

As per coding guidelines: "provide type hints throughout; code must pass strict mypy".

🤖 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/test_outcome_metrics.py` around lines 274 - 277, The _canned_app()
return annotation is too broad for strict mypy. Import FastAPI and annotate
_canned_app() with FastAPI, preserving its existing build_switchyard_app
implementation.

Source: Coding guidelines

tests/test_inference_e2e.py (1)

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

Use pytest-mock's mocker fixture instead of unittest.mock.patch.

The repository standard for general mocking in tests/ is pytest-mock, which also removes the nested with and guarantees teardown ordering.

♻️ Proposed switch to `mocker`
-    def test_post_dispatch_exception_returns_json_500(self, app: FastAPI) -> None:
+    def test_post_dispatch_exception_returns_json_500(
+        self, app: FastAPI, mocker: MockerFixture
+    ) -> None:
         """An exception raised AFTER dispatch (e.g. during result serialization)
         must surface as a JSON 500 envelope, not FastAPI's plain-text 500."""
 
         class _Unserializable:
             def model_dump(self) -> None:
                 raise RuntimeError("serialization exploded")
 
-        with patch(
-            "switchyard.lib.endpoints.openai_chat_endpoint.dispatch_chat_request",
-            new_callable=AsyncMock,
-            return_value=_Unserializable(),
-        ):
-            with TestClient(app, raise_server_exceptions=False) as client:
-                response = client.post(
-                    "/v1/chat/completions",
-                    json={"model": "any-model", "messages": [{"role": "user", "content": "hi"}]},
-                )
+        mocker.patch(
+            "switchyard.lib.endpoints.openai_chat_endpoint.dispatch_chat_request",
+            new_callable=mocker.AsyncMock,
+            return_value=_Unserializable(),
+        )
+        with TestClient(app, raise_server_exceptions=False) as client:
+            response = client.post(
+                "/v1/chat/completions",
+                json={"model": "any-model", "messages": [{"role": "user", "content": "hi"}]},
+            )

Then drop the unittest.mock import and add from pytest_mock import MockerFixture under a TYPE_CHECKING guard.

As per coding guidelines: "Use respx for HTTP mocking, pytest-mock for general mocking".

Also applies to: 549-553

🤖 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/test_inference_e2e.py` at line 16, Replace unittest.mock.patch usage in
the affected tests with the pytest-mock mocker fixture, removing nested patch
context managers while preserving existing mocked behavior and teardown scope.
Remove the unittest.mock import, and add pytest_mock.MockerFixture under a
TYPE_CHECKING guard for fixture annotations.

Source: Coding guidelines

tests/test_upstream_error_passthrough.py (1)

190-197: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Re-anchor the provider-labelled ERROR_SOURCE_HEADER assertion here.

Only the switchyard-labelled branch survives (Line 317). The provider-labelled branch lost its wire-level coverage with the removed latency-backed tests, even though it is reproducible on this Rust-native 401 path with one assertion.

💚 Proposed coverage restore
     assert response.status_code == 401
+    assert response.headers[ERROR_SOURCE_HEADER] == "provider"
     assert response.json() == {
🤖 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/test_upstream_error_passthrough.py` around lines 190 - 197, Restore
wire-level coverage in the 401 response test by asserting the response’s
ERROR_SOURCE_HEADER carries the provider label alongside the existing status and
JSON assertions. Keep this scoped to the provider-labelled upstream error path
represented by the response fixture.
🤖 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 `@examples/prometheus/README.md`:
- Around line 28-30: Update the Kubernetes discovery guidance in
examples/prometheus/README.md (lines 28-30) and the related comment in
examples/prometheus/prometheus.yml (lines 21-23) to reference a complete
Kubernetes scrape job, including the required role, rather than suggesting
kubernetes_sd_configs is a direct static-target block swap.

In `@tests/test_error_source_annotation.py`:
- Around line 8-10: Update the module docstring in
tests/test_error_source_annotation.py to correct the grammar, replacing “from
the ctx stamps a backend sets” with equivalent wording such as “from ctx stamps
set by a backend.”

---

Nitpick comments:
In `@tests/test_inference_e2e.py`:
- Line 16: Replace unittest.mock.patch usage in the affected tests with the
pytest-mock mocker fixture, removing nested patch context managers while
preserving existing mocked behavior and teardown scope. Remove the unittest.mock
import, and add pytest_mock.MockerFixture under a TYPE_CHECKING guard for
fixture annotations.

In `@tests/test_outcome_metrics.py`:
- Around line 274-277: The _canned_app() return annotation is too broad for
strict mypy. Import FastAPI and annotate _canned_app() with FastAPI, preserving
its existing build_switchyard_app implementation.

In `@tests/test_upstream_error_passthrough.py`:
- Around line 190-197: Restore wire-level coverage in the 401 response test by
asserting the response’s ERROR_SOURCE_HEADER carries the provider label
alongside the existing status and JSON assertions. Keep this scoped to the
provider-labelled upstream error path represented by the response fixture.
🪄 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: 6b69bf94-c98b-4efb-a103-541b0ed62f91

📥 Commits

Reviewing files that changed from the base of the PR and between 49b852b and ac8d0fa.

📒 Files selected for processing (49)
  • AGENTS.md
  • CHANGELOG.md
  • crates/switchyard-components/tests/stats_accumulator.rs
  • crates/switchyard-py/src/core_bindings/context.rs
  • crates/switchyard-py/src/core_bindings/response.rs
  • docs/internal/latency_service_routing.md
  • docs/internal/metrics_reference.md
  • examples/latency_service_caller_required.yaml
  • examples/prometheus/README.md
  • examples/prometheus/prometheus.yml
  • examples/prometheus/switchyard.rules.yaml
  • switchyard/__init__.py
  • switchyard/cli/launch_command.py
  • switchyard/cli/launchers/claude_code_launcher.py
  • switchyard/cli/route_bundle.py
  • switchyard/cli/switchyard_cli.py
  • switchyard/lib/affinity_pin_store.py
  • switchyard/lib/backends/__init__.py
  • switchyard/lib/backends/health_poller.py
  • switchyard/lib/backends/latency_service_llm_backend.py
  • switchyard/lib/config/__init__.py
  • switchyard/lib/config/latency_service_backend_config.py
  • switchyard/lib/endpoints/prometheus_emitter.py
  • switchyard/lib/endpoints/stats_endpoint.py
  • switchyard/lib/endpoints/upstream_error.py
  • switchyard/lib/profiles/__init__.py
  • switchyard/lib/profiles/escalation_router_profile_config.py
  • switchyard/lib/profiles/latency_service.py
  • switchyard/lib/proxy_context.py
  • switchyard/lib/redis_pin_store.py
  • switchyard/lib/session_affinity.py
  • switchyard/lib/tracing.py
  • tests/e2e/test_latency_service_llm_backend.py
  • tests/readme/test_readme.py
  • tests/test_error_source_annotation.py
  • tests/test_inference_e2e.py
  • tests/test_latency_service_health_metrics.py
  • tests/test_latency_service_llm_backend.py
  • tests/test_latency_service_spans.py
  • tests/test_latency_service_stats_metrics.py
  • tests/test_outcome_metrics.py
  • tests/test_passthrough_profile_e2e.py
  • tests/test_redis_pin_store.py
  • tests/test_route_bundle.py
  • tests/test_route_selection_headers.py
  • tests/test_session_affinity.py
  • tests/test_stream_close_chain.py
  • tests/test_stream_leak_repro.py
  • tests/test_upstream_error_passthrough.py
💤 Files with no reviewable changes (16)
  • switchyard/lib/profiles/latency_service.py
  • examples/latency_service_caller_required.yaml
  • docs/internal/latency_service_routing.md
  • switchyard/lib/backends/health_poller.py
  • switchyard/lib/config/latency_service_backend_config.py
  • switchyard/lib/config/init.py
  • switchyard/init.py
  • switchyard/lib/backends/latency_service_llm_backend.py
  • tests/test_latency_service_health_metrics.py
  • tests/test_latency_service_stats_metrics.py
  • tests/e2e/test_latency_service_llm_backend.py
  • switchyard/lib/backends/init.py
  • tests/test_latency_service_spans.py
  • tests/test_latency_service_llm_backend.py
  • switchyard/lib/profiles/init.py
  • switchyard/cli/route_bundle.py

Comment thread examples/prometheus/README.md
Comment thread tests/test_error_source_annotation.py

@ayushag-nv ayushag-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Loved -7724

@eric-liu-nvidia
eric-liu-nvidia merged commit c6a850f into main Jul 28, 2026
21 checks passed
@eric-liu-nvidia
eric-liu-nvidia deleted the eric-liu/switch-1111-delete-latency-aware-router-code-for-switchyard-02 branch July 28, 2026 16:41
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.

4 participants