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,247 @@
|
|
|
1
|
+
"""Hook registry system for managing event callbacks in the Strands Agent SDK.
|
|
2
|
+
|
|
3
|
+
This module provides the core infrastructure for the typed hook system, enabling
|
|
4
|
+
composable extension of agent functionality through strongly-typed event callbacks.
|
|
5
|
+
The registry manages the mapping between event types and their associated callback
|
|
6
|
+
functions, supporting both individual callback registration and bulk registration
|
|
7
|
+
via hook provider objects.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Generator, Generic, Protocol, Type, TypeVar
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..agent import Agent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class HookEvent:
|
|
19
|
+
"""Base class for all hook events.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
agent: The agent instance that triggered this event.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
agent: "Agent"
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def should_reverse_callbacks(self) -> bool:
|
|
29
|
+
"""Determine if callbacks for this event should be invoked in reverse order.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
False by default. Override to return True for events that should
|
|
33
|
+
invoke callbacks in reverse order (e.g., cleanup/teardown events).
|
|
34
|
+
"""
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
def _can_write(self, name: str) -> bool:
|
|
38
|
+
"""Check if the given property can be written to.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: The name of the property to check.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
True if the property can be written to, False otherwise.
|
|
45
|
+
"""
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
def __post_init__(self) -> None:
|
|
49
|
+
"""Disallow writes to non-approved properties."""
|
|
50
|
+
# This is needed as otherwise the class can't be initialized at all, so we trigger
|
|
51
|
+
# this after class initialization
|
|
52
|
+
super().__setattr__("_disallow_writes", True)
|
|
53
|
+
|
|
54
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
55
|
+
"""Prevent setting attributes on hook events.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
AttributeError: Always raised to prevent setting attributes on hook events.
|
|
59
|
+
"""
|
|
60
|
+
# Allow setting attributes:
|
|
61
|
+
# - during init (when __dict__) doesn't exist
|
|
62
|
+
# - if the subclass specifically said the property is writable
|
|
63
|
+
if not hasattr(self, "_disallow_writes") or self._can_write(name):
|
|
64
|
+
return super().__setattr__(name, value)
|
|
65
|
+
|
|
66
|
+
raise AttributeError(f"Property {name} is not writable")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
TEvent = TypeVar("TEvent", bound=HookEvent, contravariant=True)
|
|
70
|
+
"""Generic for adding callback handlers - contravariant to allow adding handlers which take in base classes."""
|
|
71
|
+
|
|
72
|
+
TInvokeEvent = TypeVar("TInvokeEvent", bound=HookEvent)
|
|
73
|
+
"""Generic for invoking events - non-contravariant to enable returning events."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class HookProvider(Protocol):
|
|
77
|
+
"""Protocol for objects that provide hook callbacks to an agent.
|
|
78
|
+
|
|
79
|
+
Hook providers offer a composable way to extend agent functionality by
|
|
80
|
+
subscribing to various events in the agent lifecycle. This protocol enables
|
|
81
|
+
building reusable components that can hook into agent events.
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
```python
|
|
85
|
+
class MyHookProvider(HookProvider):
|
|
86
|
+
def register_hooks(self, registry: HookRegistry) -> None:
|
|
87
|
+
registry.add_callback(StartRequestEvent, self.on_request_start)
|
|
88
|
+
registry.add_callback(EndRequestEvent, self.on_request_end)
|
|
89
|
+
|
|
90
|
+
agent = Agent(hooks=[MyHookProvider()])
|
|
91
|
+
```
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def register_hooks(self, registry: "HookRegistry", **kwargs: Any) -> None:
|
|
95
|
+
"""Register callback functions for specific event types.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
registry: The hook registry to register callbacks with.
|
|
99
|
+
**kwargs: Additional keyword arguments for future extensibility.
|
|
100
|
+
"""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class HookCallback(Protocol, Generic[TEvent]):
|
|
105
|
+
"""Protocol for callback functions that handle hook events.
|
|
106
|
+
|
|
107
|
+
Hook callbacks are functions that receive a single strongly-typed event
|
|
108
|
+
argument and perform some action in response. They should not return
|
|
109
|
+
values and any exceptions they raise will propagate to the caller.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
```python
|
|
113
|
+
def my_callback(event: StartRequestEvent) -> None:
|
|
114
|
+
print(f"Request started for agent: {event.agent.name}")
|
|
115
|
+
```
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __call__(self, event: TEvent) -> None:
|
|
119
|
+
"""Handle a hook event.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
event: The strongly-typed event to handle.
|
|
123
|
+
"""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class HookRegistry:
|
|
128
|
+
"""Registry for managing hook callbacks associated with event types.
|
|
129
|
+
|
|
130
|
+
The HookRegistry maintains a mapping of event types to callback functions
|
|
131
|
+
and provides methods for registering callbacks and invoking them when
|
|
132
|
+
events occur.
|
|
133
|
+
|
|
134
|
+
The registry handles callback ordering, including reverse ordering for
|
|
135
|
+
cleanup events, and provides type-safe event dispatching.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(self) -> None:
|
|
139
|
+
"""Initialize an empty hook registry."""
|
|
140
|
+
self._registered_callbacks: dict[Type, list[HookCallback]] = {}
|
|
141
|
+
|
|
142
|
+
def add_callback(self, event_type: Type[TEvent], callback: HookCallback[TEvent]) -> None:
|
|
143
|
+
"""Register a callback function for a specific event type.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
event_type: The class type of events this callback should handle.
|
|
147
|
+
callback: The callback function to invoke when events of this type occur.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
```python
|
|
151
|
+
def my_handler(event: StartRequestEvent):
|
|
152
|
+
print("Request started")
|
|
153
|
+
|
|
154
|
+
registry.add_callback(StartRequestEvent, my_handler)
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
157
|
+
callbacks = self._registered_callbacks.setdefault(event_type, [])
|
|
158
|
+
callbacks.append(callback)
|
|
159
|
+
|
|
160
|
+
def add_hook(self, hook: HookProvider) -> None:
|
|
161
|
+
"""Register all callbacks from a hook provider.
|
|
162
|
+
|
|
163
|
+
This method allows bulk registration of callbacks by delegating to
|
|
164
|
+
the hook provider's register_hooks method. This is the preferred
|
|
165
|
+
way to register multiple related callbacks.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
hook: The hook provider containing callbacks to register.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
```python
|
|
172
|
+
class MyHooks(HookProvider):
|
|
173
|
+
def register_hooks(self, registry: HookRegistry):
|
|
174
|
+
registry.add_callback(StartRequestEvent, self.on_start)
|
|
175
|
+
registry.add_callback(EndRequestEvent, self.on_end)
|
|
176
|
+
|
|
177
|
+
registry.add_hook(MyHooks())
|
|
178
|
+
```
|
|
179
|
+
"""
|
|
180
|
+
hook.register_hooks(self)
|
|
181
|
+
|
|
182
|
+
def invoke_callbacks(self, event: TInvokeEvent) -> TInvokeEvent:
|
|
183
|
+
"""Invoke all registered callbacks for the given event.
|
|
184
|
+
|
|
185
|
+
This method finds all callbacks registered for the event's type and
|
|
186
|
+
invokes them in the appropriate order. For events with should_reverse_callbacks=True,
|
|
187
|
+
callbacks are invoked in reverse registration order. Any exceptions raised by callback
|
|
188
|
+
functions will propagate to the caller.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
event: The event to dispatch to registered callbacks.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The event dispatched to registered callbacks.
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
```python
|
|
198
|
+
event = StartRequestEvent(agent=my_agent)
|
|
199
|
+
registry.invoke_callbacks(event)
|
|
200
|
+
```
|
|
201
|
+
"""
|
|
202
|
+
for callback in self.get_callbacks_for(event):
|
|
203
|
+
callback(event)
|
|
204
|
+
|
|
205
|
+
return event
|
|
206
|
+
|
|
207
|
+
def has_callbacks(self) -> bool:
|
|
208
|
+
"""Check if the registry has any registered callbacks.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if there are any registered callbacks, False otherwise.
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
```python
|
|
215
|
+
if registry.has_callbacks():
|
|
216
|
+
print("Registry has callbacks registered")
|
|
217
|
+
```
|
|
218
|
+
"""
|
|
219
|
+
return bool(self._registered_callbacks)
|
|
220
|
+
|
|
221
|
+
def get_callbacks_for(self, event: TEvent) -> Generator[HookCallback[TEvent], None, None]:
|
|
222
|
+
"""Get callbacks registered for the given event in the appropriate order.
|
|
223
|
+
|
|
224
|
+
This method returns callbacks in registration order for normal events,
|
|
225
|
+
or reverse registration order for events that have should_reverse_callbacks=True.
|
|
226
|
+
This enables proper cleanup ordering for teardown events.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
event: The event to get callbacks for.
|
|
230
|
+
|
|
231
|
+
Yields:
|
|
232
|
+
Callback functions registered for this event type, in the appropriate order.
|
|
233
|
+
|
|
234
|
+
Example:
|
|
235
|
+
```python
|
|
236
|
+
event = EndRequestEvent(agent=my_agent)
|
|
237
|
+
for callback in registry.get_callbacks_for(event):
|
|
238
|
+
callback(event)
|
|
239
|
+
```
|
|
240
|
+
"""
|
|
241
|
+
event_type = type(event)
|
|
242
|
+
|
|
243
|
+
callbacks = self._registered_callbacks.get(event_type, [])
|
|
244
|
+
if event.should_reverse_callbacks:
|
|
245
|
+
yield from reversed(callbacks)
|
|
246
|
+
else:
|
|
247
|
+
yield from callbacks
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""SDK model providers.
|
|
2
|
+
|
|
3
|
+
This package includes an abstract base Model class along with concrete implementations for specific providers.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from . import bedrock, model
|
|
7
|
+
from .bedrock import BedrockModel
|
|
8
|
+
from .model import Model
|
|
9
|
+
|
|
10
|
+
__all__ = ["bedrock", "model", "BedrockModel", "Model"]
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"""Anthropic Claude model provider.
|
|
2
|
+
|
|
3
|
+
- Docs: https://docs.anthropic.com/claude/reference/getting-started-with-the-api
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import mimetypes
|
|
10
|
+
from typing import Any, AsyncGenerator, Optional, Type, TypedDict, TypeVar, Union, cast
|
|
11
|
+
|
|
12
|
+
import anthropic
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from typing_extensions import Required, Unpack, override
|
|
15
|
+
|
|
16
|
+
from ..event_loop.streaming import process_stream
|
|
17
|
+
from ..tools import convert_pydantic_to_tool_spec
|
|
18
|
+
from ..types.content import ContentBlock, Messages
|
|
19
|
+
from ..types.exceptions import ContextWindowOverflowException, ModelThrottledException
|
|
20
|
+
from ..types.streaming import StreamEvent
|
|
21
|
+
from ..types.tools import ToolSpec
|
|
22
|
+
from .model import Model
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T", bound=BaseModel)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AnthropicModel(Model):
|
|
30
|
+
"""Anthropic model provider implementation."""
|
|
31
|
+
|
|
32
|
+
EVENT_TYPES = {
|
|
33
|
+
"message_start",
|
|
34
|
+
"content_block_start",
|
|
35
|
+
"content_block_delta",
|
|
36
|
+
"content_block_stop",
|
|
37
|
+
"message_stop",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
OVERFLOW_MESSAGES = {
|
|
41
|
+
"input is too long",
|
|
42
|
+
"input length exceeds context window",
|
|
43
|
+
"input and output tokens exceed your context limit",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class AnthropicConfig(TypedDict, total=False):
|
|
47
|
+
"""Configuration options for Anthropic models.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
max_tokens: Maximum number of tokens to generate.
|
|
51
|
+
model_id: Calude model ID (e.g., "claude-3-7-sonnet-latest").
|
|
52
|
+
For a complete list of supported models, see
|
|
53
|
+
https://docs.anthropic.com/en/docs/about-claude/models/all-models.
|
|
54
|
+
params: Additional model parameters (e.g., temperature).
|
|
55
|
+
For a complete list of supported parameters, see https://docs.anthropic.com/en/api/messages.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
max_tokens: Required[int]
|
|
59
|
+
model_id: Required[str]
|
|
60
|
+
params: Optional[dict[str, Any]]
|
|
61
|
+
|
|
62
|
+
def __init__(self, *, client_args: Optional[dict[str, Any]] = None, **model_config: Unpack[AnthropicConfig]):
|
|
63
|
+
"""Initialize provider instance.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
client_args: Arguments for the underlying Anthropic client (e.g., api_key).
|
|
67
|
+
For a complete list of supported arguments, see https://docs.anthropic.com/en/api/client-sdks.
|
|
68
|
+
**model_config: Configuration options for the Anthropic model.
|
|
69
|
+
"""
|
|
70
|
+
self.config = AnthropicModel.AnthropicConfig(**model_config)
|
|
71
|
+
|
|
72
|
+
logger.debug("config=<%s> | initializing", self.config)
|
|
73
|
+
|
|
74
|
+
client_args = client_args or {}
|
|
75
|
+
self.client = anthropic.AsyncAnthropic(**client_args)
|
|
76
|
+
|
|
77
|
+
@override
|
|
78
|
+
def update_config(self, **model_config: Unpack[AnthropicConfig]) -> None: # type: ignore[override]
|
|
79
|
+
"""Update the Anthropic model configuration with the provided arguments.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
**model_config: Configuration overrides.
|
|
83
|
+
"""
|
|
84
|
+
self.config.update(model_config)
|
|
85
|
+
|
|
86
|
+
@override
|
|
87
|
+
def get_config(self) -> AnthropicConfig:
|
|
88
|
+
"""Get the Anthropic model configuration.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The Anthropic model configuration.
|
|
92
|
+
"""
|
|
93
|
+
return self.config
|
|
94
|
+
|
|
95
|
+
def _format_request_message_content(self, content: ContentBlock) -> dict[str, Any]:
|
|
96
|
+
"""Format an Anthropic content block.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
content: Message content.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Anthropic formatted content block.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
TypeError: If the content block type cannot be converted to an Anthropic-compatible format.
|
|
106
|
+
"""
|
|
107
|
+
if "document" in content:
|
|
108
|
+
mime_type = mimetypes.types_map.get(f".{content['document']['format']}", "application/octet-stream")
|
|
109
|
+
return {
|
|
110
|
+
"source": {
|
|
111
|
+
"data": (
|
|
112
|
+
content["document"]["source"]["bytes"].decode("utf-8")
|
|
113
|
+
if mime_type == "text/plain"
|
|
114
|
+
else base64.b64encode(content["document"]["source"]["bytes"]).decode("utf-8")
|
|
115
|
+
),
|
|
116
|
+
"media_type": mime_type,
|
|
117
|
+
"type": "text" if mime_type == "text/plain" else "base64",
|
|
118
|
+
},
|
|
119
|
+
"title": content["document"]["name"],
|
|
120
|
+
"type": "document",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if "image" in content:
|
|
124
|
+
return {
|
|
125
|
+
"source": {
|
|
126
|
+
"data": base64.b64encode(content["image"]["source"]["bytes"]).decode("utf-8"),
|
|
127
|
+
"media_type": mimetypes.types_map.get(f".{content['image']['format']}", "application/octet-stream"),
|
|
128
|
+
"type": "base64",
|
|
129
|
+
},
|
|
130
|
+
"type": "image",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if "reasoningContent" in content:
|
|
134
|
+
return {
|
|
135
|
+
"signature": content["reasoningContent"]["reasoningText"]["signature"],
|
|
136
|
+
"thinking": content["reasoningContent"]["reasoningText"]["text"],
|
|
137
|
+
"type": "thinking",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if "text" in content:
|
|
141
|
+
return {"text": content["text"], "type": "text"}
|
|
142
|
+
|
|
143
|
+
if "toolUse" in content:
|
|
144
|
+
return {
|
|
145
|
+
"id": content["toolUse"]["toolUseId"],
|
|
146
|
+
"input": content["toolUse"]["input"],
|
|
147
|
+
"name": content["toolUse"]["name"],
|
|
148
|
+
"type": "tool_use",
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if "toolResult" in content:
|
|
152
|
+
return {
|
|
153
|
+
"content": [
|
|
154
|
+
self._format_request_message_content(
|
|
155
|
+
{"text": json.dumps(tool_result_content["json"])}
|
|
156
|
+
if "json" in tool_result_content
|
|
157
|
+
else cast(ContentBlock, tool_result_content)
|
|
158
|
+
)
|
|
159
|
+
for tool_result_content in content["toolResult"]["content"]
|
|
160
|
+
],
|
|
161
|
+
"is_error": content["toolResult"]["status"] == "error",
|
|
162
|
+
"tool_use_id": content["toolResult"]["toolUseId"],
|
|
163
|
+
"type": "tool_result",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
raise TypeError(f"content_type=<{next(iter(content))}> | unsupported type")
|
|
167
|
+
|
|
168
|
+
def _format_request_messages(self, messages: Messages) -> list[dict[str, Any]]:
|
|
169
|
+
"""Format an Anthropic messages array.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
messages: List of message objects to be processed by the model.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
An Anthropic messages array.
|
|
176
|
+
"""
|
|
177
|
+
formatted_messages = []
|
|
178
|
+
|
|
179
|
+
for message in messages:
|
|
180
|
+
formatted_contents: list[dict[str, Any]] = []
|
|
181
|
+
|
|
182
|
+
for content in message["content"]:
|
|
183
|
+
if "cachePoint" in content:
|
|
184
|
+
formatted_contents[-1]["cache_control"] = {"type": "ephemeral"}
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
formatted_contents.append(self._format_request_message_content(content))
|
|
188
|
+
|
|
189
|
+
if formatted_contents:
|
|
190
|
+
formatted_messages.append({"content": formatted_contents, "role": message["role"]})
|
|
191
|
+
|
|
192
|
+
return formatted_messages
|
|
193
|
+
|
|
194
|
+
def format_request(
|
|
195
|
+
self, messages: Messages, tool_specs: Optional[list[ToolSpec]] = None, system_prompt: Optional[str] = None
|
|
196
|
+
) -> dict[str, Any]:
|
|
197
|
+
"""Format an Anthropic streaming request.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
messages: List of message objects to be processed by the model.
|
|
201
|
+
tool_specs: List of tool specifications to make available to the model.
|
|
202
|
+
system_prompt: System prompt to provide context to the model.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
An Anthropic streaming request.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
TypeError: If a message contains a content block type that cannot be converted to an Anthropic-compatible
|
|
209
|
+
format.
|
|
210
|
+
"""
|
|
211
|
+
return {
|
|
212
|
+
"max_tokens": self.config["max_tokens"],
|
|
213
|
+
"messages": self._format_request_messages(messages),
|
|
214
|
+
"model": self.config["model_id"],
|
|
215
|
+
"tools": [
|
|
216
|
+
{
|
|
217
|
+
"name": tool_spec["name"],
|
|
218
|
+
"description": tool_spec["description"],
|
|
219
|
+
"input_schema": tool_spec["inputSchema"]["json"],
|
|
220
|
+
}
|
|
221
|
+
for tool_spec in tool_specs or []
|
|
222
|
+
],
|
|
223
|
+
**({"system": system_prompt} if system_prompt else {}),
|
|
224
|
+
**(self.config.get("params") or {}),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
def format_chunk(self, event: dict[str, Any]) -> StreamEvent:
|
|
228
|
+
"""Format the Anthropic response events into standardized message chunks.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
event: A response event from the Anthropic model.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The formatted chunk.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
RuntimeError: If chunk_type is not recognized.
|
|
238
|
+
This error should never be encountered as we control chunk_type in the stream method.
|
|
239
|
+
"""
|
|
240
|
+
match event["type"]:
|
|
241
|
+
case "message_start":
|
|
242
|
+
return {"messageStart": {"role": "assistant"}}
|
|
243
|
+
|
|
244
|
+
case "content_block_start":
|
|
245
|
+
content = event["content_block"]
|
|
246
|
+
|
|
247
|
+
if content["type"] == "tool_use":
|
|
248
|
+
return {
|
|
249
|
+
"contentBlockStart": {
|
|
250
|
+
"contentBlockIndex": event["index"],
|
|
251
|
+
"start": {
|
|
252
|
+
"toolUse": {
|
|
253
|
+
"name": content["name"],
|
|
254
|
+
"toolUseId": content["id"],
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {"contentBlockStart": {"contentBlockIndex": event["index"], "start": {}}}
|
|
261
|
+
|
|
262
|
+
case "content_block_delta":
|
|
263
|
+
delta = event["delta"]
|
|
264
|
+
|
|
265
|
+
match delta["type"]:
|
|
266
|
+
case "signature_delta":
|
|
267
|
+
return {
|
|
268
|
+
"contentBlockDelta": {
|
|
269
|
+
"contentBlockIndex": event["index"],
|
|
270
|
+
"delta": {
|
|
271
|
+
"reasoningContent": {
|
|
272
|
+
"signature": delta["signature"],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "thinking_delta":
|
|
279
|
+
return {
|
|
280
|
+
"contentBlockDelta": {
|
|
281
|
+
"contentBlockIndex": event["index"],
|
|
282
|
+
"delta": {
|
|
283
|
+
"reasoningContent": {
|
|
284
|
+
"text": delta["thinking"],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case "input_json_delta":
|
|
291
|
+
return {
|
|
292
|
+
"contentBlockDelta": {
|
|
293
|
+
"contentBlockIndex": event["index"],
|
|
294
|
+
"delta": {
|
|
295
|
+
"toolUse": {
|
|
296
|
+
"input": delta["partial_json"],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case "text_delta":
|
|
303
|
+
return {
|
|
304
|
+
"contentBlockDelta": {
|
|
305
|
+
"contentBlockIndex": event["index"],
|
|
306
|
+
"delta": {
|
|
307
|
+
"text": delta["text"],
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case _:
|
|
313
|
+
raise RuntimeError(
|
|
314
|
+
f"event_type=<content_block_delta>, delta_type=<{delta['type']}> | unknown type"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
case "content_block_stop":
|
|
318
|
+
return {"contentBlockStop": {"contentBlockIndex": event["index"]}}
|
|
319
|
+
|
|
320
|
+
case "message_stop":
|
|
321
|
+
message = event["message"]
|
|
322
|
+
|
|
323
|
+
return {"messageStop": {"stopReason": message["stop_reason"]}}
|
|
324
|
+
|
|
325
|
+
case "metadata":
|
|
326
|
+
usage = event["usage"]
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
"metadata": {
|
|
330
|
+
"usage": {
|
|
331
|
+
"inputTokens": usage["input_tokens"],
|
|
332
|
+
"outputTokens": usage["output_tokens"],
|
|
333
|
+
"totalTokens": usage["input_tokens"] + usage["output_tokens"],
|
|
334
|
+
},
|
|
335
|
+
"metrics": {
|
|
336
|
+
"latencyMs": 0, # TODO
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
case _:
|
|
342
|
+
raise RuntimeError(f"event_type=<{event['type']} | unknown type")
|
|
343
|
+
|
|
344
|
+
@override
|
|
345
|
+
async def stream(
|
|
346
|
+
self,
|
|
347
|
+
messages: Messages,
|
|
348
|
+
tool_specs: Optional[list[ToolSpec]] = None,
|
|
349
|
+
system_prompt: Optional[str] = None,
|
|
350
|
+
**kwargs: Any,
|
|
351
|
+
) -> AsyncGenerator[StreamEvent, None]:
|
|
352
|
+
"""Stream conversation with the Anthropic model.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
messages: List of message objects to be processed by the model.
|
|
356
|
+
tool_specs: List of tool specifications to make available to the model.
|
|
357
|
+
system_prompt: System prompt to provide context to the model.
|
|
358
|
+
**kwargs: Additional keyword arguments for future extensibility.
|
|
359
|
+
|
|
360
|
+
Yields:
|
|
361
|
+
Formatted message chunks from the model.
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
ContextWindowOverflowException: If the input exceeds the model's context window.
|
|
365
|
+
ModelThrottledException: If the request is throttled by Anthropic.
|
|
366
|
+
"""
|
|
367
|
+
logger.debug("formatting request")
|
|
368
|
+
request = self.format_request(messages, tool_specs, system_prompt)
|
|
369
|
+
logger.debug("request=<%s>", request)
|
|
370
|
+
|
|
371
|
+
logger.debug("invoking model")
|
|
372
|
+
try:
|
|
373
|
+
async with self.client.messages.stream(**request) as stream:
|
|
374
|
+
logger.debug("got response from model")
|
|
375
|
+
async for event in stream:
|
|
376
|
+
if event.type in AnthropicModel.EVENT_TYPES:
|
|
377
|
+
yield self.format_chunk(event.model_dump())
|
|
378
|
+
|
|
379
|
+
usage = event.message.usage # type: ignore
|
|
380
|
+
yield self.format_chunk({"type": "metadata", "usage": usage.model_dump()})
|
|
381
|
+
|
|
382
|
+
except anthropic.RateLimitError as error:
|
|
383
|
+
raise ModelThrottledException(str(error)) from error
|
|
384
|
+
|
|
385
|
+
except anthropic.BadRequestError as error:
|
|
386
|
+
if any(overflow_message in str(error).lower() for overflow_message in AnthropicModel.OVERFLOW_MESSAGES):
|
|
387
|
+
raise ContextWindowOverflowException(str(error)) from error
|
|
388
|
+
|
|
389
|
+
raise error
|
|
390
|
+
|
|
391
|
+
logger.debug("finished streaming response from model")
|
|
392
|
+
|
|
393
|
+
@override
|
|
394
|
+
async def structured_output(
|
|
395
|
+
self, output_model: Type[T], prompt: Messages, system_prompt: Optional[str] = None, **kwargs: Any
|
|
396
|
+
) -> AsyncGenerator[dict[str, Union[T, Any]], None]:
|
|
397
|
+
"""Get structured output from the model.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
output_model: The output model to use for the agent.
|
|
401
|
+
prompt: The prompt messages to use for the agent.
|
|
402
|
+
system_prompt: System prompt to provide context to the model.
|
|
403
|
+
**kwargs: Additional keyword arguments for future extensibility.
|
|
404
|
+
|
|
405
|
+
Yields:
|
|
406
|
+
Model events with the last being the structured output.
|
|
407
|
+
"""
|
|
408
|
+
tool_spec = convert_pydantic_to_tool_spec(output_model)
|
|
409
|
+
|
|
410
|
+
response = self.stream(messages=prompt, tool_specs=[tool_spec], system_prompt=system_prompt, **kwargs)
|
|
411
|
+
async for event in process_stream(response):
|
|
412
|
+
yield event
|
|
413
|
+
|
|
414
|
+
stop_reason, messages, _, _ = event["stop"]
|
|
415
|
+
|
|
416
|
+
if stop_reason != "tool_use":
|
|
417
|
+
raise ValueError(f'Model returned stop_reason: {stop_reason} instead of "tool_use".')
|
|
418
|
+
|
|
419
|
+
content = messages["content"]
|
|
420
|
+
output_response: dict[str, Any] | None = None
|
|
421
|
+
for block in content:
|
|
422
|
+
# if the tool use name doesn't match the tool spec name, skip, and if the block is not a tool use, skip.
|
|
423
|
+
# if the tool use name never matches, raise an error.
|
|
424
|
+
if block.get("toolUse") and block["toolUse"]["name"] == tool_spec["name"]:
|
|
425
|
+
output_response = block["toolUse"]["input"]
|
|
426
|
+
else:
|
|
427
|
+
continue
|
|
428
|
+
|
|
429
|
+
if output_response is None:
|
|
430
|
+
raise ValueError("No valid tool use or tool use input was found in the Anthropic response.")
|
|
431
|
+
|
|
432
|
+
yield {"output": output_model(**output_response)}
|