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.
- agentrun_operation_sdk/cli/__init__.py +1 -0
- agentrun_operation_sdk/cli/cli.py +19 -0
- agentrun_operation_sdk/cli/common.py +21 -0
- agentrun_operation_sdk/cli/runtime/__init__.py +1 -0
- agentrun_operation_sdk/cli/runtime/commands.py +203 -0
- agentrun_operation_sdk/client/client.py +75 -0
- agentrun_operation_sdk/operations/runtime/__init__.py +8 -0
- agentrun_operation_sdk/operations/runtime/configure.py +101 -0
- agentrun_operation_sdk/operations/runtime/launch.py +82 -0
- agentrun_operation_sdk/operations/runtime/models.py +31 -0
- agentrun_operation_sdk/services/runtime.py +152 -0
- agentrun_operation_sdk/utils/logging_config.py +72 -0
- agentrun_operation_sdk/utils/runtime/config.py +94 -0
- agentrun_operation_sdk/utils/runtime/container.py +280 -0
- agentrun_operation_sdk/utils/runtime/entrypoint.py +203 -0
- agentrun_operation_sdk/utils/runtime/schema.py +56 -0
- agentrun_sdk/__init__.py +7 -0
- agentrun_sdk/agent/__init__.py +25 -0
- agentrun_sdk/agent/agent.py +696 -0
- agentrun_sdk/agent/agent_result.py +46 -0
- agentrun_sdk/agent/conversation_manager/__init__.py +26 -0
- agentrun_sdk/agent/conversation_manager/conversation_manager.py +88 -0
- agentrun_sdk/agent/conversation_manager/null_conversation_manager.py +46 -0
- agentrun_sdk/agent/conversation_manager/sliding_window_conversation_manager.py +179 -0
- agentrun_sdk/agent/conversation_manager/summarizing_conversation_manager.py +252 -0
- agentrun_sdk/agent/state.py +97 -0
- agentrun_sdk/event_loop/__init__.py +9 -0
- agentrun_sdk/event_loop/event_loop.py +499 -0
- agentrun_sdk/event_loop/streaming.py +319 -0
- agentrun_sdk/experimental/__init__.py +4 -0
- agentrun_sdk/experimental/hooks/__init__.py +15 -0
- agentrun_sdk/experimental/hooks/events.py +123 -0
- agentrun_sdk/handlers/__init__.py +10 -0
- agentrun_sdk/handlers/callback_handler.py +70 -0
- agentrun_sdk/hooks/__init__.py +49 -0
- agentrun_sdk/hooks/events.py +80 -0
- agentrun_sdk/hooks/registry.py +247 -0
- agentrun_sdk/models/__init__.py +10 -0
- agentrun_sdk/models/anthropic.py +432 -0
- agentrun_sdk/models/bedrock.py +649 -0
- agentrun_sdk/models/litellm.py +225 -0
- agentrun_sdk/models/llamaapi.py +438 -0
- agentrun_sdk/models/mistral.py +539 -0
- agentrun_sdk/models/model.py +95 -0
- agentrun_sdk/models/ollama.py +357 -0
- agentrun_sdk/models/openai.py +436 -0
- agentrun_sdk/models/sagemaker.py +598 -0
- agentrun_sdk/models/writer.py +449 -0
- agentrun_sdk/multiagent/__init__.py +22 -0
- agentrun_sdk/multiagent/a2a/__init__.py +15 -0
- agentrun_sdk/multiagent/a2a/executor.py +148 -0
- agentrun_sdk/multiagent/a2a/server.py +252 -0
- agentrun_sdk/multiagent/base.py +92 -0
- agentrun_sdk/multiagent/graph.py +555 -0
- agentrun_sdk/multiagent/swarm.py +656 -0
- agentrun_sdk/py.typed +1 -0
- agentrun_sdk/session/__init__.py +18 -0
- agentrun_sdk/session/file_session_manager.py +216 -0
- agentrun_sdk/session/repository_session_manager.py +152 -0
- agentrun_sdk/session/s3_session_manager.py +272 -0
- agentrun_sdk/session/session_manager.py +73 -0
- agentrun_sdk/session/session_repository.py +51 -0
- agentrun_sdk/telemetry/__init__.py +21 -0
- agentrun_sdk/telemetry/config.py +194 -0
- agentrun_sdk/telemetry/metrics.py +476 -0
- agentrun_sdk/telemetry/metrics_constants.py +15 -0
- agentrun_sdk/telemetry/tracer.py +563 -0
- agentrun_sdk/tools/__init__.py +17 -0
- agentrun_sdk/tools/decorator.py +569 -0
- agentrun_sdk/tools/executor.py +137 -0
- agentrun_sdk/tools/loader.py +152 -0
- agentrun_sdk/tools/mcp/__init__.py +13 -0
- agentrun_sdk/tools/mcp/mcp_agent_tool.py +99 -0
- agentrun_sdk/tools/mcp/mcp_client.py +423 -0
- agentrun_sdk/tools/mcp/mcp_instrumentation.py +322 -0
- agentrun_sdk/tools/mcp/mcp_types.py +63 -0
- agentrun_sdk/tools/registry.py +607 -0
- agentrun_sdk/tools/structured_output.py +421 -0
- agentrun_sdk/tools/tools.py +217 -0
- agentrun_sdk/tools/watcher.py +136 -0
- agentrun_sdk/types/__init__.py +5 -0
- agentrun_sdk/types/collections.py +23 -0
- agentrun_sdk/types/content.py +188 -0
- agentrun_sdk/types/event_loop.py +48 -0
- agentrun_sdk/types/exceptions.py +81 -0
- agentrun_sdk/types/guardrails.py +254 -0
- agentrun_sdk/types/media.py +89 -0
- agentrun_sdk/types/session.py +152 -0
- agentrun_sdk/types/streaming.py +201 -0
- agentrun_sdk/types/tools.py +258 -0
- agentrun_sdk/types/traces.py +5 -0
- agentrun_sdk-0.1.2.dist-info/METADATA +51 -0
- agentrun_sdk-0.1.2.dist-info/RECORD +115 -0
- agentrun_sdk-0.1.2.dist-info/WHEEL +5 -0
- agentrun_sdk-0.1.2.dist-info/entry_points.txt +2 -0
- agentrun_sdk-0.1.2.dist-info/top_level.txt +3 -0
- agentrun_wrapper/__init__.py +11 -0
- agentrun_wrapper/_utils/__init__.py +6 -0
- agentrun_wrapper/_utils/endpoints.py +16 -0
- agentrun_wrapper/identity/__init__.py +5 -0
- agentrun_wrapper/identity/auth.py +211 -0
- agentrun_wrapper/memory/__init__.py +6 -0
- agentrun_wrapper/memory/client.py +1697 -0
- agentrun_wrapper/memory/constants.py +103 -0
- agentrun_wrapper/memory/controlplane.py +626 -0
- agentrun_wrapper/py.typed +1 -0
- agentrun_wrapper/runtime/__init__.py +13 -0
- agentrun_wrapper/runtime/app.py +473 -0
- agentrun_wrapper/runtime/context.py +34 -0
- agentrun_wrapper/runtime/models.py +25 -0
- agentrun_wrapper/services/__init__.py +1 -0
- agentrun_wrapper/services/identity.py +192 -0
- agentrun_wrapper/tools/__init__.py +6 -0
- agentrun_wrapper/tools/browser_client.py +325 -0
- 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))
|