Skip to content

fix: normalize response images missing index + guard audio duration overflow#22950

Merged
krrishdholakia merged 8 commits intoBerriAI:litellm_oss_staging_03_06_2026from
SabaPivot:fix/images-index-and-audio-duration
Mar 6, 2026
Merged

fix: normalize response images missing index + guard audio duration overflow#22950
krrishdholakia merged 8 commits intoBerriAI:litellm_oss_staging_03_06_2026from
SabaPivot:fix/images-index-and-audio-duration

Conversation

@SabaPivot
Copy link

Summary

Test plan

  • All 11 llm_response_utils tests pass
  • _normalize_images correctly adds index to images missing it
  • _normalize_images(None) returns None (no-op for non-image responses)
  • Verify OpenRouter Gemini image responses no longer raise ValidationError
  • Verify malformed audio files return None duration instead of overflow values

🤖 Generated with claude-flow

krrishdholakia and others added 7 commits March 5, 2026 16:58
…or DB storage (BerriAI#22936)

When the messages or response JSON fields in spend logs are truncated
before being written to the database, the truncation marker now includes
a note explaining:
- This is a DB storage safeguard
- Full, untruncated data is still sent to logging callbacks (OTEL, Datadog, etc.)
- The MAX_STRING_LENGTH_PROMPT_IN_DB env var can be used to increase the limit

Also emits a verbose_proxy_logger.info message when truncation occurs in
the request body or response spend log paths.

Adds 3 new tests:
- test_truncation_includes_db_safeguard_note
- test_response_truncation_logs_info_message
- test_request_body_truncation_logs_info_message

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
The /organization/list endpoint only checked for PROXY_ADMIN role,
causing PROXY_ADMIN_VIEW_ONLY users to fall into the else branch
which restricts results to orgs the user is a member of. Use the
existing _user_has_admin_view() helper to include both roles.
…r_org_list

Fix admin viewer unable to see all organizations
…eaming (BerriAI#22937)

* feat(ui): add chat message and conversation types

* feat(ui): add useChatHistory hook for localStorage-backed conversations

* feat(ui): add ConversationList sidebar component

* feat(ui): add MCPConnectPicker for attaching MCP servers to chat

* feat(ui): add ModelSelector dropdown for chat

* feat(ui): add ChatInputBar with MCP tool attachment support

* feat(ui): add MCPAppsPanel with list/detail view for MCP servers

* feat(ui): add ChatMessages component; remove auto-scrollIntoView that caused scroll-lock bypass

* feat(ui): add ChatPage — ChatGPT-like UI with scroll lock, MCP tools, streaming

* feat(ui): add /chat route wired to ChatPage

* feat(ui): remove chat from leftnav — chat accessible via navbar button

* feat(ui): add Chat button to top navbar

* feat(ui): add dismissible Chat UI announcement banner to Playground page

* feat(proxy): add Chat UI link to Swagger description

* feat(ui): add react-markdown and syntax-highlighter deps for chat UI

* fix(ui): replace missing BorderOutlined import with inline stop icon div

* fix(ui): apply remark-gfm plugin to ReactMarkdown for GFM support

* fix(ui): remove unused isEvenRow variable in MCPAppsPanel

* fix(ui): add ellipsis when truncating conversation title

* fix(ui): wire search button to chats view; remove non-functional keyboard hint

* fix(ui): use serverRootPath in navbar chat link for sub-path deployments

* fix(ui): remove unused ChatInputBar and ModelSelector files

* fix(ui): correct grid bottom-border condition for odd server count

* fix(chat): move localStorage writes out of setConversations updater (React purity)

* fix(chat): fix stale closure in handleEditAndResend - compute history before async state update

* fix(chat): fix 4 issues in ChatMessages - array redaction, clipboard error, inline detection, remove unused ref
- Add PayGo / Priority Cost Tracking section to Vertex AI provider docs
- Document trafficType to service_tier mapping (ON_DEMAND_PRIORITY, FLEX, etc.)
- Add service tier cost keys to custom pricing docs
- Add provider-specific cost tracking note to spend tracking overview

Made-with: Cursor
docs: add PayGo/priority cost tracking for Gemini Vertex AI
…verflow

1. convert_dict_to_response.py (BerriAI#22640): Providers like OpenRouter/Gemini
   return images without the required `index` field, causing pydantic
   ValidationError when constructing Message. Added _normalize_images()
   to backfill index from enumeration position.

2. audio_utils/utils.py (BerriAI#22622): libsndfile can report 2^63-1 frames
   for malformed audio files, causing astronomically large duration values
   used for cost calculation. Added guards for sentinel frame counts and
   implausible durations (>24h).

Co-Authored-By: claude-flow <ruv@ruv.net>
@vercel
Copy link

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 6, 2026 3:26am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR fixes two independent bugs: (1) providers like OpenRouter with Gemini image models returning images without the index field required by ImageURLListItem, causing ValidationError on Message construction; and (2) libsndfile reporting 2^63-1 as a sentinel for unknown frame counts in malformed audio files, causing calculate_request_duration() to produce absurdly large cost-calculation inputs.

Key changes:

  • _normalize_images() helper in convert_dict_to_response.py backfills a sequential index from enumeration position for any image dict missing the field, before passing the list to Message. The function includes proper type annotations (Optional[List[Dict[str, object]]] -> Optional[List[Dict[str, object]]]).
  • calculate_request_duration() in audio_utils/utils.py now guards against: invalid/sentinel frame counts (<= 0 or >= 2^63-1), zero/negative samplerate, and implausible durations exceeding 24 hours — all returning None to trigger the existing fallback cost path.
  • No new tests were added for either fix. The two integration-level verification items in the PR checklist remain unchecked ("Verify OpenRouter Gemini image responses no longer raise ValidationError" and "Verify malformed audio files return None duration").

Minor issues:

  • A PEP 8 E302 blank-line violation exists before the new _normalize_images function definition (only one blank line after import; two are required).

Confidence Score: 4/5

  • The code changes are logically correct and properly guarded. Both fixes are well-scoped with appropriate null/error handling.
  • Both code changes are correct and targeted. The type annotations on _normalize_images are present, the samplerate zero-check is in place, and guard conditions properly handle sentinel values and edge cases. Score is 4 (not 5) because: (1) no new tests were added for either bug fix — the integration-level checklist items remain unverified per rule 47800116; (2) a minor PEP 8 E302 linting violation will fail CI; (3) without test evidence, merging relies on code review confidence alone.
  • litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py — requires PEP 8 blank-line fix before function definition to pass linting CI.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[convert_to_model_response_object] --> B{choice message\nhas 'images'?}
    B -- No / None --> C[images = None]
    B -- Yes --> D[_normalize_images list]
    D --> E{For each img\nin list}
    E --> F{isinstance dict\nAND 'index' missing?}
    F -- Yes --> G["img = {**img, 'index': i}"]
    F -- No --> H[keep img as-is]
    G --> I[append to normalized]
    H --> I
    I --> E
    E -- done --> J[return normalized list]
    C --> K[Message constructor]
    J --> K

    L[calculate_request_duration] --> M{frames <= 0\nOR frames >= 2^63-1?}
    M -- Yes --> N[return None]
    M -- No --> O{samplerate <= 0?}
    O -- Yes --> N
    O -- No --> P[duration = frames / samplerate]
    P --> Q{duration > 86400s?}
    Q -- Yes --> N
    Q -- No --> R[return duration]
Loading

Last reviewed commit: 57e7e70

Comment on lines +48 to +57
def _normalize_images(images):
"""Normalize image items to include required 'index' field if missing."""
if images is None:
return None
normalized = []
for i, img in enumerate(images):
if isinstance(img, dict) and "index" not in img:
img = {**img, "index": i}
normalized.append(img)
return normalized
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing type annotations on _normalize_images

The new helper function lacks type hints, which are used consistently throughout the rest of this file (e.g., _safe_convert_created_field, convert_tool_call_to_json_mode). Adding them makes the expected contract explicit and helps static analysis tools catch misuse early.

Suggested change
def _normalize_images(images):
"""Normalize image items to include required 'index' field if missing."""
if images is None:
return None
normalized = []
for i, img in enumerate(images):
if isinstance(img, dict) and "index" not in img:
img = {**img, "index": i}
normalized.append(img)
return normalized
def _normalize_images(
images: Optional[List[ImageURLListItem]],
) -> Optional[List[ImageURLListItem]]:
"""Normalize image items to include required 'index' field if missing."""
if images is None:
return None
normalized: List[ImageURLListItem] = []
for i, img in enumerate(images):
if isinstance(img, dict) and "index" not in img:
img = {**img, "index": i}
normalized.append(img)
return normalized

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 57e7e70 — added type annotations to the function signature and local variable.

Comment on lines +266 to +270
frames = len(audio)
# Guard against sentinel/invalid frame counts (e.g., 2^63-1 from libsndfile)
if frames <= 0 or frames >= 2**63 - 1:
return None
duration = frames / audio.samplerate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

samplerate zero-check missing

After guarding against invalid frames values, the code still divides by audio.samplerate without verifying it is non-zero. A malformed audio file could report samplerate == 0, which would raise a ZeroDivisionError. In practice the outer except Exception block catches this and returns None, but an explicit guard is safer and communicates intent clearly.

Suggested change
frames = len(audio)
# Guard against sentinel/invalid frame counts (e.g., 2^63-1 from libsndfile)
if frames <= 0 or frames >= 2**63 - 1:
return None
duration = frames / audio.samplerate
frames = len(audio)
# Guard against sentinel/invalid frame counts (e.g., 2^63-1 from libsndfile)
if frames <= 0 or frames >= 2**63 - 1:
return None
if audio.samplerate <= 0:
return None
duration = frames / audio.samplerate

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 57e7e70. Added if audio.samplerate <= 0: return None guard before the division.

Address review feedback:
- Add type hints to _normalize_images() for consistency with codebase
- Guard against samplerate <= 0 to prevent ZeroDivisionError on
  malformed audio files

Co-Authored-By: claude-flow <ruv@ruv.net>
@CLAassistant
Copy link

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.


from .get_headers import get_response_headers

def _normalize_images(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing blank line before top-level function (PEP 8 E302)

PEP 8 requires two blank lines before a top-level function definition. Only one blank line separates the from .get_headers import get_response_headers import on line 46 from _normalize_images on line 48. This will trigger an E302 linting error.

Suggested change
def _normalize_images(
def _normalize_images(

To fix, add one more blank line before the function definition.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@krrishdholakia krrishdholakia changed the base branch from main to litellm_oss_staging_03_06_2026 March 6, 2026 04:34
@krrishdholakia krrishdholakia merged commit a58d859 into BerriAI:litellm_oss_staging_03_06_2026 Mar 6, 2026
24 of 38 checks passed
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.

6 participants