Skip to content

Litellm oss staging 03 04 2026 - #23163

Merged
Sameerlite merged 68 commits into
mainfrom
litellm_oss_staging_03_04_2026
Mar 11, 2026
Merged

Litellm oss staging 03 04 2026#23163
Sameerlite merged 68 commits into
mainfrom
litellm_oss_staging_03_04_2026

Conversation

@ghost

@ghost ghost commented Mar 9, 2026

Copy link
Copy Markdown

Relevant issues

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test

Changes

Chesars and others added 30 commits January 30, 2026 14:15
- Delete button now shows confirmation modal instead of redirecting to detail page
- Entire table row is clickable to view model details
- Fixed Last Refreshed text alignment and shortened time format
- Added dismiss button (X) to Missing provider banner with localStorage persistence
- Added compact Request Provider button in header when banner is dismissed
- Add tests for dismissable Missing provider banner with localStorage persistence
- Add tests for compact Request Provider button when banner is dismissed
- Add tests for delete modal functionality and DB Model badge
- Add tests for clickable Model ID that calls setSelectedModelId
…21405)

* helm cyclic dependency fix

* updating test case for handling edge cases
* add explicit caching to litellm proxy for gemini models via injection

* fix: add missing `supports_function_calling` for deepinfra models

All 55 deepinfra models that had `supports_tool_choice: true` were
missing the `supports_function_calling` flag, causing
`litellm.supports_function_calling()` to incorrectly return False.

Fixes #22619

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Managed batches - Address PR bot comments from #22464

* feat(togetherai): add support for TogetherAI Qwen3.5-397B-A17B model

