NVIDIA-NeMo / NVIDIA-NeMo/Switchyard
[Bug] Claude Code requests fail on Azure Opus when thinking parameters are not normalized per target
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 3.2k
- Forks
- 291
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 182
Description
Environment
- Repo / commit:
NVIDIA-NeMo/Switchyard @ 3acf3d86f10fead9f434b858dbbcf01a660b8db0 - Switchyard:
0.2.0 - OS: macOS 26.5.2
- Python: 3.13.5
- Claude Code: 2.1.220
- Key packages:
anthropic==0.99.0,litellm==1.85.0 - Upstream: NVIDIA Inference Hub (
https://inference-api.nvidia.com/v1) - Models:
aws/anthropic/bedrock-claude-opus-5azure/anthropic/claude-opus-5
Description
When Switchyard routes Claude Code traffic between the Bedrock and Azure deployments of Claude Opus 5, requests selected for Bedrock succeed while requests selected for Azure fail with HTTP 400.
Claude Code sends Anthropic thinking parameters using thinking.type=enabled. The Azure deployment requires thinking.type=adaptive and output_config.effort. Switchyard currently does not normalize this provider/model-specific difference after selecting the target, so an otherwise valid routing policy becomes partially unavailable.
The final launcher summary compounds the confusion because its per-model req value reports successful calls only, while failed attempts appear only in the aggregate error count. This can make it look as though Bedrock received all traffic even when Azure was selected and failed.
Steps to Reproduce
-
Create
/tmp/opus-deployment-random.yaml:defaults: api_key: ${NVIDIA_INFERENCE_HUB_API_KEY} base_url: ${NVIDIA_INFERENCE_HUB_API_ROOT} format: openai routes: demo/opus-deployment-random: type: random_routing strong_probability: 0.5 fallback_target_on_evict: bedrock strong: id: bedrock model: aws/anthropic/bedrock-claude-opus-5 weak: id: azure model: azure/anthropic/claude-opus-5 -
Start Switchyard:
source .venv/bin/activate switchyard serve --routing-profiles /tmp/opus-deployment-random.yaml --port 4000 -
In another terminal, route Claude Code through the proxy:
ANTHROPIC_BASE_URL=http://127.0.0.1:4000 \ ANTHROPIC_AUTH_TOKEN=switchyard \ ANTHROPIC_API_KEY="" \ ANTHROPIC_MODEL=demo/opus-deployment-random \ ANTHROPIC_SMALL_FAST_MODEL=demo/opus-deployment-random \ claude -
Send several normal requests, such as
Reply with only OK.For a deterministic comparison, temporarily setstrong_probability: 1.0to exercise Bedrock, then0.0to exercise Azure.
Actual Result
The Bedrock target succeeds. The Azure target returns:
400 litellm.BadRequestError: Azure_aiException -
{"type":"error","error":{"type":"invalid_request_error",
"message":"\"thinking.type.enabled\" is not supported for this model.
Use \"thinking.type.adaptive\" and \"output_config.effort\" to control
thinking behavior."}}
A representative final summary was:
requests : 20 (11 errors)
models:
bedrock-claude-opus-5 9 req
claude-opus-5 0 req
A minimal Azure request without Claude Code's thinking configuration succeeds, confirming that the deployment, model ID, credentials, and endpoint are valid.
Expected Result
Switchyard should normalize request parameters for the selected target so the same Claude Code request succeeds on both deployments.
For Azure Opus, enabled thinking should be represented as adaptive thinking, with supported effort configuration and without a fixed thinking budget. The session summary should also display per-model failures so failed selections are distinguishable from models that were never selected.
Suggested Fix
Add target-aware request normalization at the outbound backend boundary, after routing has selected the target:
- Rewrite unsupported
thinking.type=enabledtothinking.type=adaptivefor affected Azure Opus targets. - Remove incompatible fixed-budget fields.
- Preserve supported effort through
output_config.effort. - Add tests that send the same Claude Code request through the Bedrock and Azure targets and assert the provider-appropriate outbound payload.
- Include each model's error count in the launcher session summary.
Target-level extra_body alone cannot reliably implement this because its current shallow merge is caller-wins and therefore cannot replace an inbound thinking field.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the outbound backend boundary after routing selects a target, then inspect the launcher session summary described in the issue. Reproduce with the provided routing profile and Claude Code commands, comparing Bedrock and Azure payloads. Done means both targets accept the request with provider-appropriate thinking parameters and the summary reports per-model failures.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, azure, rust
- Domain
- api, backend, cloud
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100