chatlas 0.11.0__tar.gz → 0.12.0__tar.gz

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.

Files changed (171) hide show
  1. {chatlas-0.11.0 → chatlas-0.12.0}/.vscode/settings.json +9 -2
  2. {chatlas-0.11.0 → chatlas-0.12.0}/CHANGELOG.md +38 -5
  3. {chatlas-0.11.0 → chatlas-0.12.0}/PKG-INFO +1 -1
  4. chatlas-0.12.0/chatlas/_auto.py +290 -0
  5. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_chat.py +25 -8
  6. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_content.py +98 -3
  7. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_tools.py +36 -12
  8. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_typing_extensions.py +1 -1
  9. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_version.py +2 -2
  10. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/__init__.py +4 -0
  11. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/anthropic/_submit.py +2 -2
  12. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/openai/_client.py +2 -2
  13. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/openai/_client_azure.py +2 -2
  14. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/openai/_submit.py +2 -2
  15. {chatlas-0.11.0 → chatlas-0.12.0}/docs/_quarto.yml +2 -0
  16. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/models.qmd +28 -7
  17. {chatlas-0.11.0 → chatlas-0.12.0}/scripts/_generate_openai_types.py +14 -3
  18. {chatlas-0.11.0 → chatlas-0.12.0}/tests/conftest.py +4 -8
  19. chatlas-0.12.0/tests/test_auto.py +266 -0
  20. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_content_tools.py +48 -6
  21. chatlas-0.12.0/tests/test_register_tool_models.py +339 -0
  22. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_tool_from_mcp.py +4 -4
  23. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_tools_enhanced.py +171 -8
  24. chatlas-0.11.0/chatlas/_auto.py +0 -178
  25. chatlas-0.11.0/tests/test_auto.py +0 -109
  26. {chatlas-0.11.0 → chatlas-0.12.0}/.github/workflows/check-update-types.yml +0 -0
  27. {chatlas-0.11.0 → chatlas-0.12.0}/.github/workflows/docs-publish.yml +0 -0
  28. {chatlas-0.11.0 → chatlas-0.12.0}/.github/workflows/release.yml +0 -0
  29. {chatlas-0.11.0 → chatlas-0.12.0}/.github/workflows/test.yml +0 -0
  30. {chatlas-0.11.0 → chatlas-0.12.0}/.github/workflows/update-pricing.yml +0 -0
  31. {chatlas-0.11.0 → chatlas-0.12.0}/.gitignore +0 -0
  32. {chatlas-0.11.0 → chatlas-0.12.0}/.vscode/extensions.json +0 -0
  33. {chatlas-0.11.0 → chatlas-0.12.0}/CLAUDE.md +0 -0
  34. {chatlas-0.11.0 → chatlas-0.12.0}/LICENSE +0 -0
  35. {chatlas-0.11.0 → chatlas-0.12.0}/Makefile +0 -0
  36. {chatlas-0.11.0 → chatlas-0.12.0}/README.md +0 -0
  37. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/__init__.py +0 -0
  38. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_callbacks.py +0 -0
  39. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_content_image.py +0 -0
  40. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_content_pdf.py +0 -0
  41. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_display.py +0 -0
  42. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_interpolate.py +0 -0
  43. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_live_render.py +0 -0
  44. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_logging.py +0 -0
  45. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_mcp_manager.py +0 -0
  46. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_merge.py +0 -0
  47. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider.py +0 -0
  48. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_anthropic.py +0 -0
  49. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_cloudflare.py +0 -0
  50. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_databricks.py +0 -0
  51. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_deepseek.py +0 -0
  52. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_github.py +0 -0
  53. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_google.py +0 -0
  54. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_groq.py +0 -0
  55. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_huggingface.py +0 -0
  56. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_mistral.py +0 -0
  57. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_ollama.py +0 -0
  58. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_openai.py +0 -0
  59. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_openrouter.py +0 -0
  60. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_perplexity.py +0 -0
  61. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_portkey.py +0 -0
  62. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_provider_snowflake.py +0 -0
  63. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_tokens.py +0 -0
  64. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_tokens_old.py +0 -0
  65. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_turn.py +0 -0
  66. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/_utils.py +0 -0
  67. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/data/prices.json +0 -0
  68. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/py.typed +0 -0
  69. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/anthropic/__init__.py +0 -0
  70. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/anthropic/_client.py +0 -0
  71. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/anthropic/_client_bedrock.py +0 -0
  72. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/google/__init__.py +0 -0
  73. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/google/_client.py +0 -0
  74. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/google/_submit.py +0 -0
  75. {chatlas-0.11.0 → chatlas-0.12.0}/chatlas/types/openai/__init__.py +0 -0
  76. {chatlas-0.11.0 → chatlas-0.12.0}/docs/.gitignore +0 -0
  77. {chatlas-0.11.0 → chatlas-0.12.0}/docs/_extensions/machow/interlinks/.gitignore +0 -0
  78. {chatlas-0.11.0 → chatlas-0.12.0}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
  79. {chatlas-0.11.0 → chatlas-0.12.0}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
  80. {chatlas-0.11.0 → chatlas-0.12.0}/docs/_sidebar.yml +0 -0
  81. {chatlas-0.11.0 → chatlas-0.12.0}/docs/congressional-assets.png +0 -0
  82. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/async.qmd +0 -0
  83. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/chat.qmd +0 -0
  84. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/chatbots.qmd +0 -0
  85. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/debug.qmd +0 -0
  86. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/monitor.qmd +0 -0
  87. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/parameters.qmd +0 -0
  88. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/stream.qmd +0 -0
  89. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/structured-data.qmd +0 -0
  90. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/system-prompt.qmd +0 -0
  91. {chatlas-0.11.0 → chatlas-0.12.0}/docs/get-started/tools.qmd +0 -0
  92. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chat-app.png +0 -0
  93. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chat-console.mp4 +0 -0
  94. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chat-console.png +0 -0
  95. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chat-notebook.mp4 +0 -0
  96. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chat-parameters.png +0 -0
  97. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chatbot-gradio.png +0 -0
  98. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chatbot-shiny.png +0 -0
  99. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chatbot-streamlit.png +0 -0
  100. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chatbot-textual.png +0 -0
  101. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/chatlas-hello.png +0 -0
  102. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/client-parameters.png +0 -0
  103. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/congressional-assets.png +0 -0
  104. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/hello-chat-console.png +0 -0
  105. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/model-parameters.png +0 -0
  106. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/model-type-hints.png +0 -0
  107. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/posit-logo.png +0 -0
  108. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/shiny-mcp-run-python.png +0 -0
  109. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/shiny-tool-call-display.png +0 -0
  110. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/shiny-tool-call-map.png +0 -0
  111. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/tool-calling-right.svg +0 -0
  112. {chatlas-0.11.0 → chatlas-0.12.0}/docs/images/tool-calling-wrong.svg +0 -0
  113. {chatlas-0.11.0 → chatlas-0.12.0}/docs/index.qmd +0 -0
  114. {chatlas-0.11.0 → chatlas-0.12.0}/docs/logos/hero/hero-old.png +0 -0
  115. {chatlas-0.11.0 → chatlas-0.12.0}/docs/logos/hero/hero.png +0 -0
  116. {chatlas-0.11.0 → chatlas-0.12.0}/docs/logos/hex/logo.png +0 -0
  117. {chatlas-0.11.0 → chatlas-0.12.0}/docs/logos/small/logo.png +0 -0
  118. {chatlas-0.11.0 → chatlas-0.12.0}/docs/misc/RAG.qmd +0 -0
  119. {chatlas-0.11.0 → chatlas-0.12.0}/docs/misc/examples.qmd +0 -0
  120. {chatlas-0.11.0 → chatlas-0.12.0}/docs/misc/mcp-tools.qmd +0 -0
  121. {chatlas-0.11.0 → chatlas-0.12.0}/docs/misc/vocabulary.qmd +0 -0
  122. {chatlas-0.11.0 → chatlas-0.12.0}/docs/structured-data/article-summary.qmd +0 -0
  123. {chatlas-0.11.0 → chatlas-0.12.0}/docs/structured-data/classification.qmd +0 -0
  124. {chatlas-0.11.0 → chatlas-0.12.0}/docs/structured-data/entity-recognition.qmd +0 -0
  125. {chatlas-0.11.0 → chatlas-0.12.0}/docs/structured-data/multi-modal.qmd +0 -0
  126. {chatlas-0.11.0 → chatlas-0.12.0}/docs/structured-data/sentiment-analysis.qmd +0 -0
  127. {chatlas-0.11.0 → chatlas-0.12.0}/docs/styles.scss +0 -0
  128. {chatlas-0.11.0 → chatlas-0.12.0}/docs/tool-calling/approval.qmd +0 -0
  129. {chatlas-0.11.0 → chatlas-0.12.0}/docs/tool-calling/displays.qmd +0 -0
  130. {chatlas-0.11.0 → chatlas-0.12.0}/docs/tool-calling/how-it-works.qmd +0 -0
  131. {chatlas-0.11.0 → chatlas-0.12.0}/docs/why-chatlas.qmd +0 -0
  132. {chatlas-0.11.0 → chatlas-0.12.0}/pyproject.toml +0 -0
  133. {chatlas-0.11.0 → chatlas-0.12.0}/pytest.ini +0 -0
  134. {chatlas-0.11.0 → chatlas-0.12.0}/scripts/_generate_anthropic_types.py +0 -0
  135. {chatlas-0.11.0 → chatlas-0.12.0}/scripts/_generate_google_types.py +0 -0
  136. {chatlas-0.11.0 → chatlas-0.12.0}/scripts/_utils.py +0 -0
  137. {chatlas-0.11.0 → chatlas-0.12.0}/scripts/main.py +0 -0
  138. {chatlas-0.11.0 → chatlas-0.12.0}/tests/__init__.py +0 -0
  139. {chatlas-0.11.0 → chatlas-0.12.0}/tests/__snapshots__/test_chat.ambr +0 -0
  140. {chatlas-0.11.0 → chatlas-0.12.0}/tests/apples.pdf +0 -0
  141. {chatlas-0.11.0 → chatlas-0.12.0}/tests/images/dice.png +0 -0
  142. {chatlas-0.11.0 → chatlas-0.12.0}/tests/mcp_servers/http_add.py +0 -0
  143. {chatlas-0.11.0 → chatlas-0.12.0}/tests/mcp_servers/http_current_date.py +0 -0
  144. {chatlas-0.11.0 → chatlas-0.12.0}/tests/mcp_servers/stdio_current_date.py +0 -0
  145. {chatlas-0.11.0 → chatlas-0.12.0}/tests/mcp_servers/stdio_subtract_multiply.py +0 -0
  146. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_callbacks.py +0 -0
  147. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_chat.py +0 -0
  148. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_content.py +0 -0
  149. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_content_html.py +0 -0
  150. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_content_image.py +0 -0
  151. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_content_pdf.py +0 -0
  152. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_interpolate.py +0 -0
  153. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_mcp_client.py +0 -0
  154. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_anthropic.py +0 -0
  155. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_azure.py +0 -0
  156. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_bedrock.py +0 -0
  157. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_cloudflare.py +0 -0
  158. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_databricks.py +0 -0
  159. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_deepseek.py +0 -0
  160. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_github.py +0 -0
  161. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_google.py +0 -0
  162. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_huggingface.py +0 -0
  163. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_mistral.py +0 -0
  164. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_openai.py +0 -0
  165. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_openrouter.py +0 -0
  166. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_portkey.py +0 -0
  167. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_provider_snowflake.py +0 -0
  168. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_set_model_params.py +0 -0
  169. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_tokens.py +0 -0
  170. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_turns.py +0 -0
  171. {chatlas-0.11.0 → chatlas-0.12.0}/tests/test_utils_merge.py +0 -0
