optexity-browser-use 0.9.5__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.
Files changed (147) hide show
  1. browser_use/__init__.py +157 -0
  2. browser_use/actor/__init__.py +11 -0
  3. browser_use/actor/element.py +1175 -0
  4. browser_use/actor/mouse.py +134 -0
  5. browser_use/actor/page.py +561 -0
  6. browser_use/actor/playground/flights.py +41 -0
  7. browser_use/actor/playground/mixed_automation.py +54 -0
  8. browser_use/actor/playground/playground.py +236 -0
  9. browser_use/actor/utils.py +176 -0
  10. browser_use/agent/cloud_events.py +282 -0
  11. browser_use/agent/gif.py +424 -0
  12. browser_use/agent/judge.py +170 -0
  13. browser_use/agent/message_manager/service.py +473 -0
  14. browser_use/agent/message_manager/utils.py +52 -0
  15. browser_use/agent/message_manager/views.py +98 -0
  16. browser_use/agent/prompts.py +413 -0
  17. browser_use/agent/service.py +2316 -0
  18. browser_use/agent/system_prompt.md +185 -0
  19. browser_use/agent/system_prompt_flash.md +10 -0
  20. browser_use/agent/system_prompt_no_thinking.md +183 -0
  21. browser_use/agent/views.py +743 -0
  22. browser_use/browser/__init__.py +41 -0
  23. browser_use/browser/cloud/cloud.py +203 -0
  24. browser_use/browser/cloud/views.py +89 -0
  25. browser_use/browser/events.py +578 -0
  26. browser_use/browser/profile.py +1158 -0
  27. browser_use/browser/python_highlights.py +548 -0
  28. browser_use/browser/session.py +3225 -0
  29. browser_use/browser/session_manager.py +399 -0
  30. browser_use/browser/video_recorder.py +162 -0
  31. browser_use/browser/views.py +200 -0
  32. browser_use/browser/watchdog_base.py +260 -0
  33. browser_use/browser/watchdogs/__init__.py +0 -0
  34. browser_use/browser/watchdogs/aboutblank_watchdog.py +253 -0
  35. browser_use/browser/watchdogs/crash_watchdog.py +335 -0
  36. browser_use/browser/watchdogs/default_action_watchdog.py +2729 -0
  37. browser_use/browser/watchdogs/dom_watchdog.py +817 -0
  38. browser_use/browser/watchdogs/downloads_watchdog.py +1277 -0
  39. browser_use/browser/watchdogs/local_browser_watchdog.py +461 -0
  40. browser_use/browser/watchdogs/permissions_watchdog.py +43 -0
  41. browser_use/browser/watchdogs/popups_watchdog.py +143 -0
  42. browser_use/browser/watchdogs/recording_watchdog.py +126 -0
  43. browser_use/browser/watchdogs/screenshot_watchdog.py +62 -0
  44. browser_use/browser/watchdogs/security_watchdog.py +280 -0
  45. browser_use/browser/watchdogs/storage_state_watchdog.py +335 -0
  46. browser_use/cli.py +2359 -0
  47. browser_use/code_use/__init__.py +16 -0
  48. browser_use/code_use/formatting.py +192 -0
  49. browser_use/code_use/namespace.py +665 -0
  50. browser_use/code_use/notebook_export.py +276 -0
  51. browser_use/code_use/service.py +1340 -0
  52. browser_use/code_use/system_prompt.md +574 -0
  53. browser_use/code_use/utils.py +150 -0
  54. browser_use/code_use/views.py +171 -0
  55. browser_use/config.py +505 -0
  56. browser_use/controller/__init__.py +3 -0
  57. browser_use/dom/enhanced_snapshot.py +161 -0
  58. browser_use/dom/markdown_extractor.py +169 -0
  59. browser_use/dom/playground/extraction.py +312 -0
  60. browser_use/dom/playground/multi_act.py +32 -0
  61. browser_use/dom/serializer/clickable_elements.py +200 -0
  62. browser_use/dom/serializer/code_use_serializer.py +287 -0
  63. browser_use/dom/serializer/eval_serializer.py +478 -0
  64. browser_use/dom/serializer/html_serializer.py +212 -0
  65. browser_use/dom/serializer/paint_order.py +197 -0
  66. browser_use/dom/serializer/serializer.py +1170 -0
  67. browser_use/dom/service.py +825 -0
  68. browser_use/dom/utils.py +129 -0
  69. browser_use/dom/views.py +906 -0
  70. browser_use/exceptions.py +5 -0
  71. browser_use/filesystem/__init__.py +0 -0
  72. browser_use/filesystem/file_system.py +619 -0
  73. browser_use/init_cmd.py +376 -0
  74. browser_use/integrations/gmail/__init__.py +24 -0
  75. browser_use/integrations/gmail/actions.py +115 -0
  76. browser_use/integrations/gmail/service.py +225 -0
  77. browser_use/llm/__init__.py +155 -0
  78. browser_use/llm/anthropic/chat.py +242 -0
  79. browser_use/llm/anthropic/serializer.py +312 -0
  80. browser_use/llm/aws/__init__.py +36 -0
  81. browser_use/llm/aws/chat_anthropic.py +242 -0
  82. browser_use/llm/aws/chat_bedrock.py +289 -0
  83. browser_use/llm/aws/serializer.py +257 -0
  84. browser_use/llm/azure/chat.py +91 -0
  85. browser_use/llm/base.py +57 -0
  86. browser_use/llm/browser_use/__init__.py +3 -0
  87. browser_use/llm/browser_use/chat.py +201 -0
  88. browser_use/llm/cerebras/chat.py +193 -0
  89. browser_use/llm/cerebras/serializer.py +109 -0
  90. browser_use/llm/deepseek/chat.py +212 -0
  91. browser_use/llm/deepseek/serializer.py +109 -0
  92. browser_use/llm/exceptions.py +29 -0
  93. browser_use/llm/google/__init__.py +3 -0
  94. browser_use/llm/google/chat.py +542 -0
  95. browser_use/llm/google/serializer.py +120 -0
  96. browser_use/llm/groq/chat.py +229 -0
  97. browser_use/llm/groq/parser.py +158 -0
  98. browser_use/llm/groq/serializer.py +159 -0
  99. browser_use/llm/messages.py +238 -0
  100. browser_use/llm/models.py +271 -0
  101. browser_use/llm/oci_raw/__init__.py +10 -0
  102. browser_use/llm/oci_raw/chat.py +443 -0
  103. browser_use/llm/oci_raw/serializer.py +229 -0
  104. browser_use/llm/ollama/chat.py +97 -0
  105. browser_use/llm/ollama/serializer.py +143 -0
  106. browser_use/llm/openai/chat.py +264 -0
  107. browser_use/llm/openai/like.py +15 -0
  108. browser_use/llm/openai/serializer.py +165 -0
  109. browser_use/llm/openrouter/chat.py +211 -0
  110. browser_use/llm/openrouter/serializer.py +26 -0
  111. browser_use/llm/schema.py +176 -0
  112. browser_use/llm/views.py +48 -0
  113. browser_use/logging_config.py +330 -0
  114. browser_use/mcp/__init__.py +18 -0
  115. browser_use/mcp/__main__.py +12 -0
  116. browser_use/mcp/client.py +544 -0
  117. browser_use/mcp/controller.py +264 -0
  118. browser_use/mcp/server.py +1114 -0
  119. browser_use/observability.py +204 -0
  120. browser_use/py.typed +0 -0
  121. browser_use/sandbox/__init__.py +41 -0
  122. browser_use/sandbox/sandbox.py +637 -0
  123. browser_use/sandbox/views.py +132 -0
  124. browser_use/screenshots/__init__.py +1 -0
  125. browser_use/screenshots/service.py +52 -0
  126. browser_use/sync/__init__.py +6 -0
  127. browser_use/sync/auth.py +357 -0
  128. browser_use/sync/service.py +161 -0
  129. browser_use/telemetry/__init__.py +51 -0
  130. browser_use/telemetry/service.py +112 -0
  131. browser_use/telemetry/views.py +101 -0
  132. browser_use/tokens/__init__.py +0 -0
  133. browser_use/tokens/custom_pricing.py +24 -0
  134. browser_use/tokens/mappings.py +4 -0
  135. browser_use/tokens/service.py +580 -0
  136. browser_use/tokens/views.py +108 -0
  137. browser_use/tools/registry/service.py +572 -0
  138. browser_use/tools/registry/views.py +174 -0
  139. browser_use/tools/service.py +1675 -0
  140. browser_use/tools/utils.py +82 -0
  141. browser_use/tools/views.py +100 -0
  142. browser_use/utils.py +670 -0
  143. optexity_browser_use-0.9.5.dist-info/METADATA +344 -0
  144. optexity_browser_use-0.9.5.dist-info/RECORD +147 -0
  145. optexity_browser_use-0.9.5.dist-info/WHEEL +4 -0
  146. optexity_browser_use-0.9.5.dist-info/entry_points.txt +3 -0
  147. optexity_browser_use-0.9.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,312 @@
