Skip to content

Commit 90c1b6f

Browse files
committed
Revert "fix: properly redact toolResult blocks (strands-agents#1080)"
This reverts commit 321b4ac.
1 parent 321b4ac commit 90c1b6f

File tree

3 files changed

+5
-160
lines changed

3 files changed

+5
-160
lines changed

src/strands/agent/agent.py

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -752,9 +752,9 @@ async def _run_loop(
752752
and event.chunk.get("redactContent")
753753
and event.chunk["redactContent"].get("redactUserContentMessage")
754754
):
755-
self.messages[-1]["content"] = self._redact_user_content(
756-
self.messages[-1]["content"], str(event.chunk["redactContent"]["redactUserContentMessage"])
757-
)
755+
self.messages[-1]["content"] = [
756+
{"text": str(event.chunk["redactContent"]["redactUserContentMessage"])}
757+
]
758758
if self._session_manager:
759759
self._session_manager.redact_latest_message(self.messages[-1], self)
760760
yield event
@@ -969,29 +969,3 @@ def _append_message(self, message: Message) -> None:
969969
"""Appends a message to the agent's list of messages and invokes the callbacks for the MessageCreatedEvent."""
970970
self.messages.append(message)
971971
self.hooks.invoke_callbacks(MessageAddedEvent(agent=self, message=message))
972-
973-
def _redact_user_content(self, content: list[ContentBlock], redact_message: str) -> list[ContentBlock]:
974-
"""Redact user content preserving toolResult blocks.
975-
976-
Args:
977-
content: content blocks to be redacted
978-
redact_message: redact message to be replaced
979-
980-
Returns:
981-
Redacted content, as follows:
982-
- if the message contains at least a toolResult block,
983-
all toolResult blocks(s) are kept, redacting only the result content;
984-
- otherwise, the entire content of the message is replaced
985-
with a single text block with the redact message.
986-
"""
987-
redacted_content = []
988-
for block in content:
989-
if "toolResult" in block:
990-
block["toolResult"]["content"] = [{"text": redact_message}]
991-
redacted_content.append(block)
992-
993-
if not redacted_content:
994-
# Text content is added only if no toolResult blocks were found
995-
redacted_content = [{"text": redact_message}]
996-
997-
return redacted_content

tests/strands/agent/test_agent.py

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,59 +2160,3 @@ def shell(command: str):
21602160

21612161
# And that it continued to the LLM call
21622162
assert agent.messages[-1] == {"content": [{"text": "I invoked a tool!"}], "role": "assistant"}
2163-
2164-
2165-
2166-
@pytest.mark.parametrize(
2167-
"content, expected",
2168-
[
2169-
# Single toolResult block - preserves structure, redacts content
2170-
(
2171-
[{"toolResult": {"toolUseId": "123", "content": [{"text": "original result"}], "status": "success"}}],
2172-
[{"toolResult": {"toolUseId": "123", "content": [{"text": "REDACTED"}], "status": "success"}}],
2173-
),
2174-
# Multiple toolResult blocks - preserves all, redacts each content
2175-
(
2176-
[
2177-
{"toolResult": {"toolUseId": "123", "content": [{"text": "result1"}], "status": "success"}},
2178-
{"toolResult": {"toolUseId": "456", "content": [{"text": "result2"}], "status": "error"}},
2179-
],
2180-
[
2181-
{"toolResult": {"toolUseId": "123", "content": [{"text": "REDACTED"}], "status": "success"}},
2182-
{"toolResult": {"toolUseId": "456", "content": [{"text": "REDACTED"}], "status": "error"}},
2183-
],
2184-
),
2185-
# Text only content - replaces with single text block
2186-
(
2187-
[{"text": "sensitive data"}],
2188-
[{"text": "REDACTED"}],
2189-
),
2190-
# Mixed content with toolResult - keeps only toolResult blocks
2191-
# (This should not actually happen, toolResult is never mixed with other content)
2192-
(
2193-
[
2194-
{"text": "some text"},
2195-
{"toolResult": {"toolUseId": "789", "content": [{"text": "tool output"}], "status": "success"}},
2196-
{"image": {"format": "png", "source": {"bytes": b"fake_data"}}},
2197-
],
2198-
[{"toolResult": {"toolUseId": "789", "content": [{"text": "REDACTED"}], "status": "success"}}],
2199-
),
2200-
# Empty content - returns single text block
2201-
(
2202-
[],
2203-
[{"text": "REDACTED"}],
2204-
),
2205-
],
2206-
ids=[
2207-
"single_tool_result",
2208-
"multiple_tool_results",
2209-
"text_only",
2210-
"mixed_content_with_tool_result",
2211-
"empty_content",
2212-
],
2213-
)
2214-
def test_redact_user_content(content, expected):
2215-
"""Test _redact_user_content function with various content types."""
2216-
agent = Agent()
2217-
result = agent._redact_user_content(content, "REDACTED")
2218-
assert result == expected

