Skip to content

fix(anthropic): skip non-OpenAI file content blocks in file-id discovery helpers - #26228

Merged
krrish-berri-2 merged 1 commit into
BerriAI:litellm_oss_staging_04_22_2026from
anmolg1997:fix/file-content-block-discovery-safety
Apr 23, 2026
Merged

fix(anthropic): skip non-OpenAI file content blocks in file-id discovery helpers#26228
krrish-berri-2 merged 1 commit into
BerriAI:litellm_oss_staging_04_22_2026from
anmolg1997:fix/file-content-block-discovery-safety

Conversation

@anmolg1997

Copy link
Copy Markdown
Contributor

Relevant issues

Closes #26227. Related to #24503 (different approach, see below).

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement
  • My PR passes all unit tests on make test-unit for the affected module
  • My PR's scope is as isolated as possible, it only solves 1 specific problem

Type

Bug Fix

Problem

AnthropicConfig.validate_environment (run on every Anthropic + Anthropic-via-Vertex request) calls:

  • is_file_id_used -> get_file_ids_from_messages (common_utils.py:1049)
  • downstream also update_messages_with_model_file_ids (common_utils.py:431)

Both helpers short-circuit on c[\"type\"] == \"file\" and then do a bare c[\"file\"] access. Any content block that uses \"file\" as its type discriminator but does not match the OpenAI Chat Completions sub-shape (nested file dict) raises KeyError: 'file'. The Vertex partner layer wraps this as VertexAIError(500) -> litellm.InternalServerError, before the LLM is contacted.

Real-world trigger: LangChain v1's _normalize_messages rewrites OpenAI file blocks into {\"type\":\"file\",\"id\":...,\"base64\":...,\"mime_type\":...,\"extras\":{}} on the way into every chat model. Hits every vertex_ai/claude-* and anthropic/claude-* request that carries an attachment.

See #26227 for full RCA, traceback, and minimal repro.

Fix

type: \"file\" is a public content-block discriminator. The two helpers above are discovery passes:

  • get_file_ids_from_messages returns a list of file IDs.
  • update_messages_with_model_file_ids rewrites provider-scoped file IDs in-place.

A block with no file sub-dict has no file_id to extract or remap, so the correct behavior is to skip the block, not raise. This patch switches both from c[\"file\"] to c.get(\"file\") + isinstance(..., dict) check, and continues past non-OpenAI blocks.

Why skip and not raise BadRequestError (as in #24503)

PR #24503 raises BadRequestError in these two spots. For the stricter sites it also touches (Gemini/Bedrock/Anthropic transformers, migrate_file_to_image_url), that is the right behavior: a block that has reached the provider transformer is expected to be fully-formed OpenAI shape, and a missing file sub-dict really is a malformed request.

These two discovery helpers are different. They run unconditionally inside validate_environment, ahead of any provider-specific transformer. Raising BadRequestError here fails the whole request for any legitimate non-OpenAI block (LangChain v1, provider-native, custom user shapes) even when the downstream transformer would handle it correctly. Skip is strictly more permissive: well-formed OpenAI blocks still yield their file_id, and non-OpenAI blocks stop crashing validate_environment.

Happy to coordinate with @krisxia0506 if this should land as a delta on top of #24503, or get rolled in there directly.

Changes

File Change
litellm/litellm_core_utils/prompt_templates/common_utils.py Defensive .get(\"file\") + isinstance(..., dict) check in get_file_ids_from_messages and update_messages_with_model_file_ids; skip the block if the sub-dict is missing or malformed.
tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py 5 new regression tests (LangChain v1 shape, OpenAI happy path, mixed shapes, file set to a non-dict value, remap path for non-OpenAI blocks).

Tests

All 25 tests in test_litellm_core_utils_prompt_templates_common_utils.py pass locally:

======================== 25 passed, 2 warnings in 0.40s ========================

New tests cover:

  • test_get_file_ids_from_messages_skips_langchain_v1_file_block
  • test_get_file_ids_from_messages_still_extracts_from_openai_shape
  • test_get_file_ids_from_messages_mixed_shapes
  • test_get_file_ids_from_messages_file_field_not_dict
  • test_update_messages_with_model_file_ids_skips_non_openai_file_blocks

@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a KeyError: 'file' crash in two discovery helper functions (get_file_ids_from_messages and update_messages_with_model_file_ids) that run unconditionally during AnthropicConfig.validate_environment. The root cause is that type: "file" is a public content-block discriminator used by multiple producers (LangChain v1, provider-native shapes), not exclusively OpenAI Chat Completions, but the helpers assumed the OpenAI sub-dict shape. The fix switches from bare dict access to .get() + isinstance guard and skips non-conforming blocks, with five targeted regression tests covering all relevant shapes.

Confidence Score: 5/5

Safe to merge — targeted two-line fix with comprehensive regression tests and no behaviour change for well-formed OpenAI blocks.

Both changed call sites are mechanically equivalent; the .get() + isinstance guard is the canonical Python pattern for optional dict fields. OpenAI-shaped blocks still flow through unchanged, so there is no regression risk for existing users. All findings are P2 or lower.

No files require special attention.

Important Files Changed

Filename Overview
litellm/litellm_core_utils/prompt_templates/common_utils.py Two minimal defensive guards added: .get("file") + isinstance(..., dict) check in both update_messages_with_model_file_ids and get_file_ids_from_messages; non-OpenAI blocks are now skipped instead of raising KeyError.
tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py Five new regression tests added covering LangChain v1 shape, OpenAI happy path, mixed shapes, non-dict file field, and the remap path; no network calls, no existing tests modified.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[validate_environment] --> B[get_file_ids_from_messages]
    A --> C[update_messages_with_model_file_ids]

    B --> D{c type == file?}
    D -- No --> E[skip block]
    D -- Yes --> F[file_object.get 'file']
    F --> G{isinstance dict?}
    G -- No --> H[skip — non-OpenAI block\ne.g. LangChain v1]
    G -- Yes --> I[extract file_id]

    C --> J{c type == file?}
    J -- No --> K[skip block]
    J -- Yes --> L[file_object.get 'file']
    L --> M{isinstance dict?}
    M -- No --> N[skip — non-OpenAI block]
    M -- Yes --> O[remap file_id to provider id]
Loading

Reviews (2): Last reviewed commit: "fix(anthropic): tolerate non-OpenAI file..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing anmolg1997:fix/file-content-block-discovery-safety (2cb78aa) with main (09cd7e3)

Open in CodSpeed

@anmolg1997
anmolg1997 changed the base branch from main to litellm_oss_branch April 22, 2026 06:51
@CLAassistant

CLAassistant commented Apr 22, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@gitguardian

gitguardian Bot commented Apr 22, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
29203053 Triggered Generic Password f31d4fa .circleci/config.yml View secret
29203065 Triggered JSON Web Token e8461b5 tests/test_litellm/proxy/test_litellm_pre_call_utils.py View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. 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


🦉 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.

@gitguardian

gitguardian Bot commented Apr 22, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 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.

…scovery

`get_file_ids_from_messages` and `update_messages_with_model_file_ids`
assume every content block with `type: "file"` has a nested `file` dict in
the OpenAI Chat Completions shape. That assumption is too strong: `type:
"file"` is a public content-block discriminator and several real producers
emit blocks that use it without the OpenAI `file` sub-dict. For example,
LangChain v1's `_normalize_messages` rewrites OpenAI file blocks into
`{"type":"file","id":"...","base64":"...","mime_type":"...","extras":{}}`
before they reach LiteLLM.

`AnthropicConfig.validate_environment` calls both helpers unconditionally
on every Anthropic (and Anthropic-via-Vertex) request, so any such block
raises `KeyError: 'file'` which the Vertex partner layer then wraps as a
`500 InternalServerError` before the LLM is even contacted.

This patch switches both helpers from `c["file"]` to a defensive
`c.get("file")` + dict check. When the block does not match the OpenAI
shape there is no file_id to extract or remap, so we skip it and leave
the block untouched for the downstream provider transformer to handle.

Adds 5 regression tests covering the LangChain v1 shape, the OpenAI
happy path, mixed shapes in one message, `file` set to a non-dict value,
and the remap path for non-OpenAI blocks.

Related to BerriAI#24503, which proposed raising `BadRequestError` in the same
spots. For these two discovery functions specifically, the skip semantics
is strictly more permissive: well-formed OpenAI blocks still yield their
file_id, and legitimate non-OpenAI blocks stop crashing the request.
@anmolg1997
anmolg1997 force-pushed the fix/file-content-block-discovery-safety branch from bdc0796 to 2350cca Compare April 22, 2026 08:00
@krrish-berri-2
krrish-berri-2 changed the base branch from litellm_oss_branch to litellm_oss_staging_04_22_2026 April 23, 2026 02:22
@krrish-berri-2
krrish-berri-2 merged commit 0e23aa7 into BerriAI:litellm_oss_staging_04_22_2026 Apr 23, 2026
4 checks passed
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…scovery (BerriAI#26228)

`get_file_ids_from_messages` and `update_messages_with_model_file_ids`
assume every content block with `type: "file"` has a nested `file` dict in
the OpenAI Chat Completions shape. That assumption is too strong: `type:
"file"` is a public content-block discriminator and several real producers
emit blocks that use it without the OpenAI `file` sub-dict. For example,
LangChain v1's `_normalize_messages` rewrites OpenAI file blocks into
`{"type":"file","id":"...","base64":"...","mime_type":"...","extras":{}}`
before they reach LiteLLM.

`AnthropicConfig.validate_environment` calls both helpers unconditionally
on every Anthropic (and Anthropic-via-Vertex) request, so any such block
raises `KeyError: 'file'` which the Vertex partner layer then wraps as a
`500 InternalServerError` before the LLM is even contacted.

This patch switches both helpers from `c["file"]` to a defensive
`c.get("file")` + dict check. When the block does not match the OpenAI
shape there is no file_id to extract or remap, so we skip it and leave
the block untouched for the downstream provider transformer to handle.

Adds 5 regression tests covering the LangChain v1 shape, the OpenAI
happy path, mixed shapes in one message, `file` set to a non-dict value,
and the remap path for non-OpenAI blocks.

Related to BerriAI#24503, which proposed raising `BadRequestError` in the same
spots. For these two discovery functions specifically, the skip semantics
is strictly more permissive: well-formed OpenAI blocks still yield their
file_id, and legitimate non-OpenAI blocks stop crashing the request.
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.

3 participants