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,696 @@
1
+ """Agent Interface.
2
+
3
+ This module implements the core Agent class that serves as the primary entry point for interacting with foundation
4
+ models and tools in the SDK.
5
+
6
+ The Agent interface supports two complementary interaction patterns:
7
+
8
+ 1. Natural language for conversation: `agent("Analyze this data")`
9
+ 2. Method-style for direct tool access: `agent.tool.tool_name(param1="value")`
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import logging
15
+ import random
16
+ from concurrent.futures import ThreadPoolExecutor
17
+ from typing import Any, AsyncGenerator, AsyncIterator, Callable, Mapping, Optional, Type, TypeVar, Union, cast
18
+
19
+ from opentelemetry import trace as trace_api
20
+ from pydantic import BaseModel
21
+
22
+ from ..event_loop.event_loop import event_loop_cycle, run_tool
23
+ from ..handlers.callback_handler import PrintingCallbackHandler, null_callback_handler
24
+ from ..hooks import (
25
+ AfterInvocationEvent,
26
+ AgentInitializedEvent,
27
+ BeforeInvocationEvent,
28
+ HookProvider,
29
+ HookRegistry,
30
+ MessageAddedEvent,
31
+ )
32
+ from ..models.bedrock import BedrockModel
33
+ from ..models.model import Model
34
+ from ..session.session_manager import SessionManager
35
+ from ..telemetry.metrics import EventLoopMetrics
36
+ from ..telemetry.tracer import get_tracer
37
+ from ..tools.registry import ToolRegistry
38
+ from ..tools.watcher import ToolWatcher
39
+ from ..types.content import ContentBlock, Message, Messages
40
+ from ..types.exceptions import ContextWindowOverflowException
41
+ from ..types.tools import ToolResult, ToolUse
42
+ from ..types.traces import AttributeValue
43
+ from .agent_result import AgentResult
44
+ from .conversation_manager import (
45
+ ConversationManager,
46
+ SlidingWindowConversationManager,
47
+ )
48
+ from .state import AgentState
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+ # TypeVar for generic structured output
53
+ T = TypeVar("T", bound=BaseModel)
54
+
55
+
56
+ # Sentinel class and object to distinguish between explicit None and default parameter value
57
+ class _DefaultCallbackHandlerSentinel:
58
+ """Sentinel class to distinguish between explicit None and default parameter value."""
59
+
60
+ pass
61
+
62
+
63
+ _DEFAULT_CALLBACK_HANDLER = _DefaultCallbackHandlerSentinel()
64
+ _DEFAULT_AGENT_NAME = "Strands Agents"
65
+ _DEFAULT_AGENT_ID = "default"
66
+
67
+
68
+ class Agent:
69
+ """Core Agent interface.
70
+
71
+ An agent orchestrates the following workflow:
72
+
73
+ 1. Receives user input
74
+ 2. Processes the input using a language model
75
+ 3. Decides whether to use tools to gather information or perform actions
76
+ 4. Executes those tools and receives results
77
+ 5. Continues reasoning with the new information
78
+ 6. Produces a final response
79
+ """
80
+
81
+ class ToolCaller:
82
+ """Call tool as a function."""
83
+
84
+ def __init__(self, agent: "Agent") -> None:
85
+ """Initialize instance.
86
+
87
+ Args:
88
+ agent: Agent reference that will accept tool results.
89
+ """
90
+ # WARNING: Do not add any other member variables or methods as this could result in a name conflict with
91
+ # agent tools and thus break their execution.
92
+ self._agent = agent
93
+
94
+ def __getattr__(self, name: str) -> Callable[..., Any]:
95
+ """Call tool as a function.
96
+
97
+ This method enables the method-style interface (e.g., `agent.tool.tool_name(param="value")`).
98
+ It matches underscore-separated names to hyphenated tool names (e.g., 'some_thing' matches 'some-thing').
99
+
100
+ Args:
101
+ name: The name of the attribute (tool) being accessed.
102
+
103
+ Returns:
104
+ A function that when called will execute the named tool.
105
+
106
+ Raises:
107
+ AttributeError: If no tool with the given name exists or if multiple tools match the given name.
108
+ """
109
+
110
+ def caller(
111
+ user_message_override: Optional[str] = None,
112
+ record_direct_tool_call: Optional[bool] = None,
113
+ **kwargs: Any,
114
+ ) -> Any:
115
+ """Call a tool directly by name.
116
+
117
+ Args:
118
+ user_message_override: Optional custom message to record instead of default
119
+ record_direct_tool_call: Whether to record direct tool calls in message history. Overrides class
120
+ attribute if provided.
121
+ **kwargs: Keyword arguments to pass to the tool.
122
+
123
+ Returns:
124
+ The result returned by the tool.
125
+
126
+ Raises:
127
+ AttributeError: If the tool doesn't exist.
128
+ """
129
+ normalized_name = self._find_normalized_tool_name(name)
130
+
131
+ # Create unique tool ID and set up the tool request
132
+ tool_id = f"tooluse_{name}_{random.randint(100000000, 999999999)}"
133
+ tool_use: ToolUse = {
134
+ "toolUseId": tool_id,
135
+ "name": normalized_name,
136
+ "input": kwargs.copy(),
137
+ }
138
+
139
+ async def acall() -> ToolResult:
140
+ # Pass kwargs as invocation_state
141
+ async for event in run_tool(self._agent, tool_use, kwargs):
142
+ _ = event
143
+
144
+ return cast(ToolResult, event)
145
+
146
+ def tcall() -> ToolResult:
147
+ return asyncio.run(acall())
148
+
149
+ with ThreadPoolExecutor() as executor:
150
+ future = executor.submit(tcall)
151
+ tool_result = future.result()
152
+
153
+ if record_direct_tool_call is not None:
154
+ should_record_direct_tool_call = record_direct_tool_call
155
+ else:
156
+ should_record_direct_tool_call = self._agent.record_direct_tool_call
157
+
158
+ if should_record_direct_tool_call:
159
+ # Create a record of this tool execution in the message history
160
+ self._agent._record_tool_execution(tool_use, tool_result, user_message_override)
161
+
162
+ # Apply window management
163
+ self._agent.conversation_manager.apply_management(self._agent)
164
+
165
+ return tool_result
166
+
167
+ return caller
168
+
169
+ def _find_normalized_tool_name(self, name: str) -> str:
170
+ """Lookup the tool represented by name, replacing characters with underscores as necessary."""
171
+ tool_registry = self._agent.tool_registry.registry
172
+
173
+ if tool_registry.get(name, None):
174
+ return name
175
+
176
+ # If the desired name contains underscores, it might be a placeholder for characters that can't be
177
+ # represented as python identifiers but are valid as tool names, such as dashes. In that case, find
178
+ # all tools that can be represented with the normalized name
179
+ if "_" in name:
180
+ filtered_tools = [
181
+ tool_name for (tool_name, tool) in tool_registry.items() if tool_name.replace("-", "_") == name
182
+ ]
183
+
184
+ # The registry itself defends against similar names, so we can just take the first match
185
+ if filtered_tools:
186
+ return filtered_tools[0]
187
+
188
+ raise AttributeError(f"Tool '{name}' not found")
189
+
190
+ def __init__(
191
+ self,
192
+ model: Union[Model, str, None] = None,
193
+ messages: Optional[Messages] = None,
194
+ tools: Optional[list[Union[str, dict[str, str], Any]]] = None,
195
+ system_prompt: Optional[str] = None,
196
+ callback_handler: Optional[
197
+ Union[Callable[..., Any], _DefaultCallbackHandlerSentinel]
198
+ ] = _DEFAULT_CALLBACK_HANDLER,
199
+ conversation_manager: Optional[ConversationManager] = None,
200
+ record_direct_tool_call: bool = True,
201
+ load_tools_from_directory: bool = False,
202
+ trace_attributes: Optional[Mapping[str, AttributeValue]] = None,
203
+ *,
204
+ agent_id: Optional[str] = None,
205
+ name: Optional[str] = None,
206
+ description: Optional[str] = None,
207
+ state: Optional[Union[AgentState, dict]] = None,
208
+ hooks: Optional[list[HookProvider]] = None,
209
+ session_manager: Optional[SessionManager] = None,
210
+ ):
211
+ """Initialize the Agent with the specified configuration.
212
+
213
+ Args:
214
+ model: Provider for running inference or a string representing the model-id for Bedrock to use.
215
+ Defaults to strands.models.BedrockModel if None.
216
+ messages: List of initial messages to pre-load into the conversation.
217
+ Defaults to an empty list if None.
218
+ tools: List of tools to make available to the agent.
219
+ Can be specified as:
220
+
221
+ - String tool names (e.g., "retrieve")
222
+ - File paths (e.g., "/path/to/tool.py")
223
+ - Imported Python modules (e.g., from strands_tools import current_time)
224
+ - Dictionaries with name/path keys (e.g., {"name": "tool_name", "path": "/path/to/tool.py"})
225
+ - Functions decorated with `@strands.tool` decorator.
226
+
227
+ If provided, only these tools will be available. If None, all tools will be available.
228
+ system_prompt: System prompt to guide model behavior.
229
+ If None, the model will behave according to its default settings.
230
+ callback_handler: Callback for processing events as they happen during agent execution.
231
+ If not provided (using the default), a new PrintingCallbackHandler instance is created.
232
+ If explicitly set to None, null_callback_handler is used.
233
+ conversation_manager: Manager for conversation history and context window.
234
+ Defaults to strands.agent.conversation_manager.SlidingWindowConversationManager if None.
235
+ record_direct_tool_call: Whether to record direct tool calls in message history.
236
+ Defaults to True.
237
+ load_tools_from_directory: Whether to load and automatically reload tools in the `./tools/` directory.
238
+ Defaults to False.
239
+ trace_attributes: Custom trace attributes to apply to the agent's trace span.
240
+ agent_id: Optional ID for the agent, useful for session management and multi-agent scenarios.
241
+ Defaults to "default".
242
+ name: name of the Agent
243
+ Defaults to "Strands Agents".
244
+ description: description of what the Agent does
245
+ Defaults to None.
246
+ state: stateful information for the agent. Can be either an AgentState object, or a json serializable dict.
247
+ Defaults to an empty AgentState object.
248
+ hooks: hooks to be added to the agent hook registry
249
+ Defaults to None.
250
+ session_manager: Manager for handling agent sessions including conversation history and state.
251
+ If provided, enables session-based persistence and state management.
252
+ """
253
+ self.model = BedrockModel() if not model else BedrockModel(model_id=model) if isinstance(model, str) else model
254
+ self.messages = messages if messages is not None else []
255
+
256
+ self.system_prompt = system_prompt
257
+ self.agent_id = agent_id or _DEFAULT_AGENT_ID
258
+ self.name = name or _DEFAULT_AGENT_NAME
259
+ self.description = description
260
+
261
+ # If not provided, create a new PrintingCallbackHandler instance
262
+ # If explicitly set to None, use null_callback_handler
263
+ # Otherwise use the passed callback_handler
264
+ self.callback_handler: Union[Callable[..., Any], PrintingCallbackHandler]
265
+ if isinstance(callback_handler, _DefaultCallbackHandlerSentinel):
266
+ self.callback_handler = PrintingCallbackHandler()
267
+ elif callback_handler is None:
268
+ self.callback_handler = null_callback_handler
269
+ else:
270
+ self.callback_handler = callback_handler
271
+
272
+ self.conversation_manager = conversation_manager if conversation_manager else SlidingWindowConversationManager()
273
+
274
+ # Process trace attributes to ensure they're of compatible types
275
+ self.trace_attributes: dict[str, AttributeValue] = {}
276
+ if trace_attributes:
277
+ for k, v in trace_attributes.items():
278
+ if isinstance(v, (str, int, float, bool)) or (
279
+ isinstance(v, list) and all(isinstance(x, (str, int, float, bool)) for x in v)
280
+ ):
281
+ self.trace_attributes[k] = v
282
+
283
+ self.record_direct_tool_call = record_direct_tool_call
284
+ self.load_tools_from_directory = load_tools_from_directory
285
+
286
+ self.tool_registry = ToolRegistry()
287
+
288
+ # Process tool list if provided
289
+ if tools is not None:
290
+ self.tool_registry.process_tools(tools)
291
+
292
+ # Initialize tools and configuration
293
+ self.tool_registry.initialize_tools(self.load_tools_from_directory)
294
+ if load_tools_from_directory:
295
+ self.tool_watcher = ToolWatcher(tool_registry=self.tool_registry)
296
+
297
+ self.event_loop_metrics = EventLoopMetrics()
298
+
299
+ # Initialize tracer instance (no-op if not configured)
300
+ self.tracer = get_tracer()
301
+ self.trace_span: Optional[trace_api.Span] = None
302
+
303
+ # Initialize agent state management
304
+ if state is not None:
305
+ if isinstance(state, dict):
306
+ self.state = AgentState(state)
307
+ elif isinstance(state, AgentState):
308
+ self.state = state
309
+ else:
310
+ raise ValueError("state must be an AgentState object or a dict")
311
+ else:
312
+ self.state = AgentState()
313
+
314
+ self.tool_caller = Agent.ToolCaller(self)
315
+
316
+ self.hooks = HookRegistry()
317
+
318
+ # Initialize session management functionality
319
+ self._session_manager = session_manager
320
+ if self._session_manager:
321
+ self.hooks.add_hook(self._session_manager)
322
+
323
+ if hooks:
324
+ for hook in hooks:
325
+ self.hooks.add_hook(hook)
326
+ self.hooks.invoke_callbacks(AgentInitializedEvent(agent=self))
327
+
328
+ @property
329
+ def tool(self) -> ToolCaller:
330
+ """Call tool as a function.
331
+
332
+ Returns:
333
+ Tool caller through which user can invoke tool as a function.
334
+
335
+ Example:
336
+ ```
337
+ agent = Agent(tools=[calculator])
338
+ agent.tool.calculator(...)
339
+ ```
340
+ """
341
+ return self.tool_caller
342
+
343
+ @property
344
+ def tool_names(self) -> list[str]:
345
+ """Get a list of all registered tool names.
346
+
347
+ Returns:
348
+ Names of all tools available to this agent.
349
+ """
350
+ all_tools = self.tool_registry.get_all_tools_config()
351
+ return list(all_tools.keys())
352
+
353
+ def __call__(self, prompt: Union[str, list[ContentBlock]], **kwargs: Any) -> AgentResult:
354
+ """Process a natural language prompt through the agent's event loop.
355
+
356
+ This method implements the conversational interface (e.g., `agent("hello!")`). It adds the user's prompt to
357
+ the conversation history, processes it through the model, executes any tool calls, and returns the final result.
358
+
359
+ Args:
360
+ prompt: User input as text or list of ContentBlock objects for multi-modal content.
361
+ **kwargs: Additional parameters to pass through the event loop.
362
+
363
+ Returns:
364
+ Result object containing:
365
+
366
+ - stop_reason: Why the event loop stopped (e.g., "end_turn", "max_tokens")
367
+ - message: The final message from the model
368
+ - metrics: Performance metrics from the event loop
369
+ - state: The final state of the event loop
370
+ """
371
+
372
+ def execute() -> AgentResult:
373
+ return asyncio.run(self.invoke_async(prompt, **kwargs))
374
+
375
+ with ThreadPoolExecutor() as executor:
376
+ future = executor.submit(execute)
377
+ return future.result()
378
+
379
+ async def invoke_async(self, prompt: Union[str, list[ContentBlock]], **kwargs: Any) -> AgentResult:
380
+ """Process a natural language prompt through the agent's event loop.
381
+
382
+ This method implements the conversational interface (e.g., `agent("hello!")`). It adds the user's prompt to
383
+ the conversation history, processes it through the model, executes any tool calls, and returns the final result.
384
+
385
+ Args:
386
+ prompt: User input as text or list of ContentBlock objects for multi-modal content.
387
+ **kwargs: Additional parameters to pass through the event loop.
388
+
389
+ Returns:
390
+ Result object containing:
391
+
392
+ - stop_reason: Why the event loop stopped (e.g., "end_turn", "max_tokens")
393
+ - message: The final message from the model
394
+ - metrics: Performance metrics from the event loop
395
+ - state: The final state of the event loop
396
+ """
397
+ events = self.stream_async(prompt, **kwargs)
398
+ async for event in events:
399
+ _ = event
400
+
401
+ return cast(AgentResult, event["result"])
402
+
403
+ def structured_output(self, output_model: Type[T], prompt: Optional[Union[str, list[ContentBlock]]] = None) -> T:
404
+ """This method allows you to get structured output from the agent.
405
+
406
+ If you pass in a prompt, it will be added to the conversation history and the agent will respond to it.
407
+ If you don't pass in a prompt, it will use only the conversation history to respond.
408
+
409
+ For smaller models, you may want to use the optional prompt to add additional instructions to explicitly
410
+ instruct the model to output the structured data.
411
+
412
+ Args:
413
+ output_model: The output model (a JSON schema written as a Pydantic BaseModel)
414
+ that the agent will use when responding.
415
+ prompt: The prompt to use for the agent.
416
+
417
+ Raises:
418
+ ValueError: If no conversation history or prompt is provided.
419
+ """
420
+
421
+ def execute() -> T:
422
+ return asyncio.run(self.structured_output_async(output_model, prompt))
423
+
424
+ with ThreadPoolExecutor() as executor:
425
+ future = executor.submit(execute)
426
+ return future.result()
427
+
428
+ async def structured_output_async(
429
+ self, output_model: Type[T], prompt: Optional[Union[str, list[ContentBlock]]] = None
430
+ ) -> T:
431
+ """This method allows you to get structured output from the agent.
432
+
433
+ If you pass in a prompt, it will be added to the conversation history and the agent will respond to it.
434
+ If you don't pass in a prompt, it will use only the conversation history to respond.
435
+
436
+ For smaller models, you may want to use the optional prompt to add additional instructions to explicitly
437
+ instruct the model to output the structured data.
438
+
439
+ Args:
440
+ output_model: The output model (a JSON schema written as a Pydantic BaseModel)
441
+ that the agent will use when responding.
442
+ prompt: The prompt to use for the agent.
443
+
444
+ Raises:
445
+ ValueError: If no conversation history or prompt is provided.
446
+ """
447
+ self.hooks.invoke_callbacks(BeforeInvocationEvent(agent=self))
448
+
449
+ try:
450
+ if not self.messages and not prompt:
451
+ raise ValueError("No conversation history or prompt provided")
452
+
453
+ # add the prompt as the last message
454
+ if prompt:
455
+ content: list[ContentBlock] = [{"text": prompt}] if isinstance(prompt, str) else prompt
456
+ self._append_message({"role": "user", "content": content})
457
+
458
+ events = self.model.structured_output(output_model, self.messages, system_prompt=self.system_prompt)
459
+ async for event in events:
460
+ if "callback" in event:
461
+ self.callback_handler(**cast(dict, event["callback"]))
462
+
463
+ return event["output"]
464
+
465
+ finally:
466
+ self.hooks.invoke_callbacks(AfterInvocationEvent(agent=self))
467
+
468
+ async def stream_async(self, prompt: Union[str, list[ContentBlock]], **kwargs: Any) -> AsyncIterator[Any]:
469
+ """Process a natural language prompt and yield events as an async iterator.
470
+
471
+ This method provides an asynchronous interface for streaming agent events, allowing
472
+ consumers to process stream events programmatically through an async iterator pattern
473
+ rather than callback functions. This is particularly useful for web servers and other
474
+ async environments.
475
+
476
+ Args:
477
+ prompt: User input as text or list of ContentBlock objects for multi-modal content.
478
+ **kwargs: Additional parameters to pass to the event loop.
479
+
480
+ Yields:
481
+ An async iterator that yields events. Each event is a dictionary containing
482
+ information about the current state of processing, such as:
483
+
484
+ - data: Text content being generated
485
+ - complete: Whether this is the final chunk
486
+ - current_tool_use: Information about tools being executed
487
+ - And other event data provided by the callback handler
488
+
489
+ Raises:
490
+ Exception: Any exceptions from the agent invocation will be propagated to the caller.
491
+
492
+ Example:
493
+ ```python
494
+ async for event in agent.stream_async("Analyze this data"):
495
+ if "data" in event:
496
+ yield event["data"]
497
+ ```
498
+ """
499
+ callback_handler = kwargs.get("callback_handler", self.callback_handler)
500
+
501
+ content: list[ContentBlock] = [{"text": prompt}] if isinstance(prompt, str) else prompt
502
+ message: Message = {"role": "user", "content": content}
503
+
504
+ self.trace_span = self._start_agent_trace_span(message)
505
+ with trace_api.use_span(self.trace_span):
506
+ try:
507
+ events = self._run_loop(message, invocation_state=kwargs)
508
+ async for event in events:
509
+ if "callback" in event:
510
+ callback_handler(**event["callback"])
511
+ yield event["callback"]
512
+
513
+ result = AgentResult(*event["stop"])
514
+ callback_handler(result=result)
515
+ yield {"result": result}
516
+
517
+ self._end_agent_trace_span(response=result)
518
+
519
+ except Exception as e:
520
+ self._end_agent_trace_span(error=e)
521
+ raise
522
+
523
+ async def _run_loop(
524
+ self, message: Message, invocation_state: dict[str, Any]
525
+ ) -> AsyncGenerator[dict[str, Any], None]:
526
+ """Execute the agent's event loop with the given message and parameters.
527
+
528
+ Args:
529
+ message: The user message to add to the conversation.
530
+ invocation_state: Additional parameters to pass to the event loop.
531
+
532
+ Yields:
533
+ Events from the event loop cycle.
534
+ """
535
+ self.hooks.invoke_callbacks(BeforeInvocationEvent(agent=self))
536
+
537
+ try:
538
+ yield {"callback": {"init_event_loop": True, **invocation_state}}
539
+
540
+ self._append_message(message)
541
+
542
+ # Execute the event loop cycle with retry logic for context limits
543
+ events = self._execute_event_loop_cycle(invocation_state)
544
+ async for event in events:
545
+ # Signal from the model provider that the message sent by the user should be redacted,
546
+ # likely due to a guardrail.
547
+ if (
548
+ event.get("callback")
549
+ and event["callback"].get("event")
550
+ and event["callback"]["event"].get("redactContent")
551
+ and event["callback"]["event"]["redactContent"].get("redactUserContentMessage")
552
+ ):
553
+ self.messages[-1]["content"] = [
554
+ {"text": event["callback"]["event"]["redactContent"]["redactUserContentMessage"]}
555
+ ]
556
+ if self._session_manager:
557
+ self._session_manager.redact_latest_message(self.messages[-1], self)
558
+ yield event
559
+
560
+ finally:
561
+ self.conversation_manager.apply_management(self)
562
+ self.hooks.invoke_callbacks(AfterInvocationEvent(agent=self))
563
+
564
+ async def _execute_event_loop_cycle(self, invocation_state: dict[str, Any]) -> AsyncGenerator[dict[str, Any], None]:
565
+ """Execute the event loop cycle with retry logic for context window limits.
566
+
567
+ This internal method handles the execution of the event loop cycle and implements
568
+ retry logic for handling context window overflow exceptions by reducing the
569
+ conversation context and retrying.
570
+
571
+ Yields:
572
+ Events of the loop cycle.
573
+ """
574
+ # Add `Agent` to invocation_state to keep backwards-compatibility
575
+ invocation_state["agent"] = self
576
+
577
+ try:
578
+ # Execute the main event loop cycle
579
+ events = event_loop_cycle(
580
+ agent=self,
581
+ invocation_state=invocation_state,
582
+ )
583
+ async for event in events:
584
+ yield event
585
+
586
+ except ContextWindowOverflowException as e:
587
+ # Try reducing the context size and retrying
588
+ self.conversation_manager.reduce_context(self, e=e)
589
+
590
+ # Sync agent after reduce_context to keep conversation_manager_state up to date in the session
591
+ if self._session_manager:
592
+ self._session_manager.sync_agent(self)
593
+
594
+ events = self._execute_event_loop_cycle(invocation_state)
595
+ async for event in events:
596
+ yield event
597
+
598
+ def _record_tool_execution(
599
+ self,
600
+ tool: ToolUse,
601
+ tool_result: ToolResult,
602
+ user_message_override: Optional[str],
603
+ ) -> None:
604
+ """Record a tool execution in the message history.
605
+
606
+ Creates a sequence of messages that represent the tool execution:
607
+
608
+ 1. A user message describing the tool call
609
+ 2. An assistant message with the tool use
610
+ 3. A user message with the tool result
611
+ 4. An assistant message acknowledging the tool call
612
+
613
+ Args:
614
+ tool: The tool call information.
615
+ tool_result: The result returned by the tool.
616
+ user_message_override: Optional custom message to include.
617
+ """
618
+ # Create user message describing the tool call
619
+ input_parameters = json.dumps(tool["input"], default=lambda o: f"<<non-serializable: {type(o).__qualname__}>>")
620
+
621
+ user_msg_content: list[ContentBlock] = [
622
+ {"text": (f"agent.tool.{tool['name']} direct tool call.\nInput parameters: {input_parameters}\n")}
623
+ ]
624
+
625
+ # Add override message if provided
626
+ if user_message_override:
627
+ user_msg_content.insert(0, {"text": f"{user_message_override}\n"})
628
+
629
+ # Create the message sequence
630
+ user_msg: Message = {
631
+ "role": "user",
632
+ "content": user_msg_content,
633
+ }
634
+ tool_use_msg: Message = {
635
+ "role": "assistant",
636
+ "content": [{"toolUse": tool}],
637
+ }
638
+ tool_result_msg: Message = {
639
+ "role": "user",
640
+ "content": [{"toolResult": tool_result}],
641
+ }
642
+ assistant_msg: Message = {
643
+ "role": "assistant",
644
+ "content": [{"text": f"agent.tool.{tool['name']} was called."}],
645
+ }
646
+
647
+ # Add to message history
648
+ self._append_message(user_msg)
649
+ self._append_message(tool_use_msg)
650
+ self._append_message(tool_result_msg)
651
+ self._append_message(assistant_msg)
652
+
653
+ def _start_agent_trace_span(self, message: Message) -> trace_api.Span:
654
+ """Starts a trace span for the agent.
655
+
656
+ Args:
657
+ message: The user message.
658
+ """
659
+ model_id = self.model.config.get("model_id") if hasattr(self.model, "config") else None
660
+ return self.tracer.start_agent_span(
661
+ message=message,
662
+ agent_name=self.name,
663
+ model_id=model_id,
664
+ tools=self.tool_names,
665
+ system_prompt=self.system_prompt,
666
+ custom_trace_attributes=self.trace_attributes,
667
+ )
668
+
669
+ def _end_agent_trace_span(
670
+ self,
671
+ response: Optional[AgentResult] = None,
672
+ error: Optional[Exception] = None,
673
+ ) -> None:
674
+ """Ends a trace span for the agent.
675
+
676
+ Args:
677
+ span: The span to end.
678
+ response: Response to record as a trace attribute.
679
+ error: Error to record as a trace attribute.
680
+ """
681
+ if self.trace_span:
682
+ trace_attributes: dict[str, Any] = {
683
+ "span": self.trace_span,
684
+ }
685
+
686
+ if response:
687
+ trace_attributes["response"] = response
688
+ if error:
689
+ trace_attributes["error"] = error
690
+
691
+ self.tracer.end_agent_span(**trace_attributes)
692
+
693
+ def _append_message(self, message: Message) -> None:
694
+ """Appends a message to the agent's list of messages and invokes the callbacks for the MessageCreatedEvent."""
695
+ self.messages.append(message)
696
+ self.hooks.invoke_callbacks(MessageAddedEvent(agent=self, message=message))