Skip to content

Commit cb098d6

Browse files
committed
Update docstrings
1 parent 703cece commit cb098d6

5 files changed

Lines changed: 26 additions & 49 deletions

File tree

src/strands/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
from .agent.agent import Agent
55
from .agent.base import AgentBase
66
from .event_loop._retry import ModelRetryStrategy
7-
from .plugins import Plugin, hook
7+
from .plugins import Plugin
88
from .tools.decorator import tool
99
from .types.tools import ToolContext
1010

1111
__all__ = [
1212
"Agent",
1313
"AgentBase",
1414
"agent",
15-
"hook",
1615
"models",
1716
"ModelRetryStrategy",
1817
"Plugin",

src/strands/plugins/__init__.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,6 @@
22
33
This module provides a composable mechanism for building objects that can
44
extend agent behavior through automatic hook and tool registration.
5-
6-
Example Usage with Decorators (recommended):
7-
```python
8-
from strands.plugins import Plugin, hook
9-
from strands.hooks import BeforeModelCallEvent
10-
from strands import tool
11-
12-
class LoggingPlugin(Plugin):
13-
name = "logging"
14-
15-
@hook
16-
def on_model_call(self, event: BeforeModelCallEvent) -> None:
17-
print(f"Model called for {event.agent.name}")
18-
19-
@tool
20-
def log_message(self, message: str) -> str:
21-
'''Log a message.'''
22-
print(message)
23-
return "Logged"
24-
```
25-
26-
Example Usage with Custom Initialization:
27-
```python
28-
from strands.plugins import Plugin
29-
from strands.hooks import BeforeModelCallEvent
30-
31-
class LoggingPlugin(Plugin):
32-
name = "logging"
33-
34-
def init_agent(self, agent: Agent) -> None:
35-
# Custom initialization - no super() needed
36-
# Decorated hooks/tools are auto-registered by the registry
37-
agent.hooks.add_callback(BeforeModelCallEvent, self.on_model_call)
38-
39-
def on_model_call(self, event: BeforeModelCallEvent) -> None:
40-
print(f"Model called for {event.agent.name}")
41-
```
425
"""
436

447
from .decorator import hook

src/strands/plugins/plugin.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from collections.abc import Awaitable
1010
from typing import TYPE_CHECKING
1111

12+
from ..hooks.registry import HookCallback
1213
from ..tools.decorator import DecoratedFunctionTool
13-
from .decorator import _WrappedHookCallable
1414

1515
if TYPE_CHECKING:
1616
from ..agent import Agent
@@ -27,8 +27,8 @@ class Plugin(ABC):
2727
2828
Attributes:
2929
name: A stable string identifier for the plugin (must be provided by subclass)
30-
hooks: List of hooks the plugin provides, auto-discovered from @hook decorated methods
31-
tools: List of tools the plugin provides, auto-discovered from @tool decorated methods
30+
hooks: Hooks attached to the agent, auto-discovered from @hook decorated methods during __init__
31+
tools: Tools attached to the agent, auto-discovered from @tool decorated methods during __init__
3232
3333
Example using decorators (recommended):
3434
```python
@@ -49,6 +49,10 @@ def my_tool(self, param: str) -> str:
4949
return f"Result: {param}"
5050
```
5151
52+
Note: Decorated methods are registered in declaration order, with parent
53+
class methods registered before child class methods. If a child overrides
54+
a parent's decorated method, only the child's version is registered.
55+
5256
Example with custom initialization:
5357
```python
5458
class MyPlugin(Plugin):
@@ -57,7 +61,7 @@ class MyPlugin(Plugin):
5761
def init_agent(self, agent: Agent) -> None:
5862
# Custom initialization logic - no super() needed
5963
# Decorated hooks/tools are auto-registered by the plugin registry
60-
agent.hooks.add_callback(BeforeModelCallEvent, self.custom_hook)
64+
agent.add_hook(self.custom_hook)
6165
6266
def custom_hook(self, event: BeforeModelCallEvent):
6367
print(event)
@@ -76,12 +80,12 @@ def __init__(self) -> None:
7680
Scans the class for methods decorated with @hook and @tool and stores
7781
references for later registration when the plugin is attached to an agent.
7882
"""
79-
self._hooks: list[_WrappedHookCallable] = []
83+
self._hooks: list[HookCallback] = []
8084
self._tools: list[DecoratedFunctionTool] = []
8185
self._discover_decorated_methods()
8286

8387
@property
84-
def hooks(self) -> list[_WrappedHookCallable]:
88+
def hooks(self) -> list[HookCallback]:
8589
"""List of hooks the plugin provides, auto-discovered from @hook decorated methods."""
8690
return self._hooks
8791

@@ -120,8 +124,7 @@ def init_agent(self, agent: "Agent") -> None | Awaitable[None]:
120124
"""Initialize the agent instance.
121125
122126
Override this method to add custom initialization logic. Decorated
123-
hooks and tools are automatically registered by the plugin registry,
124-
so there's no need to call super().init_agent(agent).
127+
hooks and tools are automatically registered by the plugin registry.
125128
126129
Args:
127130
agent: The agent instance to initialize.

src/strands/plugins/registry.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ def add_and_init(self, plugin: Plugin) -> None:
9797
def _register_hooks(self, plugin: Plugin) -> None:
9898
"""Register all discovered hooks from the plugin with the agent.
9999
100+
Warns if a hook callback is already registered for an event type,
101+
which can happen when init_agent() manually registers a hook that
102+
is also decorated with @hook.
103+
100104
Args:
101105
plugin: The plugin whose hooks should be registered.
102106
"""

tests/strands/agent/test_agent.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from pydantic import BaseModel
1515

1616
import strands
17-
from strands import Agent, ToolContext
17+
from strands import Agent, Plugin, ToolContext
1818
from strands.agent import AgentResult
1919
from strands.agent.conversation_manager.null_conversation_manager import NullConversationManager
2020
from strands.agent.conversation_manager.sliding_window_conversation_manager import SlidingWindowConversationManager
@@ -2625,6 +2625,8 @@ def test_agent_plugins_sync_initialization():
26252625
"""Test that plugins with sync init_agent are initialized correctly."""
26262626
plugin_mock = unittest.mock.Mock()
26272627
plugin_mock.name = "test-plugin"
2628+
plugin_mock.hooks = []
2629+
plugin_mock.tools = []
26282630
plugin_mock.init_agent = unittest.mock.Mock()
26292631

26302632
agent = Agent(
@@ -2639,6 +2641,8 @@ def test_agent_plugins_async_initialization():
26392641
"""Test that plugins with async init_agent are initialized correctly."""
26402642
plugin_mock = unittest.mock.Mock()
26412643
plugin_mock.name = "async-plugin"
2644+
plugin_mock.hooks = []
2645+
plugin_mock.tools = []
26422646
plugin_mock.init_agent = unittest.mock.AsyncMock()
26432647

26442648
agent = Agent(
@@ -2655,10 +2659,14 @@ def test_agent_plugins_multiple_in_order():
26552659

26562660
plugin1 = unittest.mock.Mock()
26572661
plugin1.name = "plugin1"
2662+
plugin1.hooks = []
2663+
plugin1.tools = []
26582664
plugin1.init_agent = unittest.mock.Mock(side_effect=lambda agent: call_order.append("plugin1"))
26592665

26602666
plugin2 = unittest.mock.Mock()
26612667
plugin2.name = "plugin2"
2668+
plugin2.hooks = []
2669+
plugin2.tools = []
26622670
plugin2.init_agent = unittest.mock.Mock(side_effect=lambda agent: call_order.append("plugin2"))
26632671

26642672
Agent(
@@ -2673,7 +2681,7 @@ def test_agent_plugins_can_register_hooks():
26732681
"""Test that plugins can register hooks during initialization."""
26742682
hook_called = []
26752683

2676-
class TestPlugin:
2684+
class TestPlugin(Plugin):
26772685
name = "hook-plugin"
26782686

26792687
def init_agent(self, agent):

0 commit comments

Comments
 (0)