agentrun-sdk 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of agentrun-sdk might be problematic. Click here for more details.

Files changed (115) hide show
  1. agentrun_operation_sdk/cli/__init__.py +1 -0
  2. agentrun_operation_sdk/cli/cli.py +19 -0
  3. agentrun_operation_sdk/cli/common.py +21 -0
  4. agentrun_operation_sdk/cli/runtime/__init__.py +1 -0
  5. agentrun_operation_sdk/cli/runtime/commands.py +203 -0
  6. agentrun_operation_sdk/client/client.py +75 -0
  7. agentrun_operation_sdk/operations/runtime/__init__.py +8 -0
  8. agentrun_operation_sdk/operations/runtime/configure.py +101 -0
  9. agentrun_operation_sdk/operations/runtime/launch.py +82 -0
  10. agentrun_operation_sdk/operations/runtime/models.py +31 -0
  11. agentrun_operation_sdk/services/runtime.py +152 -0
  12. agentrun_operation_sdk/utils/logging_config.py +72 -0
  13. agentrun_operation_sdk/utils/runtime/config.py +94 -0
  14. agentrun_operation_sdk/utils/runtime/container.py +280 -0
  15. agentrun_operation_sdk/utils/runtime/entrypoint.py +203 -0
  16. agentrun_operation_sdk/utils/runtime/schema.py +56 -0
  17. agentrun_sdk/__init__.py +7 -0
  18. agentrun_sdk/agent/__init__.py +25 -0
  19. agentrun_sdk/agent/agent.py +696 -0
  20. agentrun_sdk/agent/agent_result.py +46 -0
  21. agentrun_sdk/agent/conversation_manager/__init__.py +26 -0
  22. agentrun_sdk/agent/conversation_manager/conversation_manager.py +88 -0
  23. agentrun_sdk/agent/conversation_manager/null_conversation_manager.py +46 -0
  24. agentrun_sdk/agent/conversation_manager/sliding_window_conversation_manager.py +179 -0
  25. agentrun_sdk/agent/conversation_manager/summarizing_conversation_manager.py +252 -0
  26. agentrun_sdk/agent/state.py +97 -0
  27. agentrun_sdk/event_loop/__init__.py +9 -0
  28. agentrun_sdk/event_loop/event_loop.py +499 -0
  29. agentrun_sdk/event_loop/streaming.py +319 -0
  30. agentrun_sdk/experimental/__init__.py +4 -0
  31. agentrun_sdk/experimental/hooks/__init__.py +15 -0
  32. agentrun_sdk/experimental/hooks/events.py +123 -0
  33. agentrun_sdk/handlers/__init__.py +10 -0
  34. agentrun_sdk/handlers/callback_handler.py +70 -0
  35. agentrun_sdk/hooks/__init__.py +49 -0
  36. agentrun_sdk/hooks/events.py +80 -0
  37. agentrun_sdk/hooks/registry.py +247 -0
  38. agentrun_sdk/models/__init__.py +10 -0
  39. agentrun_sdk/models/anthropic.py +432 -0
  40. agentrun_sdk/models/bedrock.py +649 -0
  41. agentrun_sdk/models/litellm.py +225 -0
  42. agentrun_sdk/models/llamaapi.py +438 -0
  43. agentrun_sdk/models/mistral.py +539 -0
  44. agentrun_sdk/models/model.py +95 -0
  45. agentrun_sdk/models/ollama.py +357 -0
  46. agentrun_sdk/models/openai.py +436 -0
  47. agentrun_sdk/models/sagemaker.py +598 -0
  48. agentrun_sdk/models/writer.py +449 -0
  49. agentrun_sdk/multiagent/__init__.py +22 -0
  50. agentrun_sdk/multiagent/a2a/__init__.py +15 -0
  51. agentrun_sdk/multiagent/a2a/executor.py +148 -0
  52. agentrun_sdk/multiagent/a2a/server.py +252 -0
  53. agentrun_sdk/multiagent/base.py +92 -0
  54. agentrun_sdk/multiagent/graph.py +555 -0
  55. agentrun_sdk/multiagent/swarm.py +656 -0
  56. agentrun_sdk/py.typed +1 -0
  57. agentrun_sdk/session/__init__.py +18 -0
  58. agentrun_sdk/session/file_session_manager.py +216 -0
  59. agentrun_sdk/session/repository_session_manager.py +152 -0
  60. agentrun_sdk/session/s3_session_manager.py +272 -0
  61. agentrun_sdk/session/session_manager.py +73 -0
  62. agentrun_sdk/session/session_repository.py +51 -0
  63. agentrun_sdk/telemetry/__init__.py +21 -0
  64. agentrun_sdk/telemetry/config.py +194 -0
  65. agentrun_sdk/telemetry/metrics.py +476 -0
  66. agentrun_sdk/telemetry/metrics_constants.py +15 -0
  67. agentrun_sdk/telemetry/tracer.py +563 -0
  68. agentrun_sdk/tools/__init__.py +17 -0
  69. agentrun_sdk/tools/decorator.py +569 -0
  70. agentrun_sdk/tools/executor.py +137 -0
  71. agentrun_sdk/tools/loader.py +152 -0
  72. agentrun_sdk/tools/mcp/__init__.py +13 -0
  73. agentrun_sdk/tools/mcp/mcp_agent_tool.py +99 -0
  74. agentrun_sdk/tools/mcp/mcp_client.py +423 -0
  75. agentrun_sdk/tools/mcp/mcp_instrumentation.py +322 -0
  76. agentrun_sdk/tools/mcp/mcp_types.py +63 -0
  77. agentrun_sdk/tools/registry.py +607 -0
  78. agentrun_sdk/tools/structured_output.py +421 -0
  79. agentrun_sdk/tools/tools.py +217 -0
  80. agentrun_sdk/tools/watcher.py +136 -0
  81. agentrun_sdk/types/__init__.py +5 -0
  82. agentrun_sdk/types/collections.py +23 -0
  83. agentrun_sdk/types/content.py +188 -0
  84. agentrun_sdk/types/event_loop.py +48 -0
  85. agentrun_sdk/types/exceptions.py +81 -0
  86. agentrun_sdk/types/guardrails.py +254 -0
  87. agentrun_sdk/types/media.py +89 -0
  88. agentrun_sdk/types/session.py +152 -0
  89. agentrun_sdk/types/streaming.py +201 -0
  90. agentrun_sdk/types/tools.py +258 -0
  91. agentrun_sdk/types/traces.py +5 -0
  92. agentrun_sdk-0.1.2.dist-info/METADATA +51 -0
  93. agentrun_sdk-0.1.2.dist-info/RECORD +115 -0
  94. agentrun_sdk-0.1.2.dist-info/WHEEL +5 -0
  95. agentrun_sdk-0.1.2.dist-info/entry_points.txt +2 -0
  96. agentrun_sdk-0.1.2.dist-info/top_level.txt +3 -0
  97. agentrun_wrapper/__init__.py +11 -0
  98. agentrun_wrapper/_utils/__init__.py +6 -0
  99. agentrun_wrapper/_utils/endpoints.py +16 -0
  100. agentrun_wrapper/identity/__init__.py +5 -0
  101. agentrun_wrapper/identity/auth.py +211 -0
  102. agentrun_wrapper/memory/__init__.py +6 -0
  103. agentrun_wrapper/memory/client.py +1697 -0
  104. agentrun_wrapper/memory/constants.py +103 -0
  105. agentrun_wrapper/memory/controlplane.py +626 -0
  106. agentrun_wrapper/py.typed +1 -0
  107. agentrun_wrapper/runtime/__init__.py +13 -0
  108. agentrun_wrapper/runtime/app.py +473 -0
  109. agentrun_wrapper/runtime/context.py +34 -0
  110. agentrun_wrapper/runtime/models.py +25 -0
  111. agentrun_wrapper/services/__init__.py +1 -0
  112. agentrun_wrapper/services/identity.py +192 -0
  113. agentrun_wrapper/tools/__init__.py +6 -0
  114. agentrun_wrapper/tools/browser_client.py +325 -0
  115. agentrun_wrapper/tools/code_interpreter_client.py +186 -0
