1-
21# Copyright 2026 Google LLC
32#
43# Licensed under the Apache License, Version 2.0 (the "License");
2524
2625from google .adk .models import interactions_utils
2726from google .adk .models .llm_request import LlmRequest
28- from google .genai import types
2927from google .genai import interactions
28+ from google .genai import types
3029from google .genai .interactions import CodeExecutionResultStep
3130from google .genai .interactions import FunctionCallStep
3231from google .genai .interactions import FunctionResultStep
32+ from google .genai .interactions import ImageContent
3333from google .genai .interactions import Interaction
3434from google .genai .interactions import InteractionCompletedEvent
3535from google .genai .interactions import InteractionCreatedEvent
3939from google .genai .interactions import StepStart
4040from google .genai .interactions import StepStop
4141from google .genai .interactions import TextContent
42- from google .genai .interactions import ImageContent
4342from google .genai .interactions import ThoughtStep
4443from google .genai .interactions import Usage
4544import pytest
@@ -203,13 +202,21 @@ def test_text_part(self):
203202 """Test converting a text Part."""
204203 part = types .Part (text = 'Hello, world!' )
205204 result = interactions_utils ._convert_part_to_interaction_content (part )
206- assert result == {'type' : 'user_input' , 'content' : [{'type' : 'text' , 'text' : 'Hello, world!' }]}
205+ assert result == {
206+ 'type' : 'user_input' ,
207+ 'content' : [{'type' : 'text' , 'text' : 'Hello, world!' }],
208+ }
207209
208210 def test_text_part_model_role (self ):
209211 """Test converting a text Part for model role."""
210212 part = types .Part (text = 'Hello, user!' )
211- result = interactions_utils ._convert_part_to_interaction_content (part , role = 'model' )
212- assert result == {'type' : 'model_output' , 'content' : [{'type' : 'text' , 'text' : 'Hello, user!' }]}
213+ result = interactions_utils ._convert_part_to_interaction_content (
214+ part , role = 'model'
215+ )
216+ assert result == {
217+ 'type' : 'model_output' ,
218+ 'content' : [{'type' : 'text' , 'text' : 'Hello, user!' }],
219+ }
213220
214221 def test_function_call_part (self ):
215222 """Test converting a function call Part."""
@@ -363,11 +370,17 @@ def test_inline_data_image(self):
363370 )
364371 )
365372 result = interactions_utils ._convert_part_to_interaction_content (part )
366- assert result == {'type' : 'user_input' , 'content' : [{
367- 'type' : 'image' ,
368- 'data' : 'aW1hZ2VfZGF0YQ==' , # base64.b64encode(b'image_data').decode('utf-8')
369- 'mime_type' : 'image/png' ,
370- }]}
373+ assert result == {
374+ 'type' : 'user_input' ,
375+ 'content' : [{
376+ 'type' : 'image' ,
377+ 'data' : (
378+ 'aW1hZ2VfZGF0YQ=='
379+ ), # base64.b64encode(b'image_data').decode('utf-8')
380+ 'mime_type' : 'image/png' ,
381+ }],
382+ }
383+
371384 def test_inline_data_audio (self ):
372385 """Test converting an inline audio Part."""
373386 part = types .Part (
@@ -377,11 +390,17 @@ def test_inline_data_audio(self):
377390 )
378391 )
379392 result = interactions_utils ._convert_part_to_interaction_content (part )
380- assert result == {'type' : 'user_input' , 'content' : [{
381- 'type' : 'audio' ,
382- 'data' : 'YXVkaW9fZGF0YQ==' , # base64.b64encode(b'audio_data').decode('utf-8')
383- 'mime_type' : 'audio/mp3' ,
384- }]}
393+ assert result == {
394+ 'type' : 'user_input' ,
395+ 'content' : [{
396+ 'type' : 'audio' ,
397+ 'data' : (
398+ 'YXVkaW9fZGF0YQ=='
399+ ), # base64.b64encode(b'audio_data').decode('utf-8')
400+ 'mime_type' : 'audio/mp3' ,
401+ }],
402+ }
403+
385404 def test_inline_data_video (self ):
386405 """Test converting an inline video Part."""
387406 part = types .Part (
@@ -391,11 +410,17 @@ def test_inline_data_video(self):
391410 )
392411 )
393412 result = interactions_utils ._convert_part_to_interaction_content (part )
394- assert result == {'type' : 'user_input' , 'content' : [{
395- 'type' : 'video' ,
396- 'data' : 'dmlkZW9fZGF0YQ==' , # base64.b64encode(b'video_data').decode('utf-8')
397- 'mime_type' : 'video/mp4' ,
398- }]}
413+ assert result == {
414+ 'type' : 'user_input' ,
415+ 'content' : [{
416+ 'type' : 'video' ,
417+ 'data' : (
418+ 'dmlkZW9fZGF0YQ=='
419+ ), # base64.b64encode(b'video_data').decode('utf-8')
420+ 'mime_type' : 'video/mp4' ,
421+ }],
422+ }
423+
399424 def test_inline_data_document (self ):
400425 """Test converting an inline document Part."""
401426 part = types .Part (
@@ -405,11 +430,17 @@ def test_inline_data_document(self):
405430 )
406431 )
407432 result = interactions_utils ._convert_part_to_interaction_content (part )
408- assert result == {'type' : 'user_input' , 'content' : [{
409- 'type' : 'document' ,
410- 'data' : 'ZG9jX2RhdGE=' , # base64.b64encode(b'doc_data').decode('utf-8')
411- 'mime_type' : 'application/pdf' ,
412- }]}
433+ assert result == {
434+ 'type' : 'user_input' ,
435+ 'content' : [{
436+ 'type' : 'document' ,
437+ 'data' : (
438+ 'ZG9jX2RhdGE='
439+ ), # base64.b64encode(b'doc_data').decode('utf-8')
440+ 'mime_type' : 'application/pdf' ,
441+ }],
442+ }
443+
413444 def test_file_data_image (self ):
414445 """Test converting a file data image Part."""
415446 part = types .Part (
@@ -419,11 +450,14 @@ def test_file_data_image(self):
419450 )
420451 )
421452 result = interactions_utils ._convert_part_to_interaction_content (part )
422- assert result == {'type' : 'user_input' , 'content' : [{
423- 'type' : 'image' ,
424- 'uri' : 'gs://bucket/image.png' ,
425- 'mime_type' : 'image/png' ,
426- }]}
453+ assert result == {
454+ 'type' : 'user_input' ,
455+ 'content' : [{
456+ 'type' : 'image' ,
457+ 'uri' : 'gs://bucket/image.png' ,
458+ 'mime_type' : 'image/png' ,
459+ }],
460+ }
427461
428462 def test_text_with_thought_flag (self ):
429463 """Test converting a text Part with thought=True flag."""
@@ -433,7 +467,10 @@ def test_text_with_thought_flag(self):
433467 part = types .Part (text = 'Let me think about this...' , thought = True )
434468 result = interactions_utils ._convert_part_to_interaction_content (part )
435469 # Text content is returned as-is (thought flag not represented in output)
436- assert result == {'type' : 'user_input' , 'content' : [{'type' : 'text' , 'text' : 'Let me think about this...' }]}
470+ assert result == {
471+ 'type' : 'user_input' ,
472+ 'content' : [{'type' : 'text' , 'text' : 'Let me think about this...' }],
473+ }
437474
438475 def test_thought_only_part (self ):
439476 """Test converting a thought-only Part with signature."""
@@ -708,8 +745,7 @@ class TestConvertInteractionOutputToParts:
708745 def test_text_output (self ):
709746 """Test converting text output."""
710747 output = ModelOutputStep (
711- type = 'model_output' ,
712- content = [TextContent (type = 'text' , text = 'Hello!' )]
748+ type = 'model_output' , content = [TextContent (type = 'text' , text = 'Hello!' )]
713749 )
714750 result_list = interactions_utils ._convert_interaction_step_to_parts (output )
715751 result = result_list [0 ] if result_list else None
@@ -795,11 +831,13 @@ def test_image_output_with_data(self):
795831 """Test converting image output with inline data."""
796832 output = ModelOutputStep (
797833 type = 'model_output' ,
798- content = [ImageContent (
799- type = 'image' ,
800- data = base64 .b64encode (b'image_bytes' ).decode ('utf-8' ),
801- mime_type = 'image/png' ,
802- )],
834+ content = [
835+ ImageContent (
836+ type = 'image' ,
837+ data = base64 .b64encode (b'image_bytes' ).decode ('utf-8' ),
838+ mime_type = 'image/png' ,
839+ )
840+ ],
803841 )
804842 result_list = interactions_utils ._convert_interaction_step_to_parts (output )
805843 result = result_list [0 ] if result_list else None
@@ -810,11 +848,13 @@ def test_image_output_with_uri(self):
810848 """Test converting image output with URI."""
811849 output = ModelOutputStep (
812850 type = 'model_output' ,
813- content = [ImageContent (
814- type = 'image' ,
815- uri = 'gs://bucket/image.png' ,
816- mime_type = 'image/png' ,
817- )],
851+ content = [
852+ ImageContent (
853+ type = 'image' ,
854+ uri = 'gs://bucket/image.png' ,
855+ mime_type = 'image/png' ,
856+ )
857+ ],
818858 )
819859 result_list = interactions_utils ._convert_interaction_step_to_parts (output )
820860 result = result_list [0 ] if result_list else None
@@ -1211,7 +1251,7 @@ def test_function_call_streaming_flow(self):
12111251 id = 'call_1' ,
12121252 name = 'get_weather' ,
12131253 arguments = {},
1214- )
1254+ ),
12151255 )
12161256 aggregated_parts : list [types .Part ] = []
12171257 result1 = interactions_utils .convert_interaction_event_to_llm_response (
@@ -1231,20 +1271,23 @@ def test_function_call_streaming_flow(self):
12311271 delta_event1 = StepDelta (
12321272 event_type = 'step.delta' ,
12331273 index = 0 ,
1234- delta = {'type' : 'arguments_delta' , 'arguments' : '{"city": ' }
1274+ delta = {'type' : 'arguments_delta' , 'arguments' : '{"city": ' },
12351275 )
12361276 result2 = interactions_utils .convert_interaction_event_to_llm_response (
12371277 delta_event1 , aggregated_parts , interaction_id = 'int_123'
12381278 )
12391279
12401280 assert result2 is not None
12411281 assert result2 .partial is True
1242- assert result2 .content .parts [0 ].function_call .partial_args [0 ].string_value == '{"city": '
1282+ assert (
1283+ result2 .content .parts [0 ].function_call .partial_args [0 ].string_value
1284+ == '{"city": '
1285+ )
12431286
12441287 delta_event2 = StepDelta (
12451288 event_type = 'step.delta' ,
12461289 index = 0 ,
1247- delta = {'type' : 'arguments_delta' , 'arguments' : '"Paris"}' }
1290+ delta = {'type' : 'arguments_delta' , 'arguments' : '"Paris"}' },
12481291 )
12491292 result3 = interactions_utils .convert_interaction_event_to_llm_response (
12501293 delta_event2 , aggregated_parts , interaction_id = 'int_123'
@@ -1277,7 +1320,7 @@ def test_function_call_streaming_json_parse_error(self, caplog):
12771320 id = 'call_err' ,
12781321 name = 'bad_json_tool' ,
12791322 arguments = {},
1280- )
1323+ ),
12811324 )
12821325 aggregated_parts = []
12831326 interactions_utils .convert_interaction_event_to_llm_response (
@@ -1288,7 +1331,7 @@ def test_function_call_streaming_json_parse_error(self, caplog):
12881331 delta_event = StepDelta (
12891332 event_type = 'step.delta' ,
12901333 index = 0 ,
1291- delta = {'type' : 'arguments_delta' , 'arguments' : '{"broken": "json' }
1334+ delta = {'type' : 'arguments_delta' , 'arguments' : '{"broken": "json' },
12921335 )
12931336 interactions_utils .convert_interaction_event_to_llm_response (
12941337 delta_event , aggregated_parts , interaction_id = 'int_err'
@@ -1311,7 +1354,8 @@ def test_function_call_streaming_json_parse_error(self, caplog):
13111354 assert result .interaction_id == 'int_err'
13121355
13131356 # The logging check can remain to ensure the raw exception is still logged.
1314- assert "Failed to parse function call args" in caplog .text
1357+ assert 'Failed to parse function call args' in caplog .text
1358+
13151359
13161360@pytest .mark .parametrize (
13171361 ('streamed_events_factory' , 'expected_ids' ),
0 commit comments