Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/strands/multiagent/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ class NodeResult:
execution_count: int = 0
interrupts: list[Interrupt] = field(default_factory=list)

def __str__(self) -> str:
"""Return a human-readable string representation of the node result.

Delegates to the inner result's string representation:
- AgentResult: Uses AgentResult.__str__ (extracts text content)
- MultiAgentResult: Uses MultiAgentResult.__str__ (recursive)
- Exception: Uses the exception's string representation

Returns:
String representation of the node's result.
"""
return str(self.result)

def get_agent_results(self) -> list[AgentResult]:
"""Get all AgentResult objects from this node, flattened if nested."""
if isinstance(self.result, Exception):
Expand Down Expand Up @@ -136,6 +149,27 @@ class MultiAgentResult:
execution_time: int = 0
interrupts: list[Interrupt] = field(default_factory=list)

def __str__(self) -> str:
"""Return a human-readable string representation of the multi-agent result.

Priority order:
1. Interrupts (if present) → stringified list of interrupt dicts
2. Node results → each node's string output, prefixed with node name

Returns:
String representation based on the priority order above.
"""
if self.interrupts:
return str([interrupt.to_dict() for interrupt in self.interrupts])

parts = []
for node_name, node_result in self.results.items():
node_str = str(node_result).strip()
if node_str:
parts.append(f"{node_name}: {node_str}")

return "\n".join(parts)

@classmethod
def from_dict(cls, data: dict[str, Any]) -> "MultiAgentResult":
"""Rehydrate a MultiAgentResult from persisted JSON."""
Expand Down
95 changes: 95 additions & 0 deletions tests/strands/multiagent/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,98 @@ def test_serialize_node_result_for_persist(agent_result):
assert "result" in serialized_exception
assert serialized_exception["result"]["type"] == "exception"
assert serialized_exception["result"]["message"] == "Test error"


def test_node_result_str_with_agent_result():
"""Test NodeResult.__str__ delegates to AgentResult.__str__."""
agent_result = AgentResult(
message={"role": "assistant", "content": [{"text": "Hello world"}]},
stop_reason="end_turn",
state={},
metrics={},
)
node_result = NodeResult(result=agent_result)
assert str(node_result) == str(agent_result)
assert "Hello world" in str(node_result)


def test_node_result_str_with_exception():
"""Test NodeResult.__str__ with an Exception result."""
node_result = NodeResult(result=Exception("something broke"), status=Status.FAILED)
assert str(node_result) == "something broke"


def test_multi_agent_result_str_single_node(agent_result):
"""Test MultiAgentResult.__str__ with a single node."""
result = MultiAgentResult(
status=Status.COMPLETED,
results={"writer": NodeResult(result=agent_result)},
)
output = str(result)
assert "writer: Test response" in output


def test_multi_agent_result_str_with_interrupts():
"""Test MultiAgentResult.__str__ prioritizes interrupts over node results."""
from strands.interrupt import Interrupt

ar = AgentResult(
message={"role": "assistant", "content": [{"text": "should not appear"}]},
stop_reason="end_turn",
state={},
metrics={},
)
result = MultiAgentResult(
status=Status.INTERRUPTED,
results={"node": NodeResult(result=ar)},
interrupts=[Interrupt(id="int-1", name="approval", reason="needs review")],
)
output = str(result)
assert "should not appear" not in output
assert "approval" in output


def test_multi_agent_result_str_empty():
"""Test MultiAgentResult.__str__ with no results."""
result = MultiAgentResult(status=Status.COMPLETED, results={})
assert str(result) == ""


def test_multi_agent_result_str_multiple_nodes():
"""Test MultiAgentResult.__str__ with multiple nodes."""
ar1 = AgentResult(
message={"role": "assistant", "content": [{"text": "Response 1"}]},
stop_reason="end_turn",
state={},
metrics={},
)
ar2 = AgentResult(
message={"role": "assistant", "content": [{"text": "Response 2"}]},
stop_reason="end_turn",
state={},
metrics={},
)
result = MultiAgentResult(
status=Status.COMPLETED,
results={"node1": NodeResult(result=ar1), "node2": NodeResult(result=ar2)},
)
output = str(result)
assert "node1: Response 1" in output
assert "node2: Response 2" in output
assert "\n" in output


def test_node_result_str_with_nested_multiagent():
"""Test NodeResult.__str__ with nested MultiAgentResult."""
inner_ar = AgentResult(
message={"role": "assistant", "content": [{"text": "Nested response"}]},
stop_reason="end_turn",
state={},
metrics={},
)
inner_mar = MultiAgentResult(
status=Status.COMPLETED,
results={"inner_node": NodeResult(result=inner_ar)},
)
outer_node = NodeResult(result=inner_mar)
assert "inner_node: Nested response" in str(outer_node)