@@ -0,0 +1,319 @@
1
+ """Utilities for handling streaming responses from language models."""
2
+
3
+ import json
4
+ import logging
5
+ from typing import Any, AsyncGenerator, AsyncIterable, Optional
6
+
7
+ from ..models.model import Model
8
+ from ..types.content import ContentBlock, Message, Messages
9
+ from ..types.streaming import (
10
+ ContentBlockDeltaEvent,
11
+ ContentBlockStart,
12
+ ContentBlockStartEvent,
13
+ MessageStartEvent,
14
+ MessageStopEvent,
15
+ MetadataEvent,
16
+ Metrics,
17
+ RedactContentEvent,
18
+ StopReason,
19
+ StreamEvent,
20
+ Usage,
21
+ )
22
+ from ..types.tools import ToolSpec, ToolUse
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def remove_blank_messages_content_text(messages: Messages) -> Messages:
28
+ """Remove or replace blank text in message content.
29
+
30
+ Args:
31
+ messages: Conversation messages to update.
32
+
33
+ Returns:
34
+ Updated messages.
35
+ """
36
+ removed_blank_message_content_text = False
37
+ replaced_blank_message_content_text = False
38
+
39
+ for message in messages:
40
+ # only modify assistant messages
41
+ if "role" in message and message["role"] != "assistant":
42
+ continue
43
+
44
+ if "content" in message:
45
+ content = message["content"]
46
+ has_tool_use = any("toolUse" in item for item in content)
47
+
48
+ if has_tool_use:
49
+ # Remove blank 'text' items for assistant messages
50
+ before_len = len(content)
51
+ content[:] = [item for item in content if "text" not in item or item["text"].strip()]
52
+ if not removed_blank_message_content_text and before_len != len(content):
53
+ removed_blank_message_content_text = True
54
+ else:
55
+ # Replace blank 'text' with '[blank text]' for assistant messages
56
+ for item in content:
57
+ if "text" in item and not item["text"].strip():
58
+ replaced_blank_message_content_text = True
59
+ item["text"] = "[blank text]"
60
+
61
+ if removed_blank_message_content_text:
62
+ logger.debug("removed blank message context text")
63
+ if replaced_blank_message_content_text:
64
+ logger.debug("replaced blank message context text")
65
+
66
+ return messages
67
+
68
+
69
+ def handle_message_start(event: MessageStartEvent, message: Message) -> Message:
70
+ """Handles the start of a message by setting the role in the message dictionary.
71
+
72
+ Args:
73
+ event: A message start event.
74
+ message: The message dictionary being constructed.
75
+
76
+ Returns:
77
+ Updated message dictionary with the role set.
78
+ """
79
+ message["role"] = event["role"]
80
+ return message
81
+
82
+
83
+ def handle_content_block_start(event: ContentBlockStartEvent) -> dict[str, Any]:
84
+ """Handles the start of a content block by extracting tool usage information if any.
85
+
86
+ Args:
87
+ event: Start event.
88
+
89
+ Returns:
90
+ Dictionary with tool use id and name if tool use request, empty dictionary otherwise.
91
+ """
92
+ start: ContentBlockStart = event["start"]
93
+ current_tool_use = {}
94
+
95
+ if "toolUse" in start and start["toolUse"]:
96
+ tool_use_data = start["toolUse"]
97
+ current_tool_use["toolUseId"] = tool_use_data["toolUseId"]
98
+ current_tool_use["name"] = tool_use_data["name"]
99
+ current_tool_use["input"] = ""
100
+
101
+ return current_tool_use
102
+
103
+
104
+ def handle_content_block_delta(
105
+ event: ContentBlockDeltaEvent, state: dict[str, Any]
106
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
107
+ """Handles content block delta updates by appending text, tool input, or reasoning content to the state.
108
+
109
+ Args:
110
+ event: Delta event.
111
+ state: The current state of message processing.
112
+
113
+ Returns:
114
+ Updated state with appended text or tool input.
115
+ """
116
+ delta_content = event["delta"]
117
+
118
+ callback_event = {}
119
+
120
+ if "toolUse" in delta_content:
121
+ if "input" not in state["current_tool_use"]:
122
+ state["current_tool_use"]["input"] = ""
123
+
124
+ state["current_tool_use"]["input"] += delta_content["toolUse"]["input"]
125
+ callback_event["callback"] = {"delta": delta_content, "current_tool_use": state["current_tool_use"]}
126
+
127
+ elif "text" in delta_content:
128
+ state["text"] += delta_content["text"]
129
+ callback_event["callback"] = {"data": delta_content["text"], "delta": delta_content}
130
+
131
+ elif "reasoningContent" in delta_content:
132
+ if "text" in delta_content["reasoningContent"]:
133
+ if "reasoningText" not in state:
134
+ state["reasoningText"] = ""
135
+
136
+ state["reasoningText"] += delta_content["reasoningContent"]["text"]
137
+ callback_event["callback"] = {
138
+ "reasoningText": delta_content["reasoningContent"]["text"],
139
+ "delta": delta_content,
140
+ "reasoning": True,
141
+ }
142
+
143
+ elif "signature" in delta_content["reasoningContent"]:
144
+ if "signature" not in state:
145
+ state["signature"] = ""
146
+
147
+ state["signature"] += delta_content["reasoningContent"]["signature"]
148
+ callback_event["callback"] = {
149
+ "reasoning_signature": delta_content["reasoningContent"]["signature"],
150
+ "delta": delta_content,
151
+ "reasoning": True,
152
+ }
153
+
154
+ return state, callback_event
155
+
156
+
157
+ def handle_content_block_stop(state: dict[str, Any]) -> dict[str, Any]:
158
+ """Handles the end of a content block by finalizing tool usage, text content, or reasoning content.
159
+
160
+ Args:
161
+ state: The current state of message processing.
162
+
163
+ Returns:
164
+ Updated state with finalized content block.
165
+ """
166
+ content: list[ContentBlock] = state["content"]
167
+
168
+ current_tool_use = state["current_tool_use"]
169
+ text = state["text"]
170
+ reasoning_text = state["reasoningText"]
171
+
172
+ if current_tool_use:
173
+ if "input" not in current_tool_use:
174
+ current_tool_use["input"] = ""
175
+
176
+ try:
177
+ current_tool_use["input"] = json.loads(current_tool_use["input"])
178
+ except ValueError:
179
+ current_tool_use["input"] = {}
180
+
181
+ tool_use_id = current_tool_use["toolUseId"]
182
+ tool_use_name = current_tool_use["name"]
183
+
184
+ tool_use = ToolUse(
185
+ toolUseId=tool_use_id,
186
+ name=tool_use_name,
187
+ input=current_tool_use["input"],
188
+ )
189
+ content.append({"toolUse": tool_use})
190
+ state["current_tool_use"] = {}
191
+
192
+ elif text:
193
+ content.append({"text": text})
194
+ state["text"] = ""
195
+
196
+ elif reasoning_text:
197
+ content.append(
198
+ {
199
+ "reasoningContent": {
200
+ "reasoningText": {
201
+ "text": state["reasoningText"],
202
+ "signature": state["signature"],
203
+ }
204
+ }
205
+ }
206
+ )
207
+ state["reasoningText"] = ""
208
+
209
+ return state
210
+
211
+
212
+ def handle_message_stop(event: MessageStopEvent) -> StopReason:
213
+ """Handles the end of a message by returning the stop reason.
214
+
215
+ Args:
216
+ event: Stop event.
217
+
218
+ Returns:
219
+ The reason for stopping the stream.
220
+ """
221
+ return event["stopReason"]
222
+
223
+
224
+ def handle_redact_content(event: RedactContentEvent, state: dict[str, Any]) -> None:
225
+ """Handles redacting content from the input or output.
226
+
227
+ Args:
228
+ event: Redact Content Event.
229
+ state: The current state of message processing.
230
+ """
231
+ if event.get("redactAssistantContentMessage") is not None:
232
+ state["message"]["content"] = [{"text": event["redactAssistantContentMessage"]}]
233
+
234
+
235
+ def extract_usage_metrics(event: MetadataEvent) -> tuple[Usage, Metrics]:
236
+ """Extracts usage metrics from the metadata chunk.
237
+
238
+ Args:
239
+ event: metadata.
240
+
241
+ Returns:
242
+ The extracted usage metrics and latency.
243
+ """
244
+ usage = Usage(**event["usage"])
245
+ metrics = Metrics(**event["metrics"])
246
+
247
+ return usage, metrics
248
+
249
+
250
+ async def process_stream(chunks: AsyncIterable[StreamEvent]) -> AsyncGenerator[dict[str, Any], None]:
251
+ """Processes the response stream from the API, constructing the final message and extracting usage metrics.
252
+
253
+ Args:
254
+ chunks: The chunks of the response stream from the model.
255
+
256
+ Yields:
257
+ The reason for stopping, the constructed message, and the usage metrics.
258
+ """
259
+ stop_reason: StopReason = "end_turn"
260
+
261
+ state: dict[str, Any] = {
262
+ "message": {"role": "assistant", "content": []},
263
+ "text": "",
264
+ "current_tool_use": {},
265
+ "reasoningText": "",
266
+ "signature": "",
267
+ }
268
+ state["content"] = state["message"]["content"]
269
+
270
+ usage: Usage = Usage(inputTokens=0, outputTokens=0, totalTokens=0)
271
+ metrics: Metrics = Metrics(latencyMs=0)
272
+
273
+ async for chunk in chunks:
274
+ yield {"callback": {"event": chunk}}
275
+
276
+ if "messageStart" in chunk:
277
+ state["message"] = handle_message_start(chunk["messageStart"], state["message"])
278
+ elif "contentBlockStart" in chunk:
279
+ state["current_tool_use"] = handle_content_block_start(chunk["contentBlockStart"])
280
+ elif "contentBlockDelta" in chunk:
281
+ state, callback_event = handle_content_block_delta(chunk["contentBlockDelta"], state)
282
+ yield callback_event
283
+ elif "contentBlockStop" in chunk:
284
+ state = handle_content_block_stop(state)
285
+ elif "messageStop" in chunk:
286
+ stop_reason = handle_message_stop(chunk["messageStop"])
287
+ elif "metadata" in chunk:
288
+ usage, metrics = extract_usage_metrics(chunk["metadata"])
289
+ elif "redactContent" in chunk:
290
+ handle_redact_content(chunk["redactContent"], state)
291
+
292
+ yield {"stop": (stop_reason, state["message"], usage, metrics)}
293
+
294
+
295
+ async def stream_messages(
296
+ model: Model,
297
+ system_prompt: Optional[str],
298
+ messages: Messages,
299
+ tool_specs: list[ToolSpec],
300
+ ) -> AsyncGenerator[dict[str, Any], None]:
301
+ """Streams messages to the model and processes the response.
302
+
303
+ Args:
304
+ model: Model provider.
305
+ system_prompt: The system prompt to send.
306
+ messages: List of messages to send.
307
+ tool_specs: The list of tool specs.
308
+
309
+ Yields:
310
+ The reason for stopping, the final message, and the usage metrics
311
+ """
312
+ logger.debug("model=<%s> | streaming messages", model)
313
+
314
+ messages = remove_blank_messages_content_text(messages)
315
+
316
+ chunks = model.stream(messages, tool_specs if tool_specs else None, system_prompt)
317
+
318
+ async for event in process_stream(chunks):
319
+ yield event
@@ -0,0 +1,4 @@
1
+ """Experimental features.
2
+
3
+ This module implements experimental features that are subject to change in future revisions without notice.
4
+ """
@@ -0,0 +1,15 @@
1
+ """Experimental hook functionality that has not yet reached stability."""
2
+
3
+ from .events import (
4
+ AfterModelInvocationEvent,
5
+ AfterToolInvocationEvent,
6
+ BeforeModelInvocationEvent,
7
+ BeforeToolInvocationEvent,
8
+ )
9
+
10
+ __all__ = [
11
+ "BeforeToolInvocationEvent",
12
+ "AfterToolInvocationEvent",
13
+ "BeforeModelInvocationEvent",
14
+ "AfterModelInvocationEvent",
15
+ ]
@@ -0,0 +1,123 @@
1
+ """Experimental hook events emitted as part of invoking Agents.
2
+
3
+ This module defines the events that are emitted as Agents run through the lifecycle of a request.
4
+ """
5
+
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
@@ -0,0 +1,10 @@
1
+ """Various handlers for performing custom actions on agent state.
2
+
3
+ Examples include:
4
+
5
+ - Displaying events from the event stream
6
+ """
7
+
8
+ from .callback_handler import CompositeCallbackHandler, PrintingCallbackHandler, null_callback_handler
9
+
10
+ __all__ = ["CompositeCallbackHandler", "null_callback_handler", "PrintingCallbackHandler"]
@@ -0,0 +1,70 @@
1
+ """This module provides handlers for formatting and displaying events from the agent."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+
7
+ class PrintingCallbackHandler:
8
+ """Handler for streaming text output and tool invocations to stdout."""
9
+
10
+ def __init__(self) -> None:
11
+ """Initialize handler."""
12
+ self.tool_count = 0
13
+ self.previous_tool_use = None
14
+
15
+ def __call__(self, **kwargs: Any) -> None:
16
+ """Stream text output and tool invocations to stdout.
17
+
18
+ Args:
19
+ **kwargs: Callback event data including:
20
+ - reasoningText (Optional[str]): Reasoning text to print if provided.
21
+ - data (str): Text content to stream.
22
+ - complete (bool): Whether this is the final chunk of a response.
23
+ - current_tool_use (dict): Information about the current tool being used.
24
+ """
25
+ reasoningText = kwargs.get("reasoningText", False)
26
+ data = kwargs.get("data", "")
27
+ complete = kwargs.get("complete", False)
28
+ current_tool_use = kwargs.get("current_tool_use", {})
29
+
30
+ if reasoningText:
31
+ print(reasoningText, end="")
32
+
33
+ if data:
34
+ print(data, end="" if not complete else "\n")
35
+
36
+ if current_tool_use and current_tool_use.get("name"):
37
+ tool_name = current_tool_use.get("name", "Unknown tool")
38
+ if self.previous_tool_use != current_tool_use:
39
+ self.previous_tool_use = current_tool_use
40
+ self.tool_count += 1
41
+ print(f"\nTool #{self.tool_count}: {tool_name}")
42
+
43
+ if complete and data:
44
+ print("\n")
45
+
46
+
47
+ class CompositeCallbackHandler:
48
+ """Class-based callback handler that combines multiple callback handlers.
49
+
50
+ This handler allows multiple callback handlers to be invoked for the same events,
51
+ enabling different processing or output formats for the same stream data.
52
+ """
53
+
54
+ def __init__(self, *handlers: Callable) -> None:
55
+ """Initialize handler."""
56
+ self.handlers = handlers
57
+
58
+ def __call__(self, **kwargs: Any) -> None:
59
+ """Invoke all handlers in the chain."""
60
+ for handler in self.handlers:
61
+ handler(**kwargs)
62
+
63
+
64
+ def null_callback_handler(**_kwargs: Any) -> None:
65
+ """Callback handler that discards all output.
66
+
67
+ Args:
68
+ **_kwargs: Event data (ignored).
69
+ """
70
+ return None
@@ -0,0 +1,49 @@
1
+ """Typed hook system for extending agent functionality.
2
+
3
+ This module provides a composable mechanism for building objects that can hook
4
+ into specific events during the agent lifecycle. The hook system enables both
5
+ built-in SDK components and user code to react to or modify agent behavior
6
+ through strongly-typed event callbacks.
7
+
8
+ Example Usage:
9
+ ```python
10
+ from strands.hooks import HookProvider, HookRegistry
11
+ from strands.hooks.events import StartRequestEvent, EndRequestEvent
12
+
13
+ class LoggingHooks(HookProvider):
14
+ def register_hooks(self, registry: HookRegistry) -> None:
15
+ registry.add_callback(StartRequestEvent, self.log_start)
16
+ registry.add_callback(EndRequestEvent, self.log_end)
17
+
18
+ def log_start(self, event: StartRequestEvent) -> None:
19
+ print(f"Request started for {event.agent.name}")
20
+
21
+ def log_end(self, event: EndRequestEvent) -> None:
22
+ print(f"Request completed for {event.agent.name}")
23
+
24
+ # Use with agent
25
+ agent = Agent(hooks=[LoggingHooks()])
26
+ ```
27
+
28
+ This replaces the older callback_handler approach with a more composable,
29
+ type-safe system that supports multiple subscribers per event type.
30
+ """
31
+
32
+ from .events import (
33
+ AfterInvocationEvent,
34
+ AgentInitializedEvent,
35
+ BeforeInvocationEvent,
36
+ MessageAddedEvent,
37
+ )
38
+ from .registry import HookCallback, HookEvent, HookProvider, HookRegistry
39
+
40
+ __all__ = [
41
+ "AgentInitializedEvent",
42
+ "BeforeInvocationEvent",
43
+ "AfterInvocationEvent",
44
+ "MessageAddedEvent",
45
+ "HookEvent",
46
+ "HookProvider",
47
+ "HookCallback",
48
+ "HookRegistry",
49
+ ]
@@ -0,0 +1,80 @@
1
+ """Hook events emitted as part of invoking Agents.
2
+
3
+ This module defines the events that are emitted as Agents run through the lifecycle of a request.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+
8
+ from ..types.content import Message
9
+ from .registry import HookEvent
10
+
11
+
12
+ @dataclass
13
+ class AgentInitializedEvent(HookEvent):
14
+ """Event triggered when an agent has finished initialization.
15
+
16
+ This event is fired after the agent has been fully constructed and all
17
+ built-in components have been initialized. Hook providers can use this
18
+ event to perform setup tasks that require a fully initialized agent.
19
+ """
20
+
21
+ pass
22
+
23
+
24
+ @dataclass
25
+ class BeforeInvocationEvent(HookEvent):
26
+ """Event triggered at the beginning of a new agent request.
27
+
28
+ This event is fired before the agent begins processing a new user request,
29
+ before any model inference or tool execution occurs. Hook providers can
30
+ use this event to perform request-level setup, logging, or validation.
31
+
32
+ This event is triggered at the beginning of the following api calls:
33
+ - Agent.__call__
34
+ - Agent.stream_async
35
+ - Agent.structured_output
36
+ """
37
+
38
+ pass
39
+
40
+
41
+ @dataclass
42
+ class AfterInvocationEvent(HookEvent):
43
+ """Event triggered at the end of an agent request.
44
+
45
+ This event is fired after the agent has completed processing a request,
46
+ regardless of whether it completed successfully or encountered an error.
47
+ Hook providers can use this event for cleanup, logging, or state persistence.
48
+
49
+ Note: This event uses reverse callback ordering, meaning callbacks registered
50
+ later will be invoked first during cleanup.
51
+
52
+ This event is triggered at the end of the following api calls:
53
+ - Agent.__call__
54
+ - Agent.stream_async
55
+ - Agent.structured_output
56
+ """
57
+
58
+ @property
59
+ def should_reverse_callbacks(self) -> bool:
60
+ """True to invoke callbacks in reverse order."""
61
+ return True
62
+
63
+
64
+ @dataclass
65
+ class MessageAddedEvent(HookEvent):
66
+ """Event triggered when a message is added to the agent's conversation.
67
+
68
+ This event is fired whenever the agent adds a new message to its internal
69
+ message history, including user messages, assistant responses, and tool
70
+ results. Hook providers can use this event for logging, monitoring, or
71
+ implementing custom message processing logic.
72
+
73
+ Note: This event is only triggered for messages added by the framework
74
+ itself, not for messages manually added by tools or external code.
75
+
76
+ Attributes:
77
+ message: The message that was added to the conversation history.
78
+ """
79
+
80
+ message: Message