fix(responses): stop forwarding an omitted temperature as 0 and move tests into the mapped file

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Tom Ron 2026-10-02 13:00:17 +03:00
parent 4cbf25fb09
commit 40e87a2a5f
3 changed files with 116 additions and 115 deletions

View file

@ -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,

View file

@ -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

View file

@ -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