You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
fix: ignore empty function chunk in LiteLlm streaming response
Fixes https://github.com/google/adk-python/issues/1532 PiperOrigin-RevId: 808636127
This commit is contained in:
committed by
Copybara-Service
parent
c37bd2742c
commit
8a92fd18b6
@@ -437,10 +437,17 @@ def _model_response_to_chunk(
|
||||
for tool_call in message.get("tool_calls"):
|
||||
# aggregate tool_call
|
||||
if tool_call.type == "function":
|
||||
func_name = tool_call.function.name
|
||||
func_args = tool_call.function.arguments
|
||||
|
||||
# Ignore empty chunks that don't carry any information.
|
||||
if not func_name and not func_args:
|
||||
continue
|
||||
|
||||
yield FunctionChunk(
|
||||
id=tool_call.id,
|
||||
name=tool_call.function.name,
|
||||
args=tool_call.function.arguments,
|
||||
name=func_name,
|
||||
args=func_args,
|
||||
index=tool_call.index,
|
||||
), finish_reason
|
||||
|
||||
|
||||
@@ -267,6 +267,77 @@ MULTIPLE_FUNCTION_CALLS_STREAM = [
|
||||
]
|
||||
|
||||
|
||||
STREAM_WITH_EMPTY_CHUNK = [
|
||||
ModelResponse(
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
finish_reason=None,
|
||||
delta=Delta(
|
||||
role="assistant",
|
||||
tool_calls=[
|
||||
ChatCompletionDeltaToolCall(
|
||||
type="function",
|
||||
id="call_abc",
|
||||
function=Function(
|
||||
name="test_function",
|
||||
arguments='{"test_arg":',
|
||||
),
|
||||
index=0,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
ModelResponse(
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
finish_reason=None,
|
||||
delta=Delta(
|
||||
role="assistant",
|
||||
tool_calls=[
|
||||
ChatCompletionDeltaToolCall(
|
||||
type="function",
|
||||
id=None,
|
||||
function=Function(
|
||||
name=None,
|
||||
arguments=' "value"}',
|
||||
),
|
||||
index=0,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
# This is the problematic empty chunk that should be ignored.
|
||||
ModelResponse(
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
finish_reason=None,
|
||||
delta=Delta(
|
||||
role="assistant",
|
||||
tool_calls=[
|
||||
ChatCompletionDeltaToolCall(
|
||||
type="function",
|
||||
id=None,
|
||||
function=Function(
|
||||
name=None,
|
||||
arguments="",
|
||||
),
|
||||
index=0,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
ModelResponse(
|
||||
choices=[StreamingChoices(finish_reason="tool_calls", delta=Delta())]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_response():
|
||||
return ModelResponse(
|
||||
@@ -1591,6 +1662,34 @@ async def test_generate_content_async_non_compliant_multiple_function_calls(
|
||||
assert final_response.content.parts[1].function_call.args == {"arg": "value2"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_async_stream_with_empty_chunk(
|
||||
mock_completion, lite_llm_instance
|
||||
):
|
||||
"""Tests that empty tool call chunks in a stream are ignored."""
|
||||
mock_completion.return_value = iter(STREAM_WITH_EMPTY_CHUNK)
|
||||
|
||||
responses = [
|
||||
response
|
||||
async for response in lite_llm_instance.generate_content_async(
|
||||
LLM_REQUEST_WITH_FUNCTION_DECLARATION, stream=True
|
||||
)
|
||||
]
|
||||
|
||||
assert len(responses) == 1
|
||||
final_response = responses[0]
|
||||
assert final_response.content.role == "model"
|
||||
|
||||
# Crucially, assert that only ONE tool call was generated,
|
||||
# proving the empty chunk was ignored.
|
||||
assert len(final_response.content.parts) == 1
|
||||
|
||||
function_call = final_response.content.parts[0].function_call
|
||||
assert function_call.name == "test_function"
|
||||
assert function_call.id == "call_abc"
|
||||
assert function_call.args == {"test_arg": "value"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
def test_get_completion_inputs_generation_params():
|
||||
# Test that generation_params are extracted and mapped correctly
|
||||
|
||||
Reference in New Issue
Block a user