mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-06 07:48:06 +00:00
Merge 28c7c2401c into b91fc92b6d
This commit is contained in:
commit
d405480cb5
2 changed files with 84 additions and 4 deletions
|
|
@ -3983,12 +3983,16 @@ class ModelResponseIterator:
|
|||
|
||||
# Also handle the case where the final chunk has empty
|
||||
# content (e.g. text:"") WITH finishReason. In this case
|
||||
# _process_candidates DOES create a choice, but maps
|
||||
# finishReason="STOP" to "stop" because the current chunk
|
||||
# has no tool_calls. Override if we saw tool_calls earlier.
|
||||
# _process_candidates DOES create a choice, but maps the reason
|
||||
# from what this chunk alone shows, which is never a call. Override
|
||||
# if we saw tool_calls earlier.
|
||||
# "length" is a normal termination for a call too: Gemini reports
|
||||
# MAX_TOKENS when the reply with the call ran out of budget, and the
|
||||
# non-streaming path reports "tool_calls" for that same reply. A
|
||||
# filtered reply keeps its reason, matching the non-streaming path.
|
||||
if self.has_seen_tool_calls:
|
||||
for choice in model_response.choices:
|
||||
if choice.finish_reason == "stop":
|
||||
if choice.finish_reason in ("stop", "length"):
|
||||
choice.finish_reason = "tool_calls"
|
||||
|
||||
VertexGeminiConfig.set_stream_metadata_on_response(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ Ref: https://github.com/BerriAI/litellm/issues/21041
|
|||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
|
||||
ModelResponseIterator,
|
||||
)
|
||||
|
|
@ -338,3 +340,77 @@ def test_streaming_metadata_only_chunk_does_not_yield_empty_choices():
|
|||
assert len(response.choices) == 1
|
||||
assert response.choices[0].finish_reason is None
|
||||
assert response.choices[0].delta.content is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"finish_reason, expected",
|
||||
[
|
||||
("STOP", "tool_calls"),
|
||||
("MAX_TOKENS", "tool_calls"),
|
||||
("SAFETY", "content_filter"),
|
||||
],
|
||||
)
|
||||
def test_streaming_tool_call_finish_reason_matches_non_streaming(finish_reason: str, expected: str):
|
||||
"""The same reply must report the same reason streamed and not streamed.
|
||||
|
||||
Gemini sends the call in one chunk and the reason in the next. Only "stop"
|
||||
was rewritten to "tool_calls", so a reply with a call that ran into
|
||||
MAX_TOKENS reported "length" while the non-streaming path reports
|
||||
"tool_calls" for it. A filtered reply keeps "content_filter" on both paths,
|
||||
so that one is pinned too -- the rewrite must not swallow it.
|
||||
"""
|
||||
logging_obj = _make_logging_obj()
|
||||
iterator = ModelResponseIterator(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
iterator.chunk_parser(
|
||||
{
|
||||
"candidates": [
|
||||
{
|
||||
"content": {
|
||||
"parts": [{"functionCall": {"name": "get_weather", "args": {}}}],
|
||||
"role": "model",
|
||||
},
|
||||
"index": 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
final = iterator.chunk_parser({"candidates": [{"finishReason": finish_reason, "index": 0}]})
|
||||
|
||||
assert final is not None
|
||||
assert final.choices[0].finish_reason == expected
|
||||
|
||||
|
||||
def test_streaming_tool_call_and_max_tokens_in_one_chunk():
|
||||
"""The other chunk shape: the call and the reason arrive together."""
|
||||
logging_obj = _make_logging_obj()
|
||||
iterator = ModelResponseIterator(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
response = iterator.chunk_parser(
|
||||
{
|
||||
"candidates": [
|
||||
{
|
||||
"content": {
|
||||
"parts": [
|
||||
{"functionCall": {"name": "get_weather", "args": {}}},
|
||||
{"text": ""},
|
||||
],
|
||||
"role": "model",
|
||||
},
|
||||
"finishReason": "MAX_TOKENS",
|
||||
"index": 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
assert response is not None
|
||||
assert response.choices[0].finish_reason == "tool_calls"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue