chatlas 0.2.0__py3-none-any.whl → 0.3.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/_anthropic.py CHANGED
@@ -20,7 +20,7 @@ from ._logging import log_model_default
20
20
  from ._provider import Provider
21
21
  from ._tokens import tokens_log
22
22
  from ._tools import Tool, basemodel_to_param_schema
23
- from ._turn import Turn, normalize_turns
23
+ from ._turn import Turn, normalize_turns, user_turn
24
24
 
25
25
  if TYPE_CHECKING:
26
26
  from anthropic.types import (
@@ -380,6 +380,59 @@ class AnthropicProvider(Provider[Message, RawMessageStreamEvent, Message]):
380
380
  def value_turn(self, completion, has_data_model) -> Turn:
381
381
  return self._as_turn(completion, has_data_model)
382
382
 
383
+ def token_count(
384
+ self,
385
+ *args: Content | str,
386
+ tools: dict[str, Tool],
387
+ data_model: Optional[type[BaseModel]],
388
+ ) -> int:
389
+ kwargs = self._token_count_args(
390
+ *args,
391
+ tools=tools,
392
+ data_model=data_model,
393
+ )
394
+ res = self._client.messages.count_tokens(**kwargs)
395
+ return res.input_tokens
396
+
397
+ async def token_count_async(
398
+ self,
399
+ *args: Content | str,
400
+ tools: dict[str, Tool],
401
+ data_model: Optional[type[BaseModel]],
402
+ ) -> int:
403
+ kwargs = self._token_count_args(
404
+ *args,
405
+ tools=tools,
406
+ data_model=data_model,
407
+ )
408
+ res = await self._async_client.messages.count_tokens(**kwargs)
409
+ return res.input_tokens
410
+
411
+ def _token_count_args(
412
+ self,
413
+ *args: Content | str,
414
+ tools: dict[str, Tool],
415
+ data_model: Optional[type[BaseModel]],
416
+ ) -> dict[str, Any]:
417
+ turn = user_turn(*args)
418
+
419
+ kwargs = self._chat_perform_args(
420
+ stream=False,
421
+ turns=[turn],
422
+ tools=tools,
423
+ data_model=data_model,
424
+ )
425
+
426
+ args_to_keep = [
427
+ "messages",
428
+ "model",
429
+ "system",
430
+ "tools",
431
+ "tool_choice",
432
+ ]
433
+
434
+ return {arg: kwargs[arg] for arg in args_to_keep if arg in kwargs}
435
+
383
436
  def _as_message_params(self, turns: list[Turn]) -> list["MessageParam"]:
384
437
  messages: list["MessageParam"] = []
385
438
  for turn in turns:
@@ -575,6 +628,53 @@ def ChatBedrockAnthropic(
575
628
  Additional arguments to pass to the `anthropic.AnthropicBedrock()`
576
629
  client constructor.
577
630
 
631
+ Troubleshooting
632
+ ---------------
633
+
634
+ If you encounter 400 or 403 errors when trying to use the model, keep the
635
+ following in mind:
636
+
637
+ ::: {.callout-note}
638
+ #### Incorrect model name
639
+
640
+ If the model name is completely incorrect, you'll see an error like
641
+ `Error code: 400 - {'message': 'The provided model identifier is invalid.'}`
642
+
643
+ Make sure the model name is correct and active in the specified region.
644
+ :::
645
+
646
+ ::: {.callout-note}
647
+ #### Models are region specific
648
+
649
+ If you encounter errors similar to `Error code: 403 - {'message': "You don't
650
+ have access to the model with the specified model ID."}`, make sure your
651
+ model is active in the relevant `aws_region`.
652
+
653
+ Keep in mind, if `aws_region` is not specified, and AWS_REGION is not set,
654
+ the region defaults to us-east-1, which may not match to your AWS config's
655
+ default region.
656
+ :::
657
+
658
+ ::: {.callout-note}
659
+ #### Cross region inference ID
660
+
661
+ In some cases, even if you have the right model and the right region, you
662
+ may still encounter an error like `Error code: 400 - {'message':
663
+ 'Invocation of model ID anthropic.claude-3-5-sonnet-20240620-v1:0 with
664
+ on-demand throughput isn't supported. Retry your request with the ID or ARN
665
+ of an inference profile that contains this model.'}`
666
+
667
+ In this case, you'll need to look up the 'cross region inference ID' for
668
+ your model. This might required opening your `aws-console` and navigating to
669
+ the 'Anthropic Bedrock' service page. From there, go to the 'cross region
670
+ inference' tab and copy the relevant ID.
671
+
672
+ For example, if the desired model ID is
673
+ `anthropic.claude-3-5-sonnet-20240620-v1:0`, the cross region ID might look
674
+ something like `us.anthropic.claude-3-5-sonnet-20240620-v1:0`.
675
+ :::
676
+
677
+
578
678
  Returns
579
679
  -------
580
680
  Chat
chatlas/_chat.py CHANGED
@@ -16,6 +16,7 @@ from typing import (
16
16
  Optional,
17
17
  Sequence,
18
18
  TypeVar,
19
+ overload,
19
20
  )
20
21
 
21
22
  from pydantic import BaseModel
@@ -176,17 +177,209 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
176
177
  if value is not None:
177
178
  self._turns.insert(0, Turn("system", value))
178
179
 
179
- def tokens(self) -> list[tuple[int, int] | None]:
180
+ @overload
181
+ def tokens(self) -> list[tuple[int, int] | None]: ...
182
+
183
+ @overload
184
+ def tokens(
185
+ self,
186
+ values: Literal["cumulative"],
187
+ ) -> list[tuple[int, int] | None]: ...
188
+
189
+ @overload
190
+ def tokens(
191
+ self,
192
+ values: Literal["discrete"],
193
+ ) -> list[int]: ...
194
+
195
+ def tokens(
196
+ self,
197
+ values: Literal["cumulative", "discrete"] = "discrete",
198
+ ) -> list[int] | list[tuple[int, int] | None]:
180
199
  """
181
200
  Get the tokens for each turn in the chat.
182
201
 
202
+ Parameters
203
+ ----------
204
+ values
205
+ If "cumulative" (the default), the result can be summed to get the
206
+ chat's overall token usage (helpful for computing overall cost of
207
+ the chat). If "discrete", the result can be summed to get the number of
208
+ tokens the turns will cost to generate the next response (helpful
209
+ for estimating cost of the next response, or for determining if you
210
+ are about to exceed the token limit).
211
+
212
+ Returns
213
+ -------
214
+ list[int]
215
+ A list of token counts for each (non-system) turn in the chat. The
216
+ 1st turn includes the tokens count for the system prompt (if any).
217
+
218
+ Raises
219
+ ------
220
+ ValueError
221
+ If the chat's turns (i.e., `.get_turns()`) are not in an expected
222
+ format. This may happen if the chat history is manually set (i.e.,
223
+ `.set_turns()`). In this case, you can inspect the "raw" token
224
+ values via the `.get_turns()` method (each turn has a `.tokens`
225
+ attribute).
226
+ """
227
+
228
+ turns = self.get_turns(include_system_prompt=False)
229
+
230
+ if values == "cumulative":
231
+ return [turn.tokens for turn in turns]
232
+
233
+ if len(turns) == 0:
234
+ return []
235
+
236
+ err_info = (
237
+ "This can happen if the chat history is manually set (i.e., `.set_turns()`). "
238
+ "Consider getting the 'raw' token values via the `.get_turns()` method "
239
+ "(each turn has a `.tokens` attribute)."
240
+ )
241
+
242
+ # Sanity checks for the assumptions made to figure out user token counts
243
+ if len(turns) == 1:
244
+ raise ValueError(
245
+ "Expected at least two turns in the chat history. " + err_info
246
+ )
247
+
248
+ if len(turns) % 2 != 0:
249
+ raise ValueError(
250
+ "Expected an even number of turns in the chat history. " + err_info
251
+ )
252
+
253
+ if turns[0].role != "user":
254
+ raise ValueError(
255
+ "Expected the 1st non-system turn to have role='user'. " + err_info
256
+ )
257
+
258
+ if turns[1].role != "assistant":
259
+ raise ValueError(
260
+ "Expected the 2nd turn non-system to have role='assistant'. " + err_info
261
+ )
262
+
263
+ if turns[1].tokens is None:
264
+ raise ValueError(
265
+ "Expected the 1st assistant turn to contain token counts. " + err_info
266
+ )
267
+
268
+ res: list[int] = [
269
+ # Implied token count for the 1st user input
270
+ turns[1].tokens[0],
271
+ # The token count for the 1st assistant response
272
+ turns[1].tokens[1],
273
+ ]
274
+ for i in range(1, len(turns) - 1, 2):
275
+ ti = turns[i]
276
+ tj = turns[i + 2]
277
+ if ti.role != "assistant" or tj.role != "assistant":
278
+ raise ValueError(
279
+ "Expected even turns to have role='assistant'." + err_info
280
+ )
281
+ if ti.tokens is None or tj.tokens is None:
282
+ raise ValueError(
283
+ "Expected role='assistant' turns to contain token counts."
284
+ + err_info
285
+ )
286
+ res.extend(
287
+ [
288
+ # Implied token count for the user input
289
+ tj.tokens[0] - sum(ti.tokens),
290
+ # The token count for the assistant response
291
+ tj.tokens[1],
292
+ ]
293
+ )
294
+
295
+ return res
296
+
297
+ def token_count(
298
+ self,
299
+ *args: Content | str,
300
+ data_model: Optional[type[BaseModel]] = None,
301
+ ) -> int:
302
+ """
303
+ Get an estimated token count for the given input.
304
+
305
+ Estimate the token size of input content. This can help determine whether input(s)
306
+ and/or conversation history (i.e., `.get_turns()`) should be reduced in size before
307
+ sending it to the model.
308
+
309
+ Parameters
310
+ ----------
311
+ args
312
+ The input to get a token count for.
313
+ data_model
314
+ If the input is meant for data extraction (i.e., `.extract_data()`), then
315
+ this should be the Pydantic model that describes the structure of the data to
316
+ extract.
317
+
318
+ Returns
319
+ -------
320
+ int
321
+ The token count for the input.
322
+
323
+ Note
324
+ ----
325
+ Remember that the token count is an estimate. Also, models based on
326
+ `ChatOpenAI()` currently does not take tools into account when
327
+ estimating token counts.
328
+
329
+ Examples
330
+ --------
331
+ ```python
332
+ from chatlas import ChatAnthropic
333
+
334
+ chat = ChatAnthropic()
335
+ # Estimate the token count before sending the input
336
+ print(chat.token_count("What is 2 + 2?"))
337
+
338
+ # Once input is sent, you can get the actual input and output
339
+ # token counts from the chat object
340
+ chat.chat("What is 2 + 2?", echo="none")
341
+ print(chat.token_usage())
342
+ ```
343
+ """
344
+
345
+ return self.provider.token_count(
346
+ *args,
347
+ tools=self._tools,
348
+ data_model=data_model,
349
+ )
350
+
351
+ async def token_count_async(
352
+ self,
353
+ *args: Content | str,
354
+ data_model: Optional[type[BaseModel]] = None,
355
+ ) -> int:
356
+ """
357
+ Get an estimated token count for the given input asynchronously.
358
+
359
+ Estimate the token size of input content. This can help determine whether input(s)
360
+ and/or conversation history (i.e., `.get_turns()`) should be reduced in size before
361
+ sending it to the model.
362
+
363
+ Parameters
364
+ ----------
365
+ args
366
+ The input to get a token count for.
367
+ data_model
368
+ If this input is meant for data extraction (i.e., `.extract_data_async()`),
369
+ then this should be the Pydantic model that describes the structure of the data
370
+ to extract.
371
+
183
372
  Returns
184
373
  -------
185
- list[tuple[int, int] | None]
186
- A list of tuples, where each tuple contains the start and end token
187
- indices for a turn.
374
+ int
375
+ The token count for the input.
188
376
  """
189
- return [turn.tokens for turn in self._turns]
377
+
378
+ return await self.provider.token_count_async(
379
+ *args,
380
+ tools=self._tools,
381
+ data_model=data_model,
382
+ )
190
383
 
191
384
  def app(
192
385
  self,
chatlas/_google.py CHANGED
@@ -17,8 +17,9 @@ from ._content import (
17
17
  )
18
18
  from ._logging import log_model_default
19
19
  from ._provider import Provider
20
+ from ._tokens import tokens_log
20
21
  from ._tools import Tool, basemodel_to_param_schema
21
- from ._turn import Turn, normalize_turns
22
+ from ._turn import Turn, normalize_turns, user_turn
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from google.generativeai.types.content_types import (
@@ -332,6 +333,55 @@ class GoogleProvider(
332
333
  def value_turn(self, completion, has_data_model) -> Turn:
333
334
  return self._as_turn(completion, has_data_model)
334
335
 
336
+ def token_count(
337
+ self,
338
+ *args: Content | str,
339
+ tools: dict[str, Tool],
340
+ data_model: Optional[type[BaseModel]],
341
+ ):
342
+ kwargs = self._token_count_args(
343
+ *args,
344
+ tools=tools,
345
+ data_model=data_model,
346
+ )
347
+
348
+ res = self._client.count_tokens(**kwargs)
349
+ return res.total_tokens
350
+
351
+ async def token_count_async(
352
+ self,
353
+ *args: Content | str,
354
+ tools: dict[str, Tool],
355
+ data_model: Optional[type[BaseModel]],
356
+ ):
357
+ kwargs = self._token_count_args(
358
+ *args,
359
+ tools=tools,
360
+ data_model=data_model,
361
+ )
362
+
363
+ res = await self._client.count_tokens_async(**kwargs)
364
+ return res.total_tokens
365
+
366
+ def _token_count_args(
367
+ self,
368
+ *args: Content | str,
369
+ tools: dict[str, Tool],
370
+ data_model: Optional[type[BaseModel]],
371
+ ) -> dict[str, Any]:
372
+ turn = user_turn(*args)
373
+
374
+ kwargs = self._chat_perform_args(
375
+ stream=False,
376
+ turns=[turn],
377
+ tools=tools,
378
+ data_model=data_model,
379
+ )
380
+
381
+ args_to_keep = ["contents", "tools"]
382
+
383
+ return {arg: kwargs[arg] for arg in args_to_keep if arg in kwargs}
384
+
335
385
  def _google_contents(self, turns: list[Turn]) -> list["ContentDict"]:
336
386
  contents: list["ContentDict"] = []
337
387
  for turn in turns:
@@ -421,6 +471,8 @@ class GoogleProvider(
421
471
  usage.candidates_token_count,
422
472
  )
423
473
 
474
+ tokens_log(self, tokens)
475
+
424
476
  finish = message.candidates[0].finish_reason
425
477
 
426
478
  return Turn(
chatlas/_ollama.py CHANGED
@@ -48,6 +48,13 @@ def ChatOllama(
48
48
  (e.g. `ollama pull llama3.2`).
49
49
  :::
50
50
 
51
+ ::: {.callout-note}
52
+ ## Python requirements
53
+
54
+ `ChatOllama` requires the `openai` package (e.g., `pip install openai`).
55
+ :::
56
+
57
+
51
58
  Examples
52
59
  --------
53
60
 
@@ -103,6 +110,7 @@ def ChatOllama(
103
110
 
104
111
  return ChatOpenAI(
105
112
  system_prompt=system_prompt,
113
+ api_key="ollama", # ignored
106
114
  turns=turns,
107
115
  base_url=f"{base_url}/v1",
108
116
  model=model,
chatlas/_openai.py CHANGED
@@ -8,6 +8,7 @@ from pydantic import BaseModel
8
8
  from ._chat import Chat
9
9
  from ._content import (
10
10
  Content,
11
+ ContentImage,
11
12
  ContentImageInline,
12
13
  ContentImageRemote,
13
14
  ContentJson,
@@ -20,7 +21,7 @@ from ._merge import merge_dicts
20
21
  from ._provider import Provider
21
22
  from ._tokens import tokens_log
22
23
  from ._tools import Tool, basemodel_to_param_schema
23
- from ._turn import Turn, normalize_turns
24
+ from ._turn import Turn, normalize_turns, user_turn
24
25
  from ._utils import MISSING, MISSING_TYPE, is_testing
25
26
 
26
27
  if TYPE_CHECKING:
@@ -294,10 +295,12 @@ class OpenAIProvider(Provider[ChatCompletion, ChatCompletionChunk, ChatCompletio
294
295
  "stream": stream,
295
296
  "messages": self._as_message_param(turns),
296
297
  "model": self._model,
297
- "seed": self._seed,
298
298
  **(kwargs or {}),
299
299
  }
300
300
 
301
+ if self._seed is not None:
302
+ kwargs_full["seed"] = self._seed
303
+
301
304
  if tool_schemas:
302
305
  kwargs_full["tools"] = tool_schemas
303
306
 
@@ -349,6 +352,57 @@ class OpenAIProvider(Provider[ChatCompletion, ChatCompletionChunk, ChatCompletio
349
352
  def value_turn(self, completion, has_data_model) -> Turn:
350
353
  return self._as_turn(completion, has_data_model)
351
354
 
355
+ def token_count(
356
+ self,
357
+ *args: Content | str,
358
+ tools: dict[str, Tool],
359
+ data_model: Optional[type[BaseModel]],
360
+ ) -> int:
361
+ try:
362
+ import tiktoken
363
+ except ImportError:
364
+ raise ImportError(
365
+ "The tiktoken package is required for token counting. "
366
+ "Please install it with `pip install tiktoken`."
367
+ )
368
+
369
+ encoding = tiktoken.encoding_for_model(self._model)
370
+
371
+ turn = user_turn(*args)
372
+
373
+ # Count the tokens in image contents
374
+ image_tokens = sum(
375
+ self._image_token_count(x)
376
+ for x in turn.contents
377
+ if isinstance(x, ContentImage)
378
+ )
379
+
380
+ # For other contents, get the token count from the actual message param
381
+ other_contents = [x for x in turn.contents if not isinstance(x, ContentImage)]
382
+ other_full = self._as_message_param([Turn("user", other_contents)])
383
+ other_tokens = len(encoding.encode(str(other_full)))
384
+
385
+ return other_tokens + image_tokens
386
+
387
+ async def token_count_async(
388
+ self,
389
+ *args: Content | str,
390
+ tools: dict[str, Tool],
391
+ data_model: Optional[type[BaseModel]],
392
+ ) -> int:
393
+ return self.token_count(*args, tools=tools, data_model=data_model)
394
+
395
+ @staticmethod
396
+ def _image_token_count(image: ContentImage) -> int:
397
+ if isinstance(image, ContentImageRemote) and image.detail == "low":
398
+ return 85
399
+ else:
400
+ # This is just the max token count for an image The highest possible
401
+ # resolution is 768 x 2048, and 8 tiles of size 512px can fit inside
402
+ # TODO: this is obviously a very conservative estimate and could be improved
403
+ # https://platform.openai.com/docs/guides/vision/calculating-costs
404
+ return 170 * 8 + 85
405
+
352
406
  @staticmethod
353
407
  def _as_message_param(turns: list[Turn]) -> list["ChatCompletionMessageParam"]:
354
408
  from openai.types.chat import (
@@ -412,7 +466,13 @@ class OpenAIProvider(Provider[ChatCompletion, ChatCompletionChunk, ChatCompletio
412
466
  contents.append({"type": "text", "text": ""})
413
467
  elif isinstance(x, ContentImageRemote):
414
468
  contents.append(
415
- {"type": "image_url", "image_url": {"url": x.url}}
469
+ {
470
+ "type": "image_url",
471
+ "image_url": {
472
+ "url": x.url,
473
+ "detail": x.detail,
474
+ },
475
+ }
416
476
  )
417
477
  elif isinstance(x, ContentImageInline):
418
478
  contents.append(
chatlas/_provider.py CHANGED
@@ -14,6 +14,7 @@ from typing import (
14
14
 
15
15
  from pydantic import BaseModel
16
16
 
17
+ from ._content import Content
17
18
  from ._tools import Tool
18
19
  from ._turn import Turn
19
20
 
@@ -141,3 +142,19 @@ class Provider(
141
142
  completion: ChatCompletionT,
142
143
  has_data_model: bool,
143
144
  ) -> Turn: ...
145
+
146
+ @abstractmethod
147
+ def token_count(
148
+ self,
149
+ *args: Content | str,
150
+ tools: dict[str, Tool],
151
+ data_model: Optional[type[BaseModel]],
152
+ ) -> int: ...
153
+
154
+ @abstractmethod
155
+ async def token_count_async(
156
+ self,
157
+ *args: Content | str,
158
+ tools: dict[str, Tool],
159
+ data_model: Optional[type[BaseModel]],
160
+ ) -> int: ...
@@ -18,12 +18,4 @@ class ChatClientArgs(TypedDict, total=False):
18
18
  default_headers: Optional[Mapping[str, str]]
19
19
  default_query: Optional[Mapping[str, object]]
20
20
  http_client: httpx.AsyncClient
21
- transport: httpx.AsyncBaseTransport
22
- proxies: Union[
23
- str,
24
- httpx.Proxy,
25
- dict[str | httpx.URL, Union[None, str, httpx.URL, httpx.Proxy]],
26
- None,
27
- ]
28
- connection_pool_limits: httpx.Limits
29
21
  _strict_response_validation: bool
@@ -6,7 +6,6 @@
6
6
  from typing import Iterable, Literal, Mapping, Optional, TypedDict, Union
7
7
 
8
8
  import anthropic
9
- import anthropic._types
10
9
  import anthropic.types.message_param
11
10
  import anthropic.types.text_block_param
12
11
  import anthropic.types.tool_choice_any_param
@@ -51,7 +50,7 @@ class SubmitInputArgs(TypedDict, total=False):
51
50
  tools: Union[Iterable[anthropic.types.tool_param.ToolParam], anthropic.NotGiven]
52
51
  top_k: int | anthropic.NotGiven
53
52
  top_p: float | anthropic.NotGiven
54
- extra_headers: Optional[Mapping[str, Union[str, anthropic._types.Omit]]]
53
+ extra_headers: Optional[Mapping[str, Union[str, anthropic.Omit]]]
55
54
  extra_query: Optional[Mapping[str, object]]
56
55
  extra_body: object | None
57
56
  timeout: float | anthropic.Timeout | None | anthropic.NotGiven
@@ -14,6 +14,7 @@ class ChatClientArgs(TypedDict, total=False):
14
14
  organization: str | None
15
15
  project: str | None
16
16
  base_url: str | httpx.URL | None
17
+ websocket_base_url: str | httpx.URL | None
17
18
  timeout: Union[float, openai.Timeout, None, openai.NotGiven]
18
19
  max_retries: int
19
20
  default_headers: Optional[Mapping[str, str]]
@@ -17,6 +17,7 @@ class ChatAzureClientArgs(TypedDict, total=False):
17
17
  organization: str | None
18
18
  project: str | None
19
19
  base_url: str | None
20
+ websocket_base_url: str | httpx.URL | None
20
21
  timeout: float | openai.Timeout | None | openai.NotGiven
21
22
  max_retries: int
22
23
  default_headers: Optional[Mapping[str, str]]
@@ -8,6 +8,7 @@ from typing import Iterable, Literal, Mapping, Optional, TypedDict, Union
8
8
  import openai
9
9
  import openai.types.chat.chat_completion_assistant_message_param
10
10
  import openai.types.chat.chat_completion_audio_param
11
+ import openai.types.chat.chat_completion_developer_message_param
11
12
  import openai.types.chat.chat_completion_function_call_option_param
12
13
  import openai.types.chat.chat_completion_function_message_param
13
14
  import openai.types.chat.chat_completion_named_tool_choice_param
@@ -26,6 +27,7 @@ import openai.types.shared_params.response_format_text
26
27
  class SubmitInputArgs(TypedDict, total=False):
27
28
  messages: Iterable[
28
29
  Union[
30
+ openai.types.chat.chat_completion_developer_message_param.ChatCompletionDeveloperMessageParam,
29
31
  openai.types.chat.chat_completion_system_message_param.ChatCompletionSystemMessageParam,
30
32
  openai.types.chat.chat_completion_user_message_param.ChatCompletionUserMessageParam,
31
33
  openai.types.chat.chat_completion_assistant_message_param.ChatCompletionAssistantMessageParam,
@@ -36,6 +38,8 @@ class SubmitInputArgs(TypedDict, total=False):
36
38
  model: Union[
37
39
  str,
38
40
  Literal[
41
+ "o1",
42
+ "o1-2024-12-17",
39
43
  "o1-preview",
40
44
  "o1-preview-2024-09-12",
41
45
  "o1-mini",
@@ -44,10 +48,11 @@ class SubmitInputArgs(TypedDict, total=False):
44
48
  "gpt-4o-2024-11-20",
45
49
  "gpt-4o-2024-08-06",
46
50
  "gpt-4o-2024-05-13",
47
- "gpt-4o-realtime-preview",
48
- "gpt-4o-realtime-preview-2024-10-01",
49
51
  "gpt-4o-audio-preview",
50
52
  "gpt-4o-audio-preview-2024-10-01",
53
+ "gpt-4o-audio-preview-2024-12-17",
54
+ "gpt-4o-mini-audio-preview",
55
+ "gpt-4o-mini-audio-preview-2024-12-17",
51
56
  "chatgpt-4o-latest",
52
57
  "gpt-4o-mini",
53
58
  "gpt-4o-mini-2024-07-18",
@@ -100,6 +105,7 @@ class SubmitInputArgs(TypedDict, total=False):
100
105
  openai.NotGiven,
101
106
  ]
102
107
  presence_penalty: Union[float, None, openai.NotGiven]
108
+ reasoning_effort: Union[Literal["low", "medium", "high"], openai.NotGiven]
103
109
  response_format: Union[
104
110
  openai.types.shared_params.response_format_text.ResponseFormatText,
105
111
  openai.types.shared_params.response_format_json_object.ResponseFormatJSONObject,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: chatlas
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A simple and consistent interface for chatting with LLMs
5
5
  Project-URL: Homepage, https://posit-dev.github.io/chatlas
6
6
  Project-URL: Documentation, https://posit-dev.github.io/chatlas
@@ -30,15 +30,18 @@ Requires-Dist: pillow; extra == 'dev'
30
30
  Requires-Dist: python-dotenv; extra == 'dev'
31
31
  Requires-Dist: ruff>=0.6.5; extra == 'dev'
32
32
  Requires-Dist: shiny; extra == 'dev'
33
+ Requires-Dist: tiktoken; extra == 'dev'
33
34
  Provides-Extra: docs
34
35
  Requires-Dist: griffe>=1; extra == 'docs'
35
36
  Requires-Dist: ipykernel; extra == 'docs'
36
37
  Requires-Dist: ipywidgets; extra == 'docs'
37
38
  Requires-Dist: nbclient; extra == 'docs'
38
39
  Requires-Dist: nbformat; extra == 'docs'
40
+ Requires-Dist: numpy; extra == 'docs'
39
41
  Requires-Dist: pandas; extra == 'docs'
40
42
  Requires-Dist: pyyaml; extra == 'docs'
41
43
  Requires-Dist: quartodoc>=0.7; extra == 'docs'
44
+ Requires-Dist: sentence-transformers; extra == 'docs'
42
45
  Provides-Extra: test
43
46
  Requires-Dist: pyright>=1.1.379; extra == 'test'
44
47
  Requires-Dist: pytest-asyncio; extra == 'test'
@@ -48,6 +51,14 @@ Description-Content-Type: text/markdown
48
51
 
49
52
  # chatlas
50
53
 
54
+ <p>
55
+ <!-- badges start -->
56
+ <a href="https://pypi.org/project/chatlas/"><img alt="PyPI" src="https://img.shields.io/pypi/v/chatlas?logo=python&logoColor=white&color=orange"></a>
57
+ <a href="https://choosealicense.com/licenses/mit/"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
58
+ <a href="https://github.com/posit-dev/chatlas"><img src="https://github.com/posit-dev/chatlas/actions/workflows/test.yml/badge.svg?branch=main" alt="Python Tests"></a>
59
+ <!-- badges end -->
60
+ </p>
61
+
51
62
  chatlas provides a simple and unified interface across large language model (llm) providers in Python.
52
63
  It abstracts away complexity from common tasks like streaming chat interfaces, tool calling, structured output, and much more.
53
64
  chatlas helps you prototype faster without painting you into a corner; for example, switching providers is as easy as changing one line of code, but provider specific features are still accessible when needed.
@@ -123,7 +134,7 @@ From a `chat` instance, it's simple to start a web-based or terminal-based chat
123
134
  chat.app()
124
135
  ```
125
136
 
126
- <div style="display:flex;justify-content:center;">
137
+ <div align="center">
127
138
  <img width="500" alt="A web app for chatting with an LLM via chatlas" src="https://github.com/user-attachments/assets/e43f60cb-3686-435a-bd11-8215cb024d2e" class="border rounded">
128
139
  </div>
129
140
 
@@ -279,7 +290,7 @@ asyncio.run(main())
279
290
 
280
291
  `chatlas` has full typing support, meaning that, among other things, autocompletion just works in your favorite editor:
281
292
 
282
- <div style="display:flex;justify-content:center;">
293
+ <div align="center">
283
294
  <img width="500" alt="Autocompleting model options in ChatOpenAI" src="https://github.com/user-attachments/assets/163d6d8a-7d58-422d-b3af-cc9f2adee759" class="rounded">
284
295
  </div>
285
296
 
@@ -299,7 +310,7 @@ This shows important information like tool call results, finish reasons, and mor
299
310
  If the problem isn't self-evident, you can also reach into the `.get_last_turn()`, which contains the full response object, with full details about the completion.
300
311
 
301
312
 
302
- <div style="display:flex;justify-content:center;">
313
+ <div align="center">
303
314
  <img width="500" alt="Turn completion details with typing support" src="https://github.com/user-attachments/assets/eaea338d-e44a-4e23-84a7-2e998d8af3ba" class="rounded">
304
315
  </div>
305
316
 
@@ -1,19 +1,19 @@
1
1
  chatlas/__init__.py,sha256=OJbTO71ne1O9SDxkwIKOMpCMKbh0T8eDpYPFhrAb28A,974
2
- chatlas/_anthropic.py,sha256=AUb1ZJfZo6AEVwfNrMl520-zGomkOfc-ewJoFXGGEEc,21180
3
- chatlas/_chat.py,sha256=_WTqI3v84voJ9GJJlizIPjxomx3O--fToN5NQSGeFuM,38452
2
+ chatlas/_anthropic.py,sha256=ssv6k-XZetwv1hNk7Qs8dAWlcPvElzxjrG0Fh2w50xE,24373
3
+ chatlas/_chat.py,sha256=p_MS1LQ-EBlvCzGTbjY5Bdlt1meDsfSbP517blkwSjY,44774
4
4
  chatlas/_content.py,sha256=vpWF_WKS2tCDUtnL8l9lfW6b6g9e7LbDKP-_TegauVE,5883
5
5
  chatlas/_content_image.py,sha256=4nk9wTvLtNmtcytdFp8p9otEV5-0_K6wzIxCyK0PIEI,8367
6
6
  chatlas/_display.py,sha256=_IcQcvpyTNjGHOpY70_LOrDWwTjzdkziy6pTvxHEiWI,4053
7
7
  chatlas/_github.py,sha256=D3L7Qu35K-M1qEW7-w-Oq-pF-9mVetia3MHYNNLEYtU,4373
8
- chatlas/_google.py,sha256=Y3vFcUKyEiTAGUkh6Nhw8pbsDYSN3xAECXc9jpd7-6A,13953
8
+ chatlas/_google.py,sha256=jfBp_C_qjbvs48QPE_ykSsfRhjz7htb-G389qLxvSC4,15279
9
9
  chatlas/_groq.py,sha256=3VnYiKdxJTHPhEgUKnL2nY5uYL2L4PKBo7GZMwR0D8k,4158
10
10
  chatlas/_interpolate.py,sha256=ykwLP3x-ya9Q33U4knSU75dtk6pzJAeythEEIW-43Pc,3631
11
11
  chatlas/_logging.py,sha256=7a20sAl1PkW1qBNrfd_ieUbQXV8Gf4Vuf0Wn62LNBmk,2290
12
12
  chatlas/_merge.py,sha256=Xt2uutLdEmYAGfGCa8GCEd8sdNadQM5o3l-zuIQFbWU,3923
13
- chatlas/_ollama.py,sha256=G1rGasb6cq8WhuvSpo2oHMBkeeguZE_TrurIyZSIPJ8,3584
14
- chatlas/_openai.py,sha256=mEeUTcwT7s33CWhAvavferQlc4ltk3Wm75Bqh1jQKw0,21855
13
+ chatlas/_ollama.py,sha256=ze-RoHEbf62dYmXDDKjNGqaEZaKCZdcBEyFwQMDQxkQ,3760
14
+ chatlas/_openai.py,sha256=nJgp3JJlpxeXn_dhIrXNffQk9vh3ycYy1D2fNip0qEs,24004
15
15
  chatlas/_perplexity.py,sha256=Bw_mlM8N8egGKIrbNerTn2pMlybugADOshjYOfN1ixM,4446
16
- chatlas/_provider.py,sha256=nUfJEXcVs_Yxns2WLr3BevmAnU19fnIGEK_VAeSyt6E,3601
16
+ chatlas/_provider.py,sha256=i16I2hkBat1fYEMcFsU0gYNr6Tcg8zJ2xHDnEY7WRY4,4009
17
17
  chatlas/_tokens.py,sha256=3W3EPUp9eWXUiwuzJwEPBv43AUznbK46pm59Htti7z4,2392
18
18
  chatlas/_tokens_old.py,sha256=L9d9oafrXvEx2u4nIn_Jjn7adnQyLBnYBuPwJUE8Pl8,5005
19
19
  chatlas/_tools.py,sha256=-qt4U1AFkebQoX9kpsBy5QXK8a2PpHX6Amgm44gcQ68,4113
@@ -22,16 +22,16 @@ chatlas/_typing_extensions.py,sha256=YdzmlyPSBpIEcsOkoz12e6jETT1XEMV2Q72haE4cfwY
22
22
  chatlas/_utils.py,sha256=qAiWuDx-uG8BGFZ_PWvum9wpN-WogdItO32X4pRhhLs,2762
23
23
  chatlas/types/__init__.py,sha256=pgHl8pd2Ytskd6lkfNtm98Yj1ZP0b3R35RH4Uht2BAs,694
24
24
  chatlas/types/anthropic/__init__.py,sha256=OwubA-DPHYpYo0XyRyAFwftOI0mOxtHzAyhUSLcDx54,417
25
- chatlas/types/anthropic/_client.py,sha256=Iz7U7u7_Af4UzKuDDOaYe09T_WW1w9yXtqxrgn0ITSk,918
25
+ chatlas/types/anthropic/_client.py,sha256=G0LRhoFBcsSOMr5qhP-0rAScsVXaVlHCpggfVp54bnQ,690
26
26
  chatlas/types/anthropic/_client_bedrock.py,sha256=mNazQlu0pQt8JdzrYn3LKNgE4n732GjhQUJdQQK9QkY,785
27
- chatlas/types/anthropic/_submit.py,sha256=MBsKtgPrRq4z_Ls5haT3oZKSTpM0_K9STxJZ_2ge-iA,2122
27
+ chatlas/types/anthropic/_submit.py,sha256=BhkJu0OhIMiD3qiUNqJ4litBQCW-7KRr_YZp9Xr9KGA,2091
28
28
  chatlas/types/google/__init__.py,sha256=ZJhi8Kwvio2zp8T1TQqmvdHqkS-Khb6BGESPjREADgo,337
29
29
  chatlas/types/google/_client.py,sha256=YA5hsT-m-KcONKtwpCULYMnGwMPfkScpvhjx_qBLg5o,4421
30
30
  chatlas/types/google/_submit.py,sha256=yp1wtp5eScLlHDNxeXl0qJOKv7SWLnRQ8oslupRFUBE,4839
31
31
  chatlas/types/openai/__init__.py,sha256=Q2RAr1bSH1nHsxICK05nAmKmxdhKmhbBkWD_XHiVSrI,411
32
- chatlas/types/openai/_client.py,sha256=4V9UcysJI6Iu8CsaFH68EJiGIs-H41veguldsVj4KMA,707
33
- chatlas/types/openai/_client_azure.py,sha256=zrQv0JZS47UB-ViIOKcFSv8YVJIzaridP_e-FF1f-U4,811
34
- chatlas/types/openai/_submit.py,sha256=Gl8YPARAY-kChPOdjoyLBEtVgarYrX69OOeFQH6b6cI,5679
35
- chatlas-0.2.0.dist-info/METADATA,sha256=C1CrsrVrcKI36Wnp2u-samFDFoQ6krG9RnfawFIPzSs,12617
36
- chatlas-0.2.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
37
- chatlas-0.2.0.dist-info/RECORD,,
32
+ chatlas/types/openai/_client.py,sha256=YGm_EHtRSSHeeOZe-CV7oNvMJpEblEta3UTuU7lSRO8,754
33
+ chatlas/types/openai/_client_azure.py,sha256=jx8D_p46CLDGzTP-k-TtGzj-f3junj6or-86m8DD_0w,858
34
+ chatlas/types/openai/_submit.py,sha256=f2o3rNJZcWBhwjomXrs3Mh0V_4vNa9N5eEBkMvJDofQ,6028
35
+ chatlas-0.3.0.dist-info/METADATA,sha256=eIPyvdcXpYN4s6txY5GMgBttUsmS5868D10U5zzTchs,13164
36
+ chatlas-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ chatlas-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any