chatlas 0.7.0__tar.gz → 0.8.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 (175) hide show
  1. {chatlas-0.7.0 → chatlas-0.8.0}/.github/workflows/test.yml +18 -0
  2. {chatlas-0.7.0 → chatlas-0.8.0}/CHANGELOG.md +27 -0
  3. chatlas-0.8.0/LICENSE +21 -0
  4. {chatlas-0.7.0 → chatlas-0.8.0}/PKG-INFO +9 -12
  5. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/__init__.py +2 -1
  6. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_anthropic.py +1 -4
  7. chatlas-0.8.0/chatlas/_callbacks.py +56 -0
  8. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_chat.py +131 -61
  9. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_content.py +6 -0
  10. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_databricks.py +4 -10
  11. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_github.py +0 -6
  12. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_google.py +2 -1
  13. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_groq.py +0 -6
  14. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_logging.py +29 -5
  15. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_ollama.py +0 -6
  16. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_openai.py +5 -31
  17. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_perplexity.py +0 -6
  18. chatlas-0.8.0/chatlas/_snowflake.py +670 -0
  19. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_tools.py +59 -1
  20. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_version.py +2 -2
  21. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/anthropic/_submit.py +9 -0
  22. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/openai/_submit.py +1 -0
  23. {chatlas-0.7.0 → chatlas-0.8.0}/docs/_quarto.yml +1 -0
  24. {chatlas-0.7.0 → chatlas-0.8.0}/pyproject.toml +19 -11
  25. {chatlas-0.7.0 → chatlas-0.8.0}/scripts/main.py +0 -1
  26. chatlas-0.8.0/tests/test_callbacks.py +67 -0
  27. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_chat.py +109 -4
  28. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_provider_databricks.py +14 -5
  29. chatlas-0.8.0/tests/test_provider_snowflake.py +101 -0
  30. chatlas-0.7.0/chatlas/_snowflake.py +0 -344
  31. chatlas-0.7.0/chatlas/types/snowflake/__init__.py +0 -8
  32. chatlas-0.7.0/chatlas/types/snowflake/_submit.py +0 -24
  33. chatlas-0.7.0/scripts/_generate_snowflake_types.py +0 -33
  34. {chatlas-0.7.0 → chatlas-0.8.0}/.github/workflows/check-update-types.yml +0 -0
  35. {chatlas-0.7.0 → chatlas-0.8.0}/.github/workflows/docs-publish.yml +0 -0
  36. {chatlas-0.7.0 → chatlas-0.8.0}/.github/workflows/release.yml +0 -0
  37. {chatlas-0.7.0 → chatlas-0.8.0}/.gitignore +0 -0
  38. {chatlas-0.7.0 → chatlas-0.8.0}/.vscode/extensions.json +0 -0
  39. {chatlas-0.7.0 → chatlas-0.8.0}/.vscode/settings.json +0 -0
  40. {chatlas-0.7.0 → chatlas-0.8.0}/Makefile +0 -0
  41. {chatlas-0.7.0 → chatlas-0.8.0}/README.md +0 -0
  42. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_auto.py +0 -0
  43. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_content_image.py +0 -0
  44. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_content_pdf.py +0 -0
  45. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_display.py +0 -0
  46. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_interpolate.py +0 -0
  47. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_live_render.py +0 -0
  48. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_merge.py +0 -0
  49. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_provider.py +0 -0
  50. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_tokens.py +0 -0
  51. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_tokens_old.py +0 -0
  52. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_turn.py +0 -0
  53. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_typing_extensions.py +0 -0
  54. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/_utils.py +0 -0
  55. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/py.typed +0 -0
  56. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/__init__.py +0 -0
  57. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/anthropic/__init__.py +0 -0
  58. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/anthropic/_client.py +0 -0
  59. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/anthropic/_client_bedrock.py +0 -0
  60. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/google/__init__.py +0 -0
  61. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/google/_client.py +0 -0
  62. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/google/_submit.py +0 -0
  63. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/openai/__init__.py +0 -0
  64. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/openai/_client.py +0 -0
  65. {chatlas-0.7.0 → chatlas-0.8.0}/chatlas/types/openai/_client_azure.py +0 -0
  66. {chatlas-0.7.0 → chatlas-0.8.0}/docs/.gitignore +0 -0
  67. {chatlas-0.7.0 → chatlas-0.8.0}/docs/_extensions/machow/interlinks/.gitignore +0 -0
  68. {chatlas-0.7.0 → chatlas-0.8.0}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
  69. {chatlas-0.7.0 → chatlas-0.8.0}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
  70. {chatlas-0.7.0 → chatlas-0.8.0}/docs/_sidebar.yml +0 -0
  71. {chatlas-0.7.0 → chatlas-0.8.0}/docs/congressional-assets.png +0 -0
  72. {chatlas-0.7.0 → chatlas-0.8.0}/docs/examples/third-party-testing.txt +0 -0
  73. {chatlas-0.7.0 → chatlas-0.8.0}/docs/get-started.qmd +0 -0
  74. {chatlas-0.7.0 → chatlas-0.8.0}/docs/images/congressional-assets.png +0 -0
  75. {chatlas-0.7.0 → chatlas-0.8.0}/docs/images/logo.png +0 -0
  76. {chatlas-0.7.0 → chatlas-0.8.0}/docs/images/posit-logo.png +0 -0
  77. {chatlas-0.7.0 → chatlas-0.8.0}/docs/images/tool-calling-right.svg +0 -0
  78. {chatlas-0.7.0 → chatlas-0.8.0}/docs/images/tool-calling-wrong.svg +0 -0
  79. {chatlas-0.7.0 → chatlas-0.8.0}/docs/index.py +0 -0
  80. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/favicon/android-chrome-192x192.png +0 -0
  81. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/favicon/android-chrome-512x512.png +0 -0
  82. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/favicon/apple-touch-icon.png +0 -0
  83. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/favicon/favicon-16x16.png +0 -0
  84. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/favicon/favicon-32x32.png +0 -0
  85. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/favicon/favicon.ico +0 -0
  86. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/icon/brand-yml-icon-black.png +0 -0
  87. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/icon/brand-yml-icon-black.svg +0 -0
  88. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/icon/brand-yml-icon-color.png +0 -0
  89. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/icon/brand-yml-icon-color.svg +0 -0
  90. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/icon/brand-yml-icon-white.png +0 -0
  91. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/icon/brand-yml-icon-white.svg +0 -0
  92. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/tall/brand-yml-tall-black.png +0 -0
  93. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/tall/brand-yml-tall-black.svg +0 -0
  94. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/tall/brand-yml-tall-color.png +0 -0
  95. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/tall/brand-yml-tall-color.svg +0 -0
  96. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/tall/brand-yml-tall-white.png +0 -0
  97. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/tall/brand-yml-tall-white.svg +0 -0
  98. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-black.png +0 -0
  99. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-black.svg +0 -0
  100. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-color.png +0 -0
  101. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-color.svg +0 -0
  102. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-large-black.png +0 -0
  103. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-large-color.png +0 -0
  104. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-large-white.png +0 -0
  105. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-white.png +0 -0
  106. {chatlas-0.7.0 → chatlas-0.8.0}/docs/logos/wide/brand-yml-wide-white.svg +0 -0
  107. {chatlas-0.7.0 → chatlas-0.8.0}/docs/prompt-design.qmd +0 -0
  108. {chatlas-0.7.0 → chatlas-0.8.0}/docs/rag.qmd +0 -0
  109. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/Chat.qmd +0 -0
  110. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatAnthropic.qmd +0 -0
  111. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatAzureOpenAI.qmd +0 -0
  112. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatBedrockAnthropic.qmd +0 -0
  113. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatGithub.qmd +0 -0
  114. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatGoogle.qmd +0 -0
  115. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatGroq.qmd +0 -0
  116. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatOllama.qmd +0 -0
  117. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatOpenAI.qmd +0 -0
  118. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatPerplexity.qmd +0 -0
  119. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/ChatVertex.qmd +0 -0
  120. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/Provider.qmd +0 -0
  121. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/Tool.qmd +0 -0
  122. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/Turn.qmd +0 -0
  123. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/content_image_file.qmd +0 -0
  124. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/content_image_plot.qmd +0 -0
  125. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/content_image_url.qmd +0 -0
  126. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/image_file.qmd +0 -0
  127. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/image_plot.qmd +0 -0
  128. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/image_url.qmd +0 -0
  129. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/index.qmd +0 -0
  130. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/interpolate.qmd +0 -0
  131. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/interpolate_file.qmd +0 -0
  132. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/token_usage.qmd +0 -0
  133. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ChatResponse.qmd +0 -0
  134. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ChatResponseAsync.qmd +0 -0
  135. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.Content.qmd +0 -0
  136. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentImage.qmd +0 -0
  137. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentImageInline.qmd +0 -0
  138. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentImageRemote.qmd +0 -0
  139. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentJson.qmd +0 -0
  140. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentText.qmd +0 -0
  141. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentToolRequest.qmd +0 -0
  142. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ContentToolResult.qmd +0 -0
  143. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.ImageContentTypes.qmd +0 -0
  144. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.MISSING.qmd +0 -0
  145. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.MISSING_TYPE.qmd +0 -0
  146. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.SubmitInputArgsT.qmd +0 -0
  147. {chatlas-0.7.0 → chatlas-0.8.0}/docs/reference/types.TokenUsage.qmd +0 -0
  148. {chatlas-0.7.0 → chatlas-0.8.0}/docs/structured-data.qmd +0 -0
  149. {chatlas-0.7.0 → chatlas-0.8.0}/docs/styles.scss +0 -0
  150. {chatlas-0.7.0 → chatlas-0.8.0}/docs/tool-calling.qmd +0 -0
  151. {chatlas-0.7.0 → chatlas-0.8.0}/docs/web-apps.qmd +0 -0
  152. {chatlas-0.7.0 → chatlas-0.8.0}/pytest.ini +0 -0
  153. {chatlas-0.7.0 → chatlas-0.8.0}/scripts/_generate_anthropic_types.py +0 -0
  154. {chatlas-0.7.0 → chatlas-0.8.0}/scripts/_generate_google_types.py +0 -0
  155. {chatlas-0.7.0 → chatlas-0.8.0}/scripts/_generate_openai_types.py +0 -0
  156. {chatlas-0.7.0 → chatlas-0.8.0}/scripts/_utils.py +0 -0
  157. {chatlas-0.7.0 → chatlas-0.8.0}/tests/__init__.py +0 -0
  158. {chatlas-0.7.0 → chatlas-0.8.0}/tests/__snapshots__/test_chat.ambr +0 -0
  159. {chatlas-0.7.0 → chatlas-0.8.0}/tests/apples.pdf +0 -0
  160. {chatlas-0.7.0 → chatlas-0.8.0}/tests/conftest.py +0 -0
  161. {chatlas-0.7.0 → chatlas-0.8.0}/tests/images/dice.png +0 -0
  162. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_auto.py +0 -0
  163. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_content.py +0 -0
  164. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_content_image.py +0 -0
  165. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_content_pdf.py +0 -0
  166. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_content_tools.py +0 -0
  167. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_interpolate.py +0 -0
  168. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_provider_anthropic.py +0 -0
  169. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_provider_azure.py +0 -0
  170. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_provider_bedrock.py +0 -0
  171. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_provider_google.py +0 -0
  172. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_provider_openai.py +0 -0
  173. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_tokens.py +0 -0
  174. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_turns.py +0 -0
  175. {chatlas-0.7.0 → chatlas-0.8.0}/tests/test_utils_merge.py +0 -0
