forked from strands-agents/harness-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplugin.py
More file actions
132 lines (103 loc) · 4.74 KB
/
Copy pathplugin.py
File metadata and controls
132 lines (103 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""Plugin base class for extending agent functionality.
This module defines the Plugin base class, which provides a composable way to
add behavior changes to agents through automatic hook and tool registration.
"""
import logging
from abc import ABC, abstractmethod
from collections.abc import Awaitable
from typing import TYPE_CHECKING
from ..hooks.registry import HookCallback
from ..tools.decorator import DecoratedFunctionTool
if TYPE_CHECKING:
from ..agent import Agent
logger = logging.getLogger(__name__)
class Plugin(ABC):
"""Base class for objects that extend agent functionality.
Plugins provide a composable way to add behavior changes to agents.
They support automatic discovery and registration of methods decorated
with @hook and @tool decorators.
Attributes:
name: A stable string identifier for the plugin (must be provided by subclass)
hooks: Hooks attached to the agent, auto-discovered from @hook decorated methods during __init__
tools: Tools attached to the agent, auto-discovered from @tool decorated methods during __init__
Example using decorators (recommended):
```python
from strands.plugins import Plugin, hook
from strands.hooks import BeforeModelCallEvent
from strands import tool
class MyPlugin(Plugin):
name = "my-plugin"
@hook
def on_model_call(self, event: BeforeModelCallEvent):
print(f"Model called: {event}")
@tool
def my_tool(self, param: str) -> str:
'''A tool that does something.'''
return f"Result: {param}"
```
Note: Decorated methods are registered in declaration order, with parent
class methods registered before child class methods. If a child overrides
a parent's decorated method, only the child's version is registered.
Example with custom initialization:
```python
class MyPlugin(Plugin):
name = "my-plugin"
def init_agent(self, agent: Agent) -> None:
# Custom initialization logic - no super() needed
# Decorated hooks/tools are auto-registered by the plugin registry
agent.add_hook(self.custom_hook)
def custom_hook(self, event: BeforeModelCallEvent):
print(event)
```
"""
@property
@abstractmethod
def name(self) -> str:
"""A stable string identifier for the plugin."""
...
def __init__(self) -> None:
"""Initialize the plugin and discover decorated methods.
Scans the class for methods decorated with @hook and @tool and stores
references for later registration when the plugin is attached to an agent.
"""
self._hooks: list[HookCallback] = []
self._tools: list[DecoratedFunctionTool] = []
self._discover_decorated_methods()
@property
def hooks(self) -> list[HookCallback]:
"""List of hooks the plugin provides, auto-discovered from @hook decorated methods."""
return self._hooks
@property
def tools(self) -> list[DecoratedFunctionTool]:
"""List of tools the plugin provides, auto-discovered from @tool decorated methods."""
return self._tools
def _discover_decorated_methods(self) -> None:
"""Scan class for @hook and @tool decorated methods in declaration order."""
seen: set[str] = set()
# Walk MRO so parent class hooks come first, child overrides win
for cls in reversed(type(self).__mro__):
for name in cls.__dict__:
if name in seen:
continue
seen.add(name)
# Get the bound method from self
try:
bound = getattr(self, name)
except Exception:
continue
# Check for @hook decorated methods
if hasattr(bound, "_hook_event_types") and callable(bound):
self._hooks.append(bound)
logger.debug("plugin=<%s>, hook=<%s> | discovered hook method", self.name, name)
# Check for @tool decorated methods (DecoratedFunctionTool instances)
if isinstance(bound, DecoratedFunctionTool):
self._tools.append(bound)
logger.debug("plugin=<%s>, tool=<%s> | discovered tool method", self.name, name)
def init_agent(self, agent: "Agent") -> None | Awaitable[None]:
"""Initialize the agent instance.
Override this method to add custom initialization logic. Decorated
hooks and tools are automatically registered by the plugin registry.
Args:
agent: The agent instance to initialize.
"""
return None