-
Notifications
You must be signed in to change notification settings - Fork 0
feat(hooks): expose input messages to BeforeInvocationEvent #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this. Its too specific for this document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this. the purpose of messages should be covered in the Attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this since its a no-op test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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