mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge 0758ebba97 into 72228db5c6
This commit is contained in:
commit
28b2cf8cf5
3 changed files with 93 additions and 1 deletions
|
|
@ -1067,6 +1067,7 @@ class LLMCachingHandler:
|
|||
cached_result = convert_to_model_response_object(
|
||||
response_object=cached_result,
|
||||
model_response_object=ModelResponse(),
|
||||
_preserve_finish_reason=True,
|
||||
)
|
||||
if (
|
||||
call_type == CallTypes.atext_completion.value or call_type == CallTypes.text_completion.value
|
||||
|
|
@ -1140,6 +1141,7 @@ class LLMCachingHandler:
|
|||
cached_result = convert_to_model_response_object(
|
||||
response_object=cached_result,
|
||||
model_response_object=ModelResponse(),
|
||||
_preserve_finish_reason=True,
|
||||
)
|
||||
else:
|
||||
from litellm.responses.streaming_iterator import (
|
||||
|
|
|
|||
|
|
@ -747,6 +747,7 @@ def convert_to_model_response_object(
|
|||
hidden_params: dict | None = None,
|
||||
_response_headers: dict | None = None,
|
||||
convert_tool_call_to_json_mode: bool | None = None, # used for supporting 'json_schema' on older models
|
||||
_preserve_finish_reason: bool = False, # cached responses already have their final finish_reason
|
||||
):
|
||||
additional_headers = get_response_headers(_response_headers)
|
||||
|
||||
|
|
@ -888,7 +889,12 @@ def convert_to_model_response_object(
|
|||
if finish_reason is None:
|
||||
# gpt-4 vision can return 'finish_reason' or 'finish_details'
|
||||
finish_reason = choice.get("finish_details") or "stop"
|
||||
if finish_reason == "stop" and message.tool_calls and len(message.tool_calls) > 0:
|
||||
if (
|
||||
not _preserve_finish_reason
|
||||
and finish_reason == "stop"
|
||||
and message.tool_calls
|
||||
and len(message.tool_calls) > 0
|
||||
):
|
||||
finish_reason = "tool_calls"
|
||||
|
||||
## PROVIDER SPECIFIC FIELDS ##
|
||||
|
|
|
|||
|
|
@ -2052,6 +2052,90 @@ def test_convert_cached_responses_bridge_chat_completion_nonstream():
|
|||
assert result.choices[0].message.content == "Hi!"
|
||||
|
||||
|
||||
def test_convert_cached_completion_preserves_stored_finish_reason():
|
||||
"""A cache hit must replay the stored finish_reason, not re-derive it from tool_calls."""
|
||||
caching_handler = LLMCachingHandler(
|
||||
original_function=completion, request_kwargs={}, start_time=datetime.now()
|
||||
)
|
||||
cached_result = {
|
||||
"id": "chatcmpl-cache-finish-reason",
|
||||
"object": "chat.completion",
|
||||
"created": int(time.time()),
|
||||
"model": "claude-sonnet-4-5",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "toolu_1",
|
||||
"type": "function",
|
||||
"function": {"name": "get_weather", "arguments": "{}"},
|
||||
}
|
||||
],
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
}
|
||||
|
||||
result = caching_handler._convert_cached_result_to_model_response(
|
||||
cached_result=cached_result,
|
||||
call_type=CallTypes.completion.value,
|
||||
kwargs={
|
||||
"model": "claude-sonnet-4-5",
|
||||
"stream": False,
|
||||
"messages": [{"role": "user", "content": "weather?"}],
|
||||
},
|
||||
logging_obj=_build_logging_obj(CallTypes.completion.value, stream=False),
|
||||
model="claude-sonnet-4-5",
|
||||
args=(),
|
||||
)
|
||||
|
||||
assert isinstance(result, ModelResponse)
|
||||
assert result.choices[0].message.tool_calls is not None
|
||||
assert result.choices[0].finish_reason == "stop"
|
||||
|
||||
|
||||
def test_convert_raw_completion_still_derives_finish_reason_from_tool_calls():
|
||||
"""Provider payloads that say 'stop' but carry tool calls still map to 'tool_calls'."""
|
||||
from litellm.utils import convert_to_model_response_object
|
||||
|
||||
result = convert_to_model_response_object(
|
||||
response_object={
|
||||
"id": "chatcmpl-raw-finish-reason",
|
||||
"object": "chat.completion",
|
||||
"created": int(time.time()),
|
||||
"model": "claude-sonnet-4-5",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "toolu_1",
|
||||
"type": "function",
|
||||
"function": {"name": "get_weather", "arguments": "{}"},
|
||||
}
|
||||
],
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
},
|
||||
model_response_object=ModelResponse(),
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.choices[0].finish_reason == "tool_calls"
|
||||
|
||||
|
||||
def test_convert_cached_responses_legacy_nonstream_path():
|
||||
"""Genuine ResponsesAPIResponse dict (no chatcmpl/choices) falls through legacy path."""
|
||||
from litellm import responses
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue