chatlas 0.2.0__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 chatlas might be problematic. Click here for more details.
- chatlas/__init__.py +38 -0
- chatlas/_anthropic.py +643 -0
- chatlas/_chat.py +1279 -0
- chatlas/_content.py +242 -0
- chatlas/_content_image.py +272 -0
- chatlas/_display.py +139 -0
- chatlas/_github.py +147 -0
- chatlas/_google.py +456 -0
- chatlas/_groq.py +143 -0
- chatlas/_interpolate.py +133 -0
- chatlas/_logging.py +61 -0
- chatlas/_merge.py +103 -0
- chatlas/_ollama.py +125 -0
- chatlas/_openai.py +654 -0
- chatlas/_perplexity.py +148 -0
- chatlas/_provider.py +143 -0
- chatlas/_tokens.py +87 -0
- chatlas/_tokens_old.py +148 -0
- chatlas/_tools.py +134 -0
- chatlas/_turn.py +147 -0
- chatlas/_typing_extensions.py +26 -0
- chatlas/_utils.py +106 -0
- chatlas/types/__init__.py +32 -0
- chatlas/types/anthropic/__init__.py +14 -0
- chatlas/types/anthropic/_client.py +29 -0
- chatlas/types/anthropic/_client_bedrock.py +23 -0
- chatlas/types/anthropic/_submit.py +57 -0
- chatlas/types/google/__init__.py +12 -0
- chatlas/types/google/_client.py +101 -0
- chatlas/types/google/_submit.py +113 -0
- chatlas/types/openai/__init__.py +14 -0
- chatlas/types/openai/_client.py +22 -0
- chatlas/types/openai/_client_azure.py +25 -0
- chatlas/types/openai/_submit.py +135 -0
- chatlas-0.2.0.dist-info/METADATA +319 -0
- chatlas-0.2.0.dist-info/RECORD +37 -0
- chatlas-0.2.0.dist-info/WHEEL +4 -0
chatlas/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from . import types
|
|
2
|
+
from ._anthropic import ChatAnthropic, ChatBedrockAnthropic
|
|
3
|
+
from ._chat import Chat
|
|
4
|
+
from ._content_image import content_image_file, content_image_plot, content_image_url
|
|
5
|
+
from ._github import ChatGithub
|
|
6
|
+
from ._google import ChatGoogle
|
|
7
|
+
from ._groq import ChatGroq
|
|
8
|
+
from ._interpolate import interpolate, interpolate_file
|
|
9
|
+
from ._ollama import ChatOllama
|
|
10
|
+
from ._openai import ChatAzureOpenAI, ChatOpenAI
|
|
11
|
+
from ._perplexity import ChatPerplexity
|
|
12
|
+
from ._provider import Provider
|
|
13
|
+
from ._tokens import token_usage
|
|
14
|
+
from ._tools import Tool
|
|
15
|
+
from ._turn import Turn
|
|
16
|
+
|
|
17
|
+
__all__ = (
|
|
18
|
+
"ChatAnthropic",
|
|
19
|
+
"ChatBedrockAnthropic",
|
|
20
|
+
"ChatGithub",
|
|
21
|
+
"ChatGoogle",
|
|
22
|
+
"ChatGroq",
|
|
23
|
+
"ChatOllama",
|
|
24
|
+
"ChatOpenAI",
|
|
25
|
+
"ChatAzureOpenAI",
|
|
26
|
+
"ChatPerplexity",
|
|
27
|
+
"Chat",
|
|
28
|
+
"content_image_file",
|
|
29
|
+
"content_image_plot",
|
|
30
|
+
"content_image_url",
|
|
31
|
+
"interpolate",
|
|
32
|
+
"interpolate_file",
|
|
33
|
+
"Provider",
|
|
34
|
+
"token_usage",
|
|
35
|
+
"Tool",
|
|
36
|
+
"Turn",
|
|
37
|
+
"types",
|
|
38
|
+
)
|
chatlas/_anthropic.py
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast, overload
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from ._chat import Chat
|
|
10
|
+
from ._content import (
|
|
11
|
+
Content,
|
|
12
|
+
ContentImageInline,
|
|
13
|
+
ContentImageRemote,
|
|
14
|
+
ContentJson,
|
|
15
|
+
ContentText,
|
|
16
|
+
ContentToolRequest,
|
|
17
|
+
ContentToolResult,
|
|
18
|
+
)
|
|
19
|
+
from ._logging import log_model_default
|
|
20
|
+
from ._provider import Provider
|
|
21
|
+
from ._tokens import tokens_log
|
|
22
|
+
from ._tools import Tool, basemodel_to_param_schema
|
|
23
|
+
from ._turn import Turn, normalize_turns
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from anthropic.types import (
|
|
27
|
+
Message,
|
|
28
|
+
MessageParam,
|
|
29
|
+
RawMessageStreamEvent,
|
|
30
|
+
TextBlock,
|
|
31
|
+
ToolParam,
|
|
32
|
+
ToolUseBlock,
|
|
33
|
+
)
|
|
34
|
+
from anthropic.types.image_block_param import ImageBlockParam
|
|
35
|
+
from anthropic.types.model_param import ModelParam
|
|
36
|
+
from anthropic.types.text_block_param import TextBlockParam
|
|
37
|
+
from anthropic.types.tool_result_block_param import ToolResultBlockParam
|
|
38
|
+
from anthropic.types.tool_use_block_param import ToolUseBlockParam
|
|
39
|
+
from openai.types.chat import ChatCompletionToolParam
|
|
40
|
+
|
|
41
|
+
from .types.anthropic import ChatBedrockClientArgs, ChatClientArgs, SubmitInputArgs
|
|
42
|
+
|
|
43
|
+
ContentBlockParam = Union[
|
|
44
|
+
TextBlockParam,
|
|
45
|
+
ImageBlockParam,
|
|
46
|
+
ToolUseBlockParam,
|
|
47
|
+
ToolResultBlockParam,
|
|
48
|
+
]
|
|
49
|
+
else:
|
|
50
|
+
Message = object
|
|
51
|
+
RawMessageStreamEvent = object
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def ChatAnthropic(
|
|
55
|
+
*,
|
|
56
|
+
system_prompt: Optional[str] = None,
|
|
57
|
+
turns: Optional[list[Turn]] = None,
|
|
58
|
+
model: "Optional[ModelParam]" = None,
|
|
59
|
+
api_key: Optional[str] = None,
|
|
60
|
+
max_tokens: int = 4096,
|
|
61
|
+
kwargs: Optional["ChatClientArgs"] = None,
|
|
62
|
+
) -> Chat["SubmitInputArgs", Message]:
|
|
63
|
+
"""
|
|
64
|
+
Chat with an Anthropic Claude model.
|
|
65
|
+
|
|
66
|
+
[Anthropic](https://www.anthropic.com) provides a number of chat based
|
|
67
|
+
models under the [Claude](https://www.anthropic.com/claude) moniker.
|
|
68
|
+
|
|
69
|
+
Prerequisites
|
|
70
|
+
-------------
|
|
71
|
+
|
|
72
|
+
::: {.callout-note}
|
|
73
|
+
## API key
|
|
74
|
+
|
|
75
|
+
Note that a Claude Prop membership does not give you the ability to call
|
|
76
|
+
models via the API. You will need to go to the [developer
|
|
77
|
+
console](https://console.anthropic.com/account/keys) to sign up (and pay
|
|
78
|
+
for) a developer account that will give you an API key that you can use with
|
|
79
|
+
this package.
|
|
80
|
+
:::
|
|
81
|
+
|
|
82
|
+
::: {.callout-note}
|
|
83
|
+
## Python requirements
|
|
84
|
+
|
|
85
|
+
`ChatAnthropic` requires the `anthropic` package (e.g., `pip install anthropic`).
|
|
86
|
+
:::
|
|
87
|
+
|
|
88
|
+
Examples
|
|
89
|
+
--------
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import os
|
|
93
|
+
from chatlas import ChatAnthropic
|
|
94
|
+
|
|
95
|
+
chat = ChatAnthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
|
|
96
|
+
chat.chat("What is the capital of France?")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
system_prompt
|
|
102
|
+
A system prompt to set the behavior of the assistant.
|
|
103
|
+
turns
|
|
104
|
+
A list of turns to start the chat with (i.e., continuing a previous
|
|
105
|
+
conversation). If not provided, the conversation begins from scratch. Do
|
|
106
|
+
not provide non-None values for both `turns` and `system_prompt`. Each
|
|
107
|
+
message in the list should be a dictionary with at least `role` (usually
|
|
108
|
+
`system`, `user`, or `assistant`, but `tool` is also possible). Normally
|
|
109
|
+
there is also a `content` field, which is a string.
|
|
110
|
+
model
|
|
111
|
+
The model to use for the chat. The default, None, will pick a reasonable
|
|
112
|
+
default, and warn you about it. We strongly recommend explicitly
|
|
113
|
+
choosing a model for all but the most casual use.
|
|
114
|
+
api_key
|
|
115
|
+
The API key to use for authentication. You generally should not supply
|
|
116
|
+
this directly, but instead set the `ANTHROPIC_API_KEY` environment
|
|
117
|
+
variable.
|
|
118
|
+
max_tokens
|
|
119
|
+
Maximum number of tokens to generate before stopping.
|
|
120
|
+
kwargs
|
|
121
|
+
Additional arguments to pass to the `anthropic.Anthropic()` client
|
|
122
|
+
constructor.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
Chat
|
|
127
|
+
A Chat object.
|
|
128
|
+
|
|
129
|
+
Note
|
|
130
|
+
----
|
|
131
|
+
Pasting an API key into a chat constructor (e.g., `ChatAnthropic(api_key="...")`)
|
|
132
|
+
is the simplest way to get started, and is fine for interactive use, but is
|
|
133
|
+
problematic for code that may be shared with others.
|
|
134
|
+
|
|
135
|
+
Instead, consider using environment variables or a configuration file to manage
|
|
136
|
+
your credentials. One popular way to manage credentials is to use a `.env` file
|
|
137
|
+
to store your credentials, and then use the `python-dotenv` package to load them
|
|
138
|
+
into your environment.
|
|
139
|
+
|
|
140
|
+
```shell
|
|
141
|
+
pip install python-dotenv
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```shell
|
|
145
|
+
# .env
|
|
146
|
+
ANTHROPIC_API_KEY=...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from chatlas import ChatAnthropic
|
|
151
|
+
from dotenv import load_dotenv
|
|
152
|
+
|
|
153
|
+
load_dotenv()
|
|
154
|
+
chat = ChatAnthropic()
|
|
155
|
+
chat.console()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Another, more general, solution is to load your environment variables into the shell
|
|
159
|
+
before starting Python (maybe in a `.bashrc`, `.zshrc`, etc. file):
|
|
160
|
+
|
|
161
|
+
```shell
|
|
162
|
+
export ANTHROPIC_API_KEY=...
|
|
163
|
+
```
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
if model is None:
|
|
167
|
+
model = log_model_default("claude-3-5-sonnet-latest")
|
|
168
|
+
|
|
169
|
+
return Chat(
|
|
170
|
+
provider=AnthropicProvider(
|
|
171
|
+
api_key=api_key,
|
|
172
|
+
model=model,
|
|
173
|
+
max_tokens=max_tokens,
|
|
174
|
+
kwargs=kwargs,
|
|
175
|
+
),
|
|
176
|
+
turns=normalize_turns(
|
|
177
|
+
turns or [],
|
|
178
|
+
system_prompt,
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class AnthropicProvider(Provider[Message, RawMessageStreamEvent, Message]):
|
|
184
|
+
def __init__(
|
|
185
|
+
self,
|
|
186
|
+
*,
|
|
187
|
+
max_tokens: int,
|
|
188
|
+
model: str,
|
|
189
|
+
api_key: str | None,
|
|
190
|
+
kwargs: Optional["ChatClientArgs"] = None,
|
|
191
|
+
):
|
|
192
|
+
try:
|
|
193
|
+
from anthropic import Anthropic, AsyncAnthropic
|
|
194
|
+
except ImportError:
|
|
195
|
+
raise ImportError(
|
|
196
|
+
"`ChatAnthropic()` requires the `anthropic` package. "
|
|
197
|
+
"You can install it with 'pip install anthropic'."
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
self._model = model
|
|
201
|
+
self._max_tokens = max_tokens
|
|
202
|
+
|
|
203
|
+
kwargs_full: "ChatClientArgs" = {
|
|
204
|
+
"api_key": api_key,
|
|
205
|
+
**(kwargs or {}),
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# TODO: worth bringing in sync types?
|
|
209
|
+
self._client = Anthropic(**kwargs_full) # type: ignore
|
|
210
|
+
self._async_client = AsyncAnthropic(**kwargs_full)
|
|
211
|
+
|
|
212
|
+
@overload
|
|
213
|
+
def chat_perform(
|
|
214
|
+
self,
|
|
215
|
+
*,
|
|
216
|
+
stream: Literal[False],
|
|
217
|
+
turns: list[Turn],
|
|
218
|
+
tools: dict[str, Tool],
|
|
219
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
220
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
221
|
+
): ...
|
|
222
|
+
|
|
223
|
+
@overload
|
|
224
|
+
def chat_perform(
|
|
225
|
+
self,
|
|
226
|
+
*,
|
|
227
|
+
stream: Literal[True],
|
|
228
|
+
turns: list[Turn],
|
|
229
|
+
tools: dict[str, Tool],
|
|
230
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
231
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
232
|
+
): ...
|
|
233
|
+
|
|
234
|
+
def chat_perform(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
stream: bool,
|
|
238
|
+
turns: list[Turn],
|
|
239
|
+
tools: dict[str, Tool],
|
|
240
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
241
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
242
|
+
):
|
|
243
|
+
kwargs = self._chat_perform_args(stream, turns, tools, data_model, kwargs)
|
|
244
|
+
return self._client.messages.create(**kwargs) # type: ignore
|
|
245
|
+
|
|
246
|
+
@overload
|
|
247
|
+
async def chat_perform_async(
|
|
248
|
+
self,
|
|
249
|
+
*,
|
|
250
|
+
stream: Literal[False],
|
|
251
|
+
turns: list[Turn],
|
|
252
|
+
tools: dict[str, Tool],
|
|
253
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
254
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
255
|
+
): ...
|
|
256
|
+
|
|
257
|
+
@overload
|
|
258
|
+
async def chat_perform_async(
|
|
259
|
+
self,
|
|
260
|
+
*,
|
|
261
|
+
stream: Literal[True],
|
|
262
|
+
turns: list[Turn],
|
|
263
|
+
tools: dict[str, Tool],
|
|
264
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
265
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
266
|
+
): ...
|
|
267
|
+
|
|
268
|
+
async def chat_perform_async(
|
|
269
|
+
self,
|
|
270
|
+
*,
|
|
271
|
+
stream: bool,
|
|
272
|
+
turns: list[Turn],
|
|
273
|
+
tools: dict[str, Tool],
|
|
274
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
275
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
276
|
+
):
|
|
277
|
+
kwargs = self._chat_perform_args(stream, turns, tools, data_model, kwargs)
|
|
278
|
+
return await self._async_client.messages.create(**kwargs) # type: ignore
|
|
279
|
+
|
|
280
|
+
def _chat_perform_args(
|
|
281
|
+
self,
|
|
282
|
+
stream: bool,
|
|
283
|
+
turns: list[Turn],
|
|
284
|
+
tools: dict[str, Tool],
|
|
285
|
+
data_model: Optional[type[BaseModel]] = None,
|
|
286
|
+
kwargs: Optional["SubmitInputArgs"] = None,
|
|
287
|
+
) -> "SubmitInputArgs":
|
|
288
|
+
tool_schemas = [
|
|
289
|
+
self._anthropic_tool_schema(tool.schema) for tool in tools.values()
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
# If data extraction is requested, add a "mock" tool with parameters inferred from the data model
|
|
293
|
+
data_model_tool: Tool | None = None
|
|
294
|
+
if data_model is not None:
|
|
295
|
+
|
|
296
|
+
def _structured_tool_call(**kwargs: Any):
|
|
297
|
+
"""Extract structured data"""
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
data_model_tool = Tool(_structured_tool_call)
|
|
301
|
+
|
|
302
|
+
data_model_tool.schema["function"]["parameters"] = {
|
|
303
|
+
"type": "object",
|
|
304
|
+
"properties": {
|
|
305
|
+
"data": basemodel_to_param_schema(data_model),
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
tool_schemas.append(self._anthropic_tool_schema(data_model_tool.schema))
|
|
310
|
+
|
|
311
|
+
if stream:
|
|
312
|
+
stream = False
|
|
313
|
+
warnings.warn(
|
|
314
|
+
"Anthropic does not support structured data extraction in streaming mode."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
kwargs_full: "SubmitInputArgs" = {
|
|
318
|
+
"stream": stream,
|
|
319
|
+
"messages": self._as_message_params(turns),
|
|
320
|
+
"model": self._model,
|
|
321
|
+
"max_tokens": self._max_tokens,
|
|
322
|
+
"tools": tool_schemas,
|
|
323
|
+
**(kwargs or {}),
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if data_model_tool:
|
|
327
|
+
kwargs_full["tool_choice"] = {
|
|
328
|
+
"type": "tool",
|
|
329
|
+
"name": data_model_tool.name,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if "system" not in kwargs_full:
|
|
333
|
+
if len(turns) > 0 and turns[0].role == "system":
|
|
334
|
+
kwargs_full["system"] = turns[0].text
|
|
335
|
+
|
|
336
|
+
return kwargs_full
|
|
337
|
+
|
|
338
|
+
def stream_text(self, chunk) -> Optional[str]:
|
|
339
|
+
if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
|
|
340
|
+
return chunk.delta.text
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
def stream_merge_chunks(self, completion, chunk):
|
|
344
|
+
if chunk.type == "message_start":
|
|
345
|
+
return chunk.message
|
|
346
|
+
completion = cast("Message", completion)
|
|
347
|
+
|
|
348
|
+
if chunk.type == "content_block_start":
|
|
349
|
+
completion.content.append(chunk.content_block)
|
|
350
|
+
elif chunk.type == "content_block_delta":
|
|
351
|
+
this_content = completion.content[chunk.index]
|
|
352
|
+
if chunk.delta.type == "text_delta":
|
|
353
|
+
this_content = cast("TextBlock", this_content)
|
|
354
|
+
this_content.text += chunk.delta.text
|
|
355
|
+
elif chunk.delta.type == "input_json_delta":
|
|
356
|
+
this_content = cast("ToolUseBlock", this_content)
|
|
357
|
+
if not isinstance(this_content.input, str):
|
|
358
|
+
this_content.input = ""
|
|
359
|
+
this_content.input += chunk.delta.partial_json
|
|
360
|
+
elif chunk.type == "content_block_stop":
|
|
361
|
+
this_content = completion.content[chunk.index]
|
|
362
|
+
if this_content.type == "tool_use" and isinstance(this_content.input, str):
|
|
363
|
+
try:
|
|
364
|
+
this_content.input = json.loads(this_content.input or "{}")
|
|
365
|
+
except json.JSONDecodeError as e:
|
|
366
|
+
raise ValueError(f"Invalid JSON input: {e}")
|
|
367
|
+
elif chunk.type == "message_delta":
|
|
368
|
+
completion.stop_reason = chunk.delta.stop_reason
|
|
369
|
+
completion.stop_sequence = chunk.delta.stop_sequence
|
|
370
|
+
completion.usage.output_tokens = chunk.usage.output_tokens
|
|
371
|
+
|
|
372
|
+
return completion
|
|
373
|
+
|
|
374
|
+
def stream_turn(self, completion, has_data_model, stream) -> Turn:
|
|
375
|
+
return self._as_turn(completion, has_data_model)
|
|
376
|
+
|
|
377
|
+
async def stream_turn_async(self, completion, has_data_model, stream) -> Turn:
|
|
378
|
+
return self._as_turn(completion, has_data_model)
|
|
379
|
+
|
|
380
|
+
def value_turn(self, completion, has_data_model) -> Turn:
|
|
381
|
+
return self._as_turn(completion, has_data_model)
|
|
382
|
+
|
|
383
|
+
def _as_message_params(self, turns: list[Turn]) -> list["MessageParam"]:
|
|
384
|
+
messages: list["MessageParam"] = []
|
|
385
|
+
for turn in turns:
|
|
386
|
+
if turn.role == "system":
|
|
387
|
+
continue # system prompt passed as separate arg
|
|
388
|
+
if turn.role not in ["user", "assistant"]:
|
|
389
|
+
raise ValueError(f"Unknown role {turn.role}")
|
|
390
|
+
|
|
391
|
+
content = [self._as_content_block(c) for c in turn.contents]
|
|
392
|
+
role = "user" if turn.role == "user" else "assistant"
|
|
393
|
+
messages.append({"role": role, "content": content})
|
|
394
|
+
return messages
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def _as_content_block(content: Content) -> "ContentBlockParam":
|
|
398
|
+
if isinstance(content, ContentText):
|
|
399
|
+
return {"text": content.text, "type": "text"}
|
|
400
|
+
elif isinstance(content, ContentJson):
|
|
401
|
+
return {"text": "<structured data/>", "type": "text"}
|
|
402
|
+
elif isinstance(content, ContentImageInline):
|
|
403
|
+
return {
|
|
404
|
+
"type": "image",
|
|
405
|
+
"source": {
|
|
406
|
+
"type": "base64",
|
|
407
|
+
"media_type": content.content_type,
|
|
408
|
+
"data": content.data or "",
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
elif isinstance(content, ContentImageRemote):
|
|
412
|
+
raise NotImplementedError(
|
|
413
|
+
"Remote images aren't supported by Anthropic (Claude). "
|
|
414
|
+
"Consider downloading the image and using content_image_file() instead."
|
|
415
|
+
)
|
|
416
|
+
elif isinstance(content, ContentToolRequest):
|
|
417
|
+
return {
|
|
418
|
+
"type": "tool_use",
|
|
419
|
+
"id": content.id,
|
|
420
|
+
"name": content.name,
|
|
421
|
+
"input": content.arguments,
|
|
422
|
+
}
|
|
423
|
+
elif isinstance(content, ContentToolResult):
|
|
424
|
+
return {
|
|
425
|
+
"type": "tool_result",
|
|
426
|
+
"tool_use_id": content.id,
|
|
427
|
+
"content": content.get_final_value(),
|
|
428
|
+
"is_error": content.error is not None,
|
|
429
|
+
}
|
|
430
|
+
raise ValueError(f"Unknown content type: {type(content)}")
|
|
431
|
+
|
|
432
|
+
@staticmethod
|
|
433
|
+
def _anthropic_tool_schema(schema: "ChatCompletionToolParam") -> "ToolParam":
|
|
434
|
+
fn = schema["function"]
|
|
435
|
+
name = fn["name"]
|
|
436
|
+
|
|
437
|
+
res: "ToolParam" = {
|
|
438
|
+
"name": name,
|
|
439
|
+
"input_schema": {
|
|
440
|
+
"type": "object",
|
|
441
|
+
},
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if "description" in fn:
|
|
445
|
+
res["description"] = fn["description"]
|
|
446
|
+
|
|
447
|
+
if "parameters" in fn:
|
|
448
|
+
res["input_schema"]["properties"] = fn["parameters"]["properties"]
|
|
449
|
+
|
|
450
|
+
return res
|
|
451
|
+
|
|
452
|
+
def _as_turn(self, completion: Message, has_data_model=False) -> Turn:
|
|
453
|
+
contents = []
|
|
454
|
+
for content in completion.content:
|
|
455
|
+
if content.type == "text":
|
|
456
|
+
contents.append(ContentText(content.text))
|
|
457
|
+
elif content.type == "tool_use":
|
|
458
|
+
if has_data_model and content.name == "_structured_tool_call":
|
|
459
|
+
if not isinstance(content.input, dict):
|
|
460
|
+
raise ValueError(
|
|
461
|
+
"Expected data extraction tool to return a dictionary."
|
|
462
|
+
)
|
|
463
|
+
if "data" not in content.input:
|
|
464
|
+
raise ValueError(
|
|
465
|
+
"Expected data extraction tool to return a 'data' field."
|
|
466
|
+
)
|
|
467
|
+
contents.append(ContentJson(content.input["data"]))
|
|
468
|
+
else:
|
|
469
|
+
contents.append(
|
|
470
|
+
ContentToolRequest(
|
|
471
|
+
content.id,
|
|
472
|
+
name=content.name,
|
|
473
|
+
arguments=content.input,
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
tokens = completion.usage.input_tokens, completion.usage.output_tokens
|
|
478
|
+
|
|
479
|
+
tokens_log(self, tokens)
|
|
480
|
+
|
|
481
|
+
return Turn(
|
|
482
|
+
"assistant",
|
|
483
|
+
contents,
|
|
484
|
+
tokens=tokens,
|
|
485
|
+
finish_reason=completion.stop_reason,
|
|
486
|
+
completion=completion,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def ChatBedrockAnthropic(
|
|
491
|
+
*,
|
|
492
|
+
model: Optional[str] = None,
|
|
493
|
+
max_tokens: int = 4096,
|
|
494
|
+
aws_secret_key: Optional[str] = None,
|
|
495
|
+
aws_access_key: Optional[str] = None,
|
|
496
|
+
aws_region: Optional[str] = None,
|
|
497
|
+
aws_profile: Optional[str] = None,
|
|
498
|
+
aws_session_token: Optional[str] = None,
|
|
499
|
+
base_url: Optional[str] = None,
|
|
500
|
+
system_prompt: Optional[str] = None,
|
|
501
|
+
turns: Optional[list[Turn]] = None,
|
|
502
|
+
kwargs: Optional["ChatBedrockClientArgs"] = None,
|
|
503
|
+
) -> Chat["SubmitInputArgs", Message]:
|
|
504
|
+
"""
|
|
505
|
+
Chat with an AWS bedrock model.
|
|
506
|
+
|
|
507
|
+
[AWS Bedrock](https://aws.amazon.com/bedrock/) provides a number of chat
|
|
508
|
+
based models, including those Anthropic's
|
|
509
|
+
[Claude](https://aws.amazon.com/bedrock/claude/).
|
|
510
|
+
|
|
511
|
+
Prerequisites
|
|
512
|
+
-------------
|
|
513
|
+
|
|
514
|
+
::: {.callout-note}
|
|
515
|
+
## AWS credentials
|
|
516
|
+
|
|
517
|
+
Consider using the approach outlined in this guide to manage your AWS credentials:
|
|
518
|
+
<https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html>
|
|
519
|
+
:::
|
|
520
|
+
|
|
521
|
+
::: {.callout-note}
|
|
522
|
+
## Python requirements
|
|
523
|
+
|
|
524
|
+
`ChatBedrockAnthropic`, requires the `anthropic` package with the `bedrock` extras
|
|
525
|
+
(e.g., `pip install anthropic[bedrock]`).
|
|
526
|
+
:::
|
|
527
|
+
|
|
528
|
+
Examples
|
|
529
|
+
--------
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
from chatlas import ChatBedrockAnthropic
|
|
533
|
+
|
|
534
|
+
chat = ChatBedrockAnthropic(
|
|
535
|
+
aws_profile="...",
|
|
536
|
+
aws_region="us-east",
|
|
537
|
+
aws_secret_key="...",
|
|
538
|
+
aws_access_key="...",
|
|
539
|
+
aws_session_token="...",
|
|
540
|
+
)
|
|
541
|
+
chat.chat("What is the capital of France?")
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Parameters
|
|
545
|
+
----------
|
|
546
|
+
model
|
|
547
|
+
The model to use for the chat.
|
|
548
|
+
max_tokens
|
|
549
|
+
Maximum number of tokens to generate before stopping.
|
|
550
|
+
aws_secret_key
|
|
551
|
+
The AWS secret key to use for authentication.
|
|
552
|
+
aws_access_key
|
|
553
|
+
The AWS access key to use for authentication.
|
|
554
|
+
aws_region
|
|
555
|
+
The AWS region to use. Defaults to the AWS_REGION environment variable.
|
|
556
|
+
If that is not set, defaults to `'us-east-1'`.
|
|
557
|
+
aws_profile
|
|
558
|
+
The AWS profile to use.
|
|
559
|
+
aws_session_token
|
|
560
|
+
The AWS session token to use.
|
|
561
|
+
base_url
|
|
562
|
+
The base URL to use. Defaults to the ANTHROPIC_BEDROCK_BASE_URL
|
|
563
|
+
environment variable. If that is not set, defaults to
|
|
564
|
+
`f"https://bedrock-runtime.{aws_region}.amazonaws.com"`.
|
|
565
|
+
system_prompt
|
|
566
|
+
A system prompt to set the behavior of the assistant.
|
|
567
|
+
turns
|
|
568
|
+
A list of turns to start the chat with (i.e., continuing a previous
|
|
569
|
+
conversation). If not provided, the conversation begins from scratch. Do
|
|
570
|
+
not provide non-None values for both `turns` and `system_prompt`. Each
|
|
571
|
+
message in the list should be a dictionary with at least `role` (usually
|
|
572
|
+
`system`, `user`, or `assistant`, but `tool` is also possible). Normally
|
|
573
|
+
there is also a `content` field, which is a string.
|
|
574
|
+
kwargs
|
|
575
|
+
Additional arguments to pass to the `anthropic.AnthropicBedrock()`
|
|
576
|
+
client constructor.
|
|
577
|
+
|
|
578
|
+
Returns
|
|
579
|
+
-------
|
|
580
|
+
Chat
|
|
581
|
+
A Chat object.
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
if model is None:
|
|
585
|
+
# Default model from https://github.com/anthropics/anthropic-sdk-python?tab=readme-ov-file#aws-bedrock
|
|
586
|
+
model = log_model_default("anthropic.claude-3-5-sonnet-20241022-v2:0")
|
|
587
|
+
|
|
588
|
+
return Chat(
|
|
589
|
+
provider=AnthropicBedrockProvider(
|
|
590
|
+
model=model,
|
|
591
|
+
max_tokens=max_tokens,
|
|
592
|
+
aws_secret_key=aws_secret_key,
|
|
593
|
+
aws_access_key=aws_access_key,
|
|
594
|
+
aws_region=aws_region,
|
|
595
|
+
aws_profile=aws_profile,
|
|
596
|
+
aws_session_token=aws_session_token,
|
|
597
|
+
base_url=base_url,
|
|
598
|
+
kwargs=kwargs,
|
|
599
|
+
),
|
|
600
|
+
turns=normalize_turns(
|
|
601
|
+
turns or [],
|
|
602
|
+
system_prompt,
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
class AnthropicBedrockProvider(AnthropicProvider):
|
|
608
|
+
def __init__(
|
|
609
|
+
self,
|
|
610
|
+
*,
|
|
611
|
+
model: str,
|
|
612
|
+
aws_secret_key: str | None,
|
|
613
|
+
aws_access_key: str | None,
|
|
614
|
+
aws_region: str | None,
|
|
615
|
+
aws_profile: str | None,
|
|
616
|
+
aws_session_token: str | None,
|
|
617
|
+
max_tokens: int,
|
|
618
|
+
base_url: str | None,
|
|
619
|
+
kwargs: Optional["ChatBedrockClientArgs"] = None,
|
|
620
|
+
):
|
|
621
|
+
try:
|
|
622
|
+
from anthropic import AnthropicBedrock, AsyncAnthropicBedrock
|
|
623
|
+
except ImportError:
|
|
624
|
+
raise ImportError(
|
|
625
|
+
"`ChatBedrockAnthropic()` requires the `anthropic` package. "
|
|
626
|
+
"Install it with `pip install anthropic[bedrock]`."
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
self._model = model
|
|
630
|
+
self._max_tokens = max_tokens
|
|
631
|
+
|
|
632
|
+
kwargs_full: "ChatBedrockClientArgs" = {
|
|
633
|
+
"aws_secret_key": aws_secret_key,
|
|
634
|
+
"aws_access_key": aws_access_key,
|
|
635
|
+
"aws_region": aws_region,
|
|
636
|
+
"aws_profile": aws_profile,
|
|
637
|
+
"aws_session_token": aws_session_token,
|
|
638
|
+
"base_url": base_url,
|
|
639
|
+
**(kwargs or {}),
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
self._client = AnthropicBedrock(**kwargs_full) # type: ignore
|
|
643
|
+
self._async_client = AsyncAnthropicBedrock(**kwargs_full) # type: ignore
|