1
+ import json
2
+ from typing import overload
3
+
4
+ from anthropic.types import (
5
+ Base64ImageSourceParam,
6
+ CacheControlEphemeralParam,
7
+ ImageBlockParam,
8
+ MessageParam,
9
+ TextBlockParam,
10
+ ToolUseBlockParam,
11
+ URLImageSourceParam,
12
+ )
13
+
14
+ from browser_use.llm.messages import (
15
+ AssistantMessage,
16
+ BaseMessage,
17
+ ContentPartImageParam,
18
+ ContentPartTextParam,
19
+ SupportedImageMediaType,
20
+ SystemMessage,
21
+ UserMessage,
22
+ )
23
+
24
+ NonSystemMessage = UserMessage | AssistantMessage
25
+
26
+
27
+ class AnthropicMessageSerializer:
28
+ """Serializer for converting between custom message types and Anthropic message param types."""
29
+
30
+ @staticmethod
31
+ def _is_base64_image(url: str) -> bool:
32
+ """Check if the URL is a base64 encoded image."""
33
+ return url.startswith('data:image/')
34
+
35
+ @staticmethod
36
+ def _parse_base64_url(url: str) -> tuple[SupportedImageMediaType, str]:
37
+ """Parse a base64 data URL to extract media type and data."""
38
+ # Format: data:image/jpeg;base64,<data>
39
+ if not url.startswith('data:'):
40
+ raise ValueError(f'Invalid base64 URL: {url}')
41
+
42
+ header, data = url.split(',', 1)
43
+ media_type = header.split(';')[0].replace('data:', '')
44
+
45
+ # Ensure it's a supported media type
46
+ supported_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
47
+ if media_type not in supported_types:
48
+ # Default to jpeg if not recognized
49
+ media_type = 'image/jpeg'
50
+
51
+ return media_type, data # type: ignore
52
+
53
+ @staticmethod
54
+ def _serialize_cache_control(use_cache: bool) -> CacheControlEphemeralParam | None:
55
+ """Serialize cache control."""
56
+ if use_cache:
57
+ return CacheControlEphemeralParam(type='ephemeral')
58
+ return None
59
+
60
+ @staticmethod
61
+ def _serialize_content_part_text(part: ContentPartTextParam, use_cache: bool) -> TextBlockParam:
62
+ """Convert a text content part to Anthropic's TextBlockParam."""
63
+ return TextBlockParam(
64
+ text=part.text, type='text', cache_control=AnthropicMessageSerializer._serialize_cache_control(use_cache)
65
+ )
66
+
67
+ @staticmethod
68
+ def _serialize_content_part_image(part: ContentPartImageParam) -> ImageBlockParam:
69
+ """Convert an image content part to Anthropic's ImageBlockParam."""
70
+ url = part.image_url.url
71
+
72
+ if AnthropicMessageSerializer._is_base64_image(url):
73
+ # Handle base64 encoded images
74
+ media_type, data = AnthropicMessageSerializer._parse_base64_url(url)
75
+ return ImageBlockParam(
76
+ source=Base64ImageSourceParam(
77
+ data=data,
78
+ media_type=media_type,
79
+ type='base64',
80
+ ),
81
+ type='image',
82
+ )
83
+ else:
84
+ # Handle URL images
85
+ return ImageBlockParam(source=URLImageSourceParam(url=url, type='url'), type='image')
86
+
87
+ @staticmethod
88
+ def _serialize_content_to_str(
89
+ content: str | list[ContentPartTextParam], use_cache: bool = False
90
+ ) -> list[TextBlockParam] | str:
91
+ """Serialize content to a string."""
92
+ cache_control = AnthropicMessageSerializer._serialize_cache_control(use_cache)
93
+
94
+ if isinstance(content, str):
95
+ if cache_control:
96
+ return [TextBlockParam(text=content, type='text', cache_control=cache_control)]
97
+ else:
98
+ return content
99
+
100
+ serialized_blocks: list[TextBlockParam] = []
101
+ for part in content:
102
+ if part.type == 'text':
103
+ serialized_blocks.append(AnthropicMessageSerializer._serialize_content_part_text(part, use_cache))
104
+
105
+ return serialized_blocks
106
+
107
+ @staticmethod
108
+ def _serialize_content(
109
+ content: str | list[ContentPartTextParam | ContentPartImageParam],
110
+ use_cache: bool = False,
111
+ ) -> str | list[TextBlockParam | ImageBlockParam]:
112
+ """Serialize content to Anthropic format."""
113
+ if isinstance(content, str):
114
+ if use_cache:
115
+ return [TextBlockParam(text=content, type='text', cache_control=CacheControlEphemeralParam(type='ephemeral'))]
116
+ else:
117
+ return content
118
+
119
+ serialized_blocks: list[TextBlockParam | ImageBlockParam] = []
120
+ for part in content:
121
+ if part.type == 'text':
122
+ serialized_blocks.append(AnthropicMessageSerializer._serialize_content_part_text(part, use_cache))
123
+ elif part.type == 'image_url':
124
+ serialized_blocks.append(AnthropicMessageSerializer._serialize_content_part_image(part))
125
+
126
+ return serialized_blocks
127
+
128
+ @staticmethod
129
+ def _serialize_tool_calls_to_content(tool_calls, use_cache: bool = False) -> list[ToolUseBlockParam]:
130
+ """Convert tool calls to Anthropic's ToolUseBlockParam format."""
131
+ blocks: list[ToolUseBlockParam] = []
132
+ for tool_call in tool_calls:
133
+ # Parse the arguments JSON string to object
134
+
135
+ try:
136
+ input_obj = json.loads(tool_call.function.arguments)
137
+ except json.JSONDecodeError:
138
+ # If arguments aren't valid JSON, use as string
139
+ input_obj = {'arguments': tool_call.function.arguments}
140
+
141
+ blocks.append(
142
+ ToolUseBlockParam(
143
+ id=tool_call.id,
144
+ input=input_obj,
145
+ name=tool_call.function.name,
146
+ type='tool_use',
147
+ cache_control=AnthropicMessageSerializer._serialize_cache_control(use_cache),
148
+ )
149
+ )
150
+ return blocks
151
+
152
+ # region - Serialize overloads
153
+ @overload
154
+ @staticmethod
155
+ def serialize(message: UserMessage) -> MessageParam: ...
156
+
157
+ @overload
158
+ @staticmethod
159
+ def serialize(message: SystemMessage) -> SystemMessage: ...
160
+
161
+ @overload
162
+ @staticmethod
163
+ def serialize(message: AssistantMessage) -> MessageParam: ...
164
+
165
+ @staticmethod
166
+ def serialize(message: BaseMessage) -> MessageParam | SystemMessage:
167
+ """Serialize a custom message to an Anthropic MessageParam.
168
+
169
+ Note: Anthropic doesn't have a 'system' role. System messages should be
170
+ handled separately as the system parameter in the API call, not as a message.
171
+ If a SystemMessage is passed here, it will be converted to a user message.
172
+ """
173
+ if isinstance(message, UserMessage):
174
+ content = AnthropicMessageSerializer._serialize_content(message.content, use_cache=message.cache)
175
+ return MessageParam(role='user', content=content)
176
+
177
+ elif isinstance(message, SystemMessage):
178
+ # Anthropic doesn't have system messages in the messages array
179
+ # System prompts are passed separately. Convert to user message.
180
+ return message
181
+
182
+ elif isinstance(message, AssistantMessage):
183
+ # Handle content and tool calls
184
+ blocks: list[TextBlockParam | ToolUseBlockParam] = []
185
+
186
+ # Add content blocks if present
187
+ if message.content is not None:
188
+ if isinstance(message.content, str):
189
+ blocks.append(
190
+ TextBlockParam(
191
+ text=message.content,
192
+ type='text',
193
+ cache_control=AnthropicMessageSerializer._serialize_cache_control(message.cache),
194
+ )
195
+ )
196
+ else:
197
+ # Process content parts (text and refusal)
198
+ for part in message.content:
199
+ if part.type == 'text':
200
+ blocks.append(AnthropicMessageSerializer._serialize_content_part_text(part, use_cache=message.cache))
201
+ # # Note: Anthropic doesn't have a specific refusal block type,
202
+ # # so we convert refusals to text blocks
203
+ # elif part.type == 'refusal':
204
+ # blocks.append(TextBlockParam(text=f'[Refusal] {part.refusal}', type='text'))
205
+
206
+ # Add tool use blocks if present
207
+ if message.tool_calls:
208
+ tool_blocks = AnthropicMessageSerializer._serialize_tool_calls_to_content(
209
+ message.tool_calls, use_cache=message.cache
210
+ )
211
+ blocks.extend(tool_blocks)
212
+
213
+ # If no content or tool calls, add empty text block
214
+ # (Anthropic requires at least one content block)
215
+ if not blocks:
216
+ blocks.append(
217
+ TextBlockParam(
218
+ text='', type='text', cache_control=AnthropicMessageSerializer._serialize_cache_control(message.cache)
219
+ )
220
+ )
221
+
222
+ # If caching is enabled or we have multiple blocks, return blocks as-is
223
+ # Otherwise, simplify single text blocks to plain string
224
+ if message.cache or len(blocks) > 1:
225
+ content = blocks
226
+ else:
227
+ # Only simplify when no caching and single block
228
+ single_block = blocks[0]
229
+ if single_block['type'] == 'text' and not single_block.get('cache_control'):
230
+ content = single_block['text']
231
+ else:
232
+ content = blocks
233
+
234
+ return MessageParam(
235
+ role='assistant',
236
+ content=content,
237
+ )
238
+
239
+ else:
240
+ raise ValueError(f'Unknown message type: {type(message)}')
241
+
242
+ @staticmethod
243
+ def _clean_cache_messages(messages: list[NonSystemMessage]) -> list[NonSystemMessage]:
244
+ """Clean cache settings so only the last cache=True message remains cached.
245
+
246
+ Because of how Claude caching works, only the last cache message matters.
247
+ This method automatically removes cache=True from all messages except the last one.
248
+
249
+ Args:
250
+ messages: List of non-system messages to clean
251
+
252
+ Returns:
253
+ List of messages with cleaned cache settings
254
+ """
255
+ if not messages:
256
+ return messages
257
+
258
+ # Create a copy to avoid modifying the original
259
+ cleaned_messages = [msg.model_copy(deep=True) for msg in messages]
260
+
261
+ # Find the last message with cache=True
262
+ last_cache_index = -1
263
+ for i in range(len(cleaned_messages) - 1, -1, -1):
264
+ if cleaned_messages[i].cache:
265
+ last_cache_index = i
266
+ break
267
+
268
+ # If we found a cached message, disable cache for all others
269
+ if last_cache_index != -1:
270
+ for i, msg in enumerate(cleaned_messages):
271
+ if i != last_cache_index and msg.cache:
272
+ # Set cache to False for all messages except the last cached one
273
+ msg.cache = False
274
+
275
+ return cleaned_messages
276
+
277
+ @staticmethod
278
+ def serialize_messages(messages: list[BaseMessage]) -> tuple[list[MessageParam], list[TextBlockParam] | str | None]:
279
+ """Serialize a list of messages, extracting any system message.
280
+
281
+ Returns:
282
+ A tuple of (messages, system_message) where system_message is extracted
283
+ from any SystemMessage in the list.
284
+ """
285
+ messages = [m.model_copy(deep=True) for m in messages]
286
+
287
+ # Separate system messages from normal messages
288
+ normal_messages: list[NonSystemMessage] = []
289
+ system_message: SystemMessage | None = None
290
+
291
+ for message in messages:
292
+ if isinstance(message, SystemMessage):
293
+ system_message = message
294
+ else:
295
+ normal_messages.append(message)
296
+
297
+ # Clean cache messages so only the last cache=True message remains cached
298
+ normal_messages = AnthropicMessageSerializer._clean_cache_messages(normal_messages)
299
+
300
+ # Serialize normal messages
301
+ serialized_messages: list[MessageParam] = []
302
+ for message in normal_messages:
303
+ serialized_messages.append(AnthropicMessageSerializer.serialize(message))
304
+
305
+ # Serialize system message
306
+ serialized_system_message: list[TextBlockParam] | str | None = None
307
+ if system_message:
308
+ serialized_system_message = AnthropicMessageSerializer._serialize_content_to_str(
309
+ system_message.content, use_cache=system_message.cache
310
+ )
311
+
312
+ return serialized_messages, serialized_system_message
@@ -0,0 +1,36 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ # Type stubs for lazy imports
4
+ if TYPE_CHECKING:
5
+ from browser_use.llm.aws.chat_anthropic import ChatAnthropicBedrock
6
+ from browser_use.llm.aws.chat_bedrock import ChatAWSBedrock
7
+
8
+ # Lazy imports mapping for AWS chat models
9
+ _LAZY_IMPORTS = {
10
+ 'ChatAnthropicBedrock': ('browser_use.llm.aws.chat_anthropic', 'ChatAnthropicBedrock'),
11
+ 'ChatAWSBedrock': ('browser_use.llm.aws.chat_bedrock', 'ChatAWSBedrock'),
12
+ }
13
+
14
+
15
+ def __getattr__(name: str):
16
+ """Lazy import mechanism for AWS chat models."""
17
+ if name in _LAZY_IMPORTS:
18
+ module_path, attr_name = _LAZY_IMPORTS[name]
19
+ try:
20
+ from importlib import import_module
21
+
22
+ module = import_module(module_path)
23
+ attr = getattr(module, attr_name)
24
+ # Cache the imported attribute in the module's globals
25
+ globals()[name] = attr
26
+ return attr
27
+ except ImportError as e:
28
+ raise ImportError(f'Failed to import {name} from {module_path}: {e}') from e
29
+
30
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
31
+
32
+
33
+ __all__ = [
34
+ 'ChatAWSBedrock',
35
+ 'ChatAnthropicBedrock',
36
+ ]
@@ -0,0 +1,242 @@
1
+ import json
2
+ from collections.abc import Mapping
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, TypeVar, overload
5
+
6
+ from anthropic import (
7
+ APIConnectionError,
8
+ APIStatusError,
9
+ AsyncAnthropicBedrock,
10
+ RateLimitError,
11
+ omit,
12
+ )
13
+ from anthropic.types import CacheControlEphemeralParam, Message, ToolParam
14
+ from anthropic.types.text_block import TextBlock
15
+ from anthropic.types.tool_choice_tool_param import ToolChoiceToolParam
16
+ from pydantic import BaseModel
17
+
18
+ from browser_use.llm.anthropic.serializer import AnthropicMessageSerializer
19
+ from browser_use.llm.aws.chat_bedrock import ChatAWSBedrock
20
+ from browser_use.llm.exceptions import ModelProviderError, ModelRateLimitError
21
+ from browser_use.llm.messages import BaseMessage
22
+ from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage
23
+
24
+ if TYPE_CHECKING:
25
+ from boto3.session import Session # pyright: ignore
26
+
27
+
28
+ T = TypeVar('T', bound=BaseModel)
29
+
30
+
31
+ @dataclass
32
+ class ChatAnthropicBedrock(ChatAWSBedrock):
33
+ """
34
+ AWS Bedrock Anthropic Claude chat model.
35
+
36
+ This is a convenience class that provides Claude-specific defaults
37
+ for the AWS Bedrock service. It inherits all functionality from
38
+ ChatAWSBedrock but sets Anthropic Claude as the default model.
39
+ """
40
+
41
+ # Anthropic Claude specific defaults
42
+ model: str = 'anthropic.claude-3-5-sonnet-20240620-v1:0'
43
+ max_tokens: int = 8192
44
+ temperature: float | None = None
45
+ top_p: float | None = None
46
+ top_k: int | None = None
47
+ stop_sequences: list[str] | None = None
48
+ seed: int | None = None
49
+
50
+ # AWS credentials and configuration
51
+ aws_access_key: str | None = None
52
+ aws_secret_key: str | None = None
53
+ aws_session_token: str | None = None
54
+ aws_region: str | None = None
55
+ session: 'Session | None' = None
56
+
57
+ # Client initialization parameters
58
+ max_retries: int = 10
59
+ default_headers: Mapping[str, str] | None = None
60
+ default_query: Mapping[str, object] | None = None
61
+
62
+ @property
63
+ def provider(self) -> str:
64
+ return 'anthropic_bedrock'
65
+
66
+ def _get_client_params(self) -> dict[str, Any]:
67
+ """Prepare client parameters dictionary for Bedrock."""
68
+ client_params: dict[str, Any] = {}
69
+
70
+ if self.session:
71
+ credentials = self.session.get_credentials()
72
+ client_params.update(
73
+ {
74
+ 'aws_access_key': credentials.access_key,
75
+ 'aws_secret_key': credentials.secret_key,
76
+ 'aws_session_token': credentials.token,
77
+ 'aws_region': self.session.region_name,
78
+ }
79
+ )
80
+ else:
81
+ # Use individual credentials
82
+ if self.aws_access_key:
83
+ client_params['aws_access_key'] = self.aws_access_key
84
+ if self.aws_secret_key:
85
+ client_params['aws_secret_key'] = self.aws_secret_key
86
+ if self.aws_region:
87
+ client_params['aws_region'] = self.aws_region
88
+ if self.aws_session_token:
89
+ client_params['aws_session_token'] = self.aws_session_token
90
+
91
+ # Add optional parameters
92
+ if self.max_retries:
93
+ client_params['max_retries'] = self.max_retries
94
+ if self.default_headers:
95
+ client_params['default_headers'] = self.default_headers
96
+ if self.default_query:
97
+ client_params['default_query'] = self.default_query
98
+
99
+ return client_params
100
+
101
+ def _get_client_params_for_invoke(self) -> dict[str, Any]:
102
+ """Prepare client parameters dictionary for invoke."""
103
+ client_params = {}
104
+
105
+ if self.temperature is not None:
106
+ client_params['temperature'] = self.temperature
107
+ if self.max_tokens is not None:
108
+ client_params['max_tokens'] = self.max_tokens
109
+ if self.top_p is not None:
110
+ client_params['top_p'] = self.top_p
111
+ if self.top_k is not None:
112
+ client_params['top_k'] = self.top_k
113
+ if self.seed is not None:
114
+ client_params['seed'] = self.seed
115
+ if self.stop_sequences is not None:
116
+ client_params['stop_sequences'] = self.stop_sequences
117
+
118
+ return client_params
119
+
120
+ def get_client(self) -> AsyncAnthropicBedrock:
121
+ """
122
+ Returns an AsyncAnthropicBedrock client.
123
+
124
+ Returns:
125
+ AsyncAnthropicBedrock: An instance of the AsyncAnthropicBedrock client.
126
+ """
127
+ client_params = self._get_client_params()
128
+ return AsyncAnthropicBedrock(**client_params)
129
+
130
+ @property
131
+ def name(self) -> str:
132
+ return str(self.model)
133
+
134
+ def _get_usage(self, response: Message) -> ChatInvokeUsage | None:
135
+ """Extract usage information from the response."""
136
+ usage = ChatInvokeUsage(
137
+ prompt_tokens=response.usage.input_tokens
138
+ + (
139
+ response.usage.cache_read_input_tokens or 0
140
+ ), # Total tokens in Anthropic are a bit fucked, you have to add cached tokens to the prompt tokens
141
+ completion_tokens=response.usage.output_tokens,
142
+ total_tokens=response.usage.input_tokens + response.usage.output_tokens,
143
+ prompt_cached_tokens=response.usage.cache_read_input_tokens,
144
+ prompt_cache_creation_tokens=response.usage.cache_creation_input_tokens,
145
+ prompt_image_tokens=None,
146
+ )
147
+ return usage
148
+
149
+ @overload
150
+ async def ainvoke(self, messages: list[BaseMessage], output_format: None = None) -> ChatInvokeCompletion[str]: ...
151
+
152
+ @overload
153
+ async def ainvoke(self, messages: list[BaseMessage], output_format: type[T]) -> ChatInvokeCompletion[T]: ...
154
+
155
+ async def ainvoke(
156
+ self, messages: list[BaseMessage], output_format: type[T] | None = None
157
+ ) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
158
+ anthropic_messages, system_prompt = AnthropicMessageSerializer.serialize_messages(messages)
159
+
160
+ try:
161
+ if output_format is None:
162
+ # Normal completion without structured output
163
+ response = await self.get_client().messages.create(
164
+ model=self.model,
165
+ messages=anthropic_messages,
166
+ system=system_prompt or omit,
167
+ **self._get_client_params_for_invoke(),
168
+ )
169
+
170
+ usage = self._get_usage(response)
171
+
172
+ # Extract text from the first content block
173
+ first_content = response.content[0]
174
+ if isinstance(first_content, TextBlock):
175
+ response_text = first_content.text
176
+ else:
177
+ # If it's not a text block, convert to string
178
+ response_text = str(first_content)
179
+
180
+ return ChatInvokeCompletion(
181
+ completion=response_text,
182
+ usage=usage,
183
+ )
184
+
185
+ else:
186
+ # Use tool calling for structured output
187
+ # Create a tool that represents the output format
188
+ tool_name = output_format.__name__
189
+ schema = output_format.model_json_schema()
190
+
191
+ # Remove title from schema if present (Anthropic doesn't like it in parameters)
192
+ if 'title' in schema:
193
+ del schema['title']
194
+
195
+ tool = ToolParam(
196
+ name=tool_name,
197
+ description=f'Extract information in the format of {tool_name}',
198
+ input_schema=schema,
199
+ cache_control=CacheControlEphemeralParam(type='ephemeral'),
200
+ )
201
+
202
+ # Force the model to use this tool
203
+ tool_choice = ToolChoiceToolParam(type='tool', name=tool_name)
204
+
205
+ response = await self.get_client().messages.create(
206
+ model=self.model,
207
+ messages=anthropic_messages,
208
+ tools=[tool],
209
+ system=system_prompt or omit,
210
+ tool_choice=tool_choice,
211
+ **self._get_client_params_for_invoke(),
212
+ )
213
+
214
+ usage = self._get_usage(response)
215
+
216
+ # Extract the tool use block
217
+ for content_block in response.content:
218
+ if hasattr(content_block, 'type') and content_block.type == 'tool_use':
219
+ # Parse the tool input as the structured output
220
+ try:
221
+ return ChatInvokeCompletion(completion=output_format.model_validate(content_block.input), usage=usage)
222
+ except Exception as e:
223
+ # If validation fails, try to parse it as JSON first
224
+ if isinstance(content_block.input, str):
225
+ data = json.loads(content_block.input)
226
+ return ChatInvokeCompletion(
227
+ completion=output_format.model_validate(data),
228
+ usage=usage,
229
+ )
230
+ raise e
231
+
232
+ # If no tool use block found, raise an error
233
+ raise ValueError('Expected tool use in response but none found')
234
+
235
+ except APIConnectionError as e:
236
+ raise ModelProviderError(message=e.message, model=self.name) from e
237
+ except RateLimitError as e:
238
+ raise ModelRateLimitError(message=e.message, model=self.name) from e
239
+ except APIStatusError as e:
240
+ raise ModelProviderError(message=e.message, status_code=e.status_code, model=self.name) from e
241
+ except Exception as e:
242
+ raise ModelProviderError(message=str(e), model=self.name) from e