@@ -48,6 +48,24 @@ jobs:
48
48
  - name: 📦 Install the project
49
49
  run: uv sync --python ${{ matrix.config.python-version }} --all-extras
50
50
 
51
+ - name: Create Snowflake connections.toml file
52
+ run: |
53
+ mkdir -p ~/.ssh
54
+ echo '${{ secrets.SNOWFLAKE_SSH_PUBLIC_KEY }}' > ~/.ssh/snowflake_key.pub
55
+ echo '${{ secrets.SNOWFLAKE_SSH_PRIVATE_KEY }}' > ~/.ssh/snowflake_key.p8
56
+ chmod 644 ~/.ssh/snowflake_key.pub
57
+ chmod 600 ~/.ssh/snowflake_key.p8
58
+ chmod 700 ~/.ssh
59
+
60
+ mkdir -p ~/.snowflake
61
+ cat > ~/.snowflake/connections.toml << 'EOF'
62
+ [posit]
63
+ account="duloftf-posit-software-pbc-dev"
64
+ user="carson@posit.co"
65
+ private_key_file="/home/runner/.ssh/snowflake_key.p8"
66
+ private_key_file_pwd="${{ secrets.SNOWFLAKE_SSH_PRIVATE_KEY_PASSWORD }}"
67
+ EOF
68
+
51
69
  - name: 🧪 Check tests
