581 lines
22 KiB
Python
581 lines
22 KiB
Python
|
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast, get_type_hints
|
||
|
|
|
||
|
|
import httpx
|
||
|
|
from openai.types.responses import ResponseReasoningItem
|
||
|
|
from pydantic import BaseModel, ValidationError
|
||
|
|
|
||
|
|
import litellm
|
||
|
|
from litellm._logging import verbose_logger
|
||
|
|
from litellm.litellm_core_utils.core_helpers import process_response_headers
|
||
|
|
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
|
||
|
|
_safe_convert_created_field,
|
||
|
|
)
|
||
|
|
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
|
||
|
|
from litellm.secret_managers.main import get_secret_str
|
||
|
|
from litellm.types.llms.openai import *
|
||
|
|
from litellm.types.responses.main import *
|
||
|
|
from litellm.types.router import GenericLiteLLMParams
|
||
|
|
from litellm.types.utils import LlmProviders
|
||
|
|
|
||
|
|
from ..common_utils import OpenAIError
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||
|
|
|
||
|
|
LiteLLMLoggingObj = _LiteLLMLoggingObj
|
||
|
|
else:
|
||
|
|
LiteLLMLoggingObj = Any
|
||
|
|
|
||
|
|
|
||
|
|
class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
||
|
|
@property
|
||
|
|
def custom_llm_provider(self) -> LlmProviders:
|
||
|
|
return LlmProviders.OPENAI
|
||
|
|
|
||
|
|
def get_supported_openai_params(self, model: str) -> list:
|
||
|
|
"""
|
||
|
|
All OpenAI Responses API params are supported
|
||
|
|
"""
|
||
|
|
supported_params = get_type_hints(ResponsesAPIRequestParams).keys()
|
||
|
|
return list(
|
||
|
|
set(
|
||
|
|
[
|
||
|
|
"input",
|
||
|
|
"model",
|
||
|
|
"extra_headers",
|
||
|
|
"extra_query",
|
||
|
|
"extra_body",
|
||
|
|
"timeout",
|
||
|
|
]
|
||
|
|
+ list(supported_params)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
def map_openai_params(
|
||
|
|
self,
|
||
|
|
response_api_optional_params: ResponsesAPIOptionalRequestParams,
|
||
|
|
model: str,
|
||
|
|
drop_params: bool,
|
||
|
|
) -> Dict:
|
||
|
|
"""No mapping applied since inputs are in OpenAI spec already"""
|
||
|
|
return dict(response_api_optional_params)
|
||
|
|
|
||
|
|
def transform_responses_api_request(
|
||
|
|
self,
|
||
|
|
model: str,
|
||
|
|
input: Union[str, ResponseInputParam],
|
||
|
|
response_api_optional_request_params: Dict,
|
||
|
|
litellm_params: GenericLiteLLMParams,
|
||
|
|
headers: dict,
|
||
|
|
) -> Dict:
|
||
|
|
"""No transform applied since inputs are in OpenAI spec already"""
|
||
|
|
|
||
|
|
input = self._validate_input_param(input)
|
||
|
|
final_request_params = dict(
|
||
|
|
ResponsesAPIRequestParams(
|
||
|
|
model=model, input=input, **response_api_optional_request_params
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
return final_request_params
|
||
|
|
|
||
|
|
def _validate_input_param(
|
||
|
|
self, input: Union[str, ResponseInputParam]
|
||
|
|
) -> Union[str, ResponseInputParam]:
|
||
|
|
"""
|
||
|
|
Ensure all input fields if pydantic are converted to dict
|
||
|
|
|
||
|
|
OpenAI API Fails when we try to JSON dumps specific input pydantic fields.
|
||
|
|
This function ensures all input fields are converted to dict.
|
||
|
|
"""
|
||
|
|
if isinstance(input, list):
|
||
|
|
validated_input = []
|
||
|
|
for item in input:
|
||
|
|
# if it's pydantic, convert to dict
|
||
|
|
if isinstance(item, BaseModel):
|
||
|
|
validated_input.append(item.model_dump(exclude_none=True))
|
||
|
|
elif isinstance(item, dict):
|
||
|
|
# Handle reasoning items specifically to filter out status=None
|
||
|
|
if item.get("type") == "reasoning":
|
||
|
|
verbose_logger.debug(f"Handling reasoning item: {item}")
|
||
|
|
# Type assertion since we know it's a dict at this point
|
||
|
|
dict_item = cast(Dict[str, Any], item)
|
||
|
|
filtered_item = self._handle_reasoning_item(dict_item)
|
||
|
|
else:
|
||
|
|
# For other dict items, just pass through
|
||
|
|
filtered_item = cast(Dict[str, Any], item)
|
||
|
|
validated_input.append(filtered_item)
|
||
|
|
else:
|
||
|
|
validated_input.append(item)
|
||
|
|
return validated_input # type: ignore
|
||
|
|
# Input is expected to be either str or List, no single BaseModel expected
|
||
|
|
return input
|
||
|
|
|
||
|
|
def _handle_reasoning_item(self, item: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle reasoning items specifically to filter out status=None using OpenAI's model.
|
||
|
|
Issue: https://github.com/BerriAI/litellm/issues/13484
|
||
|
|
OpenAI API does not accept ReasoningItem(status=None), so we need to:
|
||
|
|
1. Check if the item is a reasoning type
|
||
|
|
2. Create a ResponseReasoningItem object with the item data
|
||
|
|
3. Convert it back to dict with exclude_none=True to filter None values
|
||
|
|
"""
|
||
|
|
if item.get("type") == "reasoning":
|
||
|
|
try:
|
||
|
|
# Ensure required fields are present for ResponseReasoningItem
|
||
|
|
item_data = dict(item)
|
||
|
|
if "summary" not in item_data:
|
||
|
|
item_data["summary"] = (
|
||
|
|
item_data.get("reasoning_content", "")[:100] + "..."
|
||
|
|
if len(item_data.get("reasoning_content", "")) > 100
|
||
|
|
else item_data.get("reasoning_content", "")
|
||
|
|
)
|
||
|
|
|
||
|
|
# Create ResponseReasoningItem object from the item data
|
||
|
|
reasoning_item = ResponseReasoningItem(**item_data)
|
||
|
|
|
||
|
|
# Convert back to dict with exclude_none=True to exclude None fields
|
||
|
|
dict_reasoning_item = reasoning_item.model_dump(exclude_none=True)
|
||
|
|
|
||
|
|
return dict_reasoning_item
|
||
|
|
except Exception as e:
|
||
|
|
verbose_logger.debug(
|
||
|
|
f"Failed to create ResponseReasoningItem, falling back to manual filtering: {e}"
|
||
|
|
)
|
||
|
|
# Fallback: manually filter out known None fields
|
||
|
|
filtered_item = {
|
||
|
|
k: v
|
||
|
|
for k, v in item.items()
|
||
|
|
if v is not None
|
||
|
|
or k not in {"status", "content", "encrypted_content"}
|
||
|
|
}
|
||
|
|
return filtered_item
|
||
|
|
return item
|
||
|
|
|
||
|
|
def transform_response_api_response(
|
||
|
|
self,
|
||
|
|
model: str,
|
||
|
|
raw_response: httpx.Response,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> ResponsesAPIResponse:
|
||
|
|
"""No transform applied since outputs are in OpenAI spec already"""
|
||
|
|
try:
|
||
|
|
logging_obj.post_call(
|
||
|
|
original_response=raw_response.text,
|
||
|
|
additional_args={"complete_input_dict": {}},
|
||
|
|
)
|
||
|
|
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
|
||
|
|
)
|
||
|
|
raw_response_headers = dict(raw_response.headers)
|
||
|
|
processed_headers = process_response_headers(raw_response_headers)
|
||
|
|
try:
|
||
|
|
response = ResponsesAPIResponse(**raw_response_json)
|
||
|
|
except Exception:
|
||
|
|
verbose_logger.debug(
|
||
|
|
f"Error constructing ResponsesAPIResponse: {raw_response_json}, using model_construct"
|
||
|
|
)
|
||
|
|
response = ResponsesAPIResponse.model_construct(**raw_response_json)
|
||
|
|
|
||
|
|
# Store processed headers in additional_headers so they get returned to the client
|
||
|
|
response._hidden_params["additional_headers"] = processed_headers
|
||
|
|
response._hidden_params["headers"] = raw_response_headers
|
||
|
|
return response
|
||
|
|
|
||
|
|
def validate_environment(
|
||
|
|
self, headers: dict, model: str, litellm_params: Optional[GenericLiteLLMParams]
|
||
|
|
) -> dict:
|
||
|
|
litellm_params = litellm_params or GenericLiteLLMParams()
|
||
|
|
api_key = (
|
||
|
|
litellm_params.api_key
|
||
|
|
or litellm.api_key
|
||
|
|
or litellm.openai_key
|
||
|
|
or get_secret_str("OPENAI_API_KEY")
|
||
|
|
)
|
||
|
|
headers.update(
|
||
|
|
{
|
||
|
|
"Authorization": f"Bearer {api_key}",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
return headers
|
||
|
|
|
||
|
|
def get_complete_url(
|
||
|
|
self,
|
||
|
|
api_base: Optional[str],
|
||
|
|
litellm_params: dict,
|
||
|
|
) -> str:
|
||
|
|
"""
|
||
|
|
Get the endpoint for OpenAI responses API
|
||
|
|
"""
|
||
|
|
api_base = (
|
||
|
|
api_base
|
||
|
|
or litellm.api_base
|
||
|
|
or get_secret_str("OPENAI_BASE_URL")
|
||
|
|
or get_secret_str("OPENAI_API_BASE")
|
||
|
|
or "https://api.openai.com/v1"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Remove trailing slashes
|
||
|
|
api_base = api_base.rstrip("/")
|
||
|
|
|
||
|
|
return f"{api_base}/responses"
|
||
|
|
|
||
|
|
def transform_streaming_response(
|
||
|
|
self,
|
||
|
|
model: str,
|
||
|
|
parsed_chunk: dict,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> ResponsesAPIStreamingResponse:
|
||
|
|
"""
|
||
|
|
Transform a parsed streaming response chunk into a ResponsesAPIStreamingResponse
|
||
|
|
"""
|
||
|
|
# Convert the dictionary to a properly typed ResponsesAPIStreamingResponse
|
||
|
|
verbose_logger.debug("Raw OpenAI Chunk=%s", parsed_chunk)
|
||
|
|
event_type = str(parsed_chunk.get("type"))
|
||
|
|
event_pydantic_model = OpenAIResponsesAPIConfig.get_event_model_class(
|
||
|
|
event_type=event_type
|
||
|
|
)
|
||
|
|
# Some OpenAI-compatible providers send error.code: null; coalesce so validation succeeds.
|
||
|
|
try:
|
||
|
|
error_obj = parsed_chunk.get("error")
|
||
|
|
if isinstance(error_obj, dict) and error_obj.get("code") is None:
|
||
|
|
parsed_chunk = dict(parsed_chunk)
|
||
|
|
parsed_chunk["error"] = dict(error_obj)
|
||
|
|
parsed_chunk["error"]["code"] = "unknown_error"
|
||
|
|
except Exception:
|
||
|
|
verbose_logger.debug("Failed to coalesce error.code in parsed_chunk")
|
||
|
|
|
||
|
|
try:
|
||
|
|
return event_pydantic_model(**parsed_chunk)
|
||
|
|
except ValidationError:
|
||
|
|
verbose_logger.debug(
|
||
|
|
"Pydantic validation failed for %s with chunk %s, "
|
||
|
|
"falling back to model_construct",
|
||
|
|
event_pydantic_model.__name__,
|
||
|
|
parsed_chunk,
|
||
|
|
)
|
||
|
|
return event_pydantic_model.model_construct(**parsed_chunk)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_event_model_class(event_type: str) -> Any:
|
||
|
|
"""
|
||
|
|
Returns the appropriate event model class based on the event type.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
event_type (str): The type of event from the response chunk
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Any: The corresponding event model class
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ValueError: If the event type is unknown
|
||
|
|
"""
|
||
|
|
event_models = {
|
||
|
|
ResponsesAPIStreamEvents.RESPONSE_CREATED: ResponseCreatedEvent,
|
||
|
|
ResponsesAPIStreamEvents.RESPONSE_IN_PROGRESS: ResponseInProgressEvent,
|
||
|
|
ResponsesAPIStreamEvents.RESPONSE_COMPLETED: ResponseCompletedEvent,
|
||
|
|
ResponsesAPIStreamEvents.RESPONSE_FAILED: ResponseFailedEvent,
|
||
|
|
ResponsesAPIStreamEvents.RESPONSE_INCOMPLETE: ResponseIncompleteEvent,
|
||
|
|
ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED: OutputItemAddedEvent,
|
||
|
|
ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE: OutputItemDoneEvent,
|
||
|
|
ResponsesAPIStreamEvents.CONTENT_PART_ADDED: ContentPartAddedEvent,
|
||
|
|
ResponsesAPIStreamEvents.CONTENT_PART_DONE: ContentPartDoneEvent,
|
||
|
|
ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA: OutputTextDeltaEvent,
|
||
|
|
ResponsesAPIStreamEvents.OUTPUT_TEXT_ANNOTATION_ADDED: OutputTextAnnotationAddedEvent,
|
||
|
|
ResponsesAPIStreamEvents.OUTPUT_TEXT_DONE: OutputTextDoneEvent,
|
||
|
|
ResponsesAPIStreamEvents.REFUSAL_DELTA: RefusalDeltaEvent,
|
||
|
|
ResponsesAPIStreamEvents.REFUSAL_DONE: RefusalDoneEvent,
|
||
|
|
ResponsesAPIStreamEvents.FUNCTION_CALL_ARGUMENTS_DELTA: FunctionCallArgumentsDeltaEvent,
|
||
|
|
ResponsesAPIStreamEvents.FUNCTION_CALL_ARGUMENTS_DONE: FunctionCallArgumentsDoneEvent,
|
||
|
|
ResponsesAPIStreamEvents.FILE_SEARCH_CALL_IN_PROGRESS: FileSearchCallInProgressEvent,
|
||
|
|
ResponsesAPIStreamEvents.FILE_SEARCH_CALL_SEARCHING: FileSearchCallSearchingEvent,
|
||
|
|
ResponsesAPIStreamEvents.FILE_SEARCH_CALL_COMPLETED: FileSearchCallCompletedEvent,
|
||
|
|
ResponsesAPIStreamEvents.WEB_SEARCH_CALL_IN_PROGRESS: WebSearchCallInProgressEvent,
|
||
|
|
ResponsesAPIStreamEvents.WEB_SEARCH_CALL_SEARCHING: WebSearchCallSearchingEvent,
|
||
|
|
ResponsesAPIStreamEvents.WEB_SEARCH_CALL_COMPLETED: WebSearchCallCompletedEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_LIST_TOOLS_IN_PROGRESS: MCPListToolsInProgressEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_LIST_TOOLS_COMPLETED: MCPListToolsCompletedEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_LIST_TOOLS_FAILED: MCPListToolsFailedEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_CALL_IN_PROGRESS: MCPCallInProgressEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DELTA: MCPCallArgumentsDeltaEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_CALL_ARGUMENTS_DONE: MCPCallArgumentsDoneEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_CALL_COMPLETED: MCPCallCompletedEvent,
|
||
|
|
ResponsesAPIStreamEvents.MCP_CALL_FAILED: MCPCallFailedEvent,
|
||
|
|
ResponsesAPIStreamEvents.IMAGE_GENERATION_PARTIAL_IMAGE: ImageGenerationPartialImageEvent,
|
||
|
|
ResponsesAPIStreamEvents.ERROR: ErrorEvent,
|
||
|
|
# Shell tool events: passthrough as GenericEvent so payload is preserved
|
||
|
|
ResponsesAPIStreamEvents.SHELL_CALL_IN_PROGRESS: GenericEvent,
|
||
|
|
ResponsesAPIStreamEvents.SHELL_CALL_COMPLETED: GenericEvent,
|
||
|
|
ResponsesAPIStreamEvents.SHELL_CALL_OUTPUT: GenericEvent,
|
||
|
|
}
|
||
|
|
|
||
|
|
model_class = event_models.get(cast(ResponsesAPIStreamEvents, event_type))
|
||
|
|
if not model_class:
|
||
|
|
return GenericEvent
|
||
|
|
|
||
|
|
return model_class
|
||
|
|
|
||
|
|
def should_fake_stream(
|
||
|
|
self,
|
||
|
|
model: Optional[str],
|
||
|
|
stream: Optional[bool],
|
||
|
|
custom_llm_provider: Optional[str] = None,
|
||
|
|
) -> bool:
|
||
|
|
if stream is not True:
|
||
|
|
return False
|
||
|
|
if model is not None:
|
||
|
|
try:
|
||
|
|
if (
|
||
|
|
litellm.utils.supports_native_streaming(
|
||
|
|
model=model,
|
||
|
|
custom_llm_provider=custom_llm_provider,
|
||
|
|
)
|
||
|
|
is False
|
||
|
|
):
|
||
|
|
return True
|
||
|
|
except Exception as e:
|
||
|
|
verbose_logger.debug(
|
||
|
|
f"Error getting model info in OpenAIResponsesAPIConfig: {e}"
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
|
||
|
|
def supports_native_websocket(self) -> bool:
|
||
|
|
"""OpenAI supports native WebSocket for Responses API"""
|
||
|
|
return True
|
||
|
|
|
||
|
|
#########################################################
|
||
|
|
########## DELETE RESPONSE API TRANSFORMATION ##############
|
||
|
|
#########################################################
|
||
|
|
def transform_delete_response_api_request(
|
||
|
|
self,
|
||
|
|
response_id: str,
|
||
|
|
api_base: str,
|
||
|
|
litellm_params: GenericLiteLLMParams,
|
||
|
|
headers: dict,
|
||
|
|
) -> Tuple[str, Dict]:
|
||
|
|
"""
|
||
|
|
Transform the delete response API request into a URL and data
|
||
|
|
|
||
|
|
OpenAI API expects the following request
|
||
|
|
- DELETE /v1/responses/{response_id}
|
||
|
|
"""
|
||
|
|
url = f"{api_base}/{response_id}"
|
||
|
|
data: Dict = {}
|
||
|
|
return url, data
|
||
|
|
|
||
|
|
def transform_delete_response_api_response(
|
||
|
|
self,
|
||
|
|
raw_response: httpx.Response,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> DeleteResponseResult:
|
||
|
|
"""
|
||
|
|
Transform the delete response API response into a DeleteResponseResult
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
raw_response_json = raw_response.json()
|
||
|
|
except Exception:
|
||
|
|
raise OpenAIError(
|
||
|
|
message=raw_response.text, status_code=raw_response.status_code
|
||
|
|
)
|
||
|
|
return DeleteResponseResult(**raw_response_json)
|
||
|
|
|
||
|
|
#########################################################
|
||
|
|
########## GET RESPONSE API TRANSFORMATION ###############
|
||
|
|
#########################################################
|
||
|
|
def transform_get_response_api_request(
|
||
|
|
self,
|
||
|
|
response_id: str,
|
||
|
|
api_base: str,
|
||
|
|
litellm_params: GenericLiteLLMParams,
|
||
|
|
headers: dict,
|
||
|
|
) -> Tuple[str, Dict]:
|
||
|
|
"""
|
||
|
|
Transform the get response API request into a URL and data
|
||
|
|
|
||
|
|
OpenAI API expects the following request
|
||
|
|
- GET /v1/responses/{response_id}
|
||
|
|
"""
|
||
|
|
url = f"{api_base}/{response_id}"
|
||
|
|
data: Dict = {}
|
||
|
|
return url, data
|
||
|
|
|
||
|
|
def transform_get_response_api_response(
|
||
|
|
self,
|
||
|
|
raw_response: httpx.Response,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> ResponsesAPIResponse:
|
||
|
|
"""
|
||
|
|
Transform the get response API response into a ResponsesAPIResponse
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
raw_response_json = raw_response.json()
|
||
|
|
except Exception:
|
||
|
|
raise OpenAIError(
|
||
|
|
message=raw_response.text, status_code=raw_response.status_code
|
||
|
|
)
|
||
|
|
raw_response_headers = dict(raw_response.headers)
|
||
|
|
processed_headers = process_response_headers(raw_response_headers)
|
||
|
|
response = ResponsesAPIResponse(**raw_response_json)
|
||
|
|
response._hidden_params["additional_headers"] = processed_headers
|
||
|
|
response._hidden_params["headers"] = raw_response_headers
|
||
|
|
|
||
|
|
return response
|
||
|
|
|
||
|
|
#########################################################
|
||
|
|
########## LIST INPUT ITEMS TRANSFORMATION #############
|
||
|
|
#########################################################
|
||
|
|
def transform_list_input_items_request(
|
||
|
|
self,
|
||
|
|
response_id: str,
|
||
|
|
api_base: str,
|
||
|
|
litellm_params: GenericLiteLLMParams,
|
||
|
|
headers: dict,
|
||
|
|
after: Optional[str] = None,
|
||
|
|
before: Optional[str] = None,
|
||
|
|
include: Optional[List[str]] = None,
|
||
|
|
limit: int = 20,
|
||
|
|
order: Literal["asc", "desc"] = "desc",
|
||
|
|
) -> Tuple[str, Dict]:
|
||
|
|
url = f"{api_base}/{response_id}/input_items"
|
||
|
|
params: Dict[str, Any] = {}
|
||
|
|
if after is not None:
|
||
|
|
params["after"] = after
|
||
|
|
if before is not None:
|
||
|
|
params["before"] = before
|
||
|
|
if include:
|
||
|
|
params["include"] = ",".join(include)
|
||
|
|
if limit is not None:
|
||
|
|
params["limit"] = limit
|
||
|
|
if order is not None:
|
||
|
|
params["order"] = order
|
||
|
|
return url, params
|
||
|
|
|
||
|
|
def transform_list_input_items_response(
|
||
|
|
self,
|
||
|
|
raw_response: httpx.Response,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> Dict:
|
||
|
|
try:
|
||
|
|
return raw_response.json()
|
||
|
|
except Exception:
|
||
|
|
raise OpenAIError(
|
||
|
|
message=raw_response.text, status_code=raw_response.status_code
|
||
|
|
)
|
||
|
|
|
||
|
|
#########################################################
|
||
|
|
########## CANCEL RESPONSE API TRANSFORMATION ##########
|
||
|
|
#########################################################
|
||
|
|
def transform_cancel_response_api_request(
|
||
|
|
self,
|
||
|
|
response_id: str,
|
||
|
|
api_base: str,
|
||
|
|
litellm_params: GenericLiteLLMParams,
|
||
|
|
headers: dict,
|
||
|
|
) -> Tuple[str, Dict]:
|
||
|
|
"""
|
||
|
|
Transform the cancel response API request into a URL and data
|
||
|
|
|
||
|
|
OpenAI API expects the following request
|
||
|
|
- POST /v1/responses/{response_id}/cancel
|
||
|
|
"""
|
||
|
|
url = f"{api_base}/{response_id}/cancel"
|
||
|
|
data: Dict = {}
|
||
|
|
return url, data
|
||
|
|
|
||
|
|
def transform_cancel_response_api_response(
|
||
|
|
self,
|
||
|
|
raw_response: httpx.Response,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> ResponsesAPIResponse:
|
||
|
|
"""
|
||
|
|
Transform the cancel response API response into a ResponsesAPIResponse
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
raw_response_json = raw_response.json()
|
||
|
|
except Exception:
|
||
|
|
raise OpenAIError(
|
||
|
|
message=raw_response.text, status_code=raw_response.status_code
|
||
|
|
)
|
||
|
|
raw_response_headers = dict(raw_response.headers)
|
||
|
|
processed_headers = process_response_headers(raw_response_headers)
|
||
|
|
|
||
|
|
response = ResponsesAPIResponse(**raw_response_json)
|
||
|
|
response._hidden_params["additional_headers"] = processed_headers
|
||
|
|
response._hidden_params["headers"] = raw_response_headers
|
||
|
|
|
||
|
|
return response
|
||
|
|
|
||
|
|
#########################################################
|
||
|
|
########## COMPACT RESPONSE API TRANSFORMATION ##########
|
||
|
|
#########################################################
|
||
|
|
def transform_compact_response_api_request(
|
||
|
|
self,
|
||
|
|
model: str,
|
||
|
|
input: Union[str, ResponseInputParam],
|
||
|
|
response_api_optional_request_params: Dict,
|
||
|
|
api_base: str,
|
||
|
|
litellm_params: GenericLiteLLMParams,
|
||
|
|
headers: dict,
|
||
|
|
) -> Tuple[str, Dict]:
|
||
|
|
"""
|
||
|
|
Transform the compact response API request into a URL and data
|
||
|
|
|
||
|
|
OpenAI API expects the following request
|
||
|
|
- POST /v1/responses/compact
|
||
|
|
"""
|
||
|
|
# Preserve query params (e.g., api-version) while appending /compact.
|
||
|
|
parsed_url = httpx.URL(api_base)
|
||
|
|
compact_path = parsed_url.path.rstrip("/") + "/compact"
|
||
|
|
url = str(parsed_url.copy_with(path=compact_path))
|
||
|
|
|
||
|
|
input = self._validate_input_param(input)
|
||
|
|
data = dict(
|
||
|
|
ResponsesAPIRequestParams(
|
||
|
|
model=model, input=input, **response_api_optional_request_params
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
return url, data
|
||
|
|
|
||
|
|
def transform_compact_response_api_response(
|
||
|
|
self,
|
||
|
|
raw_response: httpx.Response,
|
||
|
|
logging_obj: LiteLLMLoggingObj,
|
||
|
|
) -> ResponsesAPIResponse:
|
||
|
|
"""
|
||
|
|
Transform the compact response API response into a ResponsesAPIResponse
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
logging_obj.post_call(
|
||
|
|
original_response=raw_response.text,
|
||
|
|
additional_args={"complete_input_dict": {}},
|
||
|
|
)
|
||
|
|
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
|
||
|
|
)
|
||
|
|
raw_response_headers = dict(raw_response.headers)
|
||
|
|
processed_headers = process_response_headers(raw_response_headers)
|
||
|
|
|
||
|
|
try:
|
||
|
|
response = ResponsesAPIResponse(**raw_response_json)
|
||
|
|
except Exception:
|
||
|
|
verbose_logger.debug(
|
||
|
|
f"Error constructing ResponsesAPIResponse: {raw_response_json}, using model_construct"
|
||
|
|
)
|
||
|
|
response = ResponsesAPIResponse.model_construct(**raw_response_json)
|
||
|
|
|
||
|
|
response._hidden_params["additional_headers"] = processed_headers
|
||
|
|
response._hidden_params["headers"] = raw_response_headers
|
||
|
|
|
||
|
|
return response
|