feat(server): /v1/messages/count_tokens forwarding on the Rust server - #138
Conversation
WalkthroughChangesAnthropic count-tokens flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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_anthropic_count_tokens_endpoint.py`:
- Around line 20-38: Replace the _CapturingClient fake and synchronous
TestClient usage with an async ASGI test using httpx.AsyncClient configured with
ASGITransport. Mock the outbound count-tokens request through respx, then assert
the routed request’s URL, JSON payload, and headers to preserve end-to-end
request-shape coverage.
🪄 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: 0f834f53-e4c0-4c21-9e57-1f542dd5a033
📒 Files selected for processing (4)
switchyard/cli/route_bundle.pyswitchyard/lib/endpoints/anthropic_messages_endpoint.pyswitchyard/lib/route_table.pytests/test_anthropic_count_tokens_endpoint.py
8eb7f02 to
4501b04
Compare
@sabhatinas Can you re-write that? I think that's AI stringing words together. I've been squinting at it but it doesn't really make any sense. |
I updated the PR body now, it should be easier to understand now. |
7c13bdd to
fff0cb9
Compare
Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
…166) Signed-off-by: Sabhatina Selvam <sabhatinas@nvidia.com>
fff0cb9 to
f8bae45
Compare
What
Adds
POST /v1/messages/count_tokensto the Rustswitchyard-server. Claude Code calls it to size a request before sending; without it the endpoint 503'd.How
count_tokensis a direct passthrough, not a routed call. Completions run the router; count_tokens does not:/v1/messages(completion)/v1/messages/count_tokens/v1/messages/count_tokens, no routingToken counting is a pre-flight estimate with no routing decision, so running the classifier cascade for it is wrong (on a stage router a signal-less request would abstain, or fire a billable judge call). Instead:
Algorithm::count_tokens(request)forwards viacount_tokens_client()— the route's first Anthropic-capable target.RoutedLlmClient::count_tokens(request)forwards to that client's Anthropic backend, restamping the model to the upstream id. It shares the encode/POST helper (send_encoded) with the completion path.Caveat (deferred): for a route with multiple Anthropic tiers, "first" is arbitrary — choosing which tier count_tokens should reflect is left for later.
Live snapshots
Run against real NVIDIA Anthropic (
claude-opus-4-8@ inference-api) throughswitchyard-serveron a single-target route:"hi"{"input_tokens": 8}· 200"count these tokens please"(+ system){"input_tokens": 19}· 200{"input_tokens": 44}· 200{"input_tokens": 370}· 200/v1/messagescompletion on the same routeCounts scale with content and include system + tools, matching Anthropic's own count_tokens.
Stage router
A unit test confirms the fix: on an efficient-first stage router, a completion abstains (no signals to route on) but count_tokens succeeds — it passes through to the strong (Anthropic) tier and returns a count.
Deferred to a follow-up
The Python server keeps its current forwarder for now. Making it symmetric means adding count_tokens to the
LlmBackendstack (switchyard-core / switchyard-components) and reaching the backend through the chain. Tracked off SWITCH-1048.Testing
231 unit/integration tests green (incl. the stage-router passthrough test and the shared
send_encodedpath), clippy clean, plus the live checks above.Linear: SWITCH-1048