Litellm oss staging 03 04 2026 - #23163
Conversation
- 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>
…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>
…lity/n param leak for non-ultra models
…params for consistency
…oesn't accept timeout
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 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".
| if system and fallback_messages: | ||
| fallback_messages = [{"role": "system", "content": system}] + fallback_messages |
There was a problem hiding this comment.
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 👍 / 👎.
| 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 |
There was a problem hiding this comment.
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 SummaryThis 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 Key issues found:
Confidence Score: 3/5
|
| 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
Last reviewed commit: 3dab620
| url=complete_url, | ||
| headers=headers, | ||
| json=data, |
There was a problem hiding this comment.
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.
| 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, | |
| ) |
| ) | ||
| except Exception as e: | ||
| raise BlackForestLabsError( |
There was a problem hiding this comment.
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.
| ) | |
| 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, | |
| ) |
| fallback_messages = messages or [] | ||
| if system and fallback_messages: | ||
| fallback_messages = [{"role": "system", "content": system}] + fallback_messages | ||
| local_count = litellm.token_counter( |
There was a problem hiding this comment.
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.
| 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, | |
| ) |
| budget_request.budget_reset_at = datetime.utcnow() + timedelta( | ||
| seconds=duration_in_seconds(duration=budget_request.budget_duration) | ||
| ) | ||
| return budget_request |
There was a problem hiding this comment.
datetime.utcnow() is deprecated as of Python 3.12 and should be replaced with datetime.now(timezone.utc).
| 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) | |
| ) |
| 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: |
There was a problem hiding this comment.
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)
| 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", | ||
| } |
There was a problem hiding this comment.
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)
| 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) |
There was a problem hiding this comment.
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))…04_2026 Litellm oss staging 03 04 2026
Relevant issues
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewCI (LiteLLM team)
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