diff --git a/src/google/adk/tools/agent_tool.py b/src/google/adk/tools/agent_tool.py index 8a43eb6311..f6f245702d 100644 --- a/src/google/adk/tools/agent_tool.py +++ b/src/google/adk/tools/agent_tool.py @@ -272,6 +272,16 @@ async def run_async( merged_text = '\n'.join( p.text for p in last_content.parts if p.text and not p.thought ) + # Fallback: if model returned only code_execution_result parts, use their output. + if not merged_text.strip(): + code_outputs = [ + p.code_execution_result.output + for p in last_content.parts + if p.code_execution_result and p.code_execution_result.output + ] + if code_outputs: + merged_text = '\n\n'.join(code_outputs) + output_schema = _get_output_schema(self.agent) if output_schema: tool_result = validate_schema(output_schema, merged_text) diff --git a/tests/unittests/tools/test_agent_tool.py b/tests/unittests/tools/test_agent_tool.py index cc6c0b2134..4572f30869 100644 --- a/tests/unittests/tools/test_agent_tool.py +++ b/tests/unittests/tools/test_agent_tool.py @@ -21,6 +21,7 @@ from google.adk.agents.run_config import RunConfig from google.adk.agents.sequential_agent import SequentialAgent from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService +from google.adk.events.event import Event from google.adk.features import FeatureName from google.adk.features._feature_registry import temporary_feature_override from google.adk.memory.in_memory_memory_service import InMemoryMemoryService @@ -943,6 +944,92 @@ class CustomOutput(BaseModel): } +@mark.asyncio +async def test_run_async_uses_code_execution_result_output_when_no_text( + monkeypatch, +): + """Verify run_async falls back to code execution output when text is empty.""" + + from google.adk.events.event import Event + + async def _event_stream(): + yield Event( + author='tool_agent', + content=types.Content( + parts=[ + types.Part( + code_execution_result=types.CodeExecutionResult( + output='IRR=0.1472', + outcome=types.Outcome.OUTCOME_OK, + ) + ) + ] + ), + ) + + class StubRunner: + + def __init__( + self, + *, + app_name: str, + agent: Agent, + artifact_service, + session_service, + memory_service, + credential_service, + plugins, + ): + del app_name, agent, artifact_service, memory_service + del credential_service, plugins + self.session_service = session_service + + def run_async( + self, + *, + user_id: str, + session_id: str, + invocation_id: Optional[str] = None, + new_message: Optional[types.Content] = None, + state_delta: Optional[dict[str, Any]] = None, + run_config: Optional[RunConfig] = None, + ): + del user_id, session_id, invocation_id, new_message, state_delta + del run_config + return _event_stream() + + async def close(self): + pass + + monkeypatch.setattr('google.adk.runners.Runner', StubRunner) + + tool_agent = Agent( + name='tool_agent', + model=testing_utils.MockModel.create(responses=['unused']), + ) + agent_tool = AgentTool(agent=tool_agent) + + session_service = InMemorySessionService() + session = await session_service.create_session( + app_name='test_app', user_id='test_user' + ) + + invocation_context = InvocationContext( + invocation_id='invocation_id', + agent=tool_agent, + session=session, + session_service=session_service, + ) + tool_context = ToolContext(invocation_context=invocation_context) + + tool_result = await agent_tool.run_async( + args={'request': 'test request'}, + tool_context=tool_context, + ) + + assert tool_result == 'IRR=0.1472' + + @mark.asyncio async def test_run_async_handles_none_parts_in_response(): """Verify run_async handles None parts in response without raising TypeError."""