mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
203 lines
6.6 KiB
Python
203 lines
6.6 KiB
Python
# Copyright 2025 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
from google.adk.utils import streaming_utils
|
|
from google.genai import types
|
|
import pytest
|
|
|
|
|
|
class TestStreamingResponseAggregator:
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_response_with_text(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
response = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(parts=[types.Part(text="Hello")])
|
|
)
|
|
]
|
|
)
|
|
results = []
|
|
async for r in aggregator.process_response(response):
|
|
results.append(r)
|
|
assert len(results) == 1
|
|
assert results[0].content.parts[0].text == "Hello"
|
|
assert results[0].partial
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_response_with_thought(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
response = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(
|
|
parts=[types.Part(text="Thinking...", thought=True)]
|
|
)
|
|
)
|
|
]
|
|
)
|
|
results = []
|
|
async for r in aggregator.process_response(response):
|
|
results.append(r)
|
|
assert len(results) == 1
|
|
assert results[0].content.parts[0].text == "Thinking..."
|
|
assert results[0].content.parts[0].thought
|
|
assert results[0].partial
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_response_multiple(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
response1 = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(parts=[types.Part(text="Hello ")])
|
|
)
|
|
]
|
|
)
|
|
response2 = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(parts=[types.Part(text="World!")])
|
|
)
|
|
]
|
|
)
|
|
async for _ in aggregator.process_response(response1):
|
|
pass
|
|
results = []
|
|
async for r in aggregator.process_response(response2):
|
|
results.append(r)
|
|
assert len(results) == 1
|
|
assert results[0].content.parts[0].text == "World!"
|
|
|
|
closed_response = aggregator.close()
|
|
assert closed_response is not None
|
|
assert closed_response.content.parts[0].text == "Hello World!"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_response_interleaved_thought_and_text(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
response1 = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(
|
|
parts=[types.Part(text="I am thinking...", thought=True)]
|
|
)
|
|
)
|
|
]
|
|
)
|
|
response2 = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(
|
|
parts=[types.Part(text="Okay, I have a result.")]
|
|
)
|
|
)
|
|
]
|
|
)
|
|
response3 = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(
|
|
parts=[types.Part(text=" The result is 42.")]
|
|
)
|
|
)
|
|
]
|
|
)
|
|
|
|
async for _ in aggregator.process_response(response1):
|
|
pass
|
|
async for _ in aggregator.process_response(response2):
|
|
pass
|
|
async for _ in aggregator.process_response(response3):
|
|
pass
|
|
|
|
closed_response = aggregator.close()
|
|
assert closed_response is not None
|
|
assert len(closed_response.content.parts) == 2
|
|
assert closed_response.content.parts[0].text == "I am thinking..."
|
|
assert closed_response.content.parts[0].thought
|
|
assert (
|
|
closed_response.content.parts[1].text
|
|
== "Okay, I have a result. The result is 42."
|
|
)
|
|
assert not closed_response.content.parts[1].thought
|
|
|
|
def test_close_with_no_responses(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
closed_response = aggregator.close()
|
|
assert closed_response is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_close_with_finish_reason(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
response = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(parts=[types.Part(text="Hello")]),
|
|
finish_reason=types.FinishReason.STOP,
|
|
)
|
|
]
|
|
)
|
|
async for _ in aggregator.process_response(response):
|
|
pass
|
|
closed_response = aggregator.close()
|
|
assert closed_response is not None
|
|
assert closed_response.content.parts[0].text == "Hello"
|
|
assert closed_response.error_code is None
|
|
assert closed_response.error_message is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_close_with_error(self):
|
|
aggregator = streaming_utils.StreamingResponseAggregator()
|
|
response = types.GenerateContentResponse(
|
|
candidates=[
|
|
types.Candidate(
|
|
content=types.Content(parts=[types.Part(text="Error")]),
|
|
finish_reason=types.FinishReason.RECITATION,
|
|
finish_message="Recitation error",
|
|
)
|
|
]
|
|
)
|
|
async for _ in aggregator.process_response(response):
|
|
pass
|
|
closed_response = aggregator.close()
|
|
assert closed_response is not None
|
|
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
|