Completion-To-Responses Bridge: Support passing image url's (#11833)

* fix(completion_to_responses_bridge_transformation.py): support passing image urls' to responses api models

Fixes https://github.com/BerriAI/litellm/issues/11820

* fix(base_aws_llm.py): if boto3 present, try and get the configured region name

Closes https://github.com/BerriAI/litellm/issues/8847

* fix: fix imports

* fix: fix linting error s
This commit is contained in:
Krish Dholakia 2025-06-18 12:48:17 -07:00 • committed by GitHub
parent 0319adbf5d
commit e5fd313a48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 186 additions and 10 deletions

View file

@ -11,6 +11,7 @@ from typing import (
Iterable,
Iterator,
List,
Literal,
Optional,
Tuple,
Union,
@ -25,6 +26,7 @@ from litellm.llms.base_llm.bridges.completion_transformation import (
)
if TYPE_CHECKING:
from openai.types.responses import ResponseInputImageParam
from pydantic import BaseModel
from litellm import LiteLLMLoggingObj, ModelResponse
@ -32,6 +34,7 @@ if TYPE_CHECKING:
from litellm.types.llms.openai import (
ALL_RESPONSES_API_TOOL_PARAMS,
AllMessageValues,
ChatCompletionImageObject,
ChatCompletionThinkingBlock,
OpenAIMessageContentListBlock,
)
@ -141,10 +144,10 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
responses_api_request["max_output_tokens"] = value
elif key == "tools" and value is not None:
# Convert chat completion tools to responses API tools format
responses_api_request[
"tools"
] = self._convert_tools_to_responses_format(
cast(List[Dict[str, Any]], value)
responses_api_request["tools"] = (
self._convert_tools_to_responses_format(
cast(List[Dict[str, Any]], value)
)
)
elif key in ResponsesAPIOptionalRequestParams.__annotations__.keys():
responses_api_request[key] = value # type: ignore
@ -320,6 +323,36 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
else:
return {"type": "output_text", "text": content}
def _convert_content_to_responses_format_image(
self, content: "ChatCompletionImageObject", role: str
) -> "ResponseInputImageParam":
from openai.types.responses import ResponseInputImageParam
content_image_url = content.get("image_url")
actual_image_url: Optional[str] = None
detail: Optional[Literal["low", "high", "auto"]] = None
if isinstance(content_image_url, str):
actual_image_url = content_image_url
elif isinstance(content_image_url, dict):
actual_image_url = content_image_url.get("url")
detail = cast(
Optional[Literal["low", "high", "auto"]],
content_image_url.get("detail"),
)
if actual_image_url is None:
raise ValueError(f"Invalid image URL: {content_image_url}")
image_param = ResponseInputImageParam(
image_url=actual_image_url, detail="auto", type="input_image"
)
if detail:
image_param["detail"] = detail
return image_param
def _convert_content_to_responses_format(
self,
content: Union[
@ -331,6 +364,8 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
role: str,
) -> List[Dict[str, Any]]:
"""Convert chat completion content to responses API format"""
from litellm.types.llms.openai import ChatCompletionImageObject
verbose_logger.debug(
f"Chat provider: Converting content to responses format - input type: {type(content)}"
)
@ -360,10 +395,12 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
verbose_logger.debug(f"Chat provider: text -> {converted}")
elif original_type == "image_url":
# Map to responses API image format
converted = {
"type": "input_image",
"image_url": item.get("image_url", {}),
}
converted = cast(
dict,
self._convert_content_to_responses_format_image(
cast(ChatCompletionImageObject, item), role
),
)
result.append(converted)
verbose_logger.debug(
f"Chat provider: image_url -> {converted}"

View file

@ -330,9 +330,18 @@ class BaseAWSLLM:
and isinstance(standard_aws_region_name, str)
):
aws_region_name = standard_aws_region_name
if aws_region_name is None:
aws_region_name = "us-west-2"
try:
import boto3
session = boto3.Session()
configured_region = session.region_name
if configured_region:
aws_region_name = configured_region
else:
aws_region_name = "us-west-2"
except Exception:
aws_region_name = "us-west-2"
return aws_region_name

View file

@ -0,0 +1,52 @@
import datetime
import json
import os
import sys
import unittest
from typing import List, Optional, Tuple
from unittest.mock import ANY, MagicMock, Mock, patch
import httpx
import pytest
sys.path.insert(
0, os.path.abspath("../../..")
) # Adds the parent directory to the system-path
import litellm
def test_convert_chat_completion_messages_to_responses_api_image_input():
from litellm.completion_extras.litellm_responses_transformation.transformation import (
LiteLLMResponsesTransformationHandler,
)
handler = LiteLLMResponsesTransformationHandler()
user_content = "What's in this image?"
user_image = "https://w7.pngwing.com/pngs/666/274/png-transparent-image-pictures-icon-photo-thumbnail.png"
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": user_content,
},
{
"type": "image_url",
"image_url": {"url": user_image},
},
],
},
]
response, _ = handler.convert_chat_completion_messages_to_responses_api(messages)
response_str = json.dumps(response)
assert user_content in response_str
assert user_image in response_str
print("response: ", response)
assert response[0]["content"][1]["image_url"] == user_image

