diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py
index f39d1bfb5f..bc16215393 100644
--- a/litellm/completion_extras/litellm_responses_transformation/transformation.py
+++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py
@@ -32,6 +32,12 @@ from litellm.types.llms.openai import (
ResponsesAPIOptionalRequestParams,
ResponsesAPIStreamEvents,
)
+from litellm.types.utils import (
+ Delta,
+ GenericStreamingChunk,
+ ModelResponseStream,
+ StreamingChoices,
+)
if TYPE_CHECKING:
from openai.types.responses import ResponseInputImageParam
@@ -46,7 +52,6 @@ if TYPE_CHECKING:
ChatCompletionThinkingBlock,
OpenAIMessageContentListBlock,
)
- from litellm.types.utils import GenericStreamingChunk, ModelResponseStream
class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
@@ -606,11 +611,13 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
ResponsesAPIOptionalRequestParams.__annotations__.keys()
)
# Also include params we handle specially
- supported_responses_api_params.update({
- "previous_response_id",
- "reasoning_effort", # We map this to "reasoning"
- })
-
+ supported_responses_api_params.update(
+ {
+ "previous_response_id",
+ "reasoning_effort", # We map this to "reasoning"
+ }
+ )
+
# Extract supported params from extra_body and merge into optional_params
extra_body_copy = extra_body.copy()
for key, value in extra_body_copy.items():
@@ -620,14 +627,16 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
return optional_params
- def _map_reasoning_effort(self, reasoning_effort: Union[str, Dict[str, Any]]) -> Optional[Reasoning]:
+ def _map_reasoning_effort(
+ self, reasoning_effort: Union[str, Dict[str, Any]]
+ ) -> Optional[Reasoning]:
# If dict is passed, convert it directly to Reasoning object
if isinstance(reasoning_effort, dict):
return Reasoning(**reasoning_effort) # type: ignore[typeddict-item]
# If string is passed, map without summary (default)
if reasoning_effort == "none":
- return Reasoning(effort="none") # type: ignore
+ return Reasoning(effort="none") # type: ignore
elif reasoning_effort == "high":
return Reasoning(effort="high")
elif reasoning_effort == "medium":
@@ -746,12 +755,6 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
finish_reason="",
usage=None,
)
- elif output_item.get("type") == "message":
- pass
- elif output_item.get("type") == "reasoning":
- pass
- else:
- raise ValueError(f"Chat provider: Invalid output_item {output_item}")
elif event_type == "response.function_call_arguments.delta":
content_part: Optional[str] = parsed_chunk.get("delta", None)
if content_part:
@@ -813,10 +816,6 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
return GenericStreamingChunk(
finish_reason="stop", is_finished=True, usage=None, text=""
)
- elif output_item.get("type") == "reasoning":
- pass
- else:
- raise ValueError(f"Chat provider: Invalid output_item {output_item}")
elif event_type == "response.output_text.delta":
# Content part added to output
diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py
index c7521986c7..9c4a7e3876 100644
--- a/litellm/litellm_core_utils/litellm_logging.py
+++ b/litellm/litellm_core_utils/litellm_logging.py
@@ -4358,12 +4358,12 @@ class StandardLoggingPayloadSetup:
"""
Get final response object after redacting the message input/output from logging
"""
- if response_obj is not None:
+ if response_obj:
final_response_obj: Optional[Union[dict, str, list]] = response_obj
elif isinstance(init_response_obj, list) or isinstance(init_response_obj, str):
final_response_obj = init_response_obj
else:
- final_response_obj = None
+ final_response_obj = {}
modified_final_response_obj = redact_message_input_output_from_logging(
model_call_details=kwargs,
diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py
index 8d63485be0..412a6de005 100644
--- a/litellm/proxy/_experimental/mcp_server/server.py
+++ b/litellm/proxy/_experimental/mcp_server/server.py
@@ -1286,6 +1286,7 @@ if MCP_AVAILABLE:
# Allow modifying the MCP tool call response before it is returned to the user
#########################################################
if litellm_logging_obj:
+ litellm_logging_obj.post_call(original_response=response)
end_time = datetime.now()
await litellm_logging_obj.async_post_mcp_tool_call_hook(
kwargs=litellm_logging_obj.model_call_details,
diff --git a/litellm/proxy/_experimental/out/api-reference/index.html b/litellm/proxy/_experimental/out/api-reference/index.html
index a952840f80..64972eabd7 100644
--- a/litellm/proxy/_experimental/out/api-reference/index.html
+++ b/litellm/proxy/_experimental/out/api-reference/index.html
@@ -1 +1 @@
-
LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/logs/index.html b/litellm/proxy/_experimental/out/logs/index.html
index b2cfe0a9f8..bc7490da24 100644
--- a/litellm/proxy/_experimental/out/logs/index.html
+++ b/litellm/proxy/_experimental/out/logs/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/model-hub/index.html b/litellm/proxy/_experimental/out/model-hub/index.html
index 26d700ea5a..26536b0fd8 100644
--- a/litellm/proxy/_experimental/out/model-hub/index.html
+++ b/litellm/proxy/_experimental/out/model-hub/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/model_hub_table/index.html b/litellm/proxy/_experimental/out/model_hub_table/index.html
index 7b64e2cc68..87f6fa630c 100644
--- a/litellm/proxy/_experimental/out/model_hub_table/index.html
+++ b/litellm/proxy/_experimental/out/model_hub_table/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/models-and-endpoints/index.html b/litellm/proxy/_experimental/out/models-and-endpoints/index.html
index 632e0f0998..82ab8d9fb0 100644
--- a/litellm/proxy/_experimental/out/models-and-endpoints/index.html
+++ b/litellm/proxy/_experimental/out/models-and-endpoints/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/organizations/index.html b/litellm/proxy/_experimental/out/organizations/index.html
index 4fcbc04efd..fe4127e8d4 100644
--- a/litellm/proxy/_experimental/out/organizations/index.html
+++ b/litellm/proxy/_experimental/out/organizations/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/playground/index.html b/litellm/proxy/_experimental/out/playground/index.html
index 9fa8b4870f..bb3c5a4e67 100644
--- a/litellm/proxy/_experimental/out/playground/index.html
+++ b/litellm/proxy/_experimental/out/playground/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/teams/index.html b/litellm/proxy/_experimental/out/teams/index.html
index 5632088399..d97e99608b 100644
--- a/litellm/proxy/_experimental/out/teams/index.html
+++ b/litellm/proxy/_experimental/out/teams/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/test-key/index.html b/litellm/proxy/_experimental/out/test-key/index.html
index a04cdd9800..f14a275065 100644
--- a/litellm/proxy/_experimental/out/test-key/index.html
+++ b/litellm/proxy/_experimental/out/test-key/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/usage/index.html b/litellm/proxy/_experimental/out/usage/index.html
index 5e3e1bf628..4d21084d21 100644
--- a/litellm/proxy/_experimental/out/usage/index.html
+++ b/litellm/proxy/_experimental/out/usage/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/users/index.html b/litellm/proxy/_experimental/out/users/index.html
index 7c93a185e9..13562e690b 100644
--- a/litellm/proxy/_experimental/out/users/index.html
+++ b/litellm/proxy/_experimental/out/users/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/_experimental/out/virtual-keys/index.html b/litellm/proxy/_experimental/out/virtual-keys/index.html
index 326cb1bfce..ada2ef3238 100644
--- a/litellm/proxy/_experimental/out/virtual-keys/index.html
+++ b/litellm/proxy/_experimental/out/virtual-keys/index.html
@@ -1 +1 @@
-LiteLLM Dashboard
\ No newline at end of file
+LiteLLM Dashboard
diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py
index 06b5301424..187d095a5b 100644
--- a/litellm/proxy/db/db_spend_update_writer.py
+++ b/litellm/proxy/db/db_spend_update_writer.py
@@ -8,9 +8,9 @@ Module responsible for
import asyncio
import json
import os
+import random
import time
import traceback
-import random
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast, overload
@@ -342,7 +342,7 @@ class DBSpendUpdateWriter:
):
"""
Update spend for all tags in the request.
-
+
Args:
response_cost: Cost of the request
request_tags: JSON string of tags list e.g. '["prod-tag", "test-tag"]'
@@ -863,7 +863,7 @@ class DBSpendUpdateWriter:
):
"""
Helper function to update spend for any entity type (team, org, tag, etc).
-
+
Args:
entity_name: Name of entity for logging (e.g., "Team", "Org", "Tag")
transactions: Dictionary of {entity_id: response_cost}
@@ -875,9 +875,7 @@ class DBSpendUpdateWriter:
"""
from litellm.proxy.utils import _raise_failed_update_spend_exception
- verbose_proxy_logger.debug(
- f"{entity_name} Spend transactions: {transactions}"
- )
+ verbose_proxy_logger.debug(f"{entity_name} Spend transactions: {transactions}")
if transactions is not None and len(transactions.keys()) > 0:
for i in range(n_retry_times + 1):
start_time = time.time()
@@ -1062,17 +1060,19 @@ class DBSpendUpdateWriter:
# Add cache-related fields if they exist
if "cache_read_input_tokens" in transaction:
- common_data[
- "cache_read_input_tokens"
- ] = transaction.get("cache_read_input_tokens", 0)
+ common_data["cache_read_input_tokens"] = (
+ transaction.get("cache_read_input_tokens", 0)
+ )
if "cache_creation_input_tokens" in transaction:
- common_data[
- "cache_creation_input_tokens"
- ] = transaction.get("cache_creation_input_tokens", 0)
+ common_data["cache_creation_input_tokens"] = (
+ transaction.get("cache_creation_input_tokens", 0)
+ )
if entity_type == "tag" and "request_id" in transaction:
- common_data["request_id"] = transaction.get("request_id")
-
+ common_data["request_id"] = transaction.get(
+ "request_id"
+ )
+
# Create update data structure
update_data = {
"prompt_tokens": {
diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py
index 3f85de855d..08e5624885 100644
--- a/litellm/proxy/spend_tracking/spend_tracking_utils.py
+++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py
@@ -95,9 +95,9 @@ def _get_spend_logs_metadata(
clean_metadata["applied_guardrails"] = applied_guardrails
clean_metadata["batch_models"] = batch_models
clean_metadata["mcp_tool_call_metadata"] = mcp_tool_call_metadata
- clean_metadata[
- "vector_store_request_metadata"
- ] = _get_vector_store_request_for_spend_logs_payload(vector_store_request_metadata)
+ clean_metadata["vector_store_request_metadata"] = (
+ _get_vector_store_request_for_spend_logs_payload(vector_store_request_metadata)
+ )
clean_metadata["guardrail_information"] = guardrail_information
clean_metadata["usage_object"] = usage_object
clean_metadata["model_map_information"] = model_map_information
@@ -142,28 +142,26 @@ def get_spend_logs_id(
return id
-def _extract_usage_for_ocr_call(
- response_obj: Any, response_obj_dict: dict
-) -> dict:
+def _extract_usage_for_ocr_call(response_obj: Any, response_obj_dict: dict) -> dict:
"""
Extract usage information for OCR/AOCR calls.
-
+
OCR responses use usage_info (with pages_processed) instead of token-based usage.
-
+
Args:
response_obj: The raw response object (can be dict, BaseModel, or other)
response_obj_dict: Dictionary representation of the response object
-
+
Returns:
A dict with prompt_tokens=0, completion_tokens=0, total_tokens=0,
and pages_processed from usage_info.
"""
usage_info = None
-
+
# Try to extract usage_info from dict
if isinstance(response_obj_dict, dict) and "usage_info" in response_obj_dict:
usage_info = response_obj_dict.get("usage_info")
-
+
# Try to extract usage_info from object attributes if not found in dict
if not usage_info and hasattr(response_obj, "usage_info"):
usage_info = response_obj.usage_info
@@ -171,7 +169,7 @@ def _extract_usage_for_ocr_call(
usage_info = usage_info.model_dump()
elif hasattr(usage_info, "__dict__"):
usage_info = vars(usage_info)
-
+
# For OCR, we track pages instead of tokens
if usage_info is not None:
# Handle dict or object with attributes
@@ -193,7 +191,7 @@ def _extract_usage_for_ocr_call(
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0,
- "pages_processed": 0
+ "pages_processed": 0,
}
else:
return {}
@@ -206,17 +204,18 @@ def get_logging_payload( # noqa: PLR0915
if kwargs is None:
kwargs = {}
- if response_obj is None or (
- not isinstance(response_obj, BaseModel) and not isinstance(response_obj, dict)
- ):
+
+ if response_obj is None:
response_obj = {}
+ elif not isinstance(response_obj, BaseModel) and not isinstance(response_obj, dict):
+ response_obj = {"result": str(response_obj)}
# standardize this function to be used across, s3, dynamoDB, langfuse logging
litellm_params = kwargs.get("litellm_params", {})
metadata = get_litellm_metadata_from_kwargs(kwargs)
completion_start_time = kwargs.get("completion_start_time", end_time)
call_type = kwargs.get("call_type")
cache_hit = kwargs.get("cache_hit", False)
-
+
# Convert response_obj to dict first
if isinstance(response_obj, dict):
response_obj_dict = response_obj
@@ -224,7 +223,7 @@ def get_logging_payload( # noqa: PLR0915
response_obj_dict = response_obj.model_dump()
else:
response_obj_dict = {}
-
+
# Handle OCR responses which use usage_info instead of usage
if call_type in ["ocr", "aocr"]:
usage = _extract_usage_for_ocr_call(response_obj, response_obj_dict)
@@ -658,6 +657,7 @@ def _get_response_for_spend_logs_payload(
sanitized_wrapper = _sanitize_request_body_for_spend_logs_payload(
{"response": response_obj}
)
+
sanitized_response = sanitized_wrapper.get("response", response_obj)
if sanitized_response is None:
diff --git a/tests/llm_translation/test_openai.py b/tests/llm_translation/test_openai.py
index ba1d9e6ac2..10aab93051 100644
--- a/tests/llm_translation/test_openai.py
+++ b/tests/llm_translation/test_openai.py
@@ -348,7 +348,11 @@ def test_openai_image_generation_forwards_organization(mock_get_openai_client):
return {
"created": 123,
"data": [{"url": "http://example.com/image.png"}],
- "usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0},
+ "usage": {
+ "input_tokens": 0,
+ "output_tokens": 0,
+ "total_tokens": 0,
+ },
}
return _Resp()
@@ -797,7 +801,9 @@ async def test_openai_service_tier_parameter():
# Verify the request contains the service_tier parameter
assert "service_tier" in request_body, "service_tier should be in request body"
# Verify service_tier is correctly sent to the API
- assert request_body["service_tier"] == "priority", "service_tier should be 'priority'"
+ assert (
+ request_body["service_tier"] == "priority"
+ ), "service_tier should be 'priority'"
def test_openai_service_tier_parameter_sync():
@@ -826,7 +832,9 @@ def test_openai_service_tier_parameter_sync():
# Verify the request contains the service_tier parameter
assert "service_tier" in request_body, "service_tier should be in request body"
# Verify service_tier is correctly sent to the API
- assert request_body["service_tier"] == "priority", "service_tier should be 'priority'"
+ assert (
+ request_body["service_tier"] == "priority"
+ ), "service_tier should be 'priority'"
def test_gpt_5_reasoning_streaming():
@@ -1358,7 +1366,7 @@ async def test_streaming_tool_calls_with_n_greater_than_1(model):
"""
Test that the index field in a choice object is correctly populated
when using streaming mode with n>1 and tool calls.
-
+
Regression test for: https://github.com/BerriAI/litellm/issues/8977
"""
tools = [
@@ -1386,7 +1394,7 @@ async def test_streaming_tool_calls_with_n_greater_than_1(model):
},
}
]
-
+
response = litellm.completion(
model=model,
messages=[
@@ -1399,20 +1407,30 @@ async def test_streaming_tool_calls_with_n_greater_than_1(model):
stream=True,
n=3,
)
-
+
# Collect all chunks and their indices
indices_seen = []
for chunk in response:
- assert len(chunk.choices) == 1, "Each streaming chunk should have exactly 1 choice"
- assert hasattr(chunk.choices[0], "index"), "Choice should have an index attribute"
+ assert (
+ len(chunk.choices) == 1
+ ), "Each streaming chunk should have exactly 1 choice"
+ assert hasattr(
+ chunk.choices[0], "index"
+ ), "Choice should have an index attribute"
index = chunk.choices[0].index
indices_seen.append(index)
-
+
# Verify that we got chunks with different indices (0, 1, 2 for n=3)
unique_indices = set(indices_seen)
- assert unique_indices == {0, 1, 2}, f"Should have indices 0, 1, 2 for n=3, got {unique_indices}"
-
- print(f"✓ Test passed: streaming with n=3 and tool calls correctly populates index field")
+ assert unique_indices == {
+ 0,
+ 1,
+ 2,
+ }, f"Should have indices 0, 1, 2 for n=3, got {unique_indices}"
+
+ print(
+ f"✓ Test passed: streaming with n=3 and tool calls correctly populates index field"
+ )
print(f" Indices seen: {indices_seen}")
print(f" Unique indices: {unique_indices}")
@@ -1436,19 +1454,42 @@ async def test_streaming_content_with_n_greater_than_1(model):
n=2,
max_tokens=10,
)
-
+
# Collect all chunks and their indices
indices_seen = []
for chunk in response:
- assert len(chunk.choices) == 1, "Each streaming chunk should have exactly 1 choice"
- assert hasattr(chunk.choices[0], "index"), "Choice should have an index attribute"
+ assert (
+ len(chunk.choices) == 1
+ ), "Each streaming chunk should have exactly 1 choice"
+ assert hasattr(
+ chunk.choices[0], "index"
+ ), "Choice should have an index attribute"
index = chunk.choices[0].index
indices_seen.append(index)
-
+
# Verify that we got chunks with different indices (0, 1 for n=2)
unique_indices = set(indices_seen)
- assert unique_indices == {0, 1}, f"Should have indices 0, 1 for n=2, got {unique_indices}"
-
- print(f"✓ Test passed: streaming with n=2 and regular content correctly populates index field")
+ assert unique_indices == {
+ 0,
+ 1,
+ }, f"Should have indices 0, 1 for n=2, got {unique_indices}"
+
+ print(
+ f"✓ Test passed: streaming with n=2 and regular content correctly populates index field"
+ )
print(f" Indices seen: {indices_seen}")
print(f" Unique indices: {unique_indices}")
+
+
+def test_gpt_5_web_search():
+ response = litellm.completion(
+ model="openai/responses/gpt-5",
+ messages=[{"role": "user", "content": "get price of nvda"}],
+ stream=True,
+ temperature=1,
+ max_tokens=8192,
+ tools=[{"type": "web_search"}],
+ )
+
+ for chunk in response:
+ print("chunk: ", chunk)
diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py
index 64f3a652ba..477fd1396f 100644
--- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py
+++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py
@@ -568,10 +568,11 @@ async def test_e2e_generate_cold_storage_object_key_successful():
start_time = datetime(2025, 1, 15, 10, 30, 45, 123456, timezone.utc)
response_id = "chatcmpl-test-12345"
team_alias = "test-team"
-
- with patch("litellm.cold_storage_custom_logger", return_value="s3"), \
- patch("litellm.integrations.s3.get_s3_object_key") as mock_get_s3_key:
-
+
+ with patch("litellm.cold_storage_custom_logger", return_value="s3"), patch(
+ "litellm.integrations.s3.get_s3_object_key"
+ ) as mock_get_s3_key:
+
# Mock the S3 object key generation to return a predictable result
mock_get_s3_key.return_value = (
"2025-01-15/time-10-30-45-123456_chatcmpl-test-12345.json"
@@ -613,11 +614,13 @@ async def test_e2e_generate_cold_storage_object_key_with_custom_logger_s3_path()
# Create mock custom logger with s3_path
mock_custom_logger = MagicMock()
mock_custom_logger.s3_path = "storage"
-
- with patch("litellm.cold_storage_custom_logger", "s3_v2"), \
- patch("litellm.logging_callback_manager.get_active_custom_logger_for_callback_name") as mock_get_logger, \
- patch("litellm.integrations.s3.get_s3_object_key") as mock_get_s3_key:
-
+
+ with patch("litellm.cold_storage_custom_logger", "s3_v2"), patch(
+ "litellm.logging_callback_manager.get_active_custom_logger_for_callback_name"
+ ) as mock_get_logger, patch(
+ "litellm.integrations.s3.get_s3_object_key"
+ ) as mock_get_s3_key:
+
# Setup mocks
mock_get_logger.return_value = mock_custom_logger
mock_get_s3_key.return_value = (
@@ -663,11 +666,13 @@ async def test_e2e_generate_cold_storage_object_key_with_logger_no_s3_path():
# Create mock custom logger without s3_path
mock_custom_logger = MagicMock()
mock_custom_logger.s3_path = None # or could be missing attribute
-
- with patch("litellm.cold_storage_custom_logger", "s3_v2"), \
- patch("litellm.logging_callback_manager.get_active_custom_logger_for_callback_name") as mock_get_logger, \
- patch("litellm.integrations.s3.get_s3_object_key") as mock_get_s3_key:
-
+
+ with patch("litellm.cold_storage_custom_logger", "s3_v2"), patch(
+ "litellm.logging_callback_manager.get_active_custom_logger_for_callback_name"
+ ) as mock_get_logger, patch(
+ "litellm.integrations.s3.get_s3_object_key"
+ ) as mock_get_s3_key:
+
# Setup mocks
mock_get_logger.return_value = mock_custom_logger
mock_get_s3_key.return_value = (
@@ -708,7 +713,7 @@ async def test_e2e_generate_cold_storage_object_key_not_configured():
team_alias = "another-team"
# Use patch to ensure test isolation
- with patch.object(litellm, 'cold_storage_custom_logger', None):
+ with patch.object(litellm, "cold_storage_custom_logger", None):
# Call the function
result = StandardLoggingPayloadSetup._generate_cold_storage_object_key(
start_time=start_time, response_id=response_id, team_alias=team_alias
@@ -716,3 +721,41 @@ async def test_e2e_generate_cold_storage_object_key_not_configured():
# Verify the result is None when cold storage is not configured
assert result is None
+
+
+def test_get_final_response_obj_with_empty_response_obj_and_list_init():
+ """
+ Test get_final_response_obj when response_obj is empty dict and init_response_obj is a list.
+
+ When response_obj is empty (falsy), the method should return init_response_obj if it's a list.
+ """
+ from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
+
+ # Create test objects
+ class TestObject1:
+ def __init__(self):
+ self.name = "Object1"
+
+ class TestObject2:
+ def __init__(self):
+ self.name = "Object2"
+
+ obj1 = TestObject1()
+ obj2 = TestObject2()
+
+ # Test case: empty response_obj, list init_response_obj
+ response_obj = {}
+ init_response_obj = [obj1, obj2]
+ kwargs = {}
+
+ # Call the method
+ result = StandardLoggingPayloadSetup.get_final_response_obj(
+ response_obj=response_obj, init_response_obj=init_response_obj, kwargs=kwargs
+ )
+
+ # Verify the result
+ assert result == [obj1, obj2]
+ assert result is init_response_obj # Should be the exact same list object
+ assert len(result) == 2
+ assert result[0].name == "Object1"
+ assert result[1].name == "Object2"