52
70
  run: make check-tests
53
71
 
@@ -7,6 +7,33 @@ 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.8.0] - 2025-05-30
11
+
12
+ ### New features
13
+
14
+ * New `.on_tool_request()` and `.on_tool_result()` methods register callbacks that fire when a tool is requested or produces a result. These callbacks can be used to implement custom logging or other actions when tools are called, without modifying the tool function (#101).
15
+ * New `ToolRejectError` exception can be thrown from tool request/result callbacks or from within a tool function itself to prevent the tool from executing. Moreover, this exception will provide some context for the the LLM to know that the tool didn't produce a result because it was rejected. (#101)
16
+
17
+ ### Improvements
18
+
19
+ * The `CHATLAS_LOG` environment variable now enables logs for the relevant model provider. It now also supports a level of `debug` in addition to `info`. (#97)
20
+ * `ChatSnowflake()` now supports tool calling. (#98)
21
+ * `Chat` instances can now be deep copied, which is useful for forking the chat session. (#96)
22
+
23
+ ### Changes
24
+
25
+ * `ChatDatabricks()`'s `model` now defaults to `databricks-claude-3-7-sonnet` instead of `databricks-dbrx-instruct`. (#95)
26
+ * `ChatSnowflake()`'s `model` now defaults to `claude-3-7-sonnet` instead of `llama3.1-70b`. (#98)
27
+
28
+ ### Bug fixes
29
+
30
+ * Fixed an issue where `ChatDatabricks()` with an Anthropic `model` wasn't handling empty-string responses gracefully. (#95)
31
+
32
+
33
+ ## [0.7.1] - 2025-05-10
34
+
35
+ * Added `openai` as a hard dependency, making installation easier for a wide range of use cases. (#91)
36
+
10
37
  ## [0.7.0] - 2025-04-22
11
38
 
12
39
  ### New features
chatlas-0.8.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2025 Posit Software, PBC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatlas
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -8,6 +8,8 @@ Project-URL: Repository, https://github.com/posit-dev/chatlas
8
8
  Project-URL: Issues, https://github.com/posit-dev/chatlas/issues/
9
9
  Project-URL: Changelog, https://github.com/posit-dev/chatlas/blob/main/CHANGELOG.md
10
10
  Author-email: Carson Sievert <carson@posit.co>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
11
13
  Classifier: Development Status :: 4 - Beta
12
14
  Classifier: Intended Audience :: Developers
13
15
  Classifier: License :: OSI Approved :: MIT License
@@ -18,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
18
20
  Classifier: Programming Language :: Python :: 3.13
19
21
  Requires-Python: >=3.9
20
22
  Requires-Dist: jinja2
23
+ Requires-Dist: openai
21
24
  Requires-Dist: orjson
22
25
  Requires-Dist: pydantic>=2.0
23
26
  Requires-Dist: requests
@@ -25,15 +28,14 @@ Requires-Dist: rich
25
28
  Provides-Extra: anthropic
26
29
  Requires-Dist: anthropic; extra == 'anthropic'
27
30
  Provides-Extra: azure-openai
28
- Requires-Dist: openai; extra == 'azure-openai'
29
31
  Provides-Extra: bedrock-anthropic
30
32
  Requires-Dist: anthropic[bedrock]; extra == 'bedrock-anthropic'
31
33
  Provides-Extra: databricks
32
- Requires-Dist: databricks-sdk[openai]; extra == 'databricks'
34
+ Requires-Dist: databricks-sdk; extra == 'databricks'
33
35
  Provides-Extra: dev
34
36
  Requires-Dist: anthropic[bedrock]; extra == 'dev'
35
37
  Requires-Dist: databricks-sdk; extra == 'dev'
36
- Requires-Dist: google-genai>=1.2.0; extra == 'dev'
38
+ Requires-Dist: google-genai>=1.14.0; extra == 'dev'
37
39
  Requires-Dist: htmltools; extra == 'dev'
38
40
  Requires-Dist: matplotlib; extra == 'dev'
39
41
  Requires-Dist: numpy>1.24.4; extra == 'dev'
@@ -42,7 +44,7 @@ Requires-Dist: pillow; extra == 'dev'
42
44
  Requires-Dist: python-dotenv; extra == 'dev'
43
45
  Requires-Dist: ruff>=0.6.5; extra == 'dev'
44
46
  Requires-Dist: shiny; extra == 'dev'
45
- Requires-Dist: snowflake-ml-python; extra == 'dev'
47
+ Requires-Dist: snowflake-ml-python>=1.8.4; extra == 'dev'
46
48
  Requires-Dist: tenacity; extra == 'dev'
47
49
  Requires-Dist: tiktoken; extra == 'dev'
48
50
  Requires-Dist: torch; (python_version <= '3.11') and extra == 'dev'
@@ -58,17 +60,12 @@ Requires-Dist: pyyaml; extra == 'docs'
58
60
  Requires-Dist: quartodoc>=0.7; extra == 'docs'
59
61
  Requires-Dist: sentence-transformers; extra == 'docs'
60
62
  Provides-Extra: github
61
- Requires-Dist: openai; extra == 'github'
62
63
  Provides-Extra: google
63
- Requires-Dist: google-genai; extra == 'google'
64
+ Requires-Dist: google-genai>=1.14.0; extra == 'google'
64
65
  Provides-Extra: groq
65
- Requires-Dist: openai; extra == 'groq'
66
66
  Provides-Extra: ollama
67
- Requires-Dist: openai; extra == 'ollama'
68
67
  Provides-Extra: openai
69
- Requires-Dist: openai; extra == 'openai'
70
68
  Provides-Extra: perplexity
71
- Requires-Dist: openai; extra == 'perplexity'
72
69
  Provides-Extra: snowflake
73
70
  Requires-Dist: snowflake-ml-python; extra == 'snowflake'
74
71
  Provides-Extra: test
@@ -77,7 +74,7 @@ Requires-Dist: pytest-asyncio; extra == 'test'
77
74
  Requires-Dist: pytest>=8.3.2; extra == 'test'
78
75
  Requires-Dist: syrupy>=4; extra == 'test'
79
76
  Provides-Extra: vertex
80
- Requires-Dist: google-genai; extra == 'vertex'
77
+ Requires-Dist: google-genai>=1.14.0; extra == 'vertex'
81
78
  Description-Content-Type: text/markdown
82
79
 
83
80
  <h1 class="unnumbered unlisted"> chatlas <a href="https://posit-dev.github.io/chatlas"><img src="docs/images/logo.png" align="right" height="138" alt="chatlas website" /></a> </h1>
@@ -16,7 +16,7 @@ from ._perplexity import ChatPerplexity
16
16
  from ._provider import Provider
17
17
  from ._snowflake import ChatSnowflake
18
18
  from ._tokens import token_usage
19
- from ._tools import Tool
19
+ from ._tools import Tool, ToolRejectError
20
20
  from ._turn import Turn
21
21
 
22
22
  try:
@@ -51,6 +51,7 @@ __all__ = (
51
51
  "Provider",
52
52
  "token_usage",
53
53
  "Tool",
54
+ "ToolRejectError",
54
55
  "Turn",
55
56
  "types",
56
57
  )
@@ -451,10 +451,7 @@ class AnthropicProvider(Provider[Message, RawMessageStreamEvent, Message]):
451
451
  @staticmethod
452
452
  def _as_content_block(content: Content) -> "ContentBlockParam":
453
453
  if isinstance(content, ContentText):
454
- text = content.text
455
- if text == "" or text.isspace():
456
- text = "[empty string]"
457
- return {"type": "text", "text": text}
454
+ return {"text": content.text, "type": "text"}
458
455
  elif isinstance(content, ContentJson):
459
456
  return {"text": "<structured data/>", "type": "text"}
460
457
  elif isinstance(content, ContentPDF):
@@ -0,0 +1,56 @@
1
+ from collections import OrderedDict
2
+ from typing import Any, Callable
3
+
4
+ from ._utils import is_async_callable
5
+
6
+
7
+ class CallbackManager:
8
+ def __init__(self) -> None:
9
+ self._callbacks: dict[str, Callable[..., Any]] = OrderedDict()
10
+ self._id: int = 1
11
+
12
+ def add(self, callback: Callable[..., Any]) -> Callable[[], None]:
13
+ callback_id = self._next_id()
14
+ self._callbacks[callback_id] = callback
15
+
16
+ def _rm_callback() -> None:
17
+ self._callbacks.pop(callback_id, None)
18
+
19
+ return _rm_callback
20
+
21
+ def invoke(self, *args: Any, **kwargs: Any) -> None:
22
+ if not self._callbacks:
23
+ return
24
+
25
+ # Invoke in reverse insertion order
26
+ for callback_id in reversed(list(self._callbacks.keys())):
27
+ callback = self._callbacks[callback_id]
28
+ if is_async_callable(callback):
29
+ raise RuntimeError(
30
+ "Can't use async callbacks with `.chat()`/`.stream()`."
31
+ "Async callbacks can only be used with `.chat_async()`/`.stream_async()`."
32
+ )
33
+ callback(*args, **kwargs)
34
+
35
+ async def invoke_async(self, *args: Any, **kwargs: Any) -> None:
36
+ if not self._callbacks:
37
+ return
38
+
39
+ # Invoke in reverse insertion order
40
+ for callback_id in reversed(list(self._callbacks.keys())):
41
+ callback = self._callbacks[callback_id]
42
+ if is_async_callable(callback):
43
+ await callback(*args, **kwargs)
44
+ else:
45
+ callback(*args, **kwargs)
46
+
47
+ def count(self) -> int:
48
+ return len(self._callbacks)
49
+
50
+ def get_callbacks(self) -> list[Callable[..., Any]]:
51
+ return list(self._callbacks.values())
52
+
53
+ def _next_id(self) -> str:
54
+ current_id = self._id
55
+ self._id += 1
56
+ return str(current_id)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  import inspect
4
5
  import os
5
6
  import sys
@@ -25,6 +26,7 @@ from typing import (
25
26
 
26
27
  from pydantic import BaseModel
27
28
 
29
+ from ._callbacks import CallbackManager
28
30
  from ._content import (
29
31
  Content,
30
32
  ContentJson,
@@ -41,7 +43,7 @@ from ._display import (
41
43
  )
42
44
  from ._logging import log_tool_error
43
45
  from ._provider import Provider
44
- from ._tools import Tool
46
+ from ._tools import Tool, ToolRejectError
45
47
  from ._turn import Turn, user_turn
46
48
  from ._typing_extensions import TypedDict
47
49
  from ._utils import html_escape, wrap_async
@@ -95,6 +97,8 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
95
97
  self.provider = provider
96
98
  self._turns: list[Turn] = list(turns or [])
97
99
  self._tools: dict[str, Tool] = {}
100
+ self._on_tool_request_callbacks = CallbackManager()
101
+ self._on_tool_result_callbacks = CallbackManager()
98
102
  self._current_display: Optional[MarkdownDisplay] = None
99
103
  self._echo_options: EchoDisplayOptions = {
100
104
  "rich_markdown": {},
@@ -631,31 +635,18 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
631
635
  def stream(
632
636
  self,
633
637
  *args: Content | str,
634
- ) -> Generator[str, None, None]: ...
635
-
636
- @overload
637
- def stream(
638
- self,
639
- *args: Content | str,
640
- echo: EchoOptions,
641
- ) -> Generator[str, None, None]: ...
642
-
643
- @overload
644
- def stream(
645
- self,
646
- *args: Content | str,
647
- echo: EchoOptions,
648
638
  content: Literal["text"],
649
- kwargs: Optional[SubmitInputArgsT],
639
+ echo: EchoOptions = "none",
640
+ kwargs: Optional[SubmitInputArgsT] = None,
650
641
  ) -> Generator[str, None, None]: ...
651
642
 
652
643
  @overload
653
644
  def stream(
654
645
  self,
655
646
  *args: Content | str,
656
- echo: EchoOptions,
657
647
  content: Literal["all"],
658
- kwargs: Optional[SubmitInputArgsT],
648
+ echo: EchoOptions = "none",
649
+ kwargs: Optional[SubmitInputArgsT] = None,
659
650
  ) -> Generator[str | ContentToolRequest | ContentToolResult, None, None]: ...
660
651
 
661
652
  def stream(
@@ -712,31 +703,18 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
712
703
  async def stream_async(
713
704
  self,
714
705
  *args: Content | str,
715
- ) -> AsyncGenerator[str, None]: ...
716
-
717
- @overload
718
- async def stream_async(
719
- self,
720
- *args: Content | str,
721
- echo: EchoOptions,
722
- ) -> AsyncGenerator[str, None]: ...
723
-
724
- @overload
725
- async def stream_async(
726
- self,
727
- *args: Content | str,
728
- echo: EchoOptions,
729
706
  content: Literal["text"],
730
- kwargs: Optional[SubmitInputArgsT],
707
+ echo: EchoOptions = "none",
708
+ kwargs: Optional[SubmitInputArgsT] = None,
731
709
  ) -> AsyncGenerator[str, None]: ...
732
710
 
733
711
  @overload
734
712
  async def stream_async(
735
713
  self,
736
714
  *args: Content | str,
737
- echo: EchoOptions,
738
715
  content: Literal["all"],
739
- kwargs: Optional[SubmitInputArgsT],
716
+ echo: EchoOptions = "none",
717
+ kwargs: Optional[SubmitInputArgsT] = None,
740
718
  ) -> AsyncGenerator[str | ContentToolRequest | ContentToolResult, None]: ...
741
719
 
742
720
  async def stream_async(
@@ -987,6 +965,53 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
987
965
  tool = Tool(func, model=model)
988
966
  self._tools[tool.name] = tool
989
967
 
968
+ def on_tool_request(self, callback: Callable[[ContentToolRequest], None]):
969
+ """
970
+ Register a callback for a tool request event.
971
+
972
+ A tool request event occurs when the assistant requests a tool to be
973
+ called on its behalf. Before invoking the tool, `on_tool_request`
974
+ handlers are called with the relevant `ContentToolRequest` object. This
975
+ is useful if you want to handle tool requests in a custom way, such as
976
+ requiring logging them or requiring user approval before invoking the
977
+ tool
978
+
979
+ Parameters
980
+ ----------
981
+ callback
982
+ A function to be called when a tool request event occurs.
983
+ This function must have a single argument, which will be the
984
+ tool request (i.e., a `ContentToolRequest` object).
985
+
986
+ Returns
987
+ -------
988
+ A callable that can be used to remove the callback later.
989
+ """
990
+ return self._on_tool_request_callbacks.add(callback)
991
+
992
+ def on_tool_result(self, callback: Callable[[ContentToolResult], None]):
993
+ """
994
+ Register a callback for a tool result event.
995
+
996
+ A tool result event occurs when a tool has been invoked and the
997
+ result is ready to be provided to the assistant. After the tool
998
+ has been invoked, `on_tool_result` handlers are called with the
999
+ relevant `ContentToolResult` object. This is useful if you want to
1000
+ handle tool results in a custom way such as logging them.
1001
+
1002
+ Parameters
1003
+ ----------
1004
+ callback
1005
+ A function to be called when a tool result event occurs.
1006
+ This function must have a single argument, which will be the
1007
+ tool result (i.e., a `ContentToolResult` object).
1008
+
1009
+ Returns
1010
+ -------
1011
+ A callable that can be used to remove the callback later.
1012
+ """
1013
+ return self._on_tool_result_callbacks.add(callback)
1014
+
990
1015
  @property
991
1016
  def current_display(self) -> Optional[MarkdownDisplay]:
992
1017
  """
@@ -1417,28 +1442,43 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1417
1442
  e = RuntimeError(f"Unknown tool: {x.name}")
1418
1443
  return ContentToolResult(value=None, error=e, request=x)
1419
1444
 
1420
- args = x.arguments
1421
-
1445
+ # First, invoke the request callbacks. If a ToolRejectError is raised,
1446
+ # treat it like a tool failure (i.e., gracefully handle it).
1447
+ result: ContentToolResult | None = None
1422
1448
  try:
1423
- if isinstance(args, dict):
1424
- result = func(**args)
1425
- else:
1426
- result = func(args)
1449
+ self._on_tool_request_callbacks.invoke(x)
1450
+ except ToolRejectError as e:
1451
+ result = ContentToolResult(value=None, error=e, request=x)
1452
+
1453
+ # Invoke the tool (if it hasn't been rejected).
1454
+ if result is None:
1455
+ try:
1456
+ if isinstance(x.arguments, dict):
1457
+ res = func(**x.arguments)
1458
+ else:
1459
+ res = func(x.arguments)
1460
+
1461
+ if isinstance(res, ContentToolResult):
1462
+ result = res
1463
+ else:
1464
+ result = ContentToolResult(value=res)
1427
1465
 
1428
- if not isinstance(result, ContentToolResult):
1429
- result = ContentToolResult(value=result)
1466
+ result.request = x
1467
+ except Exception as e:
1468
+ result = ContentToolResult(value=None, error=e, request=x)
1430
1469
 
1431
- result.request = x
1432
- return result
1433
- except Exception as e:
1470
+ # If we've captured an error, notify and log it.
1471
+ if result.error:
1434
1472
  warnings.warn(
1435
1473
  f"Calling tool '{x.name}' led to an error.",
1436
1474
  ToolFailureWarning,
1437
1475
  stacklevel=2,
1438
1476
  )
1439
1477
  traceback.print_exc()
1440
- log_tool_error(x.name, str(args), e)
1441
- return ContentToolResult(value=None, error=e, request=x)
1478
+ log_tool_error(x.name, str(x.arguments), result.error)
1479
+
1480
+ self._on_tool_result_callbacks.invoke(result)
1481
+ return result
1442
1482
 
1443
1483
  async def _invoke_tool_async(self, x: ContentToolRequest) -> ContentToolResult:
1444
1484
  tool_def = self._tools.get(x.name, None)
@@ -1453,28 +1493,43 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1453
1493
  e = RuntimeError(f"Unknown tool: {x.name}")
1454
1494
  return ContentToolResult(value=None, error=e, request=x)
1455
1495
 
1456
- args = x.arguments
1457
-
1496
+ # First, invoke the request callbacks. If a ToolRejectError is raised,
1497
+ # treat it like a tool failure (i.e., gracefully handle it).
1498
+ result: ContentToolResult | None = None
1458
1499
  try:
1459
- if isinstance(args, dict):
1460
- result = await func(**args)
1461
- else:
1462
- result = await func(args)
1500
+ await self._on_tool_request_callbacks.invoke_async(x)
1501
+ except ToolRejectError as e:
1502
+ result = ContentToolResult(value=None, error=e, request=x)
1503
+
1504
+ # Invoke the tool (if it hasn't been rejected).
1505
+ if result is None:
1506
+ try:
1507
+ if isinstance(x.arguments, dict):
1508
+ res = await func(**x.arguments)
1509
+ else:
1510
+ res = await func(x.arguments)
1511
+
1512
+ if isinstance(res, ContentToolResult):
1513
+ result = res
1514
+ else:
1515
+ result = ContentToolResult(value=res)
1463
1516
 
1464
- if not isinstance(result, ContentToolResult):
1465
- result = ContentToolResult(value=result)
1517
+ result.request = x
1518
+ except Exception as e:
1519
+ result = ContentToolResult(value=None, error=e, request=x)
1466
1520
 
1467
- result.request = x
1468
- return result
1469
- except Exception as e:
1521
+ # If we've captured an error, notify and log it.
1522
+ if result.error:
1470
1523
  warnings.warn(
1471
1524
  f"Calling tool '{x.name}' led to an error.",
1472
1525
  ToolFailureWarning,
1473
1526
  stacklevel=2,
1474
1527
  )
1475
1528
  traceback.print_exc()
1476
- log_tool_error(x.name, str(args), e)
1477
- return ContentToolResult(value=None, error=e, request=x)
1529
+ log_tool_error(x.name, str(x.arguments), result.error)
1530
+
1531
+ await self._on_tool_result_callbacks.invoke_async(result)
1532
+ return result
1478
1533
 
1479
1534
  def _markdown_display(self, echo: EchoOptions) -> ChatMarkdownDisplay:
1480
1535
  """
@@ -1545,6 +1600,21 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
1545
1600
  res += "\n" + turn.__repr__(indent=2)
1546
1601
  return res + "\n"
1547
1602
 
1603
+ def __deepcopy__(self, memo):
1604
+ result = self.__class__.__new__(self.__class__)
1605
+
1606
+ # Avoid recursive references
1607
+ memo[id(self)] = result
1608
+
1609
+ # Copy all attributes except the problematic provider attribute
1610
+ for key, value in self.__dict__.items():
1611
+ if key != "provider":
1612
+ setattr(result, key, copy.deepcopy(value, memo))
1613
+ else:
1614
+ setattr(result, key, value)
1615
+
1616
+ return result
1617
+
1548
1618
 
1549
1619
  class ChatResponse:
1550
1620
  """
@@ -60,6 +60,12 @@ class ContentText(Content):
60
60
  text: str
61
61
  content_type: ContentTypeEnum = "text"
62
62
 
63
+ def __init__(self, **data: Any):
64
+ super().__init__(**data)
65
+
66
+ if self.text == "" or self.text.isspace():
67
+ self.text = "[empty string]"
68
+
63
69
  def __str__(self):
64
70
  return self.text
65
71
 
@@ -85,7 +85,7 @@ def ChatDatabricks(
85
85
  A chat object that retains the state of the conversation.
86
86
  """
87
87
  if model is None:
88
- model = log_model_default("databricks-dbrx-instruct")
88
+ model = log_model_default("databricks-claude-3-7-sonnet")
89
89
 
90
90
  return Chat(
91
91
  provider=DatabricksProvider(
@@ -111,17 +111,11 @@ class DatabricksProvider(OpenAIProvider):
111
111
  except ImportError:
112
112
  raise ImportError(
113
113
  "`ChatDatabricks()` requires the `databricks-sdk` package. "
114
- "Install it with `pip install databricks-sdk[openai]`."
114
+ "Install it with `pip install databricks-sdk`."
115
115
  )
116
116
 
117
- try:
118
- import httpx
119
- from openai import AsyncOpenAI
120
- except ImportError:
121
- raise ImportError(
122
- "`ChatDatabricks()` requires the `openai` package. "
123
- "Install it with `pip install openai`."
124
- )
117
+ import httpx
118
+ from openai import AsyncOpenAI
125
119
 
126
120
  self._model = model
127
121
  self._seed = None
@@ -40,12 +40,6 @@ def ChatGithub(
40
40
  You may need to apply for and be accepted into a beta access program.
41
41
  :::
42
42
 
43
- ::: {.callout-note}
44
- ## Python requirements
45
-
46
- `ChatGithub` requires the `openai` package: `pip install "chatlas[github]"`.
47
- :::
48
-
49
43
 
50
44
  Examples
51
45
  --------
@@ -291,7 +291,8 @@ class GoogleProvider(
291
291
  GoogleTool(
292
292
  function_declarations=[
293
293
  FunctionDeclaration.from_callable(
294
- client=self._client, callable=tool.func
294
+ client=self._client._api_client,
295
+ callable=tool.func,
295
296
  )
296
297
  for tool in tools.values()
297
298
  ]
@@ -38,12 +38,6 @@ def ChatGroq(
38
38
  Sign up at <https://groq.com> to get an API key.
39
39
  :::
40
40
 
41
- ::: {.callout-note}
42
- ## Python requirements
43
-
44
- `ChatGroq` requires the `openai` package: `pip install "chatlas[groq]"`.
45
- :::
46
-
47
41
  Examples
48
42
  --------
49
43
 
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  import warnings
4
+ from typing import Literal
4
5
 
5
6
  from rich.logging import RichHandler
6
7
 
@@ -12,15 +13,38 @@ def _rich_handler() -> RichHandler:
12
13
  return handler
13
14
 
14
15
 
15
- logger = logging.getLogger("chatlas")
16
-
17
- if os.environ.get("CHATLAS_LOG") == "info":
16
+ def setup_logger(x: str, level: Literal["debug", "info"]) -> logging.Logger:
17
+ logger = logging.getLogger(x)
18
+ if level == "debug":
19
+ logger.setLevel(logging.DEBUG)
20
+ elif level == "info":
21
+ logger.setLevel(logging.INFO)
18
22
  # By adding a RichHandler to chatlas' logger, we can guarantee that they
19
23
  # never get dropped, even if the root logger's handlers are not
20
24
  # RichHandlers.
21
- logger.setLevel(logging.INFO)
22
- logger.addHandler(_rich_handler())
25
+ if not any(isinstance(h, RichHandler) for h in logger.handlers):
26
+ logger.addHandler(_rich_handler())
23
27
  logger.propagate = False
28
+ return logger
29
+
30
+
31
+ logger = logging.getLogger("chatlas")
32
+ log_level = os.environ.get("CHATLAS_LOG")
33
+ if log_level:
34
+ if log_level != "debug" and log_level != "info":
35
+ warnings.warn(
36
+ f"CHATLAS_LOG is set to '{log_level}', but the log level must "
37
+ "be one of 'debug' or 'info'. Defaulting to 'info'.",
38
+ )
39
+ log_level = "info"
40
+
41
+ # Manually setup the logger for each dependency we care about. This way, we
42
+ # can ensure that the logs won't get dropped when a rich display is activate
43
+ logger = setup_logger("chatlas", log_level)
44
+ openai_logger = setup_logger("openai", log_level)
45
+ anthropic_logger = setup_logger("anthropic", log_level)
46
+ google_logger = setup_logger("google_genai.models", log_level)
47
+ httpx_logger = setup_logger("httpx", log_level)
24
48
 
25
49
  # Add a RichHandler to the root logger if there are no handlers. Note that
26
50
  # if chatlas is imported before other libraries that set up logging, (like