From 2b41dccd6bd192e2c5aa89eb5ff6bdd8208a8ebb Mon Sep 17 00:00:00 2001 From: HaoLiangXu Date: Fri, 20 Mar 2026 20:47:37 +0800 Subject: [PATCH] feat: add AgentResult.text property, AgentResult.__repr__, and Agent.clear() - AgentResult.text: convenience property for extracting text content, equivalent to str(result) but more discoverable and Pythonic - AgentResult.__repr__: debug-friendly representation showing stop_reason and a truncated text preview - Agent.clear(): reset conversation history and event loop metrics in one call, useful for multi-turn testing and conversation restart scenarios --- src/strands/agent/agent.py | 18 +++++++ src/strands/agent/agent_result.py | 27 ++++++++++ tests/strands/agent/test_agent.py | 29 +++++++++++ tests/strands/agent/test_agent_result.py | 66 ++++++++++++++++++++++++ 4 files changed, 140 insertions(+) diff --git a/src/strands/agent/agent.py b/src/strands/agent/agent.py index f378a886a..81e0f2479 100644 --- a/src/strands/agent/agent.py +++ b/src/strands/agent/agent.py @@ -361,6 +361,24 @@ def cancel(self) -> None: """ self._cancel_signal.set() + def clear(self) -> None: + """Clear the agent's conversation history and reset accumulated state. + + This method removes all messages from the conversation history and resets + the event loop metrics. Use this to start a fresh conversation without + creating a new Agent instance. + + Example: + ```python + agent = Agent(model=model) + agent("What is 2+2?") + agent.clear() + agent("Hello!") # Fresh conversation, no prior context + ``` + """ + self.messages.clear() + self.event_loop_metrics.reset_usage_metrics() + @property def system_prompt(self) -> str | None: """Get the system prompt as a string for backwards compatibility. diff --git a/src/strands/agent/agent_result.py b/src/strands/agent/agent_result.py index 63b7a0d4a..35ce16eb8 100644 --- a/src/strands/agent/agent_result.py +++ b/src/strands/agent/agent_result.py @@ -35,6 +35,22 @@ class AgentResult: interrupts: Sequence[Interrupt] | None = None structured_output: BaseModel | None = None + @property + def text(self) -> str: + """Extract the text content from the agent result. + + Returns: + The concatenated text content from the message, same as ``str(self)``. + Returns empty string if no text content is available. + + Example: + ```python + result = agent("Hello!") + print(result.text) # "Hi there! How can I help?" + ``` + """ + return str(self) + def __str__(self) -> str: """Return a string representation of the agent result. @@ -67,6 +83,17 @@ def __str__(self) -> str: return result + def __repr__(self) -> str: + """Return a detailed representation for debugging. + + Returns: + A string containing the stop_reason and a preview of the message text. + """ + text_preview = str(self).strip() + if len(text_preview) > 80: + text_preview = text_preview[:77] + "..." + return f"AgentResult(stop_reason={self.stop_reason!r}, text={text_preview!r})" + @classmethod def from_dict(cls, data: dict[str, Any]) -> "AgentResult": """Rehydrate an AgentResult from persisted JSON. diff --git a/tests/strands/agent/test_agent.py b/tests/strands/agent/test_agent.py index 967a0dafb..1cd786ac9 100644 --- a/tests/strands/agent/test_agent.py +++ b/tests/strands/agent/test_agent.py @@ -2699,3 +2699,32 @@ def hook_callback(event: BeforeModelCallEvent): agent("test") assert len(hook_called) == 1 + + +def test_agent_clear(): + """Test that Agent.clear() removes conversation history and resets metrics.""" + agent = Agent( + model=MockedModelProvider([{"role": "assistant", "content": [{"text": "response"}]}]), + ) + + # Simulate a conversation + agent.messages = [ + {"role": "user", "content": [{"text": "Hello"}]}, + {"role": "assistant", "content": [{"text": "Hi!"}]}, + ] + + agent.clear() + + assert len(agent.messages) == 0 + + +def test_agent_clear_idempotent(): + """Test that clear() can be called multiple times safely.""" + agent = Agent( + model=MockedModelProvider([{"role": "assistant", "content": [{"text": "response"}]}]), + ) + + agent.clear() + agent.clear() + + assert len(agent.messages) == 0 diff --git a/tests/strands/agent/test_agent_result.py b/tests/strands/agent/test_agent_result.py index a4478c3ca..91d456155 100644 --- a/tests/strands/agent/test_agent_result.py +++ b/tests/strands/agent/test_agent_result.py @@ -370,3 +370,69 @@ def test__str__empty_interrupts_returns_agent_message(mock_metrics, simple_messa # Empty list is falsy, should fall through to text content assert message_string == "Hello world!\n" + + +def test_text_property_returns_same_as_str(mock_metrics, simple_message: Message): + """Test that .text property returns the same value as str().""" + result = AgentResult(stop_reason="end_turn", message=simple_message, metrics=mock_metrics, state={}) + + assert result.text == str(result) + assert result.text == "Hello world!\n" + + +def test_text_property_with_empty_message(mock_metrics, empty_message: Message): + """Test that .text returns empty string for empty message.""" + result = AgentResult(stop_reason="end_turn", message=empty_message, metrics=mock_metrics, state={}) + + assert result.text == "" + + +def test_text_property_with_complex_message(mock_metrics, complex_message: Message): + """Test that .text concatenates text blocks from complex messages.""" + result = AgentResult(stop_reason="end_turn", message=complex_message, metrics=mock_metrics, state={}) + + assert result.text == str(result) + assert "First paragraph" in result.text + assert "Second paragraph" in result.text + assert "Third paragraph" in result.text + + +def test_text_property_with_structured_output(mock_metrics, simple_message: Message): + """Test that .text returns structured output JSON 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, + ) + + assert result.text == str(result) + assert '"name": "test"' in result.text or '"name":"test"' in result.text + + +def test_repr(mock_metrics, simple_message: Message): + """Test that __repr__ returns a useful debug representation.""" + result = AgentResult(stop_reason="end_turn", message=simple_message, metrics=mock_metrics, state={}) + + repr_str = repr(result) + + assert "AgentResult(" in repr_str + assert "end_turn" in repr_str + assert "Hello world!" in repr_str + + +def test_repr_with_long_text(mock_metrics): + """Test that __repr__ truncates long text content.""" + long_text = "A" * 200 + long_message: Message = {"role": "assistant", "content": [{"text": long_text}]} + + result = AgentResult(stop_reason="end_turn", message=long_message, metrics=mock_metrics, state={}) + + repr_str = repr(result) + + assert "..." in repr_str + assert len(repr_str) < len(long_text) + 100 # Should be truncated + assert "AgentResult(" in repr_str