From 157946f3b4ec308a6367b8fa32c59dc4f5970bac Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Fri, 6 Feb 2026 03:53:28 -0500 Subject: [PATCH 1/9] Fix AgentTool skip_summarization to include text output in final event When skip_summarization=True, AgentTool previously terminated the flow with a FunctionResponse event that contained no text, causing UIs to appear stuck. This change appends a Text part to the response event when skipping summarization so tool output is displayed. The implementation avoids duplication, safely serializes non-string results, and preserves existing behavior for normal flows. Added regression tests to verify text output is present for both string and JSON-like tool results. --- src/google/adk/flows/llm_flows/functions.py | 12 ++++ tests/unittests/tools/test_agent_tool.py | 76 +++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index 664d0d6c67..f4601148d8 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -21,6 +21,7 @@ import copy import functools import inspect +import json import logging import threading from typing import Any @@ -962,6 +963,17 @@ def __build_response_event( parts=[part_function_response], ) + if tool_context.actions.skip_summarization: + # When summarization is skipped, ensure a displayable text part is added. + if isinstance(function_result.get('result'), str): + result_text = function_result['result'] + else: + # Safely serialize non-string results to JSON for display. + result_text = json.dumps( + function_result, ensure_ascii=False, default=str + ) + content.parts.append(types.Part.from_text(text=result_text)) + function_response_event = Event( invocation_id=invocation_context.invocation_id, author=invocation_context.agent.name, diff --git a/tests/unittests/tools/test_agent_tool.py b/tests/unittests/tools/test_agent_tool.py index b5f59be0fc..e7d3167b66 100644 --- a/tests/unittests/tools/test_agent_tool.py +++ b/tests/unittests/tools/test_agent_tool.py @@ -1164,3 +1164,79 @@ def test_empty_sequential_agent_falls_back_to_request(self): # Should fall back to 'request' parameter assert declaration.parameters.properties['request'].type == 'STRING' + + +def test_agent_tool_skip_summarization_has_text_output(): + """Tests that when skip_summarization is True, the final event contains text content.""" + + tool_agent_model = testing_utils.MockModel.create(responses=["tool_response_text"]) + tool_agent = Agent( + name="tool_agent", + model=tool_agent_model, + ) + + agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) + + root_agent_model = testing_utils.MockModel.create( + responses=[ + function_call_no_schema, + "final_summary_text_that_should_not_be_reached", + ] + ) + + root_agent = Agent( + name="root_agent", + model=root_agent_model, + tools=[agent_tool], + ) + + runner = testing_utils.InMemoryRunner(root_agent) + events = runner.run("start") + + final_events = [e for e in events if e.is_final_response()] + assert final_events + last_event = final_events[-1] + assert last_event.is_final_response() + + assert any(p.function_response for p in last_event.content.parts) + + assert any("tool_response_text" in (p.text or "") for p in last_event.content.parts) + + +def test_agent_tool_skip_summarization_preserves_json_string_output(): + """Tests that structured output string is preserved as text when skipping summarization.""" + + tool_agent_model = testing_utils.MockModel.create(responses=['{"field": "value"}']) + tool_agent = Agent( + name="tool_agent", + model=tool_agent_model, + ) + + agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) + + root_agent_model = testing_utils.MockModel.create( + responses=[function_call_no_schema] + ) + + root_agent = Agent( + name="root_agent", + model=root_agent_model, + tools=[agent_tool], + ) + + runner = testing_utils.InMemoryRunner(root_agent) + events = runner.run("start") + + final_events = [e for e in events if e.is_final_response()] + assert final_events + last_event = final_events[-1] + assert last_event.is_final_response() + + text_parts = [p.text for p in last_event.content.parts if p.text] + assert text_parts + + # Check that the JSON string content is preserved + assert any( + "field" in (p.text or "") and "value" in (p.text or "") + for p in last_event.content.parts + ) From ef17a2ad106121e10e73a50b48b640201baacbee Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Fri, 6 Feb 2026 04:21:43 -0500 Subject: [PATCH 2/9] Feat: improve serialization of non-string tool outputs by extracting the 'result' key for JSON display. - assert the exact string content - new test case for a non-string dictionary - more precise assertion checking for the exact text content --- src/google/adk/flows/llm_flows/functions.py | 2 +- tests/unittests/tools/test_agent_tool.py | 44 ++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index f4601148d8..e6cf8c56f2 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -970,7 +970,7 @@ def __build_response_event( else: # Safely serialize non-string results to JSON for display. result_text = json.dumps( - function_result, ensure_ascii=False, default=str + function_result.get('result', function_result), ensure_ascii=False, default=str ) content.parts.append(types.Part.from_text(text=result_text)) diff --git a/tests/unittests/tools/test_agent_tool.py b/tests/unittests/tools/test_agent_tool.py index e7d3167b66..d8e4753a71 100644 --- a/tests/unittests/tools/test_agent_tool.py +++ b/tests/unittests/tools/test_agent_tool.py @@ -1200,7 +1200,7 @@ def test_agent_tool_skip_summarization_has_text_output(): assert any(p.function_response for p in last_event.content.parts) - assert any("tool_response_text" in (p.text or "") for p in last_event.content.parts) + assert [p.text for p in last_event.content.parts if p.text] == ["tool_response_text"] def test_agent_tool_skip_summarization_preserves_json_string_output(): @@ -1235,8 +1235,42 @@ def test_agent_tool_skip_summarization_preserves_json_string_output(): text_parts = [p.text for p in last_event.content.parts if p.text] assert text_parts - # Check that the JSON string content is preserved - assert any( - "field" in (p.text or "") and "value" in (p.text or "") - for p in last_event.content.parts + # Check that the JSON string content is preserved exactly + assert text_parts == ['{"field": "value"}'] + + +def test_agent_tool_skip_summarization_handles_non_string_result(): + """Tests that non-string (dict) output is correctly serialized as JSON text.""" + + class CustomOutput(BaseModel): + value: int + + tool_agent_model = testing_utils.MockModel.create(responses=['{"value": 123}']) + tool_agent = Agent( + name="tool_agent", + model=tool_agent_model, + output_schema=CustomOutput + ) + + agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) + + root_agent_model = testing_utils.MockModel.create( + responses=[function_call_no_schema] ) + + root_agent = Agent( + name="root_agent", + model=root_agent_model, + tools=[agent_tool], + ) + + runner = testing_utils.InMemoryRunner(root_agent) + events = runner.run("start") + + final_events = [e for e in events if e.is_final_response()] + assert final_events + last_event = final_events[-1] + + text_parts = [p.text for p in last_event.content.parts if p.text] + + assert text_parts == ['{"value": 123}'] From f7f1a0036376bf0030d6cceeb878d9ade6f9d825 Mon Sep 17 00:00:00 2001 From: Mac Howe <69370250+ItsMacto@users.noreply.github.com> Date: Fri, 6 Feb 2026 04:28:32 -0500 Subject: [PATCH 3/9] Update src/google/adk/flows/llm_flows/functions.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/flows/llm_flows/functions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index e6cf8c56f2..df16a6cd81 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -965,12 +965,13 @@ def __build_response_event( if tool_context.actions.skip_summarization: # When summarization is skipped, ensure a displayable text part is added. - if isinstance(function_result.get('result'), str): - result_text = function_result['result'] + result_payload = function_result.get('result', function_result) + if isinstance(result_payload, str): + result_text = result_payload else: # Safely serialize non-string results to JSON for display. result_text = json.dumps( - function_result.get('result', function_result), ensure_ascii=False, default=str + result_payload, ensure_ascii=False, default=str ) content.parts.append(types.Part.from_text(text=result_text)) From 88ec6c8c94ba75bf22841a97e110907a73f79e04 Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Fri, 6 Feb 2026 04:35:18 -0500 Subject: [PATCH 4/9] refactor: use pytest fixture to de-duplicate AgentTool skip_summarization test setup. --- tests/unittests/tools/test_agent_tool.py | 98 ++++++++---------------- 1 file changed, 34 insertions(+), 64 deletions(-) diff --git a/tests/unittests/tools/test_agent_tool.py b/tests/unittests/tools/test_agent_tool.py index d8e4753a71..49341c07ce 100644 --- a/tests/unittests/tools/test_agent_tool.py +++ b/tests/unittests/tools/test_agent_tool.py @@ -14,6 +14,7 @@ from typing import Any from typing import Optional +import pytest from google.adk.agents.callback_context import CallbackContext from google.adk.agents.invocation_context import InvocationContext @@ -1166,31 +1167,37 @@ def test_empty_sequential_agent_falls_back_to_request(self): assert declaration.parameters.properties['request'].type == 'STRING' -def test_agent_tool_skip_summarization_has_text_output(): - """Tests that when skip_summarization is True, the final event contains text content.""" +@pytest.fixture +def setup_skip_summarization_runner(): + def _setup_runner(tool_agent_model_responses, tool_agent_output_schema=None): + tool_agent_model = testing_utils.MockModel.create(responses=tool_agent_model_responses) + tool_agent = Agent( + name="tool_agent", + model=tool_agent_model, + output_schema=tool_agent_output_schema + ) - tool_agent_model = testing_utils.MockModel.create(responses=["tool_response_text"]) - tool_agent = Agent( - name="tool_agent", - model=tool_agent_model, - ) + agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) - agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) + root_agent_model = testing_utils.MockModel.create( + responses=[ + function_call_no_schema, + "final_summary_text_that_should_not_be_reached", + ] + ) - root_agent_model = testing_utils.MockModel.create( - responses=[ - function_call_no_schema, - "final_summary_text_that_should_not_be_reached", - ] - ) + root_agent = Agent( + name="root_agent", + model=root_agent_model, + tools=[agent_tool], + ) + return testing_utils.InMemoryRunner(root_agent) + return _setup_runner - root_agent = Agent( - name="root_agent", - model=root_agent_model, - tools=[agent_tool], - ) - runner = testing_utils.InMemoryRunner(root_agent) +def test_agent_tool_skip_summarization_has_text_output(setup_skip_summarization_runner): + """Tests that when skip_summarization is True, the final event contains text content.""" + runner = setup_skip_summarization_runner(tool_agent_model_responses=["tool_response_text"]) events = runner.run("start") final_events = [e for e in events if e.is_final_response()] @@ -1203,28 +1210,9 @@ def test_agent_tool_skip_summarization_has_text_output(): assert [p.text for p in last_event.content.parts if p.text] == ["tool_response_text"] -def test_agent_tool_skip_summarization_preserves_json_string_output(): +def test_agent_tool_skip_summarization_preserves_json_string_output(setup_skip_summarization_runner): """Tests that structured output string is preserved as text when skipping summarization.""" - - tool_agent_model = testing_utils.MockModel.create(responses=['{"field": "value"}']) - tool_agent = Agent( - name="tool_agent", - model=tool_agent_model, - ) - - agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) - - root_agent_model = testing_utils.MockModel.create( - responses=[function_call_no_schema] - ) - - root_agent = Agent( - name="root_agent", - model=root_agent_model, - tools=[agent_tool], - ) - - runner = testing_utils.InMemoryRunner(root_agent) + runner = setup_skip_summarization_runner(tool_agent_model_responses=['{"field": "value"}']) events = runner.run("start") final_events = [e for e in events if e.is_final_response()] @@ -1233,38 +1221,20 @@ def test_agent_tool_skip_summarization_preserves_json_string_output(): assert last_event.is_final_response() text_parts = [p.text for p in last_event.content.parts if p.text] - assert text_parts - + # Check that the JSON string content is preserved exactly assert text_parts == ['{"field": "value"}'] -def test_agent_tool_skip_summarization_handles_non_string_result(): +def test_agent_tool_skip_summarization_handles_non_string_result(setup_skip_summarization_runner): """Tests that non-string (dict) output is correctly serialized as JSON text.""" - class CustomOutput(BaseModel): value: int - tool_agent_model = testing_utils.MockModel.create(responses=['{"value": 123}']) - tool_agent = Agent( - name="tool_agent", - model=tool_agent_model, - output_schema=CustomOutput - ) - - agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) - - root_agent_model = testing_utils.MockModel.create( - responses=[function_call_no_schema] + runner = setup_skip_summarization_runner( + tool_agent_model_responses=['{"value": 123}'], + tool_agent_output_schema=CustomOutput ) - - root_agent = Agent( - name="root_agent", - model=root_agent_model, - tools=[agent_tool], - ) - - runner = testing_utils.InMemoryRunner(root_agent) events = runner.run("start") final_events = [e for e in events if e.is_final_response()] From 8c64faab3de48a9c194c75d6d0a43db9c812aa13 Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Fri, 6 Feb 2026 04:42:21 -0500 Subject: [PATCH 5/9] docs: Clarify unwrapping of function results for display when summarization is skipped. --- src/google/adk/flows/llm_flows/functions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index df16a6cd81..605d6fe8ba 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -963,8 +963,10 @@ def __build_response_event( parts=[part_function_response], ) + # When summarization is skipped, ensure a displayable text part is added. if tool_context.actions.skip_summarization: - # When summarization is skipped, ensure a displayable text part is added. + # If the tool returned a non-dict, it was wrapped in {'result': ...}. + # This unwraps the value for display; otherwise, it uses the original dict. result_payload = function_result.get('result', function_result) if isinstance(result_payload, str): result_text = result_payload From 01ea4fc3a1371b9e6eb7ef40a507402afa2b4f7c Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Tue, 10 Feb 2026 16:13:57 -0500 Subject: [PATCH 6/9] formating fix --- src/google/adk/flows/llm_flows/functions.py | 4 +- tests/unittests/tools/test_agent_tool.py | 55 +++++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index 605d6fe8ba..4e6d9021f0 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -972,9 +972,7 @@ def __build_response_event( result_text = result_payload else: # Safely serialize non-string results to JSON for display. - result_text = json.dumps( - result_payload, ensure_ascii=False, default=str - ) + result_text = json.dumps(result_payload, ensure_ascii=False, default=str) content.parts.append(types.Part.from_text(text=result_text)) function_response_event = Event( diff --git a/tests/unittests/tools/test_agent_tool.py b/tests/unittests/tools/test_agent_tool.py index 49341c07ce..6a8940a74d 100644 --- a/tests/unittests/tools/test_agent_tool.py +++ b/tests/unittests/tools/test_agent_tool.py @@ -14,7 +14,6 @@ from typing import Any from typing import Optional -import pytest from google.adk.agents.callback_context import CallbackContext from google.adk.agents.invocation_context import InvocationContext @@ -1170,11 +1169,13 @@ def test_empty_sequential_agent_falls_back_to_request(self): @pytest.fixture def setup_skip_summarization_runner(): def _setup_runner(tool_agent_model_responses, tool_agent_output_schema=None): - tool_agent_model = testing_utils.MockModel.create(responses=tool_agent_model_responses) + tool_agent_model = testing_utils.MockModel.create( + responses=tool_agent_model_responses + ) tool_agent = Agent( - name="tool_agent", + name='tool_agent', model=tool_agent_model, - output_schema=tool_agent_output_schema + output_schema=tool_agent_output_schema, ) agent_tool = AgentTool(agent=tool_agent, skip_summarization=True) @@ -1182,23 +1183,28 @@ def _setup_runner(tool_agent_model_responses, tool_agent_output_schema=None): root_agent_model = testing_utils.MockModel.create( responses=[ function_call_no_schema, - "final_summary_text_that_should_not_be_reached", + 'final_summary_text_that_should_not_be_reached', ] ) root_agent = Agent( - name="root_agent", + name='root_agent', model=root_agent_model, tools=[agent_tool], ) return testing_utils.InMemoryRunner(root_agent) + return _setup_runner -def test_agent_tool_skip_summarization_has_text_output(setup_skip_summarization_runner): +def test_agent_tool_skip_summarization_has_text_output( + setup_skip_summarization_runner, +): """Tests that when skip_summarization is True, the final event contains text content.""" - runner = setup_skip_summarization_runner(tool_agent_model_responses=["tool_response_text"]) - events = runner.run("start") + runner = setup_skip_summarization_runner( + tool_agent_model_responses=['tool_response_text'] + ) + events = runner.run('start') final_events = [e for e in events if e.is_final_response()] assert final_events @@ -1207,13 +1213,19 @@ def test_agent_tool_skip_summarization_has_text_output(setup_skip_summarization_ assert any(p.function_response for p in last_event.content.parts) - assert [p.text for p in last_event.content.parts if p.text] == ["tool_response_text"] + assert [p.text for p in last_event.content.parts if p.text] == [ + 'tool_response_text' + ] -def test_agent_tool_skip_summarization_preserves_json_string_output(setup_skip_summarization_runner): +def test_agent_tool_skip_summarization_preserves_json_string_output( + setup_skip_summarization_runner, +): """Tests that structured output string is preserved as text when skipping summarization.""" - runner = setup_skip_summarization_runner(tool_agent_model_responses=['{"field": "value"}']) - events = runner.run("start") + runner = setup_skip_summarization_runner( + tool_agent_model_responses=['{"field": "value"}'] + ) + events = runner.run('start') final_events = [e for e in events if e.is_final_response()] assert final_events @@ -1221,26 +1233,29 @@ def test_agent_tool_skip_summarization_preserves_json_string_output(setup_skip_s assert last_event.is_final_response() text_parts = [p.text for p in last_event.content.parts if p.text] - + # Check that the JSON string content is preserved exactly assert text_parts == ['{"field": "value"}'] -def test_agent_tool_skip_summarization_handles_non_string_result(setup_skip_summarization_runner): +def test_agent_tool_skip_summarization_handles_non_string_result( + setup_skip_summarization_runner, +): """Tests that non-string (dict) output is correctly serialized as JSON text.""" + class CustomOutput(BaseModel): - value: int + value: int runner = setup_skip_summarization_runner( tool_agent_model_responses=['{"value": 123}'], - tool_agent_output_schema=CustomOutput + tool_agent_output_schema=CustomOutput, ) - events = runner.run("start") + events = runner.run('start') final_events = [e for e in events if e.is_final_response()] assert final_events last_event = final_events[-1] - + text_parts = [p.text for p in last_event.content.parts if p.text] - + assert text_parts == ['{"value": 123}'] From 7535d8c2fe5628ab899740c67f270a0620958e39 Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Tue, 10 Feb 2026 16:31:08 -0500 Subject: [PATCH 7/9] Fixed unit tests --- src/google/adk/flows/llm_flows/functions.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index 4e6d9021f0..641a7b8684 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -964,16 +964,19 @@ def __build_response_event( ) # When summarization is skipped, ensure a displayable text part is added. - if tool_context.actions.skip_summarization: + if tool_context.actions.skip_summarization and 'error' not in function_result: # If the tool returned a non-dict, it was wrapped in {'result': ...}. # This unwraps the value for display; otherwise, it uses the original dict. result_payload = function_result.get('result', function_result) - if isinstance(result_payload, str): - result_text = result_payload - else: - # Safely serialize non-string results to JSON for display. - result_text = json.dumps(result_payload, ensure_ascii=False, default=str) - content.parts.append(types.Part.from_text(text=result_text)) + if result_payload is not None: + if isinstance(result_payload, str): + result_text = result_payload + else: + # Safely serialize non-string results to JSON for display. + result_text = json.dumps( + result_payload, ensure_ascii=False, default=str + ) + content.parts.append(types.Part.from_text(text=result_text)) function_response_event = Event( invocation_id=invocation_context.invocation_id, From 63fbda22ac5eda92ed86c372d80d16703d3962ef Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Tue, 10 Feb 2026 16:38:07 -0500 Subject: [PATCH 8/9] remove bad comments --- src/google/adk/flows/llm_flows/functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index 6adc0241eb..bac025ec91 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -959,14 +959,12 @@ def __build_response_event( # When summarization is skipped, ensure a displayable text part is added. if tool_context.actions.skip_summarization and 'error' not in function_result: - # If the tool returned a non-dict, it was wrapped in {'result': ...}. # This unwraps the value for display; otherwise, it uses the original dict. result_payload = function_result.get('result', function_result) if result_payload is not None: if isinstance(result_payload, str): result_text = result_payload else: - # Safely serialize non-string results to JSON for display. result_text = json.dumps( result_payload, ensure_ascii=False, default=str ) From 5df57e0e530ddcd9e7e97338bca9ac30e7a35d63 Mon Sep 17 00:00:00 2001 From: Mac Howe Date: Tue, 10 Feb 2026 17:15:36 -0500 Subject: [PATCH 9/9] stop data loss from ambiguous unwrapping --- src/google/adk/flows/llm_flows/functions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index bac025ec91..408f136b34 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -943,6 +943,9 @@ def __build_response_event( tool_context: ToolContext, invocation_context: InvocationContext, ) -> Event: + # Capture the raw result for display purposes before any normalization. + display_result = function_result + # Specs requires the result to be a dict. if not isinstance(function_result, dict): function_result = {'result': function_result} @@ -959,14 +962,12 @@ def __build_response_event( # When summarization is skipped, ensure a displayable text part is added. if tool_context.actions.skip_summarization and 'error' not in function_result: - # This unwraps the value for display; otherwise, it uses the original dict. - result_payload = function_result.get('result', function_result) - if result_payload is not None: - if isinstance(result_payload, str): - result_text = result_payload + if display_result is not None: + if isinstance(display_result, str): + result_text = display_result else: result_text = json.dumps( - result_payload, ensure_ascii=False, default=str + display_result, ensure_ascii=False, default=str ) content.parts.append(types.Part.from_text(text=result_text))