forked from strands-agents/harness-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinterrupt.py
More file actions
145 lines (109 loc) · 4.38 KB
/
Copy pathinterrupt.py
File metadata and controls
145 lines (109 loc) · 4.38 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
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Interrupt related type definitions for human-in-the-loop workflows.
Interrupt Flow:
```mermaid
flowchart TD
A[Invoke Agent] --> B[Execute Hook/Tool]
B --> C{Interrupts Raised?}
C -->|No| D[Continue Agent Loop]
C -->|Yes| E[Stop Agent Loop]
E --> F[Return Interrupts]
F --> G[Respond to Interrupts]
G --> H[Execute Hook/Tool with Responses]
H --> I{New Interrupts?}
I -->|Yes| E
I -->|No| D
```
Example:
```Python
from typing import Any
from strands import Agent, tool
from strands.hooks import BeforeToolCallEvent, HookProvider, HookRegistry
@tool
def delete_tool(key: str) -> bool:
print("DELETE_TOOL | deleting")
return True
class ToolInterruptHook(HookProvider):
def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
registry.add_callback(BeforeToolCallEvent, self.approve)
def approve(self, event: BeforeToolCallEvent) -> None:
if event.tool_use["name"] != "delete_tool":
return
approval = event.interrupt("for_delete_tool", reason="APPROVAL")
if approval != "A":
event.cancel_tool = "approval was not granted"
agent = Agent(
hooks=[ToolInterruptHook()],
tools=[delete_tool],
system_prompt="You delete objects given their keys.",
callback_handler=None,
)
result = agent(f"delete object with key 'X'")
if result.stop_reason == "interrupt":
responses = []
for interrupt in result.interrupts:
if interrupt.name == "for_delete_tool":
responses.append({"interruptResponse": {"interruptId": interrupt.id, "response": "A"})
result = agent(responses)
...
```
Details:
- User raises interrupt on their hook event by calling `event.interrupt()`.
- User can raise one interrupt per hook callback.
- Interrupts stop the agent event loop.
- Interrupts are returned to the user in AgentResult.
- User resumes by invoking agent with interrupt responses.
- Second call to `event.interrupt()` returns user response.
- Process repeats if user raises additional interrupts.
- Interrupts are session managed in-between return and user response.
"""
from typing import Any, Protocol, TypedDict
from ..interrupt import Interrupt, InterruptException
class _Interruptible(Protocol):
"""Interface that adds interrupt support to hook events and tools."""
def interrupt(self, name: str, reason: Any = None, response: Any = None) -> Any:
"""Trigger the interrupt with a reason.
Args: name: User defined name for the interrupt.
Must be unique across hook callbacks.
reason: User provided reason for the interrupt.
response: Preemptive response from user if available.
Returns:
The response from a human user when resuming from an interrupt state.
Raises:
InterruptException: If human input is required.
RuntimeError: If agent instance attribute not set.
"""
for attr_name in ["agent", "source"]:
if hasattr(self, attr_name):
agent = getattr(self, attr_name)
break
else:
raise RuntimeError("agent instance attribute not set")
id = self._interrupt_id(name)
state = agent._interrupt_state
interrupt_ = state.interrupts.setdefault(id, Interrupt(id, name, reason, response))
if interrupt_.response is not None:
return interrupt_.response
raise InterruptException(interrupt_)
def _interrupt_id(self, name: str) -> str:
"""Unique id for the interrupt.
Args:
name: User defined name for the interrupt.
reason: User provided reason for the interrupt.
Returns:
Interrupt id.
"""
...
class InterruptResponse(TypedDict):
"""User response to an interrupt.
Attributes:
interruptId: Unique identifier for the interrupt.
response: User response to the interrupt.
"""
interruptId: str
response: Any
class InterruptResponseContent(TypedDict):
"""Content block containing a user response to an interrupt.
Attributes:
interruptResponse: User response to an interrupt event.
"""
interruptResponse: InterruptResponse