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
3838from google .genai .interactions import StepStart
3939from google .genai .interactions import StepStop
4040from google .genai .interactions import TextContent
41- from google .genai .interactions import ImageContent
4241from google .genai .interactions import ThoughtStep
4342from google .genai .interactions import Usage
4443import pytest
@@ -202,13 +201,21 @@ def test_text_part(self):
202201 """Test converting a text Part."""
203202 part = types .Part (text = 'Hello, world!' )
204203 result = interactions_utils ._convert_part_to_interaction_content (part )
205- assert result == {'type' : 'user_input' , 'content' : [{'type' : 'text' , 'text' : 'Hello, world!' }]}
204+ assert result == {
205+ 'type' : 'user_input' ,
206+ 'content' : [{'type' : 'text' , 'text' : 'Hello, world!' }],
207+ }
206208
207209 def test_text_part_model_role (self ):
208210 """Test converting a text Part for model role."""
209211 part = types .Part (text = 'Hello, user!' )
210- result = interactions_utils ._convert_part_to_interaction_content (part , role = 'model' )
211- assert result == {'type' : 'model_output' , 'content' : [{'type' : 'text' , 'text' : 'Hello, user!' }]}
212+ result = interactions_utils ._convert_part_to_interaction_content (
213+ part , role = 'model'
214+ )
215+ assert result == {
216+ 'type' : 'model_output' ,
217+ 'content' : [{'type' : 'text' , 'text' : 'Hello, user!' }],
218+ }
212219
213220 def test_function_call_part (self ):
214221 """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
@@ -777,11 +813,13 @@ def test_image_output_with_data(self):
777813 """Test converting image output with inline data."""
778814 output = ModelOutputStep (
779815 type = 'model_output' ,
780- content = [ImageContent (
781- type = 'image' ,
782- data = base64 .b64encode (b'image_bytes' ).decode ('utf-8' ),
783- mime_type = 'image/png' ,
784- )],
816+ content = [
817+ ImageContent (
818+ type = 'image' ,
819+ data = base64 .b64encode (b'image_bytes' ).decode ('utf-8' ),
820+ mime_type = 'image/png' ,
821+ )
822+ ],
785823 )
786824 result_list = interactions_utils ._convert_interaction_step_to_parts (output )
787825 result = result_list [0 ] if result_list else None
@@ -792,11 +830,13 @@ def test_image_output_with_uri(self):
792830 """Test converting image output with URI."""
793831 output = ModelOutputStep (
794832 type = 'model_output' ,
795- content = [ImageContent (
796- type = 'image' ,
797- uri = 'gs://bucket/image.png' ,
798- mime_type = 'image/png' ,
799- )],
833+ content = [
834+ ImageContent (
835+ type = 'image' ,
836+ uri = 'gs://bucket/image.png' ,
837+ mime_type = 'image/png' ,
838+ )
839+ ],
800840 )
801841 result_list = interactions_utils ._convert_interaction_step_to_parts (output )
802842 result = result_list [0 ] if result_list else None
@@ -871,10 +911,12 @@ def test_successful_text_response(self):
871911 status = 'completed' ,
872912 created = datetime .now (timezone .utc ),
873913 updated = datetime .now (timezone .utc ),
874- steps = [ModelOutputStep (
875- type = 'model_output' ,
876- content = [TextContent (type = 'text' , text = 'The answer is 4.' )]
877- )],
914+ steps = [
915+ ModelOutputStep (
916+ type = 'model_output' ,
917+ content = [TextContent (type = 'text' , text = 'The answer is 4.' )],
918+ )
919+ ],
878920 usage = Usage (total_input_tokens = 10 , total_output_tokens = 5 ),
879921 )
880922 result = interactions_utils .convert_interaction_to_llm_response (interaction )
@@ -910,12 +952,14 @@ def test_requires_action_response(self):
910952 status = 'requires_action' ,
911953 created = datetime .now (timezone .utc ),
912954 updated = datetime .now (timezone .utc ),
913- steps = [FunctionCallStep (
914- type = 'function_call' ,
915- id = 'call_1' ,
916- name = 'get_weather' ,
917- arguments = {'city' : 'Paris' },
918- )],
955+ steps = [
956+ FunctionCallStep (
957+ type = 'function_call' ,
958+ id = 'call_1' ,
959+ name = 'get_weather' ,
960+ arguments = {'city' : 'Paris' },
961+ )
962+ ],
919963 )
920964 result = interactions_utils .convert_interaction_to_llm_response (interaction )
921965
@@ -1172,7 +1216,7 @@ def test_function_call_streaming_flow(self):
11721216 id = 'call_1' ,
11731217 name = 'get_weather' ,
11741218 arguments = {},
1175- )
1219+ ),
11761220 )
11771221 aggregated_parts : list [types .Part ] = []
11781222 result1 = interactions_utils .convert_interaction_event_to_llm_response (
@@ -1192,20 +1236,23 @@ def test_function_call_streaming_flow(self):
11921236 delta_event1 = StepDelta (
11931237 event_type = 'step.delta' ,
11941238 index = 0 ,
1195- delta = {'type' : 'arguments_delta' , 'arguments' : '{"city": ' }
1239+ delta = {'type' : 'arguments_delta' , 'arguments' : '{"city": ' },
11961240 )
11971241 result2 = interactions_utils .convert_interaction_event_to_llm_response (
11981242 delta_event1 , aggregated_parts , interaction_id = 'int_123'
11991243 )
12001244
12011245 assert result2 is not None
12021246 assert result2 .partial is True
1203- assert result2 .content .parts [0 ].function_call .partial_args [0 ].string_value == '{"city": '
1247+ assert (
1248+ result2 .content .parts [0 ].function_call .partial_args [0 ].string_value
1249+ == '{"city": '
1250+ )
12041251
12051252 delta_event2 = StepDelta (
12061253 event_type = 'step.delta' ,
12071254 index = 0 ,
1208- delta = {'type' : 'arguments_delta' , 'arguments' : '"Paris"}' }
1255+ delta = {'type' : 'arguments_delta' , 'arguments' : '"Paris"}' },
12091256 )
12101257 result3 = interactions_utils .convert_interaction_event_to_llm_response (
12111258 delta_event2 , aggregated_parts , interaction_id = 'int_123'
@@ -1238,7 +1285,7 @@ def test_function_call_streaming_json_parse_error(self, caplog):
12381285 id = 'call_err' ,
12391286 name = 'bad_json_tool' ,
12401287 arguments = {},
1241- )
1288+ ),
12421289 )
12431290 aggregated_parts = []
12441291 interactions_utils .convert_interaction_event_to_llm_response (
@@ -1249,7 +1296,7 @@ def test_function_call_streaming_json_parse_error(self, caplog):
12491296 delta_event = StepDelta (
12501297 event_type = 'step.delta' ,
12511298 index = 0 ,
1252- delta = {'type' : 'arguments_delta' , 'arguments' : '{"broken": "json' }
1299+ delta = {'type' : 'arguments_delta' , 'arguments' : '{"broken": "json' },
12531300 )
12541301 interactions_utils .convert_interaction_event_to_llm_response (
12551302 delta_event , aggregated_parts , interaction_id = 'int_err'
@@ -1272,7 +1319,8 @@ def test_function_call_streaming_json_parse_error(self, caplog):
12721319 assert result .interaction_id == 'int_err'
12731320
12741321 # The logging check can remain to ensure the raw exception is still logged.
1275- assert "Failed to parse function call args" in caplog .text
1322+ assert 'Failed to parse function call args' in caplog .text
1323+
12761324
12771325@pytest .mark .parametrize (
12781326 ('streamed_events_factory' , 'expected_ids' ),
0 commit comments