Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8858f9d
support redactedContent
mazyu36 May 28, 2025
4a19f45
test
mazyu36 May 28, 2025
530e2ce
fix
mazyu36 Jun 4, 2025
3af994e
Merge remote-tracking branch 'upstream/main' into feature/redactedCon…
mazyu36 Jun 4, 2025
3310114
add callback test
mazyu36 Jun 4, 2025
797c643
Merge remote-tracking branch 'strands/main' into feature/redactedContent
awsarron Jun 10, 2025
4eec5f1
format
awsarron Jun 16, 2025
1d462e7
remove unused ignore type
awsarron Jun 16, 2025
ed10796
Merge branch 'main' into feature/redactedContent
awsarron Jun 16, 2025
b75fa27
Merge branch 'main' into feature/redactedContent
afarntrog Sep 11, 2025
5a43f9e
feat: add RedactedContentStreamEvent for proper redacted content hand…
afarntrog Sep 11, 2025
f7d161c
tests
afarntrog Sep 11, 2025
8eb5abe
update RedactedContentStreamEvent to make reasoning optional with def…
afarntrog Sep 11, 2025
206b829
add redactedContent to state only when present
afarntrog Sep 12, 2025
f11b134
update event type
afarntrog Sep 12, 2025
c9c419e
Merge branch 'main' into feature/redactedContent
afarntrog Sep 12, 2025
0110789
update event type
afarntrog Sep 12, 2025
46002eb
use reasoningRedactedContent bec we will remove the reasoning param i…
afarntrog Sep 12, 2025
a1f4ea4
use reasoningRedactedContent bec we will remove the reasoning param i…
afarntrog Sep 12, 2025
d50b1d4
test updates and typed event param removed as discussed
afarntrog Sep 15, 2025
9aaf4fa
add test agent events for redacted reasoning content
afarntrog Sep 15, 2025
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
9 changes: 9 additions & 0 deletions src/strands/event_loop/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ModelStopReason,
ModelStreamChunkEvent,
ModelStreamEvent,
ReasoningRedactedContentStreamEvent,
ReasoningSignatureStreamEvent,
ReasoningTextStreamEvent,
TextStreamEvent,
Expand Down Expand Up @@ -170,6 +171,10 @@ def handle_content_block_delta(
delta=delta_content,
)

elif redacted_content := delta_content["reasoningContent"].get("redactedContent"):
state["redactedContent"] = state.get("redactedContent", b"") + redacted_content
typed_event = ReasoningRedactedContentStreamEvent(redacted_content=redacted_content, delta=delta_content)

return state, typed_event


Expand All @@ -188,6 +193,7 @@ def handle_content_block_stop(state: dict[str, Any]) -> dict[str, Any]:
text = state["text"]
reasoning_text = state["reasoningText"]
citations_content = state["citationsContent"]
redacted_content = state.get("redactedContent")

if current_tool_use:
if "input" not in current_tool_use:
Expand Down Expand Up @@ -231,6 +237,9 @@ def handle_content_block_stop(state: dict[str, Any]) -> dict[str, Any]:

content.append(content_block)
state["reasoningText"] = ""
elif redacted_content:
content.append({"reasoningContent": {"redactedContent": redacted_content}})
state["redactedContent"] = b""

return state

Expand Down
8 changes: 8 additions & 0 deletions src/strands/types/_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ def __init__(self, delta: ContentBlockDelta, reasoning_text: str | None) -> None
super().__init__({"reasoningText": reasoning_text, "delta": delta, "reasoning": True})


class ReasoningRedactedContentStreamEvent(ModelStreamEvent):
"""Event emitted during redacted content streaming."""

def __init__(self, delta: ContentBlockDelta, redacted_content: bytes | None) -> None:
"""Initialize with delta and redacted content."""
super().__init__({"reasoningRedactedContent": redacted_content, "delta": delta, "reasoning": True})


class ReasoningSignatureStreamEvent(ModelStreamEvent):
"""Event emitted during reasoning signature streaming."""

Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/mocked_model_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def map_agent_message_to_events(self, agent_message: Union[Message, RedactionMes
stop_reason = "guardrail_intervened"
else:
for content in agent_message["content"]:
if "reasoningContent" in content:
yield {"contentBlockStart": {"start": {}}}
yield {"contentBlockDelta": {"delta": {"reasoningContent": content["reasoningContent"]}}}
yield {"contentBlockStop": {}}
if "text" in content:
yield {"contentBlockStart": {"start": {}}}
yield {"contentBlockDelta": {"delta": {"text": content["text"]}}}
Expand Down
78 changes: 78 additions & 0 deletions tests/strands/agent/hooks/test_agent_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,84 @@ async def test_stream_e2e_throttle_and_redact(alist, mock_sleep):
assert typed_events == []


@pytest.mark.asyncio
async def test_stream_e2e_reasoning_redacted_content(alist):
mock_provider = MockedModelProvider(
[
{
"role": "assistant",
"content": [
{"reasoningContent": {"redactedContent": b"test_redacted_data"}},
{"text": "Response with redacted reasoning"},
],
},
]
)

mock_callback = unittest.mock.Mock()
agent = Agent(model=mock_provider, callback_handler=mock_callback)

stream = agent.stream_async("Test redacted content")

tru_events = await alist(stream)
exp_events = [
{"init_event_loop": True},
{"start": True},
{"start_event_loop": True},
{"event": {"messageStart": {"role": "assistant"}}},
{"event": {"contentBlockStart": {"start": {}}}},
{"event": {"contentBlockDelta": {"delta": {"reasoningContent": {"redactedContent": b"test_redacted_data"}}}}},
{
**any_props,
"reasoningRedactedContent": b"test_redacted_data",
"delta": {"reasoningContent": {"redactedContent": b"test_redacted_data"}},
"reasoning": True,
},
{"event": {"contentBlockStop": {}}},
{"event": {"contentBlockStart": {"start": {}}}},
{"event": {"contentBlockDelta": {"delta": {"text": "Response with redacted reasoning"}}}},
{
**any_props,
"data": "Response with redacted reasoning",
"delta": {"text": "Response with redacted reasoning"},
},
{"event": {"contentBlockStop": {}}},
{"event": {"messageStop": {"stopReason": "end_turn"}}},
{
"message": {
"content": [
{"reasoningContent": {"redactedContent": b"test_redacted_data"}},
{"text": "Response with redacted reasoning"},
],
"role": "assistant",
}
},
{
"result": AgentResult(
stop_reason="end_turn",
message={
"content": [
{"reasoningContent": {"redactedContent": b"test_redacted_data"}},
{"text": "Response with redacted reasoning"},
],
"role": "assistant",
},
metrics=ANY,
state={},
)
},
]
assert tru_events == exp_events

exp_calls = [call(**event) for event in exp_events]
act_calls = mock_callback.call_args_list
assert act_calls == exp_calls

# Ensure that all events coming out of the agent are *not* typed events
typed_events = [event for event in tru_events if isinstance(event, TypedEvent)]
assert typed_events == []


@pytest.mark.asyncio
async def test_event_loop_cycle_text_response_throttling_early_end(
agenerator,
Expand Down
Loading
Loading