feat(hooks): expose input messages to BeforeInvocationEvent#9
feat(hooks): expose input messages to BeforeInvocationEvent#9github-actions[bot] wants to merge 5 commits intomainfrom
Conversation
docs/HOOKS.md
Outdated
|
|
||
| ## Input Guardrails with BeforeInvocationEvent | ||
|
|
||
| The `BeforeInvocationEvent` provides access to input messages through its `messages` attribute, enabling hooks to implement input-side guardrails that run before messages are added to the agent's conversation history. | ||
|
|
||
| ### Use Cases | ||
|
|
||
| - **PII Detection/Redaction**: Scan and redact sensitive information before processing | ||
| - **Content Moderation**: Filter toxic or inappropriate content | ||
| - **Prompt Attack Prevention**: Detect and block malicious prompt injection attempts | ||
|
|
||
| ### Example: Input Redaction Hook | ||
|
|
||
| ```python | ||
| from strands import Agent | ||
| from strands.hooks import BeforeInvocationEvent, HookProvider, HookRegistry | ||
|
|
||
| class InputGuardrailHook(HookProvider): | ||
| def register_hooks(self, registry: HookRegistry) -> None: | ||
| registry.add_callback(BeforeInvocationEvent, self.check_input) | ||
|
|
||
| async def check_input(self, event: BeforeInvocationEvent) -> None: | ||
| if event.messages is None: | ||
| return | ||
|
|
||
| for message in event.messages: | ||
| if message.get("role") == "user": | ||
| content = message.get("content", []) | ||
| for block in content: | ||
| if "text" in block: | ||
| # Option 1: Redact in-place | ||
| block["text"] = redact_pii(block["text"]) | ||
|
|
||
| # Option 2: Abort invocation by raising an exception | ||
| # if contains_malicious_content(block["text"]): | ||
| # raise ValueError("Malicious content detected") | ||
|
|
||
| agent = Agent(hooks=[InputGuardrailHook()]) | ||
| agent("Process this message") # Guardrail runs before message is added to memory | ||
| ``` | ||
|
|
||
| ### Key Behaviors | ||
|
|
||
| - `messages` defaults to `None` for backward compatibility (e.g., when invoked from deprecated methods) | ||
| - `messages` is writable, allowing hooks to modify content in-place | ||
| - The `AfterInvocationEvent` is always triggered even if a hook raises an exception, maintaining the paired event guarantee |
There was a problem hiding this comment.
Remove this. Its too specific for this document.
There was a problem hiding this comment.
Done - removed the Input Guardrails section from HOOKS.md.
src/strands/hooks/events.py
Outdated
| The messages attribute provides access to the input messages for this invocation, | ||
| allowing hooks to inspect or modify message content before processing. This is | ||
| particularly useful for implementing input guardrails (e.g., PII detection, | ||
| content moderation, prompt attack prevention) that need to run before messages | ||
| are added to the agent's conversation history. | ||
|
|
There was a problem hiding this comment.
Remove this. the purpose of messages should be covered in the Attributes.
There was a problem hiding this comment.
Done - removed the detailed guardrails description from the docstring.
src/strands/hooks/events.py
Outdated
| to redact or transform content before processing. May be None for | ||
| backward compatibility or when invoked from deprecated methods. |
There was a problem hiding this comment.
| to redact or transform content before processing. May be None for | |
| backward compatibility or when invoked from deprecated methods. | |
| to redact or transform content before processing. |
There was a problem hiding this comment.
Done - removed the backward compatibility note from the attribute description.
| def test_before_invocation_event_messages_set_on_init(agent, sample_messages): | ||
| """Test that BeforeInvocationEvent.messages can be set on initialization.""" | ||
| event = BeforeInvocationEvent(agent=agent, messages=sample_messages) | ||
| assert event.messages is sample_messages | ||
| assert event.messages == [{"role": "user", "content": [{"text": "Hello, agent!"}]}] |
There was a problem hiding this comment.
Remove this since its a no-op test.
There was a problem hiding this comment.
Done - removed the no-op test.
|
/strands |
Add messages attribute to BeforeInvocationEvent to enable input-side guardrails for PII detection, content moderation, and prompt attack prevention. Hooks can now inspect and modify messages before they are added to the agent's conversation history. - Add writable messages attribute to BeforeInvocationEvent (None default) - Pass messages parameter from _run_loop() to BeforeInvocationEvent - Add unit tests for new messages attribute and writability - Add integration tests for message modification use case - Update docs/HOOKS.md with input guardrails documentation Resolves #8
- Remove detailed Input Guardrails section from docs/HOOKS.md - Simplify BeforeInvocationEvent docstring per review - Remove backward compatibility note from messages attribute - Remove no-op test for messages initialization
d244c27 to
128730b
Compare
| # Verify BeforeInvocationEvent includes messages | ||
| before_event_2 = next(events) | ||
| assert isinstance(before_event_2, BeforeInvocationEvent) |
There was a problem hiding this comment.
Any reason this can't be something like:
assert next(events) == BeforeInvocationEvent(agent=agent, messages=agent.messages[0:1])
That would be a lot more concise & readable
There was a problem hiding this comment.
Good point! Updated to use the concise equality comparison pattern.
|
/strands |
Use concise equality comparison for BeforeInvocationEvent assertions instead of verbose instance checks and property assertions.
29646b1 to
bae96c0
Compare
664745d to
ab51706
Compare
Motivation
Hook providers implementing input-side guardrails (PII detection, content moderation, prompt attack prevention) need access to input messages before they are added to the agent's conversation history. The
BeforeInvocationEventpreviously only provided a reference to the agent with no visibility into incoming messages, forcing guardrail implementations to hook onMessageAddedEventinstead—which runs after messages are already in memory and creates ordering dependencies with session/memory managers.Resolves #8
Public API Changes
BeforeInvocationEventnow includes a writablemessagesattribute:The
messagesattribute defaults toNonefor backward compatibility with deprecated code paths.Use Cases