Skip to content

Commit 4600037

Browse files
committed
refactor: simplify AgentResult __str__ to concatenate structured output
Change the __str__ method to append structured output JSON directly to text instead of wrapping both in a JSON object with "text" and "structured_output" keys. This provides a simpler, more predictable output format.
1 parent 85ace40 commit 4600037

File tree

2 files changed

+14
-79
lines changed

2 files changed

+14
-79
lines changed

src/strands/agent/agent_result.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
This module defines the AgentResult class which encapsulates the complete response from an agent's processing cycle.
44
"""
55

6-
import json
76
from collections.abc import Sequence
87
from dataclasses import dataclass
98
from typing import Any, cast
@@ -42,12 +41,7 @@ def __str__(self) -> str:
4241
This method extracts and concatenates all text content from the final message,
4342
including text from both "text" blocks and "citationsContent" blocks.
4443
45-
When both text and structured output exist, the output is JSON-formatted so users
46-
can parse it programmatically:
47-
{"text": "...", "structured_output": {...}}
48-
49-
When only text exists, returns the raw text.
50-
When only structured output exists, returns the JSON of the structured output.
44+
When structured output exists, its JSON representation is appended to the text.
5145
5246
Returns:
5347
The agent's last message as a string, including any structured output.
@@ -70,18 +64,9 @@ def __str__(self) -> str:
7064
# Join text parts with newline, preserving original content
7165
result = "\n".join(text_parts) + "\n" if text_parts else ""
7266

73-
# Always include structured output when present
67+
# Append structured output JSON when present
7468
if self.structured_output:
75-
structured_data = self.structured_output.model_dump()
76-
if text_parts:
77-
# Both text and structured output exist - return JSON-parseable format
78-
# Join text parts without adding extra newlines
79-
text_content = "\n".join(text_parts)
80-
combined = {"text": text_content, "structured_output": structured_data}
81-
return json.dumps(combined)
82-
else:
83-
# Only structured output exists - return just the structured output JSON
84-
return self.structured_output.model_dump_json()
69+
result += self.structured_output.model_dump_json()
8570

8671
return result
8772

tests/strands/agent/test_agent_result.py

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import unittest.mock
32
from typing import cast
43

@@ -244,7 +243,7 @@ def test__init__structured_output_defaults_to_none(mock_metrics, simple_message:
244243

245244

246245
def test__str__with_structured_output(mock_metrics, simple_message: Message):
247-
"""Test that str() includes BOTH text and structured_output in JSON format."""
246+
"""Test that str() concatenates text and structured_output."""
248247
structured_output = StructuredOutputModel(name="test", value=42)
249248

250249
result = AgentResult(
@@ -255,13 +254,9 @@ def test__str__with_structured_output(mock_metrics, simple_message: Message):
255254
structured_output=structured_output,
256255
)
257256

258-
# str() should now include BOTH text AND structured output in JSON format
257+
# str() should concatenate text and structured output
259258
message_string = str(result)
260-
# Output should be valid JSON
261-
parsed = json.loads(message_string)
262-
assert parsed["text"] == "Hello world!"
263-
assert parsed["structured_output"]["name"] == "test"
264-
assert parsed["structured_output"]["value"] == 42
259+
assert message_string == 'Hello world!\n{"name":"test","value":42,"optional_field":null}'
265260

266261

267262
def test__str__empty_message_with_structured_output(mock_metrics, empty_message: Message):
@@ -303,10 +298,7 @@ def test__str__structured_output_only():
303298

304299

305300
def test__str__both_text_and_structured_output():
306-
"""Test __str__ includes BOTH text and structured output when both exist.
307-
308-
Output should be JSON-parseable with text and structured_output fields.
309-
"""
301+
"""Test __str__ concatenates text and structured output when both exist."""
310302
structured = StructuredOutputModel(name="test", value=42)
311303
result = AgentResult(
312304
stop_reason="end_turn",
@@ -316,11 +308,7 @@ def test__str__both_text_and_structured_output():
316308
structured_output=structured,
317309
)
318310
output = str(result)
319-
# Output should be valid JSON
320-
parsed = json.loads(output)
321-
assert parsed["text"] == "Here is the analysis"
322-
assert parsed["structured_output"]["name"] == "test"
323-
assert parsed["structured_output"]["value"] == 42
311+
assert output == 'Here is the analysis\n{"name":"test","value":42,"optional_field":null}'
324312

325313

326314
def test__str__multiple_text_blocks_with_structured_output():
@@ -340,12 +328,7 @@ def test__str__multiple_text_blocks_with_structured_output():
340328
structured_output=structured,
341329
)
342330
output = str(result)
343-
# Output should be valid JSON
344-
parsed = json.loads(output)
345-
assert "First paragraph." in parsed["text"]
346-
assert "Second paragraph." in parsed["text"]
347-
assert parsed["structured_output"]["name"] == "multi"
348-
assert parsed["structured_output"]["value"] == 100
331+
assert output == 'First paragraph.\nSecond paragraph.\n{"name":"multi","value":100,"optional_field":null}'
349332

350333

351334
def test__str__non_text_content_only():
@@ -379,37 +362,11 @@ def test__str__mixed_content_with_structured_output():
379362
structured_output=structured,
380363
)
381364
output = str(result)
382-
# Output should be valid JSON
383-
parsed = json.loads(output)
384-
assert parsed["text"] == "Processing complete."
385-
assert parsed["structured_output"]["name"] == "mixed"
386-
assert parsed["structured_output"]["value"] == 50
387-
# toolUse should not appear in the text
388-
assert "toolUse" not in parsed["text"]
389-
assert "helper" not in parsed["text"]
390-
391-
392-
def test__str__json_parseable():
393-
"""Test that output with both text and structured output is JSON-parseable."""
394-
structured = StructuredOutputModel(name="parseable", value=99)
395-
result = AgentResult(
396-
stop_reason="end_turn",
397-
message={"role": "assistant", "content": [{"text": "Result text"}]},
398-
metrics=EventLoopMetrics(),
399-
state={},
400-
structured_output=structured,
401-
)
402-
output = str(result)
403-
# Should be valid JSON that can be parsed
404-
parsed = json.loads(output)
405-
assert "text" in parsed
406-
assert "structured_output" in parsed
407-
assert parsed["text"] == "Result text"
408-
assert parsed["structured_output"] == {"name": "parseable", "value": 99, "optional_field": None}
365+
assert output == 'Processing complete.\n{"name":"mixed","value":50,"optional_field":null}'
409366

410367

411368
def test__str__citations_with_structured_output(mock_metrics, citations_message: Message):
412-
"""Test that str() includes BOTH citationsContent text and structured_output."""
369+
"""Test that str() concatenates citationsContent text and structured_output."""
413370
structured_output = StructuredOutputModel(name="cited", value=77)
414371

415372
result = AgentResult(
@@ -421,11 +378,7 @@ def test__str__citations_with_structured_output(mock_metrics, citations_message:
421378
)
422379

423380
message_string = str(result)
424-
# Output should be valid JSON with both text and structured output
425-
parsed = json.loads(message_string)
426-
assert parsed["text"] == "This is cited text from the document."
427-
assert parsed["structured_output"]["name"] == "cited"
428-
assert parsed["structured_output"]["value"] == 77
381+
assert message_string == 'This is cited text from the document.\n{"name":"cited","value":77,"optional_field":null}'
429382

430383

431384
def test__str__mixed_text_citations_with_structured_output(mock_metrics, mixed_text_and_citations_message: Message):
@@ -441,8 +394,5 @@ def test__str__mixed_text_citations_with_structured_output(mock_metrics, mixed_t
441394
)
442395

443396
message_string = str(result)
444-
# Output should be valid JSON
445-
parsed = json.loads(message_string)
446-
assert parsed["text"] == "Introduction paragraph\nCited content here.\nConclusion paragraph"
447-
assert parsed["structured_output"]["name"] == "complex"
448-
assert parsed["structured_output"]["value"] == 999
397+
expected = 'Introduction paragraph\nCited content here.\nConclusion paragraph\n{"name":"complex","value":999,"optional_field":null}'
398+
assert message_string == expected

0 commit comments

Comments
 (0)