fix(bedrock): allowlist Bedrock Invoke body fields and filter all anthropic-beta values - #26148
Conversation
|
|
Greptile SummaryThis PR fixes a The TypedDict design ( Confidence Score: 5/5Safe to merge; all remaining findings are P2 style/coverage gaps that don't affect correctness on the happy path. The core fix (allowlist + full-union beta filtering) is correct and well-tested for the reported failure mode. All open findings are P2: a missing test that was promised but not added, a backwards-compatibility note (the affected scenario — user-provided betas that previously 400'd at Bedrock — was already broken), and a minor redundant function call. No P0/P1 issues identified. anthropic_claude3_transformation.py (backwards-compat note on user beta filtering) and the test file (missing exact-set allowlist pin test).
|
| Filename | Overview |
|---|---|
| litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py | Core transformation logic: adds TypedDict-driven allowlist filtering (step 7) and moves full-union beta filtering to cover user-provided headers too; removes dedicated output_config pop (now handled by allowlist). Logic is sound but includes a redundant per-beta filter call for the warning log and a backwards-incompatible behavioral change for user-supplied betas with no feature flag. |
| litellm/types/llms/bedrock.py | Adds BedrockInvokeAnthropicMessagesRequest TypedDict with 13 allowlisted fields anchored to the AWS docs; clean single-source-of-truth design with a helpful docstring. No issues. |
| tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py | Four of the five promised new tests are present and well-scoped. The exact-set allowlist pin test (test_bedrock_invoke_allowlist_exact_contents) mentioned as a critical guard in the PR description is absent. |
| tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py | Drive-by fix: replaces output-128k-2025-02-19 (null/unsupported in Bedrock mapping, old test encoded buggy pass-through) with context-1m-2025-08-07 (properly supported). Change is correct and improves test validity. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Client Request
/v1/messages → Bedrock] --> B[transform_anthropic_messages_request]
B --> C[Steps 1-5: build request,
pop output_format, remove custom tools]
C --> D[Step 6: Build beta_set
user_beta_set + auto_betas]
D --> E[filter_and_transform_beta_headers
full beta_set through Bedrock mapping]
E --> F{filtered_betas
non-empty?}
F -- Yes --> G[Set anthropic_beta
in request]
F -- No --> H[Skip anthropic_beta]
G --> I[Step 7: Allowlist filter
BedrockInvokeAnthropicMessagesRequest
.__annotations__.keys]
H --> I
I --> J{Field in
allowlist?}
J -- Yes --> K[Keep field]
J -- No --> L[Strip field
+ DEBUG log]
K --> M[Signed Bedrock
InvokeModel request]
L --> M
style E fill:#d4edda
style I fill:#d4edda
style L fill:#fff3cd
Reviews (5): Last reviewed commit: "chore: make `uv` newer than 0.10 allowab..." | Re-trigger Greptile
| # litellm_metadata, and any future Anthropic additions Claude Code may send) | ||
| # is filtered out before the request is forwarded, preventing | ||
| # "Extra inputs are not permitted" 400s. | ||
| BEDROCK_INVOKE_ALLOWED_TOP_LEVEL_FIELDS = frozenset( |
There was a problem hiding this comment.
Why is this not just a type, Bedrock Invoke Request body ?
There was a problem hiding this comment.
Great call out -- it's a type now
|
is there any workaround available until this is merged? Seems like claude code is not working for me with litellm as its not just happening with opus 4.7 but also 4.6 and sonnet 4.6 when using aws bedrock? |
bd15e3f to
77465ef
Compare
77465ef to
47c77f4
Compare
47c77f4 to
363d9ce
Compare
363d9ce to
021a702
Compare
…hropic-beta values Two fail-safes for the /v1/messages → Bedrock Invoke pass-through so new Anthropic-only extensions Claude Code starts sending can't reach Bedrock and trigger a 400 "Extra inputs are not permitted": 1. Top-level body fields are filtered to a typed allowlist. New `BedrockInvokeAnthropicMessagesRequest` TypedDict (in `litellm/types/llms/bedrock.py`) captures the Bedrock Invoke Anthropic Messages body schema; the runtime allowlist is derived from its `__annotations__` so the type and the filter can't drift. Anchored to the AWS reference page in docstrings + transform comment. An exact-set test pins the resolved allowlist so any future edit forces conscious review. Drops context_management, output_config, speed, mcp_servers, container, inference_geo, internal litellm_metadata, and any future Anthropic addition. output_format stays as an active inline-schema conversion (not just a strip). 2. The anthropic-beta header list is filtered + transformed against the bedrock mapping for ALL betas, not just auto-injected ones. The previous code union'd user-provided betas back in unfiltered, so a client on a new Anthropic-direct beta (e.g. advisor-tool-…, context-management-…) could still pin the request to fail. In a proxy context the client can't know the backend is Bedrock; the provider mapping is authoritative. User-provided drops are logged at WARNING so intentional overrides leave a breadcrumb. Updates one existing test that happened to assert on the old buggy pass-through (it used output-128k-2025-02-19, which is null in the bedrock mapping and would 400 at runtime); rewrote it against a bedrock-supported beta. Scope: messages/invoke only. The same user-beta bypass exists in chat/invoke but that's a different code path with different user-expectation trade-offs — follow-up.
021a702 to
583bdd3
Compare
…itellm_fix-bedrock-invoke-allowlist
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 29203053 | Triggered | Generic Password | 6da9ee9 | .circleci/config.yml | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
Seems like this is a result of merging |
@Rainson12 For now, add this header to your settings file: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Tested working on: Claude Code v2.1.116 |
… 6e) (#60) * fix(passthrough): record real TTFT and start_time for streaming requests Pass-through streaming requests (/v1/messages, /vertex_ai/*, /gemini/*, /cohere/*, /assemblyai/*, /openai/*, /cursor/*) all share PassThroughStreamingHandler.chunk_processor, which had two timing bugs that interacted to collapse spend_logs.completionStartTime onto spend_logs.endTime (off by ~1ms of clock resolution, not literally identical) — making the streaming phase (endTime - completionStartTime) round to roughly zero and TTFT effectively soak up the entire request duration for every pass-through streaming row. Root cause 1. `start_time` arg too late. The caller's start_time originates in BaseAnthropicMessagesStreamingIterator.__init__, which runs AFTER the upstream HTTP response has already been received. SpendLogs. startTime therefore reflects "moment we started reading the stream", not "moment the client request entered the proxy" — the real TTFT window is silently subtracted from Duration. 2. First-chunk arrival never recorded. The chunk loop yielded bytes to the client and collected them for logging, but never noted when the first byte arrived. With litellm_logging_obj.completion_start_time left as None, the fallback at litellm_logging.py:1834-1837 sets it to end_time — completionStartTime lands within ~1ms of endTime and streaming_phase rounds to 0. Both bugs hide each other. Fixing only #2 gives TTFT close to 0 with Duration deflated by ~TTFT. Fixing only #1 leaves completionStartTime still pinned to endTime. Both must be fixed for the math to be correct. Fix In chunk_processor, at the top of the try block: - Override start_time with litellm_logging_obj.start_time when the latter is an earlier datetime — that's the true request-entry timestamp set in common_request_processing.base_process_llm_request. - On the first chunk yielded by response.aiter_bytes(), call litellm_logging_obj._update_completion_start_time(datetime.now()) to populate the field that downstream payload builders look for. Verified end-to-end (Anthropic claude-sonnet-4-6, 200-word stream): Before fix: Duration=6528ms TTFT=6527ms streaming_phase=1ms After fix: Duration=8496ms TTFT=2373ms streaming_phase=6123ms Control: /v1/chat/completions (same model+prompt) Duration=8143ms TTFT=2071ms streaming_phase=6072ms Test plan - New e2e case 13 (`13_passthrough_streaming_ttft.md` + data/13_*.sh) sends a real ~200-word streamed completion through /v1/messages, polls spend_logs for up to 30s, asserts: * streaming_phase_ms > 1000 (catches bug #2 regression) * ttft_ms > 300 (catches bug #1 regression) * ttft_ms < duration_ms / 2 (catches either regression) Plus a soft parity check vs /v1/chat/completions for the same upstream model. - Cost ~$0.005 per case run. - GREEN against the fix; was RED before (streaming_phase=1ms, ttft=6527ms) — assertions 1 and 3 fired. Blast radius: all pass-through endpoints — they all flow through this single chunk_processor. User noticed the bug via Anthropic; the same fix improves TTFT observability for Vertex AI, Gemini, Cohere, and the other pass-through providers in the same release. * hotfix(bedrock-anthropic): clean stray conflict markers from Wave 6d merge PR #59 (Wave 6d) accidentally committed unresolved cherry-pick conflict markers in the Bedrock anthropic_claude3_transformation Invoke filter. The file is broken in ship/v1.87.0 — the Python module fails to import. Fix: apply the intended Wave 6d resolution — keep upstream's single `filtered_betas = sorted(...)` shape from PR BerriAI#26148, layer our `overrides=_overrides` argument on top so the `anthropic_beta_overrides` per-deployment config still threads through. This commit only touches the unresolved hunk; no behavioral change vs the intent of Wave 6d.
…oke-allowlist fix(bedrock): allowlist Bedrock Invoke body fields and filter all anthropic-beta values
Relevant issues
Fixes a 400 from Bedrock Invoke when Claude Code (or any client on a newer Anthropic extension) calls
bedrock/us.anthropic.claude-opus-4-7through the/v1/messagespass-through:The narrow trigger is Claude Code sending
context_managementon every request. But the underlying pattern — Claude Code sends Anthropic-direct features, LiteLLM forwards them to Bedrock, Bedrock 400s on anything it doesn't recognize — will repeat whenever Anthropic ships a new body field or beta header. This PR closes both leak points.What changed
Two fail-safes in the messages/invoke transformation (
litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py):1. Top-level body fields → typed allowlist
New
BedrockInvokeAnthropicMessagesRequestTypedDict inlitellm/types/llms/bedrock.pycaptures the Bedrock Invoke Anthropic Messages body schema. The runtime allowlist is derived from__annotations__so the type and the filter share one definition — editing the type automatically updates the filter. Anchored to the AWS reference page in the TypedDict docstring and the transform comment.Fields (13 total):
anthropic_version, anthropic_beta, messages, system, max_tokens, stop_sequences, temperature, top_p, top_k, tools, tool_choice, thinking, metadata. The first 11 come straight from the AWS docs;thinkingandmetadataare part of the Anthropic Messages surface Bedrock implements but aren't spelled out on that AWS page (thinkingis actively constructed for Opus 4.5 / Sonnet 4 extended thinking).Drops
context_management,output_config,speed,mcp_servers,container,inference_geo,cache_controltop-level, internallitellm_metadata, and any future Anthropic addition Claude Code starts sending.output_formatstays as an active inline-schema conversion rather than a plain strip. Stripped keys logged at DEBUG.An exact-set assertion test pins the resolved allowlist so any future edit forces a conscious review — you can't silently broaden or narrow the surface.
2.
anthropic-betavalues → filtered + transformed for the full union (not just auto-injected)Previously the code only ran
filter_and_transform_beta_headersover auto-injected betas (beta_set - user_beta_set) and union'd user-provided values back in untouched. That left a real hole: a client (Claude Code, any SDK) on a new Anthropic-direct beta — e.g.advisor-tool-2026-03-01, or thecontext-management-2025-06-27header that pairs with the body-field fix above — gets forwarded as-is and 400s. It also skipped the Bedrock-side rename (e.g.advanced-tool-use-2025-11-20→tool-search-tool-2025-10-19), so even supported betas could fail because of Anthropic-spelling.Mental model flip: in a proxy deployment the client doesn't know the backend, so its beta-header declarations are advisory; the provider mapping is authoritative. The code now filters + transforms the full
beta_set, and warns at WARNING when a user-provided beta gets dropped so intentional overrides leave a breadcrumb.Drive-by test fix
tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py::test_messages_transformation_anthropic_betaasserted onoutput-128k-2025-02-19passing through — a beta that'snullin the Bedrock mapping and would 400 at runtime. The test was encoding the old buggy-pass-through as expected behavior. Rewrote it againstcontext-1m-2025-08-07(which IS in the Bedrock mapping) to preserve the actual intent.Scope
Messages pass-through only. The same user-beta bypass exists in
chat/invoke_transformations/but that path is the OpenAI-format → Bedrock route where manual beta-header use is more common and filter behavior may need different trade-offs — intentionally left as follow-up.Pre-Submission checklist
tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py:test_bedrock_invoke_allowlist_exact_contents— exact-set pin on the resolved allowlisttest_bedrock_messages_strips_context_management— reproduces the reported tracetest_bedrock_messages_allowlist_filters_anthropic_only_fields— allowlist vs 8 Anthropic-only/internal fieldstest_bedrock_messages_filters_user_provided_unsupported_beta_header— unsupported user beta dropped, supported one passestest_bedrock_messages_renames_user_provided_aliased_beta_header— user-provided alias rewritten to Bedrock-side spellingmake test-unit— ran fulltests/test_litellm/llms/bedrock/suite, 485 tests passScreenshots / Proof of Fix
Before: Claude Code → LiteLLM proxy →
bedrock/us.anthropic.claude-opus-4-7returns 400"context_management: Extra inputs are not permitted"(trace8fa3e31a-8027-46e7-a77a-276eef3ae500).After: the
context_managementbody field and any unsupported beta-header values are stripped before signing. Per-case regression tests green on this branch and red onmain.Type
🐛 Bug Fix
Changes
litellm/types/llms/bedrock.py— newBedrockInvokeAnthropicMessagesRequestTypedDictlitellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py— allowlist derived from TypedDict, beta-header full-union filtertests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py— 5 new teststests/test_litellm/llms/bedrock/test_anthropic_beta_support.py— drive-by: updated test to use a bedrock-supported beta