mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
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:
parent
4cbf25fb09
commit
40e87a2a5f
3 changed files with 116 additions and 115 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue