Skip to content

Commit 0476544

Browse files
authored
Merge branch 'strands-agents:main' into feature/agent-config-clean
2 parents 6f44805 + eef11cc commit 0476544

24 files changed

+495
-282
lines changed

src/strands/event_loop/event_loop.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@
1515

1616
from opentelemetry import trace as trace_api
1717

18-
from ..experimental.hooks import (
19-
AfterModelInvocationEvent,
20-
BeforeModelInvocationEvent,
21-
)
22-
from ..hooks import (
23-
MessageAddedEvent,
24-
)
18+
from ..hooks import AfterModelCallEvent, BeforeModelCallEvent, MessageAddedEvent
2519
from ..telemetry.metrics import Trace
2620
from ..telemetry.tracer import get_tracer
2721
from ..tools._validator import validate_and_prepare_tools
@@ -133,7 +127,7 @@ async def event_loop_cycle(agent: "Agent", invocation_state: dict[str, Any]) ->
133127
)
134128
with trace_api.use_span(model_invoke_span):
135129
agent.hooks.invoke_callbacks(
136-
BeforeModelInvocationEvent(
130+
BeforeModelCallEvent(
137131
agent=agent,
138132
)
139133
)
@@ -149,9 +143,9 @@ async def event_loop_cycle(agent: "Agent", invocation_state: dict[str, Any]) ->
149143
invocation_state.setdefault("request_state", {})
150144

151145
agent.hooks.invoke_callbacks(
152-
AfterModelInvocationEvent(
146+
AfterModelCallEvent(
153147
agent=agent,
154-
stop_response=AfterModelInvocationEvent.ModelStopResponse(
148+
stop_response=AfterModelCallEvent.ModelStopResponse(
155149
stop_reason=stop_reason,
156150
message=message,
157151
),
@@ -170,7 +164,7 @@ async def event_loop_cycle(agent: "Agent", invocation_state: dict[str, Any]) ->
170164
tracer.end_span_with_error(model_invoke_span, str(e), e)
171165

172166
agent.hooks.invoke_callbacks(
173-
AfterModelInvocationEvent(
167+
AfterModelCallEvent(
174168
agent=agent,
175169
exception=e,
176170
)

src/strands/experimental/hooks/events.py

Lines changed: 16 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -3,121 +3,19 @@
33
This module defines the events that are emitted as Agents run through the lifecycle of a request.
44
"""
55

6-
from dataclasses import dataclass
7-
from typing import Any, Optional
8-
9-
from ...hooks import HookEvent
10-
from ...types.content import Message
11-
from ...types.streaming import StopReason
12-
from ...types.tools import AgentTool, ToolResult, ToolUse
13-
14-
15-
@dataclass
16-
class BeforeToolInvocationEvent(HookEvent):
17-
"""Event triggered before a tool is invoked.
18-
19-
This event is fired just before the agent executes a tool, allowing hook
20-
providers to inspect, modify, or replace the tool that will be executed.
21-
The selected_tool can be modified by hook callbacks to change which tool
22-
gets executed.
23-
24-
Attributes:
25-
selected_tool: The tool that will be invoked. Can be modified by hooks
26-
to change which tool gets executed. This may be None if tool lookup failed.
27-
tool_use: The tool parameters that will be passed to selected_tool.
28-
invocation_state: Keyword arguments that will be passed to the tool.
29-
"""
30-
31-
selected_tool: Optional[AgentTool]
32-
tool_use: ToolUse
33-
invocation_state: dict[str, Any]
34-
35-
def _can_write(self, name: str) -> bool:
36-
return name in ["selected_tool", "tool_use"]
37-
38-
39-
@dataclass
40-
class AfterToolInvocationEvent(HookEvent):
41-
"""Event triggered after a tool invocation completes.
42-
43-
This event is fired after the agent has finished executing a tool,
44-
regardless of whether the execution was successful or resulted in an error.
45-
Hook providers can use this event for cleanup, logging, or post-processing.
46-
47-
Note: This event uses reverse callback ordering, meaning callbacks registered
48-
later will be invoked first during cleanup.
49-
50-
Attributes:
51-
selected_tool: The tool that was invoked. It may be None if tool lookup failed.
52-
tool_use: The tool parameters that were passed to the tool invoked.
53-
invocation_state: Keyword arguments that were passed to the tool
54-
result: The result of the tool invocation. Either a ToolResult on success
55-
or an Exception if the tool execution failed.
56-
"""
57-
58-
selected_tool: Optional[AgentTool]
59-
tool_use: ToolUse
60-
invocation_state: dict[str, Any]
61-
result: ToolResult
62-
exception: Optional[Exception] = None
63-
64-
def _can_write(self, name: str) -> bool:
65-
return name == "result"
66-
67-
@property
68-
def should_reverse_callbacks(self) -> bool:
69-
"""True to invoke callbacks in reverse order."""
70-
return True
71-
72-
73-
@dataclass
74-
class BeforeModelInvocationEvent(HookEvent):
75-
"""Event triggered before the model is invoked.
76-
77-
This event is fired just before the agent calls the model for inference,
78-
allowing hook providers to inspect or modify the messages and configuration
79-
that will be sent to the model.
80-
81-
Note: This event is not fired for invocations to structured_output.
82-
"""
83-
84-
pass
85-
86-
87-
@dataclass
88-
class AfterModelInvocationEvent(HookEvent):
89-
"""Event triggered after the model invocation completes.
90-
91-
This event is fired after the agent has finished calling the model,
92-
regardless of whether the invocation was successful or resulted in an error.
93-
Hook providers can use this event for cleanup, logging, or post-processing.
94-
95-
Note: This event uses reverse callback ordering, meaning callbacks registered
96-
later will be invoked first during cleanup.
97-
98-
Note: This event is not fired for invocations to structured_output.
99-
100-
Attributes:
101-
stop_response: The model response data if invocation was successful, None if failed.
102-
exception: Exception if the model invocation failed, None if successful.
103-
"""
104-
105-
@dataclass
106-
class ModelStopResponse:
107-
"""Model response data from successful invocation.
108-
109-
Attributes:
110-
stop_reason: The reason the model stopped generating.
111-
message: The generated message from the model.
112-
"""
113-
114-
message: Message
115-
stop_reason: StopReason
116-
117-
stop_response: Optional[ModelStopResponse] = None
118-
exception: Optional[Exception] = None
119-
120-
@property
121-
def should_reverse_callbacks(self) -> bool:
122-
"""True to invoke callbacks in reverse order."""
123-
return True
6+
import warnings
7+
from typing import TypeAlias
8+
9+
from ...hooks.events import AfterModelCallEvent, AfterToolCallEvent, BeforeModelCallEvent, BeforeToolCallEvent
10+
11+
warnings.warn(
12+
"These events have been moved to production with updated names. Use BeforeModelCallEvent, "
13+
"AfterModelCallEvent, BeforeToolCallEvent, and AfterToolCallEvent from strands.hooks instead.",
14+
DeprecationWarning,
15+
stacklevel=2,
16+
)
17+
18+
BeforeToolInvocationEvent: TypeAlias = BeforeToolCallEvent
19+
AfterToolInvocationEvent: TypeAlias = AfterToolCallEvent
20+
BeforeModelInvocationEvent: TypeAlias = BeforeModelCallEvent
21+
AfterModelInvocationEvent: TypeAlias = AfterModelCallEvent

src/strands/hooks/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,29 @@ def log_end(self, event: AfterInvocationEvent) -> None:
3131

3232
from .events import (
3333
AfterInvocationEvent,
34+
AfterModelCallEvent,
35+
AfterToolCallEvent,
3436
AgentInitializedEvent,
3537
BeforeInvocationEvent,
38+
BeforeModelCallEvent,
39+
BeforeToolCallEvent,
3640
MessageAddedEvent,
3741
)
38-
from .registry import HookCallback, HookEvent, HookProvider, HookRegistry
42+
from .registry import BaseHookEvent, HookCallback, HookEvent, HookProvider, HookRegistry
3943

4044
__all__ = [
4145
"AgentInitializedEvent",
4246
"BeforeInvocationEvent",
47+
"BeforeToolCallEvent",
48+
"AfterToolCallEvent",
49+
"BeforeModelCallEvent",
50+
"AfterModelCallEvent",
4351
"AfterInvocationEvent",
4452
"MessageAddedEvent",
4553
"HookEvent",
4654
"HookProvider",
4755
"HookCallback",
4856
"HookRegistry",
57+
"HookEvent",
58+
"BaseHookEvent",
4959
]

src/strands/hooks/events.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
"""
55

66
from dataclasses import dataclass
7+
from typing import Any, Optional
78

89
from ..types.content import Message
10+
from ..types.streaming import StopReason
11+
from ..types.tools import AgentTool, ToolResult, ToolUse
912
from .registry import HookEvent
1013

1114

@@ -78,3 +81,114 @@ class MessageAddedEvent(HookEvent):
7881
"""
7982

8083
message: Message
84+
85+
86+
@dataclass
87+
class BeforeToolCallEvent(HookEvent):
88+
"""Event triggered before a tool is invoked.
89+
90+
This event is fired just before the agent executes a tool, allowing hook
91+
providers to inspect, modify, or replace the tool that will be executed.
92+
The selected_tool can be modified by hook callbacks to change which tool
93+
gets executed.
94+
95+
Attributes:
96+
selected_tool: The tool that will be invoked. Can be modified by hooks
97+
to change which tool gets executed. This may be None if tool lookup failed.
98+
tool_use: The tool parameters that will be passed to selected_tool.
99+
invocation_state: Keyword arguments that will be passed to the tool.
100+
"""
101+
102+
selected_tool: Optional[AgentTool]
103+
tool_use: ToolUse
104+
invocation_state: dict[str, Any]
105+
106+
def _can_write(self, name: str) -> bool:
107+
return name in ["selected_tool", "tool_use"]
108+
109+
110+
@dataclass
111+
class AfterToolCallEvent(HookEvent):
112+
"""Event triggered after a tool invocation completes.
113+
114+
This event is fired after the agent has finished executing a tool,
115+
regardless of whether the execution was successful or resulted in an error.
116+
Hook providers can use this event for cleanup, logging, or post-processing.
117+
118+
Note: This event uses reverse callback ordering, meaning callbacks registered
119+
later will be invoked first during cleanup.
120+
121+
Attributes:
122+
selected_tool: The tool that was invoked. It may be None if tool lookup failed.
123+
tool_use: The tool parameters that were passed to the tool invoked.
124+
invocation_state: Keyword arguments that were passed to the tool
125+
result: The result of the tool invocation. Either a ToolResult on success
126+
or an Exception if the tool execution failed.
127+
"""
128+
129+
selected_tool: Optional[AgentTool]
130+
tool_use: ToolUse
131+
invocation_state: dict[str, Any]
132+
result: ToolResult
133+
exception: Optional[Exception] = None
134+
135+
def _can_write(self, name: str) -> bool:
136+
return name == "result"
137+
138+
@property
139+
def should_reverse_callbacks(self) -> bool:
140+
"""True to invoke callbacks in reverse order."""
141+
return True
142+
143+
144+
@dataclass
145+
class BeforeModelCallEvent(HookEvent):
146+
"""Event triggered before the model is invoked.
147+
148+
This event is fired just before the agent calls the model for inference,
149+
allowing hook providers to inspect or modify the messages and configuration
150+
that will be sent to the model.
151+
152+
Note: This event is not fired for invocations to structured_output.
153+
"""
154+
155+
pass
156+
157+
158+
@dataclass
159+
class AfterModelCallEvent(HookEvent):
160+
"""Event triggered after the model invocation completes.
161+
162+
This event is fired after the agent has finished calling the model,
163+
regardless of whether the invocation was successful or resulted in an error.
164+
Hook providers can use this event for cleanup, logging, or post-processing.
165+
166+
Note: This event uses reverse callback ordering, meaning callbacks registered
167+
later will be invoked first during cleanup.
168+
169+
Note: This event is not fired for invocations to structured_output.
170+
171+
Attributes:
172+
stop_response: The model response data if invocation was successful, None if failed.
173+
exception: Exception if the model invocation failed, None if successful.
174+
"""
175+
176+
@dataclass
177+
class ModelStopResponse:
178+
"""Model response data from successful invocation.
179+
180+
Attributes:
181+
stop_reason: The reason the model stopped generating.
182+
message: The generated message from the model.
183+
"""
184+
185+
message: Message
186+
stop_reason: StopReason
187+
188+
stop_response: Optional[ModelStopResponse] = None
189+
exception: Optional[Exception] = None
190+
191+
@property
192+
def should_reverse_callbacks(self) -> bool:
193+
"""True to invoke callbacks in reverse order."""
194+
return True

src/strands/hooks/registry.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,8 @@
1515

1616

1717
@dataclass
18-
class HookEvent:
19-
"""Base class for all hook events.
20-
21-
Attributes:
22-
agent: The agent instance that triggered this event.
23-
"""
24-
25-
agent: "Agent"
18+
class BaseHookEvent:
19+
"""Base class for all hook events."""
2620

2721
@property
2822
def should_reverse_callbacks(self) -> bool:
@@ -66,10 +60,21 @@ def __setattr__(self, name: str, value: Any) -> None:
6660
raise AttributeError(f"Property {name} is not writable")
6761

6862

69-
TEvent = TypeVar("TEvent", bound=HookEvent, contravariant=True)
63+
@dataclass
64+
class HookEvent(BaseHookEvent):
65+
"""Base class for single agent hook events.
66+
67+
Attributes:
68+
agent: The agent instance that triggered this event.
69+
"""
70+
71+
agent: "Agent"
72+
73+
74+
TEvent = TypeVar("TEvent", bound=BaseHookEvent, contravariant=True)
7075
"""Generic for adding callback handlers - contravariant to allow adding handlers which take in base classes."""
7176

72-
TInvokeEvent = TypeVar("TInvokeEvent", bound=HookEvent)
77+
TInvokeEvent = TypeVar("TInvokeEvent", bound=BaseHookEvent)
7378
"""Generic for invoking events - non-contravariant to enable returning events."""
7479

7580

src/strands/hooks/rules.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
- All hook events have a suffix of `Event`
1111
- Paired events follow the naming convention of `Before{Item}Event` and `After{Item}Event`
12+
- Pre actions in the name. i.e. prefer `BeforeToolCallEvent` over `BeforeToolEvent`.
1213

1314
## Paired Events
1415

@@ -17,4 +18,4 @@
1718

1819
## Writable Properties
1920

20-
For events with writable properties, those values are re-read after invoking the hook callbacks and used in subsequent processing. For example, `BeforeToolInvocationEvent.selected_tool` is writable - after invoking the callback for `BeforeToolInvocationEvent`, the `selected_tool` takes effect for the tool call.
21+
For events with writable properties, those values are re-read after invoking the hook callbacks and used in subsequent processing. For example, `BeforeToolEvent.selected_tool` is writable - after invoking the callback for `BeforeToolEvent`, the `selected_tool` takes effect for the tool call.

0 commit comments

Comments
 (0)