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-5
azure/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 set strong_probability: 1.0 to exercise Bedrock, then 0.0 to 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=enabled to thinking.type=adaptive for 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.
Environment
NVIDIA-NeMo/Switchyard @ 3acf3d86f10fead9f434b858dbbcf01a660b8db00.2.0anthropic==0.99.0,litellm==1.85.0https://inference-api.nvidia.com/v1)aws/anthropic/bedrock-claude-opus-5azure/anthropic/claude-opus-5Description
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 requiresthinking.type=adaptiveandoutput_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
reqvalue 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:Start Switchyard:
source .venv/bin/activate switchyard serve --routing-profiles /tmp/opus-deployment-random.yaml --port 4000In 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 \ claudeSend 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:
A representative final summary was:
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:
thinking.type=enabledtothinking.type=adaptivefor affected Azure Opus targets.output_config.effort.Target-level
extra_bodyalone cannot reliably implement this because its current shallow merge is caller-wins and therefore cannot replace an inboundthinkingfield.