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
18 changes: 18 additions & 0 deletions src/strands/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions src/strands/agent/agent_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
29 changes: 29 additions & 0 deletions tests/strands/agent/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
66 changes: 66 additions & 0 deletions tests/strands/agent/test_agent_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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