chore: Do not treat FinishReason.STOP as error case for LLM responses containing candidates with empty contents

Fixes #2905

PiperOrigin-RevId: 825263302
This commit is contained in:
Ayush Agrawal
2025-10-28 16:53:30 -07:00
committed by Copybara-Service
parent 8eeff35b35
commit 2f72ceb49b
3 changed files with 41 additions and 1 deletions
+3 -1
View File
@@ -148,7 +148,9 @@ class LlmResponse(BaseModel):
usage_metadata = generate_content_response.usage_metadata
if generate_content_response.candidates:
candidate = generate_content_response.candidates[0]
if candidate.content and candidate.content.parts:
if (
candidate.content and candidate.content.parts
) or candidate.finish_reason == types.FinishReason.STOP:
return LlmResponse(
content=candidate.content,
grounding_metadata=candidate.grounding_metadata,
@@ -317,3 +317,20 @@ def test_llm_response_create_error_case_with_citation_metadata():
assert (
response.error_message == 'Response blocked due to recitation triggered'
)
def test_llm_response_create_empty_content_with_stop_reason():
"""Test LlmResponse.create() with empty content and stop finish reason."""
generate_content_response = types.GenerateContentResponse(
candidates=[
types.Candidate(
content=types.Content(parts=[]),
finish_reason=types.FinishReason.STOP,
)
]
)
response = LlmResponse.create(generate_content_response)
assert response.error_code is None
assert response.content is not None
@@ -179,3 +179,24 @@ class TestStreamingResponseAggregator:
assert closed_response.content.parts[0].text == "Error"
assert closed_response.error_code == types.FinishReason.RECITATION
assert closed_response.error_message == "Recitation error"
@pytest.mark.asyncio
async def test_process_response_with_none_content(self):
"""Test that StreamingResponseAggregator handles content=None."""
aggregator = streaming_utils.StreamingResponseAggregator()
response = types.GenerateContentResponse(
candidates=[
types.Candidate(
content=types.Content(parts=[]),
finish_reason=types.FinishReason.STOP,
)
]
)
results = []
async for r in aggregator.process_response(response):
results.append(r)
assert len(results) == 1
assert results[0].content is not None
closed_response = aggregator.close()
assert closed_response is None