@@ -7,5 +7,12 @@
7
7
  },
8
8
  "editor.defaultFormatter": "charliermarsh.ruff",
9
9
  },
10
- "flake8.args": ["--max-line-length=120"]
11
- }
10
+ "flake8.args": [
11
+ "--max-line-length=120"
12
+ ],
13
+ "python.testing.pytestArgs": [
14
+ "tests"
15
+ ],
16
+ "python.testing.unittestEnabled": false,
17
+ "python.testing.pytestEnabled": true
18
+ }
@@ -7,6 +7,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
  -->
9
9
 
10
+ ## [0.12.0] - 2025-09-08
11
+
12
+ ### Breaking changes
13
+
14
+ * `ChatAuto()`'s first (optional) positional parameter has changed from `system_prompt` to `provider_model`, and `system_prompt` is now a keyword parameter. As a result, you may need to change `ChatAuto("[system prompt]")` -> `ChatAuto(system_prompt="[system prompt]")`. In addition, the `provider` and `model` keyword arguments are now deprecated, but continue to work with a warning, as are the previous `CHATLAS_CHAT_PROVIDER` and `CHATLAS_CHAT_MODEL` environment variables. (#159)
15
+
16
+ ### New features
17
+
18
+ * `ChatAuto()`'s new `provider_model` takes both provider and model in a single string in the format `"{provider}/{model}"`, e.g. `"openai/gpt-5"`. If not provided, `ChatAuto()` looks for the `CHATLAS_CHAT_PROVIDER_MODEL` environment variable, defaulting to `"openai"` if neither are provided. Unlike previous versions of `ChatAuto()`, the environment variables are now used *only if function arguments are not provided*. In other words, if `provider_model` is given, the `CHATLAS_CHAT_PROVIDER_MODEL` environment variable is ignored. Similarly, `CHATLAS_CHAT_ARGS` are only used if no `kwargs` are provided. This improves interactive use cases, makes it easier to introduce application-specific environment variables, and puts more control in the hands of the developer. (#159)
19
+ * The `.register_tool()` method now:
20
+ * Accepts a `Tool` instance as input. This is primarily useful for binding things like `annotations` to the `Tool` in one place, and registering it in another. (#172)
21
+ * Supports function parameter names that start with an underscore. (#174)
22
+ * The `ToolAnnotations` type gains an `extra` key field -- providing a place for providing additional information that other consumers of tool annotations (e.g., [shinychat](https://posit-dev.github.io/shinychat/)) may make use of.
23
+
24
+ ### Bug fixes
25
+
26
+ * `ChatAuto()` now supports recently added providers such as `ChatCloudflare()`, `ChatDeepseek()`, `ChatHuggingFace()`, etc. (#159)
27
+
28
+ ## [0.11.1] - 2025-08-29
29
+
30
+ ### New features
31
+
32
+ * `.register_tool()` gains a `name` parameter (useful for overriding the name of the function). (#162)
33
+
34
+ ### Bug fixes
35
+
36
+ * `ContentToolRequest` is (once again) serializable to/from JSON via Pydantic. (#164)
37
+ * `.register_tool(model=model)` no longer unexpectedly errors when `model` contains `pydantic.Field(alias='_my_alias')`. (#161)
38
+
39
+ ### Changes
40
+
41
+ * `.register_tool(annotations=annotations)` drops support for `mcp.types.ToolAnnotations()` and instead expects a dictionary of the same info. (#164)
42
+
10
43
  ## [0.11.0] - 2025-08-26
11
44
 
12
45
  ### New features
@@ -26,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
26
59
 
27
60
  ### New features
28
61
 
29
- * Added `ChatCloudflare()` for chatting via [Cloudflare AI](https://developers.cloudflare.com/workers-ai/get-started/rest-api/). (#150)
62
+ * Added `ChatCloudflare()` for chatting via [Cloudflare AI](https://developers.cloudflare.com/workers-ai/get-started/rest-api/). (#150)
30
63
  * Added `ChatDeepSeek()` for chatting via [DeepSeek](https://www.deepseek.com/). (#147)
31
64
  * Added `ChatOpenRouter()` for chatting via [Open Router](https://openrouter.ai/). (#148)
32
65
  * Added `ChatHuggingFace()` for chatting via [Hugging Face](https://huggingface.co/). (#144)
@@ -62,7 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
62
95
 
63
96
  ### New features
64
97
 
65
- * `Chat` gains a handful of new methods:
98
+ * `Chat` gains a handful of new methods:
66
99
  * `.register_mcp_tools_http_stream_async()` and `.register_mcp_tools_stdio_async()`: for registering tools from a [MCP server](https://modelcontextprotocol.io/). (#39)
67
100
  * `.get_tools()` and `.set_tools()`: for fine-grained control over registered tools. (#39)
68
101
  * `.set_model_params()`: for setting common LLM parameters in a model-agnostic fashion. (#127)
@@ -71,7 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
71
104
  * Tool functions passed to `.register_tool()` can now `yield` numerous results. (#39)
72
105
  * A `ContentToolResultImage` content class was added for returning images from tools. It is currently only works with `ChatAnthropic`. (#39)
73
106
  * A `Tool` can now be constructed from a pre-existing tool schema (via a new `__init__` method). (#39)
74
- * The `Chat.app()` method gains a `host` parameter. (#122)
107
+ * The `Chat.app()` method gains a `host` parameter. (#122)
75
108
  * `ChatGithub()` now supports the more standard `GITHUB_TOKEN` environment variable for storing the API key. (#123)
76
109
 
77
110
  ### Changes
@@ -133,7 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
133
166
 
134
167
  ## [0.7.1] - 2025-05-10
135
168
 
136
- * Added `openai` as a hard dependency, making installation easier for a wide range of use cases. (#91)
169
+ * Added `openai` as a hard dependency, making installation easier for a wide range of use cases. (#91)
137
170
 
138
171
  ## [0.7.0] - 2025-04-22
139
172
 
@@ -143,7 +176,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
143
176
  * `.stream()` and `.stream_async()` gain a `content` argument. Set this to `"all"` to include `ContentToolResult`/`ContentToolRequest` objects in the stream. (#75)
144
177
  * `ContentToolResult`/`ContentToolRequest` are now exported to `chatlas` namespace. (#75)
145
178
  * `ContentToolResult`/`ContentToolRequest` gain a `.tagify()` method so they render sensibly in a Shiny app. (#75)
146
- * A tool can now return a `ContentToolResult`. This is useful for:
179
+ * A tool can now return a `ContentToolResult`. This is useful for:
147
180
  * Specifying the format used for sending the tool result to the chat model (`model_format`). (#87)
148
181
  * Custom rendering of the tool result (by overriding relevant methods in a subclass). (#75)
149
182
  * `Chat` gains a new `.current_display` property. When a `.chat()` or `.stream()` is currently active, this property returns an object with a `.echo()` method (to echo new content to the display). This is primarily useful for displaying custom content during a tool call. (#79)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatlas
3
- Version: 0.11.0
3
+ Version: 0.12.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
@@ -0,0 +1,290 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import warnings
5
+ from typing import Callable, Literal, Optional
6
+
7
+ import orjson
8
+
9
+ from ._chat import Chat
10
+ from ._provider_anthropic import ChatAnthropic, ChatBedrockAnthropic
11
+ from ._provider_cloudflare import ChatCloudflare
12
+ from ._provider_databricks import ChatDatabricks
13
+ from ._provider_deepseek import ChatDeepSeek
14
+ from ._provider_github import ChatGithub
15
+ from ._provider_google import ChatGoogle, ChatVertex
16
+ from ._provider_groq import ChatGroq
17
+ from ._provider_huggingface import ChatHuggingFace
18
+ from ._provider_mistral import ChatMistral
19
+ from ._provider_ollama import ChatOllama
20
+ from ._provider_openai import ChatAzureOpenAI, ChatOpenAI
21
+ from ._provider_openrouter import ChatOpenRouter
22
+ from ._provider_perplexity import ChatPerplexity
23
+ from ._provider_portkey import ChatPortkey
24
+ from ._provider_snowflake import ChatSnowflake
25
+ from ._utils import MISSING_TYPE as DEPRECATED_TYPE
26
+
27
+ AutoProviders = Literal[
28
+ "anthropic",
29
+ "bedrock-anthropic",
30
+ "cloudflare",
31
+ "databricks",
32
+ "deep-seek",
33
+ "github",
34
+ "google",
35
+ "groq",
36
+ "hugging-face",
37
+ "mistral",
38
+ "ollama",
39
+ "openai",
40
+ "azure-openai",
41
+ "open-router",
42
+ "perplexity",
43
+ "portkey",
44
+ "snowflake",
45
+ "vertex",
46
+ ]
47
+
48
+ _provider_chat_model_map: dict[AutoProviders, Callable[..., Chat]] = {
49
+ "anthropic": ChatAnthropic,
50
+ "bedrock-anthropic": ChatBedrockAnthropic,
51
+ "cloudflare": ChatCloudflare,
52
+ "databricks": ChatDatabricks,
53
+ "deep-seek": ChatDeepSeek,
54
+ "github": ChatGithub,
55
+ "google": ChatGoogle,
56
+ "groq": ChatGroq,
57
+ "hugging-face": ChatHuggingFace,
58
+ "mistral": ChatMistral,
59
+ "ollama": ChatOllama,
60
+ "openai": ChatOpenAI,
61
+ "azure-openai": ChatAzureOpenAI,
62
+ "open-router": ChatOpenRouter,
63
+ "perplexity": ChatPerplexity,
64
+ "portkey": ChatPortkey,
65
+ "snowflake": ChatSnowflake,
66
+ "vertex": ChatVertex,
67
+ }
68
+
69
+ DEPRECATED = DEPRECATED_TYPE()
70
+
71
+
72
+ def ChatAuto(
73
+ provider_model: Optional[str] = None,
74
+ *,
75
+ system_prompt: Optional[str] = None,
76
+ provider: AutoProviders | DEPRECATED_TYPE = DEPRECATED,
77
+ model: str | DEPRECATED_TYPE = DEPRECATED,
78
+ **kwargs,
79
+ ) -> Chat:
80
+ """
81
+ Chat with any provider.
82
+
83
+ This is a generic interface to all the other `Chat*()` functions, allowing
84
+ you to pick the provider (and model) with a simple string.
85
+
86
+ Prerequisites
87
+ -------------
88
+
89
+ ::: {.callout-note}
90
+ ## API key
91
+
92
+ Follow the instructions for the specific provider to obtain an API key.
93
+ :::
94
+
95
+ ::: {.callout-note}
96
+ ## Python requirements
97
+
98
+ Follow the instructions for the specific provider to install the required
99
+ Python packages.
100
+ :::
101
+
102
+ Examples
103
+ --------
104
+
105
+ `ChatAuto()` makes it easy to switch between different chat providers and models.
106
+
107
+ ```python
108
+ import pandas as pd
109
+ from chatlas import ChatAuto
110
+
111
+ # Default provider (OpenAI) & model
112
+ chat = ChatAuto()
113
+ print(chat.provider.name)
114
+ print(chat.provider.model)
115
+
116
+ # Different provider (Anthropic) & default model
117
+ chat = ChatAuto("anthropic")
118
+
119
+ # List models available through the provider
120
+ models = chat.list_models()
121
+ print(pd.DataFrame(models))
122
+
123
+ # Choose specific provider/model (Claude Sonnet 4)
124
+ chat = ChatAuto("anthropic/claude-sonnet-4-0")
125
+ ```
126
+
127
+ The default provider/model can also be controlled through an environment variable:
128
+
129
+ ```bash
130
+ export CHATLAS_CHAT_PROVIDER_MODEL="anthropic/claude-sonnet-4-0"
131
+ ```
132
+
133
+ ```python
134
+ from chatlas import ChatAuto
135
+
136
+ chat = ChatAuto()
137
+ print(chat.provider.name) # anthropic
138
+ print(chat.provider.model) # claude-sonnet-4-0
139
+ ```
140
+
141
+ For application-specific configurations, consider defining your own environment variables:
142
+
143
+ ```bash
144
+ export MYAPP_PROVIDER_MODEL="google/gemini-2.5-flash"
145
+ ```
146
+
147
+ And passing them to `ChatAuto()` as an alternative way to configure the provider/model:
148
+
149
+ ```python
150
+ import os
151
+ from chatlas import ChatAuto
152
+
153
+ chat = ChatAuto(os.getenv("MYAPP_PROVIDER_MODEL"))
154
+ print(chat.provider.name) # google
155
+ print(chat.provider.model) # gemini-2.5-flash
156
+ ```
157
+
158
+ Parameters
159
+ ----------
160
+ provider_model
161
+ The name of the provider and model to use in the format
162
+ `"{provider}/{model}"`. Providers are strings formatted in kebab-case,
163
+ e.g. to use `ChatBedrockAnthropic` set `provider="bedrock-anthropic"`,
164
+ and models are the provider-specific model names, e.g.
165
+ `"claude-3-7-sonnet-20250219"`. The `/{model}` portion may also be
166
+ omitted, in which case, the default model for that provider will be
167
+ used.
168
+
169
+ If no value is provided, the `CHATLAS_CHAT_PROVIDER_MODEL` environment
170
+ variable will be consulted for a fallback value. If this variable is also
171
+ not set, a default value of `"openai"` is used.
172
+ system_prompt
173
+ A system prompt to set the behavior of the assistant.
174
+ provider
175
+ Deprecated; use `provider_model` instead.
176
+ model
177
+ Deprecated; use `provider_model` instead.
178
+ **kwargs
179
+ Additional keyword arguments to pass to the `Chat` constructor. See the
180
+ documentation for each provider for more details on the available
181
+ options.
182
+
183
+ These arguments can also be provided via the `CHATLAS_CHAT_ARGS`
184
+ environment variable as a JSON string. When any additional arguments are
185
+ provided to `ChatAuto()`, the env var is ignored.
186
+
187
+ Note that `system_prompt` and `turns` can't be set via environment variables.
188
+ They must be provided/set directly to/on `ChatAuto()`.
189
+
190
+ Note
191
+ ----
192
+ If you want to work with a specific provider, but don't know what models are
193
+ available (or the exact model name), use
194
+ `ChatAuto('provider_name').list_models()` to list available models. Another
195
+ option is to use the provider more directly (e.g., `ChatAnthropic()`). There,
196
+ the `model` parameter may have type hints for available models.
197
+
198
+ Returns
199
+ -------
200
+ Chat
201
+ A chat instance using the specified provider.
202
+
203
+ Raises
204
+ ------
205
+ ValueError
206
+ If no valid provider is specified either through parameters or
207
+ environment variables.
208
+ """
209
+ if provider is not DEPRECATED:
210
+ warn_deprecated_param("provider")
211
+
212
+ if model is not DEPRECATED:
213
+ if provider is DEPRECATED:
214
+ raise ValueError(
215
+ "The `model` parameter is deprecated and cannot be used without the `provider` parameter. "
216
+ "Use `provider_model` instead."
217
+ )
218
+ warn_deprecated_param("model")
219
+
220
+ if provider_model is None:
221
+ provider_model = os.environ.get("CHATLAS_CHAT_PROVIDER_MODEL")
222
+
223
+ # Backwards compatibility: construct from old env vars as a fallback
224
+ if provider_model is None:
225
+ env_provider = get_legacy_env_var("CHATLAS_CHAT_PROVIDER", provider)
226
+ env_model = get_legacy_env_var("CHATLAS_CHAT_MODEL", model)
227
+
228
+ if env_provider:
229
+ provider_model = env_provider
230
+ if env_model:
231
+ provider_model += f"/{env_model}"
232
+
233
+ # Fall back to OpenAI if nothing is specified
234
+ if provider_model is None:
235
+ provider_model = "openai"
236
+
237
+ if "/" in provider_model:
238
+ the_provider, the_model = provider_model.split("/", 1)
239
+ else:
240
+ the_provider, the_model = provider_model, None
241
+
242
+ if the_provider not in _provider_chat_model_map:
243
+ raise ValueError(
244
+ f"Provider name '{the_provider}' is not a known chatlas provider: "
245
+ f"{', '.join(_provider_chat_model_map.keys())}"
246
+ )
247
+
248
+ # `system_prompt`, `turns` and `model` always come from `ChatAuto()`
249
+ base_args = {
250
+ "system_prompt": system_prompt,
251
+ "turns": None,
252
+ "model": the_model,
253
+ }
254
+
255
+ # Environment kwargs, used only if no kwargs provided
256
+ env_kwargs = {}
257
+ if not kwargs:
258
+ env_kwargs = orjson.loads(os.environ.get("CHATLAS_CHAT_ARGS", "{}"))
259
+
260
+ final_kwargs = {**env_kwargs, **kwargs, **base_args}
261
+ final_kwargs = {k: v for k, v in final_kwargs.items() if v is not None}
262
+
263
+ return _provider_chat_model_map[the_provider](**final_kwargs)
264
+
265
+
266
+ def get_legacy_env_var(
267
+ env_var_name: str,
268
+ default: str | DEPRECATED_TYPE,
269
+ ) -> str | None:
270
+ env_value = os.environ.get(env_var_name)
271
+ if env_value:
272
+ warnings.warn(
273
+ f"The '{env_var_name}' environment variable is deprecated. "
274
+ "Use 'CHATLAS_CHAT_PROVIDER_MODEL' instead.",
275
+ DeprecationWarning,
276
+ stacklevel=3,
277
+ )
278
+ return env_value
279
+ elif isinstance(default, DEPRECATED_TYPE):
280
+ return None
281
+ else:
282
+ return default
283
+
284
+
285
+ def warn_deprecated_param(param_name: str, stacklevel: int = 3) -> None:
286
+ warnings.warn(
287
+ f"The '{param_name}' parameter is deprecated. Use 'provider_model' instead.",
288
+ DeprecationWarning,
289
+ stacklevel=stacklevel,
290
+ )
@@ -34,6 +34,7 @@ from ._content import (
34
34
  ContentText,
35
35
  ContentToolRequest,
36
36
  ContentToolResult,
37
+ ToolInfo,
37
38
  )
38
39
  from ._display import (
39
40
  EchoDisplayOptions,
@@ -52,7 +53,7 @@ from ._typing_extensions import TypedDict, TypeGuard
52
53
  from ._utils import MISSING, MISSING_TYPE, html_escape, wrap_async
53
54
 
54
55
  if TYPE_CHECKING:
55
- from mcp.types import ToolAnnotations
56
+ from ._content import ToolAnnotations
56
57
 
57
58
 
58
59
  class TokensDict(TypedDict):
@@ -1534,9 +1535,10 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1534
1535
 
1535
1536
  def register_tool(
1536
1537
  self,
1537
- func: Callable[..., Any] | Callable[..., Awaitable[Any]],
1538
+ func: Callable[..., Any] | Callable[..., Awaitable[Any]] | Tool,
1538
1539
  *,
1539
1540
  force: bool = False,
1541
+ name: Optional[str] = None,
1540
1542
  model: Optional[type[BaseModel]] = None,
1541
1543
  annotations: "Optional[ToolAnnotations]" = None,
1542
1544
  ):
@@ -1610,6 +1612,9 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1610
1612
  force
1611
1613
  If `True`, overwrite any existing tool with the same name. If `False`
1612
1614
  (the default), raise an error if a tool with the same name already exists.
1615
+ name
1616
+ The name of the tool. If not provided, the name will be inferred from the
1617
+ `func`'s name (or the `model`'s name, if provided).
1613
1618
  model
1614
1619
  A Pydantic model that describes the input parameters for the function.
1615
1620
  If not provided, the model will be inferred from the function's type hints.
@@ -1618,14 +1623,22 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1618
1623
  name and docstring of the function.
1619
1624
  annotations
1620
1625
  Additional properties that describe the tool and its behavior.
1621
- Should be a `from mcp.types import ToolAnnotations` instance.
1622
1626
 
1623
1627
  Raises
1624
1628
  ------
1625
1629
  ValueError
1626
1630
  If a tool with the same name already exists and `force` is `False`.
1627
1631
  """
1628
- tool = Tool.from_func(func, model=model, annotations=annotations)
1632
+ if isinstance(func, Tool):
1633
+ name = name or func.name
1634
+ annotations = annotations or func.annotations
1635
+ if model is not None:
1636
+ func = Tool.from_func(
1637
+ func.func, name=name, model=model, annotations=annotations
1638
+ )
1639
+ func = func.func
1640
+
1641
+ tool = Tool.from_func(func, name=name, model=model, annotations=annotations)
1629
1642
  if tool.name in self._tools and not force:
1630
1643
  raise ValueError(
1631
1644
  f"Tool with name '{tool.name}' is already registered. "
@@ -1933,7 +1946,9 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1933
1946
  all_results: list[ContentToolResult] = []
1934
1947
  for x in turn.contents:
1935
1948
  if isinstance(x, ContentToolRequest):
1936
- x.tool = self._tools.get(x.name)
1949
+ tool = self._tools.get(x.name)
1950
+ if tool is not None:
1951
+ x.tool = ToolInfo.from_tool(tool)
1937
1952
  if echo == "output":
1938
1953
  self._echo_content(f"\n\n{x}\n\n")
1939
1954
  if content == "all":
@@ -1994,7 +2009,9 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1994
2009
  all_results: list[ContentToolResult] = []
1995
2010
  for x in turn.contents:
1996
2011
  if isinstance(x, ContentToolRequest):
1997
- x.tool = self._tools.get(x.name)
2012
+ tool = self._tools.get(x.name)
2013
+ if tool is not None:
2014
+ x.tool = ToolInfo.from_tool(tool)
1998
2015
  if echo == "output":
1999
2016
  self._echo_content(f"\n\n{x}\n\n")
2000
2017
  if content == "all":
@@ -2152,7 +2169,7 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
2152
2169
  self._turns.extend([user_turn, turn])
2153
2170
 
2154
2171
  def _invoke_tool(self, request: ContentToolRequest):
2155
- tool = request.tool
2172
+ tool = self._tools.get(request.name)
2156
2173
  func = tool.func if tool is not None else None
2157
2174
 
2158
2175
  if func is None:
@@ -2200,7 +2217,7 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
2200
2217
  yield self._handle_tool_error_result(request, e)
2201
2218
 
2202
2219
  async def _invoke_tool_async(self, request: ContentToolRequest):
2203
- tool = request.tool
2220
+ tool = self._tools.get(request.name)
2204
2221
 
2205
2222
  if tool is None:
2206
2223
  yield self._handle_tool_error_result(
@@ -6,9 +6,64 @@ from typing import TYPE_CHECKING, Any, Literal, Optional, Union
6
6
  import orjson
7
7
  from pydantic import BaseModel, ConfigDict
8
8
 
9
+ from ._typing_extensions import TypedDict
10
+
9
11
  if TYPE_CHECKING:
10
12
  from ._tools import Tool
11
13
 
14
+
15
+ class ToolAnnotations(TypedDict, total=False):
16
+ """
17
+ Additional properties describing a Tool to clients.
18
+
19
+ NOTE: all properties in ToolAnnotations are **hints**.
20
+ They are not guaranteed to provide a faithful description of
21
+ tool behavior (including descriptive properties like `title`).
22
+
23
+ Clients should never make tool use decisions based on ToolAnnotations
24
+ received from untrusted servers.
25
+ """
26
+
27
+ title: str
28
+ """A human-readable title for the tool."""
29
+
30
+ readOnlyHint: bool
31
+ """
32
+ If true, the tool does not modify its environment.
33
+ Default: false
34
+ """
35
+
36
+ destructiveHint: bool
37
+ """
38
+ If true, the tool may perform destructive updates to its environment.
39
+ If false, the tool performs only additive updates.
40
+ (This property is meaningful only when `readOnlyHint == false`)
41
+ Default: true
42
+ """
43
+
44
+ idempotentHint: bool
45
+ """
46
+ If true, calling the tool repeatedly with the same arguments
47
+ will have no additional effect on the its environment.
48
+ (This property is meaningful only when `readOnlyHint == false`)
49
+ Default: false
50
+ """
51
+
52
+ openWorldHint: bool
53
+ """
54
+ If true, this tool may interact with an "open world" of external
55
+ entities. If false, the tool's domain of interaction is closed.
56
+ For example, the world of a web search tool is open, whereas that
57
+ of a memory tool is not.
58
+ Default: true
59
+ """
60
+
61
+ extra: dict[str, Any]
62
+ """
63
+ Additional metadata about the tool.
64
+ """
65
+
66
+
12
67
  ImageContentTypes = Literal[
13
68
  "image/png",
14
69
  "image/jpeg",
@@ -19,6 +74,45 @@ ImageContentTypes = Literal[
19
74
  Allowable content types for images.
20
75
  """
21
76
 
77
+
78
+ class ToolInfo(BaseModel):
79
+ """
80
+ Serializable tool information
81
+
82
+ This contains only the serializable parts of a Tool that are needed
83
+ for ContentToolRequest to be JSON-serializable. This allows tool
84
+ metadata to be preserved without including the non-serializable
85
+ function reference.
86
+
87
+ Parameters
88
+ ----------
89
+ name
90
+ The name of the tool.
91
+ description
92
+ A description of what the tool does.
93
+ parameters
94
+ A dictionary describing the input parameters and their types.
95
+ annotations
96
+ Additional properties that describe the tool and its behavior.
97
+ """
98
+
99
+ name: str
100
+ description: str
101
+ parameters: dict[str, Any]
102
+ annotations: Optional[ToolAnnotations] = None
103
+
104
+ @classmethod
105
+ def from_tool(cls, tool: "Tool") -> "ToolInfo":
106
+ """Create a ToolInfo from a Tool instance."""
107
+ func_schema = tool.schema["function"]
108
+ return cls(
109
+ name=tool.name,
110
+ description=func_schema.get("description", ""),
111
+ parameters=func_schema.get("parameters", {}),
112
+ annotations=tool.annotations,
113
+ )
114
+
115
+
22
116
  ContentTypeEnum = Literal[
23
117
  "text",
24
118
  "image_remote",
@@ -175,14 +269,15 @@ class ContentToolRequest(Content):
175
269
  arguments
176
270
  The arguments to pass to the tool/function.
177
271
  tool
178
- The tool/function to be called. This is set internally by chatlas's tool
179
- calling loop.
272
+ Serializable information about the tool. This is set internally by
273
+ chatlas's tool calling loop and contains only the metadata needed
274
+ for serialization (name, description, parameters, annotations).
180
275
  """
181
276
 
182
277
  id: str
183
278
  name: str
184
279
  arguments: object
185
- tool: Optional["Tool"] = None
280
+ tool: Optional[ToolInfo] = None
186
281
 
187
282
  content_type: ContentTypeEnum = "tool_request"
188
283