mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
fix: Don't set error_code and error_message when model stop response in normal case
PiperOrigin-RevId: 783415488
This commit is contained in:
committed by
Copybara-Service
parent
ce037ec888
commit
3c6e18fd1c
@@ -27,6 +27,7 @@ from typing import Union
|
||||
|
||||
from google.genai import Client
|
||||
from google.genai import types
|
||||
from google.genai.types import FinishReason
|
||||
from typing_extensions import override
|
||||
|
||||
from .. import version
|
||||
@@ -161,8 +162,12 @@ class Gemini(BaseLlm):
|
||||
parts.append(types.Part.from_text(text=text))
|
||||
yield LlmResponse(
|
||||
content=types.ModelContent(parts=parts),
|
||||
error_code=response.candidates[0].finish_reason,
|
||||
error_message=response.candidates[0].finish_message,
|
||||
error_code=None
|
||||
if response.candidates[0].finish_reason == FinishReason.STOP
|
||||
else response.candidates[0].finish_reason,
|
||||
error_message=None
|
||||
if response.candidates[0].finish_reason == FinishReason.STOP
|
||||
else response.candidates[0].finish_message,
|
||||
usage_metadata=usage_metadata,
|
||||
)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from google.adk.utils.variant_utils import GoogleLLMVariant
|
||||
from google.genai import types
|
||||
from google.genai import version as genai_version
|
||||
from google.genai.types import Content
|
||||
from google.genai.types import FinishReason
|
||||
from google.genai.types import Part
|
||||
import pytest
|
||||
|
||||
@@ -754,6 +755,7 @@ async def test_generate_content_async_stream_aggregated_content_regardless_of_fi
|
||||
# Final response should have aggregated content with error info
|
||||
final_response = responses[2]
|
||||
assert final_response.content.parts[0].text == "Hello world"
|
||||
# After the code changes, error_code and error_message are set for non-STOP finish reasons
|
||||
assert final_response.error_code == finish_reason
|
||||
assert final_response.error_message == f"Finished with {finish_reason}"
|
||||
|
||||
@@ -835,6 +837,163 @@ async def test_generate_content_async_stream_with_thought_and_text_error_handlin
|
||||
assert final_response.content.parts[0].text == "Think1"
|
||||
assert final_response.content.parts[0].thought is True
|
||||
assert final_response.content.parts[1].text == "Answer"
|
||||
# After the code changes, error_code and error_message are set for non-STOP finish reasons
|
||||
assert final_response.error_code == types.FinishReason.MAX_TOKENS
|
||||
assert final_response.error_message == "Maximum tokens reached"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_async_stream_error_info_none_for_stop_finish_reason():
|
||||
"""Test that error_code and error_message are None when finish_reason is STOP."""
|
||||
gemini_llm = Gemini(model="gemini-1.5-flash")
|
||||
llm_request = LlmRequest(
|
||||
model="gemini-1.5-flash",
|
||||
contents=[Content(role="user", parts=[Part.from_text(text="Hello")])],
|
||||
config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
response_modalities=[types.Modality.TEXT],
|
||||
system_instruction="You are a helpful assistant",
|
||||
),
|
||||
)
|
||||
|
||||
with mock.patch.object(gemini_llm, "api_client") as mock_client:
|
||||
|
||||
class MockAsyncIterator:
|
||||
|
||||
def __init__(self, seq):
|
||||
self.iter = iter(seq)
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
try:
|
||||
return next(self.iter)
|
||||
except StopIteration:
|
||||
raise StopAsyncIteration
|
||||
|
||||
mock_responses = [
|
||||
types.GenerateContentResponse(
|
||||
candidates=[
|
||||
types.Candidate(
|
||||
content=Content(
|
||||
role="model", parts=[Part.from_text(text="Hello")]
|
||||
),
|
||||
finish_reason=None,
|
||||
)
|
||||
]
|
||||
),
|
||||
types.GenerateContentResponse(
|
||||
candidates=[
|
||||
types.Candidate(
|
||||
content=Content(
|
||||
role="model", parts=[Part.from_text(text=" world")]
|
||||
),
|
||||
finish_reason=types.FinishReason.STOP,
|
||||
finish_message="Successfully completed",
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
async def mock_coro():
|
||||
return MockAsyncIterator(mock_responses)
|
||||
|
||||
mock_client.aio.models.generate_content_stream.return_value = mock_coro()
|
||||
|
||||
responses = [
|
||||
resp
|
||||
async for resp in gemini_llm.generate_content_async(
|
||||
llm_request, stream=True
|
||||
)
|
||||
]
|
||||
|
||||
# Should have 3 responses: 2 partial and 1 final aggregated
|
||||
assert len(responses) == 3
|
||||
assert responses[0].partial is True
|
||||
assert responses[1].partial is True
|
||||
|
||||
# Final response should have aggregated content with error info None for STOP finish reason
|
||||
final_response = responses[2]
|
||||
assert final_response.content.parts[0].text == "Hello world"
|
||||
assert final_response.error_code is None
|
||||
assert final_response.error_message is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_async_stream_error_info_set_for_non_stop_finish_reason():
|
||||
"""Test that error_code and error_message are set for non-STOP finish reasons."""
|
||||
gemini_llm = Gemini(model="gemini-1.5-flash")
|
||||
llm_request = LlmRequest(
|
||||
model="gemini-1.5-flash",
|
||||
contents=[Content(role="user", parts=[Part.from_text(text="Hello")])],
|
||||
config=types.GenerateContentConfig(
|
||||
temperature=0.1,
|
||||
response_modalities=[types.Modality.TEXT],
|
||||
system_instruction="You are a helpful assistant",
|
||||
),
|
||||
)
|
||||
|
||||
with mock.patch.object(gemini_llm, "api_client") as mock_client:
|
||||
|
||||
class MockAsyncIterator:
|
||||
|
||||
def __init__(self, seq):
|
||||
self.iter = iter(seq)
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
try:
|
||||
return next(self.iter)
|
||||
except StopIteration:
|
||||
raise StopAsyncIteration
|
||||
|
||||
mock_responses = [
|
||||
types.GenerateContentResponse(
|
||||
candidates=[
|
||||
types.Candidate(
|
||||
content=Content(
|
||||
role="model", parts=[Part.from_text(text="Hello")]
|
||||
),
|
||||
finish_reason=None,
|
||||
)
|
||||
]
|
||||
),
|
||||
types.GenerateContentResponse(
|
||||
candidates=[
|
||||
types.Candidate(
|
||||
content=Content(
|
||||
role="model", parts=[Part.from_text(text=" world")]
|
||||
),
|
||||
finish_reason=types.FinishReason.MAX_TOKENS,
|
||||
finish_message="Maximum tokens reached",
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
async def mock_coro():
|
||||
return MockAsyncIterator(mock_responses)
|
||||
|
||||
mock_client.aio.models.generate_content_stream.return_value = mock_coro()
|
||||
|
||||
responses = [
|
||||
resp
|
||||
async for resp in gemini_llm.generate_content_async(
|
||||
llm_request, stream=True
|
||||
)
|
||||
]
|
||||
|
||||
# Should have 3 responses: 2 partial and 1 final aggregated
|
||||
assert len(responses) == 3
|
||||
assert responses[0].partial is True
|
||||
assert responses[1].partial is True
|
||||
|
||||
# Final response should have aggregated content with error info set for non-STOP finish reason
|
||||
final_response = responses[2]
|
||||
assert final_response.content.parts[0].text == "Hello world"
|
||||
assert final_response.error_code == types.FinishReason.MAX_TOKENS
|
||||
assert final_response.error_message == "Maximum tokens reached"
|
||||
|
||||
@@ -1023,7 +1182,9 @@ async def test_generate_content_async_stream_mixed_text_function_call_text():
|
||||
|
||||
# Final aggregated text with error info
|
||||
assert responses[4].content.parts[0].text == " second text"
|
||||
assert responses[4].error_code == types.FinishReason.STOP
|
||||
assert (
|
||||
responses[4].error_code is None
|
||||
) # STOP finish reason should have None error_code
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1220,7 +1381,9 @@ async def test_generate_content_async_stream_complex_mixed_thought_text_function
|
||||
|
||||
# Final aggregated response should have both thought and text
|
||||
final_response = responses[-1]
|
||||
assert final_response.error_code == types.FinishReason.STOP
|
||||
assert (
|
||||
final_response.error_code is None
|
||||
) # STOP finish reason should have None error_code
|
||||
assert len(final_response.content.parts) == 2 # thought part + text part
|
||||
assert final_response.content.parts[0].thought is True
|
||||
assert "More thinking..." in final_response.content.parts[0].text
|
||||
@@ -1356,7 +1519,9 @@ async def test_generate_content_async_stream_two_separate_text_aggregations():
|
||||
# Final aggregation should contain "Second chunk" and have error info
|
||||
final_aggregation = aggregated_text_responses[-1]
|
||||
assert final_aggregation.content.parts[0].text == "Second chunk"
|
||||
assert final_aggregation.error_code == types.FinishReason.STOP
|
||||
assert (
|
||||
final_aggregation.error_code is None
|
||||
) # STOP finish reason should have None error_code
|
||||
|
||||
# Verify the function call is preserved between aggregations
|
||||
function_call_responses = [
|
||||
|
||||
Reference in New Issue
Block a user