tests_integ/test_bedrock_guardrails.py

Lines changed: 2 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import boto3
66
import pytest
77

8-
from strands import Agent, tool
8+
from strands import Agent
99
from strands.models.bedrock import BedrockModel
1010
from strands.session.file_session_manager import FileSessionManager
1111

@@ -187,7 +187,7 @@ def test_guardrail_output_intervention_redact_output(bedrock_guardrail, processi
187187
In async streaming: The buffering is non-blocking.
188188
Tokens are streamed while Guardrails processes the buffered content in the background.
189189
This means the response may be returned before Guardrails has finished processing.
190-
As a result, we cannot guarantee that the REDACT_MESSAGE is in the response.
190+
As a result, we cannot guarantee that the REDACT_MESSAGE is in the response
191191
"""
192192
if processing_mode == "sync":
193193
assert REDACT_MESSAGE in str(response1)
@@ -203,79 +203,6 @@ def test_guardrail_output_intervention_redact_output(bedrock_guardrail, processi
203203
)
204204

205205

206-
@pytest.mark.parametrize("processing_mode", ["sync", "async"])
207-
def test_guardrail_intervention_properly_redacts_tool_result(bedrock_guardrail, processing_mode):
208-
INPUT_REDACT_MESSAGE = "Input redacted."
209-
OUTPUT_REDACT_MESSAGE = "Output redacted."
210-
bedrock_model = BedrockModel(
211-
guardrail_id=bedrock_guardrail,
212-
guardrail_version="DRAFT",
213-
guardrail_stream_processing_mode=processing_mode,
214-
guardrail_redact_output=True,
215-
guardrail_redact_input_message=INPUT_REDACT_MESSAGE,
216-
guardrail_redact_output_message=OUTPUT_REDACT_MESSAGE,
217-
region_name="us-east-1",
218-
)
219-
220-
@tool
221-
def list_users() -> str:
222-
"List my users"
223-
return """[{"name": "Jerry Merry"}, {"name": "Mr. CACTUS"}]"""
224-
225-
agent = Agent(
226-
model=bedrock_model,
227-
system_prompt="You are a helpful assistant.",
228-
callback_handler=None,
229-
load_tools_from_directory=False,
230-
tools=[list_users],
231-
)
232-
233-
response1 = agent("List my users.")
234-
response2 = agent("Thank you!")
235-
236-
""" Message sequence:
237-
0 (user): request1
238-
1 (assistant): reasoning + tool call
239-
2 (user): tool result
240-
3 (assistant): response1 -> output guardrail intervenes
241-
4 (user): request2
242-
5 (assistant): response2
243-
244-
Guardrail intervened on output in message 3 will cause
245-
the redaction of the preceding input (message 2) and message 3.
246-
"""
247-
248-
assert response1.stop_reason == "guardrail_intervened"
249-
250-
if processing_mode == "sync":
251-
""" In sync mode the guardrail processing is blocking.
252-
The response is already blocked and redacted. """
253-
254-
assert OUTPUT_REDACT_MESSAGE in str(response1)
255-
assert OUTPUT_REDACT_MESSAGE not in str(response2)
256-
257-
"""
258-
In async streaming, the buffering is non-blocking,
259-
so the response may be returned before Guardrails has finished processing.
260-
261-
However, in both sync and async, with guardrail_redact_output=True:
262-
263-
1. the content should be properly redacted in memory, so that
264-
response2 is not blocked by guardrails;
265-
"""
266-
assert response2.stop_reason != "guardrail_intervened"
267-
268-
"""
269-
2. the tool result block should be redacted properly, so that the
270-
conversation is not corrupted.
271-
"""
272-
273-
tool_call = [b for b in agent.messages[1]["content"] if "toolUse" in b][0]["toolUse"]
274-
tool_result = [b for b in agent.messages[2]["content"] if "toolResult" in b][0]["toolResult"]
275-
assert tool_result["toolUseId"] == tool_call["toolUseId"]
276-
assert tool_result["content"][0]["text"] == INPUT_REDACT_MESSAGE
277-
278-
279206
def test_guardrail_input_intervention_properly_redacts_in_session(boto_session, bedrock_guardrail, temp_dir):
280207
bedrock_model = BedrockModel(
281208
guardrail_id=bedrock_guardrail,

0 commit comments

Comments
 (0)