* Agent Tracing - support context_id based trace id propogation + nested llm calls  (#22626)

* style(ui/): distinguish agent calls from llm calls on ui

* feat: initial grouping working

* feat: set stable contextid for a2a calls - allows for easily passing to downstream llm/mcp calls

* feat(a2a_endpoints.py): fix tracing to avoid recreating logging objects for the same call

allows stable trace id usage

* fix(guardrail_endpoints): handle string ui_type values in _build_field_dict

_build_field_dict unconditionally called .value on ui_type, which crashes
for guardrail configs that use plain strings (e.g. BlockCodeExecutionGuardrailConfigModel
uses "multiselect" and "percentage"). Now checks with hasattr before calling .value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: propagate trace/session id from headers in MCP server calls

Cherry-picked mcp_server/server.py fixes from 6feb9ba: adds
get_chain_id_from_headers to extract x-litellm-trace-id /
x-litellm-session-id from raw headers, and uses it in call_tool
and list_tools to keep spend logs and tracing consistent with A2A.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* [Feat] UI - Add Open in New Tab on leftnav Bar (#22731)

* Add minimal dev_config.yaml for proxy development

Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>

* feat(ui): wrap left nav items in <a> tags for open-in-new-tab support

Nav items are now rendered as <a> elements with proper href attributes,
enabling right-click → 'Open in new tab', Ctrl/Cmd+click, and
middle-click to open any sidebar page in a new browser tab.

Normal clicks continue to use SPA navigation (no full page reload).

Applied to both leftnav.tsx (query-param routing) and Sidebar2.tsx
(Next.js file-based routing).

Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>

* [Feat] Add Tool Policies for AI Gateway  (#22732)

* fix: fix ui render

* fix: fix minor bugs

* refactor: use prisma functions instead of raw sql (safer)

* fix(add-new-tiles-to-tool-policies): allow developer to see what's available

* feat: ensure tool allowlist runs correctly for tool names + mcp's

* refactor: more ui improvements

* feat: working key tool blocking

* feat(tools): show tool logs

* refactor: backend code improvements

* refactor: improve log viewer for tools

* fix: address PR review feedback for tool access control

- Add missing blocked_tools column to root schema.prisma (schema drift)
- Invalidate ToolPolicyRegistry after policy mutations so changes take effect immediately
- Remove dead code: unused get_effective_policies, get_tool_policies_cached, and helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: race condition in permission resolution and remove duplicate allowlist check

- Use atomic update_many with object_permission_id=None to prevent concurrent
  requests from creating orphaned permission rows and losing tool blocks
- Remove duplicate allowed_tools enforcement from guardrail (already enforced
  in auth layer via check_tools_allowlist)
- Move inline uuid import to module level

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* update to account for  userAgent

* UI - Add ToolDetails

* input/output policy

* LiteLLM_PolicyAttachmentTable

* LiteLLM_PolicyAttachmentTable

* fix: add _enqueue_tool_registry_upsert

* fix: tool mgmt endpoints

* tool mgmt endpoints

* Update tests/test_litellm/proxy/db/test_tool_registry_writer.py

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update tests/test_litellm/proxy/db/test_tool_registry_writer.py

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update tests/test_litellm/proxy/db/test_tool_registry_writer.py

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: sync root schema.prisma and fix test_tool_registry_writer for input/output policy

- Migrate root schema.prisma LiteLLM_ToolTable from call_policy to
  input_policy/output_policy, add missing user_agent and last_used_at columns
  (now consistent with litellm/proxy/schema.prisma and litellm-proxy-extras)
- Fix SpendLogToolIndex comment across all three schema files
- Fix all call_policy references in test_tool_registry_writer.py:
  swapped update_tool_policy arguments, wrong get_tools_by_names return type
  assertions, _mock_tool_row setting call_policy instead of input_policy

Addresses Greptile review feedback on PR #22732.

Made-with: Cursor

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* feat(proxy): add key_alias, key_hash, requested_model DD APM span tags (#22710)

* feat(proxy): add key_alias, key_hash, requested_model tags to DD APM spans

* refactor(proxy): consolidate DD APM tag helpers into DDSpanTagger class

* refactor(proxy): move DDSpanTagger to its own file litellm/proxy/dd_span_tagger.py

---------

Co-authored-by: liweiguang <codingpunk@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ephrim Stanley <ephrim.stanley@point72.com>
Co-authored-by: Varad Khonde <varadkhonde@gmail.com>
Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com>
Co-authored-by: Sameer Kankute <sameer@berri.ai>
Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Add MistralAudioTranscriptionConfig for Mistral's /v1/audio/transcriptions
endpoint, enabling litellm.transcription() with mistral/voxtral-mini-latest
and other Voxtral models. Supports multipart form-data with OpenAI-compatible
params (language, temperature, response_format, timestamp_granularities)
plus Mistral-specific params like diarize.
Add Mistral to the supported providers list in audio_transcription.md
and add Audio Transcription section to the Mistral provider page with
SDK usage, optional params, diarize support, and proxy configuration.
str(True) produces "True" but Mistral API expects lowercase "true".
Use str(value).lower() for bool params in provider-specific fields.
…2.x models

Add global media_resolution support for Gemini 2.x models (2.0, 2.5) when
using OpenAI's detail parameter on images. Previously, the detail parameter
was only working for Gemini 3+ models (per-part) and was silently ignored
for older Gemini models.

- Add _get_highest_media_resolution() and _extract_max_media_resolution_from_messages()
  to extract highest detail from all images/files in a request
- Update _transform_request_body() to add mediaResolution to generationConfig
  for Gemini 2.x models only (not 1.x which doesn't support it, not 3+ which
  uses per-part)
- Add mediaResolution field to GenerationConfig TypedDict
- Support detail extraction from both image_url and file content types
- Add comprehensive unit tests and update documentation
The AllModelsTab component was passing onRowClick to AllModelsDataTable
but the prop was not defined in the interface, causing the TypeScript
build to fail.
…ss providers

Providers like Cerebras return delta.reasoning in streaming responses
for gpt-oss models, but LiteLLM's Delta class expects reasoning_content.
This causes reasoning content to be silently dropped during streaming.

Fixes #13300
…components/AllModelsTab.tsx

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…-detail-parameter

fix(gemini): support detail parameter for image resolution on Gemini 2.x models
…tent-delta

fix(streaming): map reasoning to reasoning_content in Delta for gpt-oss providers
feat(mistral): add Voxtral audio transcription support
…ModelsAndEndpointsView.test.tsx

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Fix model management page UI improvements
…st Labs

Add native integration for Black Forest Labs image editing models
(flux-kontext-pro, flux-kontext-max, flux-pro-1.0-fill, flux-pro-1.0-expand).

Changes:
- Add BlackForestLabsImageEditConfig for BFL API transformation
- Add BLACK_FOREST_LABS to LlmProviders enum
- Add use_multipart_form_data() to BaseImageEditConfig for JSON vs form-data
- Modify image_edit_handler to support JSON request bodies
- Add comprehensive unit tests

Closes #11401
Add BFL models to model_prices_and_context_window.json with pricing:
- flux-kontext-pro: $0.04/image
- flux-kontext-max: $0.08/image
- flux-pro-1.0-fill: $0.05/image
- flux-pro-1.0-expand: $0.05/image

Add black_forest_labs_models set to __init__.py for model discovery.
Replace direct httpx.get() calls with _get_httpx_client() to reuse
cached HTTP client, following the pattern used by other providers
(RunwayML, Azure AI OCR, Sagemaker, etc.).
Add native text-to-image generation for Black Forest Labs Flux models
(flux-pro-1.1, flux-pro-1.1-ultra, flux-dev, flux-pro).

- Polling-based async API with sync and async support
- OpenAI-compatible parameter mapping (size, n, quality)
- Reuses shared HTTP clients via _get_httpx_client()
- 39 unit tests added
- Create handler.py for image generation and image edit
- Move polling logic from transformation to handlers
- Handlers use _get_httpx_client() / get_async_httpx_client()
- Transformation files now only transform request/response data
- Follows Bedrock pattern for provider-specific handlers

Addresses feedback: transformation files should not make HTTP requests
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
_read_image_bytes was not handling string inputs (URLs or file paths),
causing a TypeError when passing a URL as the image source.
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Chesars and others added 16 commits March 4, 2026 19:46
…tom-client-17148

Fix: User specified async client ignored with Gemini streaming+async
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…cs format, system-only fallback

- Fix F821: add BaseTokenCounter TYPE_CHECKING import in gpt_transformation.py
- Remove duplicate auth invocation in count_response_input_tokens endpoint
- Preserve `strict` field during chat→Responses API tool conversion
- Fix docs tools example to use chat completions format (not Responses API format)
- Return None early for system-only messages to avoid noisy error logs
feat(openai): add litellm.acount_tokens() public API + OpenAI token counting support
…us code

- Propagate timeout to each polling GET request to prevent indefinite hangs
- Validate HTTP status code of initial POST before parsing JSON
- Fix inline import and add 60s timeout to image URL download in _read_image_bytes
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
mask was missing from get_supported_openai_params, causing it to be
dropped before reaching transform_image_edit_request where it is
already handled correctly for flux-pro-1.0-fill inpainting.
Add raise_for_status() to avoid sending error page content as image data to BFL API.
@vercel

vercel Bot commented Mar 9, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
litellm Error Error Mar 11, 2026 1:03pm

Request Review

@ghost
ghost requested a review from Sameerlite March 9, 2026 15:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdb02c092e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/main.py
Comment on lines +7621 to +7622
if system and fallback_messages:
fallback_messages = [{"role": "system", "content": system}] + fallback_messages

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count system-only instructions in local token fallback

When provider-side counting is unavailable, the fallback only prepends the system prompt if fallback_messages is non-empty, so calls that pass system without any messages return a token count that ignores the instructions entirely. This undercounts valid inputs (for example Responses-style calls that only use instructions) and can break budgeting/rate-limit logic that depends on accurate token accounting.

Useful? React with 👍 / 👎.

Comment on lines +244 to +246
call_types = get_call_types_for_route(route)
if not call_types or not any(ct.value in TOOL_CAPABLE_CALL_TYPES for ct in call_types):
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce tool allowlist on unmapped route aliases

The allowlist check exits early whenever get_call_types_for_route(route) returns None, which leaves tool-capable aliases outside API_ROUTE_TO_CALL_TYPES (for example /openai/v1/responses) completely unguarded. In those routes, requests with disallowed tools bypass allowed_tools enforcement, so keys/teams can invoke tools they are supposed to be blocked from using.

Useful? React with 👍 / 👎.

Resolved 14 file conflicts:
- image_edits.md: combined OpenRouter + Black Forest Labs providers
- utils.py: kept staging's message-level cache_control check
- networking.tsx: kept export on 4 tool interfaces
- tool_management_endpoints.py: kept ToolOutputPolicy import
- Accepted main's version for: schema.prisma, a2a_protocol, mcp_server,
  _types.py, auth_checks.py, db_spend_update_writer, endpoints.py,
  spend_tracking_utils, a2a_endpoints, model_prices backup
@greptile-apps

greptile-apps Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This is a large staging PR that bundles several independent features: full Black Forest Labs (BFL) image generation and image edit support (with polling), OpenAI Responses API token counting, Mistral Voxtral audio transcription, Gemini 2.x global mediaResolution support, Vertex AI context-caching minimum-token guard, a Delta.reasoningreasoning_content alias, automatic budget_reset_at computation in customer endpoints, and a batch of UI static-file reorganisations.

Key issues found:

  • Hardcoded "gemini-2" in model check (litellm/llms/vertex_ai/gemini/transformation.py:684) — the mediaResolution feature gate uses a string substring match instead of a capability flag in model_prices_and_context_window.json, violating the project rule. New Gemini generations will not work without a code change.
  • Hardcoded BFL model-to-endpoint mappings (litellm/llms/black_forest_labs/common_utils.py:26–42) — IMAGE_EDIT_MODELS and IMAGE_GENERATION_MODELS are static dicts in code rather than being driven by the JSON config, so adding a new BFL model requires a code change on top of the JSON update.
  • image_url content blocks silently dropped in OpenAI token-count transformation (litellm/llms/openai/responses/count_tokens/transformation.py:124–132) — for multimodal user messages, only "text" parts are forwarded to the /v1/responses/input_tokens endpoint; image blocks are discarded, causing the returned token count to be substantially under-counted.

Confidence Score: 3/5

  • Mergeable with caution — the BFL and Mistral additions are largely additive, but the OpenAI token-count undercounting and the hardcoded Gemini-2 flag are correctness issues that should be addressed before shipping.
  • Score reflects two rule violations (hardcoded model flags) and one correctness bug (image tokens dropped from token-count requests) that affect the reliability of new features. The existing codebase is not broken by this PR, but the new OpenAI token-counting API will silently return wrong values for any multimodal conversation.
  • litellm/llms/openai/responses/count_tokens/transformation.py (image content dropped), litellm/llms/vertex_ai/gemini/transformation.py (hardcoded gemini-2 check), litellm/llms/black_forest_labs/common_utils.py (hardcoded model-endpoint map)

Important Files Changed

Filename Overview
litellm/llms/black_forest_labs/image_edit/handler.py New BFL image-edit handler with sync/async polling. Logic is well-structured; the previously flagged timeout-not-forwarded issue is the main concern but is tracked elsewhere.
litellm/llms/black_forest_labs/image_generation/handler.py New BFL image-generation handler mirroring the image-edit handler; same polling pattern and same previously flagged timeout concern.
litellm/llms/black_forest_labs/common_utils.py Hardcodes IMAGE_EDIT_MODELS and IMAGE_GENERATION_MODELS dicts mapping model names to BFL API paths, violating the rule to store model-specific metadata in model_prices_and_context_window.json.
litellm/llms/vertex_ai/gemini/transformation.py Adds global mediaResolution support for Gemini 2.x using a hardcoded "gemini-2" in model string check, violating the project rule against hardcoding model-specific flags.
litellm/llms/openai/responses/count_tokens/transformation.py New OpenAI Responses API token-counting transformation; silently drops image_url content blocks from user messages, leading to incorrect (under-counted) token counts for multimodal conversations.
litellm/llms/openai/responses/count_tokens/handler.py Clean async handler for the OpenAI /v1/responses/input_tokens endpoint; error handling and logging are appropriate.
litellm/llms/openai/responses/count_tokens/token_counter.py Implements OpenAITokenCounter; correctly falls back when no input items are produced and uses a global handler instance. The system-only message fallback silently returning None is acceptable.
litellm/llms/mistral/audio_transcription/transformation.py New Mistral Voxtral audio transcription transformation; clean implementation following the base class pattern with correct form-data handling.
litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py Fixes user-provided HTTPHandler being incorrectly forwarded to the async make_call context by using a separate gemini_client parameter; logic looks correct.
litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py Adds minimum token count guard before attempting Vertex AI context caching to avoid API errors; uses existing is_prompt_caching_valid_prompt utility correctly.
litellm/main.py Adds acount_tokens public API with provider-specific routing and local tiktoken fallback; the system-prompt-not-prepended-when-messages-empty issue is tracked elsewhere.
model_prices_and_context_window.json Adds 8 Black Forest Labs models with correct pricing, modes, and supported_endpoints metadata.
litellm/proxy/management_endpoints/customer_endpoints.py Adds automatic budget_reset_at calculation from budget_duration when not explicitly set; still uses deprecated datetime.utcnow() (tracked separately in previous threads).

Sequence Diagram

sequenceDiagram
    participant Caller
    participant images/main.py
    participant BFLHandler as BFL Handler (image_edit / image_generation)
    participant BFLConfig as BFL Transformation Config
    participant BFLAPI as BFL API (api.bfl.ai)

    Caller->>images/main.py: image_edit() / image_generation()
    images/main.py->>BFLHandler: bfl_image_edit.image_edit() / bfl_image_generation.image_generation()
    BFLHandler->>BFLConfig: validate_environment() → headers
    BFLHandler->>BFLConfig: get_complete_url() → url
    BFLHandler->>BFLConfig: transform_image_edit_request() → data
    BFLHandler->>BFLAPI: POST /v1/{model-path} (JSON body)
    BFLAPI-->>BFLHandler: {polling_url, id}
    loop Poll until Ready / timeout
        BFLHandler->>BFLAPI: GET polling_url
        BFLAPI-->>BFLHandler: {status: "Pending"|"Ready"|"Error"}
    end
    BFLHandler->>BFLConfig: transform_image_edit_response() → ImageResponse
    BFLHandler-->>images/main.py: ImageResponse
    images/main.py-->>Caller: ImageResponse
Loading

Last reviewed commit: 3dab620

Comment on lines +256 to +258
url=complete_url,
headers=headers,
json=data,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The timeout parameter is declared in the _poll_for_result_sync and _poll_for_result_async method signatures but is never forwarded to the actual polling GET requests. This means each request uses the HTTP client's default timeout instead of the user-specified one, risking stuck polling loops.

Both the sync and async polling paths in image_edit/handler.py and image_generation/handler.py have this issue.

Suggested change
url=complete_url,
headers=headers,
json=data,
while time.time() - start_time < max_wait:
response = sync_client.get(
url=polling_url,
headers=polling_headers,
timeout=timeout,
)

Comment on lines +256 to +258
)
except Exception as e:
raise BlackForestLabsError(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The timeout parameter is declared in the _poll_for_result_sync and _poll_for_result_async method signatures but is never forwarded to the actual polling GET requests. This means each request uses the HTTP client's default timeout instead of the user-specified one, risking stuck polling loops.

Suggested change
)
except Exception as e:
raise BlackForestLabsError(
while time.time() - start_time < max_wait:
response = sync_client.get(
url=polling_url,
headers=polling_headers,
timeout=timeout,
)

Comment thread litellm/main.py
Comment on lines +7658 to +7661
fallback_messages = messages or []
if system and fallback_messages:
fallback_messages = [{"role": "system", "content": system}] + fallback_messages
local_count = litellm.token_counter(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The condition if system and fallback_messages: only prepends the system prompt when fallback_messages is non-empty. When the caller passes messages=None (empty list) together with a system prompt, the system prompt is silently dropped and token_counter receives an empty input, returning an incorrect token count.

Suggested change
fallback_messages = messages or []
if system and fallback_messages:
fallback_messages = [{"role": "system", "content": system}] + fallback_messages
local_count = litellm.token_counter(
# Fallback to local tiktoken-based token counting
fallback_messages = messages or []
if system:
fallback_messages = [{"role": "system", "content": system}] + fallback_messages
local_count = litellm.token_counter(
model=model,
messages=fallback_messages,
tools=tools,
)

Comment on lines +168 to +171
budget_request.budget_reset_at = datetime.utcnow() + timedelta(
seconds=duration_in_seconds(duration=budget_request.budget_duration)
)
return budget_request

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

datetime.utcnow() is deprecated as of Python 3.12 and should be replaced with datetime.now(timezone.utc).

Suggested change
budget_request.budget_reset_at = datetime.utcnow() + timedelta(
seconds=duration_in_seconds(duration=budget_request.budget_duration)
)
return budget_request
if budget_request.budget_reset_at is None and budget_request.budget_duration is not None:
from datetime import timezone
budget_request.budget_reset_at = datetime.now(timezone.utc) + timedelta(
seconds=duration_in_seconds(duration=budget_request.budget_duration)
)

Comment on lines +684 to +690
if "gemini-2" in model:
max_media_resolution = _extract_max_media_resolution_from_messages(messages)
if max_media_resolution:
media_resolution_value = _convert_detail_to_media_resolution_enum(
max_media_resolution
)
if media_resolution_value and generation_config is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hardcoded model-version check violates provider-agnostic pattern

The check if "gemini-2" in model: hardcodes a model family name to gate the mediaResolution feature. Per the project's custom rule, model-specific capability flags should be stored in model_prices_and_context_window.json and read via get_model_info, so that support for new models works without requiring a code change.

For example, you could add a "supports_global_media_resolution": true field to the relevant Gemini 2.x entries in the JSON and then check it here:

model_info = litellm.get_model_info(model=model, custom_llm_provider="vertex_ai")
if model_info and model_info.get("supports_global_media_resolution"):
    max_media_resolution = _extract_max_media_resolution_from_messages(messages)
    ...

This way, future Gemini versions that adopt (or drop) global mediaResolution can be handled by updating the JSON only.

Rule Used: What: Do not hardcode model-specific flags in the ... (source)

Comment on lines +26 to +42
IMAGE_EDIT_MODELS: Dict[str, str] = {
"flux-kontext-pro": "/v1/flux-kontext-pro",
"flux-kontext-max": "/v1/flux-kontext-max",
"flux-pro-1.0-fill": "/v1/flux-pro-1.0-fill",
"flux-pro-1.0-expand": "/v1/flux-pro-1.0-expand",
}

# Model to endpoint mapping for image generation
IMAGE_GENERATION_MODELS: Dict[str, str] = {
"flux-pro-1.1": "/v1/flux-pro-1.1",
"flux-pro-1.1-ultra": "/v1/flux-pro-1.1-ultra",
"flux-dev": "/v1/flux-dev",
"flux-pro": "/v1/flux-pro",
# Kontext models support both text-to-image and image editing
"flux-kontext-pro": "/v1/flux-kontext-pro",
"flux-kontext-max": "/v1/flux-kontext-max",
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hardcoded model-to-endpoint mapping requires code changes to support new models

IMAGE_EDIT_MODELS and IMAGE_GENERATION_MODELS are static dicts that map model names to BFL-specific API paths. Adding a new Black Forest Labs model therefore requires a code change here in addition to updating model_prices_and_context_window.json.

Per the project rule, model-specific metadata should be stored in the JSON config and loaded via get_model_info. Consider adding a BFL endpoint path field (e.g. "api_path") to each entry in model_prices_and_context_window.json and reading it through get_model_info at runtime:

"black_forest_labs/flux-kontext-pro": {
    "litellm_provider": "black_forest_labs",
    "mode": "image_edit",
    "api_path": "/v1/flux-kontext-pro",
    ...
}

Then in _get_model_endpoint:

model_info = litellm.get_model_info(model=model, custom_llm_provider="black_forest_labs")
if model_info and model_info.get("api_path"):
    return model_info["api_path"]
raise ValueError(f"Unknown BFL model: {model}")

This way, adding a new BFL model only requires updating the JSON.

Rule Used: What: Do not hardcode model-specific flags in the ... (source)

Comment on lines +124 to +132
if isinstance(content, list):
# Extract text from content blocks for Responses API
text_parts = []
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
text_parts.append(block.get("text", ""))
elif isinstance(block, str):
text_parts.append(block)
content = "\n".join(text_parts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

image_url content blocks are silently dropped from the token count request

When a user message contains a list of content blocks (as in a multimodal request), only "text" blocks are forwarded to the OpenAI Responses API token-counting endpoint. Any "image_url" or "image_file" blocks are silently discarded.

Since image tokens can be substantial, this leads to a significant undercount for multimodal conversations. The OpenAI Responses API does support image inputs, so image blocks should be forwarded too. For example:

if isinstance(content, list):
    transformed_parts = []
    for block in content:
        if not isinstance(block, dict):
            if isinstance(block, str):
                transformed_parts.append(block)
            continue
        if block.get("type") == "text":
            transformed_parts.append(block.get("text", ""))
        elif block.get("type") == "image_url":
            # Forward image_url to the Responses API for accurate token counting
            image_url = block.get("image_url", {})
            transformed_parts.append({
                "type": "input_image",
                "image_url": image_url.get("url", ""),
            })
    content = transformed_parts if any(isinstance(p, dict) for p in transformed_parts) else "\n".join(p for p in transformed_parts if isinstance(p, str))

@Sameerlite
Sameerlite merged commit 2343149 into main Mar 11, 2026
64 of 94 checks passed
@yuneng-jiang yuneng-jiang mentioned this pull request Mar 13, 2026
7 tasks
@ishaan-berri
ishaan-berri deleted the litellm_oss_staging_03_04_2026 branch March 26, 2026 22:29
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
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.

5 participants