diff --git a/src/strands/agent/agent_result.py b/src/strands/agent/agent_result.py index ef8a11029..2a8d7965d 100644 --- a/src/strands/agent/agent_result.py +++ b/src/strands/agent/agent_result.py @@ -56,6 +56,36 @@ def __str__(self) -> str: return result + def to_node_output(self) -> str: + """Get a complete representation for graph node transitions. + + This method generates output suitable for passing between graph nodes, + including both text content and structured output when present. Unlike + __str__, this method always includes structured output alongside text + to ensure no data is lost during graph transitions. + + Returns: + A string representation containing both text and structured output. + """ + parts = [] + + # Extract text content from message + content_array = self.message.get("content", []) + text_content = "" + for item in content_array: + if isinstance(item, dict) and "text" in item: + text_content += item.get("text", "") + "\n" + + if text_content: + parts.append(text_content.rstrip("\n")) + + # Include structured output when present + if self.structured_output: + structured_json = self.structured_output.model_dump_json() + parts.append(f"[Structured Output]: {structured_json}") + + return "\n".join(parts) if parts else "" + @classmethod def from_dict(cls, data: dict[str, Any]) -> "AgentResult": """Rehydrate an AgentResult from persisted JSON. diff --git a/src/strands/multiagent/graph.py b/src/strands/multiagent/graph.py index 6156d332c..beda83c2d 100644 --- a/src/strands/multiagent/graph.py +++ b/src/strands/multiagent/graph.py @@ -1001,7 +1001,7 @@ def _build_node_input(self, node: GraphNode) -> list[ContentBlock]: agent_results = node_result.get_agent_results() for result in agent_results: agent_name = getattr(result, "agent_name", "Agent") - result_text = str(result) + result_text = result.to_node_output() if hasattr(result, "to_node_output") else str(result) node_input.append(ContentBlock(text=f" - {agent_name}: {result_text}")) return node_input diff --git a/tests/strands/agent/test_agent_result.py b/tests/strands/agent/test_agent_result.py index 5d1f02089..402999c13 100644 --- a/tests/strands/agent/test_agent_result.py +++ b/tests/strands/agent/test_agent_result.py @@ -225,3 +225,86 @@ def test__str__empty_message_with_structured_output(mock_metrics, empty_message: assert "example" in message_string assert "123" in message_string assert "optional" in message_string + + +# Tests for to_node_output() method - graph node transitions +def test_to_node_output_text_only(mock_metrics, simple_message: Message): + """Test to_node_output() with text-only message returns just the text.""" + result = AgentResult(stop_reason="end_turn", message=simple_message, metrics=mock_metrics, state={}) + + node_output = result.to_node_output() + assert node_output == "Hello world!" + assert "[Structured Output]" not in node_output + + +def test_to_node_output_structured_output_only(mock_metrics, empty_message: Message): + """Test to_node_output() with only structured_output.""" + structured_output = StructuredOutputModel(name="test", value=42) + + result = AgentResult( + stop_reason="end_turn", + message=empty_message, + metrics=mock_metrics, + state={}, + structured_output=structured_output, + ) + + node_output = result.to_node_output() + assert "[Structured Output]:" in node_output + assert '"name":"test"' in node_output + assert '"value":42' in node_output + + +def test_to_node_output_text_and_structured_output(mock_metrics, simple_message: Message): + """Test to_node_output() includes BOTH text AND structured_output when present.""" + structured_output = StructuredOutputModel(name="test", value=42) + + result = AgentResult( + stop_reason="end_turn", + message=simple_message, + metrics=mock_metrics, + state={}, + structured_output=structured_output, + ) + + node_output = result.to_node_output() + + # Should contain both text and structured output + assert "Hello world!" in node_output + assert "[Structured Output]:" in node_output + assert '"name":"test"' in node_output + assert '"value":42' in node_output + + +def test_to_node_output_empty_message_no_structured_output(mock_metrics, empty_message: Message): + """Test to_node_output() with empty message and no structured_output returns empty string.""" + result = AgentResult(stop_reason="end_turn", message=empty_message, metrics=mock_metrics, state={}) + + node_output = result.to_node_output() + assert node_output == "" + + +def test_to_node_output_complex_message(mock_metrics, complex_message: Message): + """Test to_node_output() with complex message containing multiple text blocks.""" + structured_output = StructuredOutputModel(name="complex", value=999, optional_field="extra") + + result = AgentResult( + stop_reason="end_turn", + message=complex_message, + metrics=mock_metrics, + state={}, + structured_output=structured_output, + ) + + node_output = result.to_node_output() + + # Should contain all text blocks + assert "First paragraph" in node_output + assert "Second paragraph" in node_output + assert "Third paragraph" in node_output + + # Should contain structured output + assert "[Structured Output]:" in node_output + assert '"name":"complex"' in node_output + assert '"value":999' in node_output + assert '"optional_field":"extra"' in node_output