diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 11dc0c94756..5299fcf9a2a 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -448,7 +448,7 @@ class LiteLLMCompletionResponsesConfig: "tools": tools, "top_p": responses_api_request.get("top_p"), "user": responses_api_request.get("user"), - "temperature": responses_api_request.get("temperature", 0), + "temperature": responses_api_request.get("temperature"), "parallel_tool_calls": responses_api_request.get("parallel_tool_calls"), "max_tokens": responses_api_request.get("max_output_tokens"), "stream": stream, diff --git a/tests/unit/responses/litellm_completion_transformation/test_incomplete_status_transformation.py b/tests/unit/responses/litellm_completion_transformation/test_incomplete_status_transformation.py deleted file mode 100644 index 3410a56c42e..00000000000 --- a/tests/unit/responses/litellm_completion_transformation/test_incomplete_status_transformation.py +++ /dev/null @@ -1,114 +0,0 @@ -import asyncio - -from unittest.mock import AsyncMock, MagicMock - -import pytest - -from litellm.responses.litellm_completion_transformation.streaming_iterator import ( - LiteLLMCompletionStreamingIterator, -) -from litellm.responses.litellm_completion_transformation.transformation import ( - LiteLLMCompletionResponsesConfig, -) -from litellm.responses.streaming_iterator import ( - CachedResponsesAPIStreamingIterator, - build_synthetic_response_events, -) -from litellm.types.llms.openai import ResponsesAPIStreamEvents -from litellm.types.utils import Choices, Message, ModelResponse, Usage - - -def _chat_response(finish_reason: str) -> ModelResponse: - choice = Choices(index=0, finish_reason="stop", message=Message(role="assistant", content="cut off")) - choice.finish_reason = finish_reason - return ModelResponse( - id="chatcmpl-1", - model="some-model", - choices=[choice], - usage=Usage(prompt_tokens=10, completion_tokens=60, total_tokens=70), - ) - - -def _transform(finish_reason: str, request: dict): - return LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response( - request_input="hi", - responses_api_request=request, - chat_completion_response=_chat_response(finish_reason), - ) - - -@pytest.mark.parametrize( - "finish_reason, reason", - [("length", "max_output_tokens"), ("content_filter", "content_filter")], -) -def test_truncated_finish_reason_sets_incomplete_details(finish_reason, reason): - result = _transform(finish_reason, {}) - assert result.status == "incomplete" - assert result.incomplete_details is not None - assert result.incomplete_details.reason == reason - - -def test_stop_finish_reason_has_no_incomplete_details(): - result = _transform("stop", {}) - assert result.status == "completed" - assert result.incomplete_details is None - - -def test_request_sampling_params_are_echoed(): - result = _transform("stop", {"temperature": 0.3, "top_p": 0.9, "max_output_tokens": 60}) - assert (result.temperature, result.top_p, result.max_output_tokens) == (0.3, 0.9, 60) - - -@pytest.mark.parametrize( - "finish_reason, event_type", - [ - ("length", ResponsesAPIStreamEvents.RESPONSE_INCOMPLETE), - ("stop", ResponsesAPIStreamEvents.RESPONSE_COMPLETED), - ], -) -def test_stream_terminal_event_follows_status(finish_reason, event_type): - iterator = LiteLLMCompletionStreamingIterator( - model="some-model", - litellm_custom_stream_wrapper=AsyncMock(), - request_input="hi", - responses_api_request={}, - ) - event = iterator._emit_response_completed_event(_chat_response(finish_reason)) - assert event is not None - assert event.type == event_type - - -@pytest.mark.parametrize( - "finish_reason, event_type", - [ - ("length", ResponsesAPIStreamEvents.RESPONSE_INCOMPLETE), - ("stop", ResponsesAPIStreamEvents.RESPONSE_COMPLETED), - ], -) -def test_replayed_stream_terminal_event_follows_status(finish_reason, event_type): - events = build_synthetic_response_events( - transformed=_transform(finish_reason, {}), - logging_obj=None, - chunk_size=10, - ) - assert events[-1].type == event_type - - -def test_omitted_temperature_defaults_to_zero(): - assert _transform("stop", {}).temperature == 0 - - -@pytest.mark.asyncio -@pytest.mark.parametrize("finish_reason", ["length", "stop"]) -async def test_replayed_stream_logs_success_exactly_once(finish_reason): - logging_obj = MagicMock() - logging_obj._on_deferred_stream_complete = None - logging_obj.dispatch_success_handlers = AsyncMock() - iterator = CachedResponsesAPIStreamingIterator( - response=_transform(finish_reason, {}), - logging_obj=logging_obj, - ) - async for _ in iterator: - pass - await asyncio.sleep(0) - assert logging_obj.dispatch_success_handlers.await_count == 1 diff --git a/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py index 7b9de4644b4..1a55176e64e 100644 --- a/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -1,5 +1,7 @@ +import asyncio import json from copy import deepcopy +from unittest.mock import AsyncMock, MagicMock from typing import Final, Literal import pytest @@ -17,6 +19,14 @@ from litellm.responses.litellm_completion_transformation.transformation import ( TOOL_CALLS_CACHE, LiteLLMCompletionResponsesConfig, ) +from litellm.responses.litellm_completion_transformation.streaming_iterator import ( + LiteLLMCompletionStreamingIterator, +) +from litellm.responses.streaming_iterator import ( + CachedResponsesAPIStreamingIterator, + build_synthetic_response_events, +) +from litellm.types.llms.openai import ResponsesAPIStreamEvents from litellm.types.responses.main import build_web_search_call from litellm.types.utils import ( ChatCompletionMessageToolCall, @@ -5172,3 +5182,108 @@ async def test_bridge_rejects_untranslatable_tool_choice_with_a_400(stream: bool ) assert exc_info.value.status_code == 400 assert "tool_choice={'type': 'file_search'}" in str(exc_info.value) + + +def _chat_response(finish_reason: str) -> ModelResponse: + choice = Choices(index=0, finish_reason="stop", message=Message(role="assistant", content="cut off")) + choice.finish_reason = finish_reason + return ModelResponse( + id="chatcmpl-1", + model="some-model", + choices=[choice], + usage=Usage(prompt_tokens=10, completion_tokens=60, total_tokens=70), + ) + + +def _transform(finish_reason: str, request: dict): + return LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response( + request_input="hi", + responses_api_request=request, + chat_completion_response=_chat_response(finish_reason), + ) + + +@pytest.mark.parametrize( + "finish_reason, reason", + [("length", "max_output_tokens"), ("content_filter", "content_filter")], +) +def test_truncated_finish_reason_sets_incomplete_details(finish_reason, reason): + result = _transform(finish_reason, {}) + assert result.status == "incomplete" + assert result.incomplete_details is not None + assert result.incomplete_details.reason == reason + + +def test_stop_finish_reason_has_no_incomplete_details(): + result = _transform("stop", {}) + assert result.status == "completed" + assert result.incomplete_details is None + + +def test_request_sampling_params_are_echoed(): + result = _transform("stop", {"temperature": 0.3, "top_p": 0.9, "max_output_tokens": 60}) + assert (result.temperature, result.top_p, result.max_output_tokens) == (0.3, 0.9, 60) + + +@pytest.mark.parametrize( + "finish_reason, event_type", + [ + ("length", ResponsesAPIStreamEvents.RESPONSE_INCOMPLETE), + ("stop", ResponsesAPIStreamEvents.RESPONSE_COMPLETED), + ], +) +def test_stream_terminal_event_follows_status(finish_reason, event_type): + iterator = LiteLLMCompletionStreamingIterator( + model="some-model", + litellm_custom_stream_wrapper=AsyncMock(), + request_input="hi", + responses_api_request={}, + ) + event = iterator._emit_response_completed_event(_chat_response(finish_reason)) + assert event is not None + assert event.type == event_type + + +@pytest.mark.parametrize( + "finish_reason, event_type", + [ + ("length", ResponsesAPIStreamEvents.RESPONSE_INCOMPLETE), + ("stop", ResponsesAPIStreamEvents.RESPONSE_COMPLETED), + ], +) +def test_replayed_stream_terminal_event_follows_status(finish_reason, event_type): + events = build_synthetic_response_events( + transformed=_transform(finish_reason, {}), + logging_obj=None, + chunk_size=10, + ) + assert events[-1].type == event_type + + +def test_omitted_temperature_defaults_to_zero(): + assert _transform("stop", {}).temperature == 0 + + +@pytest.mark.asyncio +@pytest.mark.parametrize("finish_reason", ["length", "stop"]) +async def test_replayed_stream_logs_success_exactly_once(finish_reason): + logging_obj = MagicMock() + logging_obj._on_deferred_stream_complete = None + logging_obj.dispatch_success_handlers = AsyncMock() + iterator = CachedResponsesAPIStreamingIterator( + response=_transform(finish_reason, {}), + logging_obj=logging_obj, + ) + async for _ in iterator: + pass + await asyncio.sleep(0) + assert logging_obj.dispatch_success_handlers.await_count == 1 + + +def test_omitted_temperature_is_not_forwarded_to_the_chat_request(): + result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( + model="some-model", + input="hi", + responses_api_request={}, + ) + assert result.get("temperature") is None