[Bump] Litellm responses format (#12253)

* Add responses format changes

* Add check

* Add check

* added more testing
This commit is contained in:
Jugal D. Bhatt 2025-07-03 05:02:06 +05:30 • committed by GitHub
parent 1ce71d6d61
commit 88834b8550
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 149 additions and 4 deletions

View file

@ -11,6 +11,7 @@ from litellm.types.responses.main import *
from litellm.types.router import GenericLiteLLMParams
from ..common_utils import OpenAIError
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import _safe_convert_created_field
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
@ -85,6 +86,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
"""No transform applied since outputs are in OpenAI spec already"""
try:
raw_response_json = raw_response.json()
raw_response_json["created_at"] = _safe_convert_created_field(raw_response_json["created_at"])
except Exception:
raise OpenAIError(
message=raw_response.text, status_code=raw_response.status_code

View file

@ -1010,7 +1010,7 @@ class ResponseAPIUsage(BaseLiteLLMOpenAIResponseObject):
class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject):
id: str
created_at: float
created_at: int
error: Optional[dict]
incomplete_details: Optional[IncompleteDetails]
instructions: Optional[str]
@ -1034,6 +1034,7 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject):
truncation: Optional[Literal["auto", "disabled"]]
usage: Optional[ResponseAPIUsage]
user: Optional[str]
store: Optional[bool] = None
# Define private attributes using PrivateAttr
_hidden_params: dict = PrivateAttr(default_factory=dict)

View file

@ -1,4 +1,3 @@
import httpx
import json
import pytest
@ -51,8 +50,8 @@ def validate_responses_api_response(response, final_chunk: bool = False):
response["id"], str
), "Response should have a string 'id' field"
assert "created_at" in response and isinstance(
response["created_at"], (int, float)
), "Response should have a numeric 'created_at' field"
response["created_at"], int
), "Response should have an integer 'created_at' field"
assert "output" in response and isinstance(
response["output"], list
), "Response should have a list 'output' field"
@ -80,6 +79,7 @@ def validate_responses_api_response(response, final_chunk: bool = False):
"truncation": (str, type(None)),
"usage": ResponseAPIUsage,
"user": (str, type(None)),
"store": (bool, type(None)),
}
if final_chunk is False:
optional_fields["usage"] = type(None)

View file

@ -4,6 +4,11 @@ import pytest
import asyncio
from typing import Optional, cast
from unittest.mock import patch, AsyncMock
import httpx
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
import time
import json
sys.path.insert(0, os.path.abspath("../.."))
import litellm
@ -1152,3 +1157,140 @@ def test_mcp_tools_with_responses_api():
print(response_with_mcp_call)
@pytest.mark.asyncio
async def test_openai_responses_api_field_types():
"""Test that specific fields in the response have the correct types"""
litellm._turn_on_debug()
litellm.set_verbose = True
# Test with store=True
response = await litellm.aresponses(
model="gpt-4o",
input="hi",
)
# Verify created_at is an integer
assert isinstance(response.created_at, int), "created_at should be an integer"
# Verify store field is present and matches input
assert hasattr(response, "store"), "store field should be present"
assert response.store is True, "store field should match input value"
# Test without store parameter
response_without_store = await litellm.aresponses(
model="gpt-4o",
input="hi"
)
# Verify created_at is still an integer
assert isinstance(response_without_store.created_at, int), "created_at should be an integer"
# Verify store field is present but None when not specified
assert hasattr(response_without_store, "store"), "store field should be present"
@pytest.mark.asyncio
async def test_store_field_transformation():
"""Test store field transformation with mocked API responses"""
config = OpenAIResponsesAPIConfig()
# Initialize logging object with required parameters
logging_obj = LiteLLMLoggingObj(
model="gpt-4o",
messages=[],
stream=False,
call_type="aresponses",
start_time=time.time(),
litellm_call_id="test-call-id",
function_id="test-function-id"
)
# Base response data with all required fields
base_response = {
"id": "test_id",
"created_at": 1751443898,
"model": "gpt-4o",
"object": "response",
"output": [{"type": "message", "id": "msg_1", "status": "completed", "role": "assistant", "content": [{"type": "output_text", "text": "Hello", "annotations": []}]}],
"parallel_tool_calls": True,
"tool_choice": "auto",
"tools": [],
"error": None,
"incomplete_details": None,
"instructions": "test instructions",
"metadata": {},
"temperature": 0.7,
"top_p": 1.0,
"max_output_tokens": 100,
"previous_response_id": None,
"reasoning": None,
"status": "completed",
"text": None,
"truncation": "auto",
"usage": {"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
"user": "test_user"
}
# Test case 1: API returns store=True
mock_response_store_true = httpx.Response(
status_code=200,
content=json.dumps({**base_response, "store": True}).encode()
)
# Test case 2: API returns store=False
mock_response_store_false = httpx.Response(
status_code=200,
content=json.dumps({**base_response, "store": False}).encode()
)
# Test case 3: API returns store=null
mock_response_store_null = httpx.Response(
status_code=200,
content=json.dumps({**base_response, "store": None}).encode()
)
# Test case 4: API omits store field
mock_response_no_store = httpx.Response(
status_code=200,
content=json.dumps(base_response).encode()
)
# Test when store=True in request
logging_obj.optional_params = {"store": True}
response = config.transform_response_api_response(
model="gpt-4o",
raw_response=mock_response_store_true,
logging_obj=logging_obj
)
assert response.store is True, "store should be True when specified in request and API returns True"
# Test when store=False in request
logging_obj.optional_params = {"store": False}
response = config.transform_response_api_response(
model="gpt-4o",
raw_response=mock_response_store_false,
logging_obj=logging_obj
)
assert response.store is False, "store should be False when specified in request and API returns False"
# Test when store not in request but API returns null
response = config.transform_response_api_response(
model="gpt-4o",
raw_response=mock_response_store_null,
logging_obj=logging_obj
)
assert response.store is None, "store should be None when not specified in request and API returns null"
# Test when store not in request and API omits store field
response = config.transform_response_api_response(
model="gpt-4o",
raw_response=mock_response_no_store,
logging_obj=logging_obj
)
assert response.store is None, "store should be None when not specified in request and API omits store"
# Verify created_at is always converted to integer
assert isinstance(response.created_at, int), "created_at should always be converted to integer"
assert response.created_at == 1751443898, "created_at should maintain the same value after conversion"