View file

@ -98,3 +98,81 @@ def test_auth_functions_tracer_wrapping():
assert (
has_tracer_wrap
), f"Auth function on line {line_number} is not wrapped with @tracer.wrap: {line.strip()}"
def test_get_aws_region_name_boto3_fallback():
"""
Test the boto3 session fallback logic in _get_aws_region_name method.
This tests the specific code block that tries to get the region from boto3.Session()
when aws_region_name is None and not found in environment variables.
"""
base_aws_llm = BaseAWSLLM()
# Test case 1: boto3.Session() returns a configured region
with patch("litellm.llms.bedrock.base_aws_llm.get_secret") as mock_get_secret:
mock_get_secret.return_value = None # No region in env vars
with patch("boto3.Session") as mock_boto3_session:
mock_session = MagicMock()
mock_session.region_name = "us-east-1"
mock_boto3_session.return_value = mock_session
optional_params = {}
result = base_aws_llm._get_aws_region_name(optional_params)
assert result == "us-east-1"
mock_boto3_session.assert_called_once()
# Test case 2: boto3.Session() returns None for region (should default to us-west-2)
with patch("litellm.llms.bedrock.base_aws_llm.get_secret") as mock_get_secret:
mock_get_secret.return_value = None # No region in env vars
with patch("boto3.Session") as mock_boto3_session:
mock_session = MagicMock()
mock_session.region_name = None
mock_boto3_session.return_value = mock_session
optional_params = {}
result = base_aws_llm._get_aws_region_name(optional_params)
assert result == "us-west-2"
mock_boto3_session.assert_called_once()
# Test case 3: boto3 import/session creation raises exception (should default to us-west-2)
with patch("litellm.llms.bedrock.base_aws_llm.get_secret") as mock_get_secret:
mock_get_secret.return_value = None # No region in env vars
with patch("boto3.Session") as mock_boto3_session:
mock_boto3_session.side_effect = Exception("boto3 not available")
optional_params = {}
result = base_aws_llm._get_aws_region_name(optional_params)
assert result == "us-west-2"
mock_boto3_session.assert_called_once()
# Test case 4: aws_region_name is provided in optional_params (should not use boto3)
with patch("boto3.Session") as mock_boto3_session:
optional_params = {"aws_region_name": "eu-west-1"}
result = base_aws_llm._get_aws_region_name(optional_params)
assert result == "eu-west-1"
mock_boto3_session.assert_not_called()
# Test case 5: aws_region_name found in environment variables (should not use boto3)
with patch("litellm.llms.bedrock.base_aws_llm.get_secret") as mock_get_secret:
def side_effect(key, default=None):
if key == "AWS_REGION_NAME":
return "ap-southeast-1"
return default
mock_get_secret.side_effect = side_effect
with patch("boto3.Session") as mock_boto3_session:
optional_params = {}
result = base_aws_llm._get_aws_region_name(optional_params)
assert result == "ap-southeast-1"
mock_boto3_session.assert_not_called()