chatlas 0.11.0__tar.gz → 0.11.1__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.
- {chatlas-0.11.0 → chatlas-0.11.1}/CHANGELOG.md +16 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/PKG-INFO +1 -1
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_chat.py +15 -7
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_content.py +93 -3
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_tools.py +25 -9
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_typing_extensions.py +1 -1
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_version.py +2 -2
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/__init__.py +4 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/_quarto.yml +2 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/conftest.py +2 -6
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_content_tools.py +48 -6
- chatlas-0.11.1/tests/test_register_tool_models.py +339 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_tool_from_mcp.py +4 -4
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_tools_enhanced.py +40 -8
- {chatlas-0.11.0 → chatlas-0.11.1}/.github/workflows/check-update-types.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.github/workflows/docs-publish.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.github/workflows/release.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.github/workflows/test.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.github/workflows/update-pricing.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.gitignore +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.vscode/extensions.json +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/.vscode/settings.json +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/CLAUDE.md +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/LICENSE +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/Makefile +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/README.md +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/__init__.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_auto.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_callbacks.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_content_image.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_content_pdf.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_display.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_interpolate.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_live_render.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_logging.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_mcp_manager.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_merge.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_anthropic.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_cloudflare.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_databricks.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_deepseek.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_github.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_google.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_groq.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_huggingface.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_mistral.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_ollama.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_openai.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_openrouter.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_perplexity.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_portkey.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_provider_snowflake.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_tokens.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_tokens_old.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_turn.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/_utils.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/data/prices.json +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/py.typed +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/anthropic/__init__.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/anthropic/_client.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/anthropic/_client_bedrock.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/anthropic/_submit.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/google/__init__.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/google/_client.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/google/_submit.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/openai/__init__.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/openai/_client.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/openai/_client_azure.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/chatlas/types/openai/_submit.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/.gitignore +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/_extensions/machow/interlinks/.gitignore +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/_sidebar.yml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/congressional-assets.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/async.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/chat.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/chatbots.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/debug.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/models.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/monitor.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/parameters.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/stream.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/structured-data.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/system-prompt.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/get-started/tools.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chat-app.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chat-console.mp4 +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chat-console.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chat-notebook.mp4 +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chat-parameters.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chatbot-gradio.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chatbot-shiny.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chatbot-streamlit.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chatbot-textual.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/chatlas-hello.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/client-parameters.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/congressional-assets.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/hello-chat-console.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/model-parameters.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/model-type-hints.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/posit-logo.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/shiny-mcp-run-python.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/shiny-tool-call-display.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/shiny-tool-call-map.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/tool-calling-right.svg +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/images/tool-calling-wrong.svg +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/index.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/logos/hero/hero-old.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/logos/hero/hero.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/logos/hex/logo.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/logos/small/logo.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/misc/RAG.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/misc/examples.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/misc/mcp-tools.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/misc/vocabulary.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/structured-data/article-summary.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/structured-data/classification.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/structured-data/entity-recognition.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/structured-data/multi-modal.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/structured-data/sentiment-analysis.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/styles.scss +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/tool-calling/approval.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/tool-calling/displays.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/tool-calling/how-it-works.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/docs/why-chatlas.qmd +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/pyproject.toml +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/pytest.ini +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/scripts/_generate_anthropic_types.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/scripts/_generate_google_types.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/scripts/_generate_openai_types.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/scripts/_utils.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/scripts/main.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/__init__.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/__snapshots__/test_chat.ambr +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/apples.pdf +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/images/dice.png +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/mcp_servers/http_add.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/mcp_servers/http_current_date.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/mcp_servers/stdio_current_date.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/mcp_servers/stdio_subtract_multiply.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_auto.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_callbacks.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_chat.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_content.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_content_html.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_content_image.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_content_pdf.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_interpolate.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_mcp_client.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_anthropic.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_azure.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_bedrock.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_cloudflare.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_databricks.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_deepseek.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_github.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_google.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_huggingface.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_mistral.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_openai.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_openrouter.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_portkey.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_provider_snowflake.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_set_model_params.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_tokens.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_turns.py +0 -0
- {chatlas-0.11.0 → chatlas-0.11.1}/tests/test_utils_merge.py +0 -0
|
@@ -7,6 +7,22 @@ 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.11.1] - 2025-08-29
|
|
11
|
+
|
|
12
|
+
### New features
|
|
13
|
+
|
|
14
|
+
* `.register_tool()` gains a `name` parameter (useful for overriding the name of the function). (#162)
|
|
15
|
+
|
|
16
|
+
### Bug fixes
|
|
17
|
+
|
|
18
|
+
* `ContentToolRequest` is (once again) serializable to/from JSON via Pydantic. (#164)
|
|
19
|
+
* `.register_tool(model=model)` no longer unexpectedly errors when `model` contains `pydantic.Field(alias='_my_alias')`. (#161)
|
|
20
|
+
|
|
21
|
+
### Changes
|
|
22
|
+
|
|
23
|
+
* `.register_tool(annotations=annotations)` drops support for `mcp.types.ToolAnnotations()` and instead expects a dictionary of the same info. (#164)
|
|
24
|
+
|
|
25
|
+
|
|
10
26
|
## [0.11.0] - 2025-08-26
|
|
11
27
|
|
|
12
28
|
### New features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chatlas
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.1
|
|
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
|
|
@@ -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
|
|
56
|
+
from ._content import ToolAnnotations
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
class TokensDict(TypedDict):
|
|
@@ -1537,6 +1538,7 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
|
|
|
1537
1538
|
func: Callable[..., Any] | Callable[..., Awaitable[Any]],
|
|
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,13 @@ 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
|
+
tool = Tool.from_func(func, name=name, model=model, annotations=annotations)
|
|
1629
1633
|
if tool.name in self._tools and not force:
|
|
1630
1634
|
raise ValueError(
|
|
1631
1635
|
f"Tool with name '{tool.name}' is already registered. "
|
|
@@ -1933,7 +1937,9 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
|
|
|
1933
1937
|
all_results: list[ContentToolResult] = []
|
|
1934
1938
|
for x in turn.contents:
|
|
1935
1939
|
if isinstance(x, ContentToolRequest):
|
|
1936
|
-
|
|
1940
|
+
tool = self._tools.get(x.name)
|
|
1941
|
+
if tool is not None:
|
|
1942
|
+
x.tool = ToolInfo.from_tool(tool)
|
|
1937
1943
|
if echo == "output":
|
|
1938
1944
|
self._echo_content(f"\n\n{x}\n\n")
|
|
1939
1945
|
if content == "all":
|
|
@@ -1994,7 +2000,9 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
|
|
|
1994
2000
|
all_results: list[ContentToolResult] = []
|
|
1995
2001
|
for x in turn.contents:
|
|
1996
2002
|
if isinstance(x, ContentToolRequest):
|
|
1997
|
-
|
|
2003
|
+
tool = self._tools.get(x.name)
|
|
2004
|
+
if tool is not None:
|
|
2005
|
+
x.tool = ToolInfo.from_tool(tool)
|
|
1998
2006
|
if echo == "output":
|
|
1999
2007
|
self._echo_content(f"\n\n{x}\n\n")
|
|
2000
2008
|
if content == "all":
|
|
@@ -2152,7 +2160,7 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
|
|
|
2152
2160
|
self._turns.extend([user_turn, turn])
|
|
2153
2161
|
|
|
2154
2162
|
def _invoke_tool(self, request: ContentToolRequest):
|
|
2155
|
-
tool = request.
|
|
2163
|
+
tool = self._tools.get(request.name)
|
|
2156
2164
|
func = tool.func if tool is not None else None
|
|
2157
2165
|
|
|
2158
2166
|
if func is None:
|
|
@@ -2200,7 +2208,7 @@ class Chat(Generic[SubmitInputArgsT, CompletionT]):
|
|
|
2200
2208
|
yield self._handle_tool_error_result(request, e)
|
|
2201
2209
|
|
|
2202
2210
|
async def _invoke_tool_async(self, request: ContentToolRequest):
|
|
2203
|
-
tool = request.
|
|
2211
|
+
tool = self._tools.get(request.name)
|
|
2204
2212
|
|
|
2205
2213
|
if tool is None:
|
|
2206
2214
|
yield self._handle_tool_error_result(
|
|
@@ -6,9 +6,59 @@ 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 NotRequired, 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: NotRequired[str]
|
|
28
|
+
"""A human-readable title for the tool."""
|
|
29
|
+
|
|
30
|
+
readOnlyHint: NotRequired[bool]
|
|
31
|
+
"""
|
|
32
|
+
If true, the tool does not modify its environment.
|
|
33
|
+
Default: false
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
destructiveHint: NotRequired[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: NotRequired[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: NotRequired[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
|
+
|
|
12
62
|
ImageContentTypes = Literal[
|
|
13
63
|
"image/png",
|
|
14
64
|
"image/jpeg",
|
|
@@ -19,6 +69,45 @@ ImageContentTypes = Literal[
|
|
|
19
69
|
Allowable content types for images.
|
|
20
70
|
"""
|
|
21
71
|
|
|
72
|
+
|
|
73
|
+
class ToolInfo(BaseModel):
|
|
74
|
+
"""
|
|
75
|
+
Serializable tool information
|
|
76
|
+
|
|
77
|
+
This contains only the serializable parts of a Tool that are needed
|
|
78
|
+
for ContentToolRequest to be JSON-serializable. This allows tool
|
|
79
|
+
metadata to be preserved without including the non-serializable
|
|
80
|
+
function reference.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
name
|
|
85
|
+
The name of the tool.
|
|
86
|
+
description
|
|
87
|
+
A description of what the tool does.
|
|
88
|
+
parameters
|
|
89
|
+
A dictionary describing the input parameters and their types.
|
|
90
|
+
annotations
|
|
91
|
+
Additional properties that describe the tool and its behavior.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
name: str
|
|
95
|
+
description: str
|
|
96
|
+
parameters: dict[str, Any]
|
|
97
|
+
annotations: Optional[ToolAnnotations] = None
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_tool(cls, tool: "Tool") -> "ToolInfo":
|
|
101
|
+
"""Create a ToolInfo from a Tool instance."""
|
|
102
|
+
func_schema = tool.schema["function"]
|
|
103
|
+
return cls(
|
|
104
|
+
name=tool.name,
|
|
105
|
+
description=func_schema.get("description", ""),
|
|
106
|
+
parameters=func_schema.get("parameters", {}),
|
|
107
|
+
annotations=tool.annotations,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
22
111
|
ContentTypeEnum = Literal[
|
|
23
112
|
"text",
|
|
24
113
|
"image_remote",
|
|
@@ -175,14 +264,15 @@ class ContentToolRequest(Content):
|
|
|
175
264
|
arguments
|
|
176
265
|
The arguments to pass to the tool/function.
|
|
177
266
|
tool
|
|
178
|
-
|
|
179
|
-
calling loop
|
|
267
|
+
Serializable information about the tool. This is set internally by
|
|
268
|
+
chatlas's tool calling loop and contains only the metadata needed
|
|
269
|
+
for serialization (name, description, parameters, annotations).
|
|
180
270
|
"""
|
|
181
271
|
|
|
182
272
|
id: str
|
|
183
273
|
name: str
|
|
184
274
|
arguments: object
|
|
185
|
-
tool: Optional[
|
|
275
|
+
tool: Optional[ToolInfo] = None
|
|
186
276
|
|
|
187
277
|
content_type: ContentTypeEnum = "tool_request"
|
|
188
278
|
|
|
@@ -2,7 +2,15 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import warnings
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import (
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Any,
|
|
8
|
+
AsyncGenerator,
|
|
9
|
+
Awaitable,
|
|
10
|
+
Callable,
|
|
11
|
+
Optional,
|
|
12
|
+
cast,
|
|
13
|
+
)
|
|
6
14
|
|
|
7
15
|
import openai
|
|
8
16
|
from pydantic import BaseModel, Field, create_model
|
|
@@ -12,6 +20,7 @@ from ._content import (
|
|
|
12
20
|
ContentToolResult,
|
|
13
21
|
ContentToolResultImage,
|
|
14
22
|
ContentToolResultResource,
|
|
23
|
+
ToolAnnotations,
|
|
15
24
|
)
|
|
16
25
|
|
|
17
26
|
__all__ = (
|
|
@@ -22,7 +31,6 @@ __all__ = (
|
|
|
22
31
|
if TYPE_CHECKING:
|
|
23
32
|
from mcp import ClientSession as MCPClientSession
|
|
24
33
|
from mcp import Tool as MCPTool
|
|
25
|
-
from mcp.types import ToolAnnotations
|
|
26
34
|
from openai.types.chat import ChatCompletionToolParam
|
|
27
35
|
|
|
28
36
|
|
|
@@ -44,8 +52,7 @@ class Tool:
|
|
|
44
52
|
parameters
|
|
45
53
|
A dictionary describing the input parameters and their types.
|
|
46
54
|
annotations
|
|
47
|
-
Additional properties that describe the tool and its behavior.
|
|
48
|
-
a `from mcp.types import ToolAnnotations` instance.
|
|
55
|
+
Additional properties that describe the tool and its behavior.
|
|
49
56
|
"""
|
|
50
57
|
|
|
51
58
|
func: Callable[..., Any] | Callable[..., Awaitable[Any]]
|
|
@@ -77,6 +84,7 @@ class Tool:
|
|
|
77
84
|
cls: type["Tool"],
|
|
78
85
|
func: Callable[..., Any] | Callable[..., Awaitable[Any]],
|
|
79
86
|
*,
|
|
87
|
+
name: Optional[str] = None,
|
|
80
88
|
model: Optional[type[BaseModel]] = None,
|
|
81
89
|
annotations: "Optional[ToolAnnotations]" = None,
|
|
82
90
|
) -> "Tool":
|
|
@@ -87,6 +95,9 @@ class Tool:
|
|
|
87
95
|
----------
|
|
88
96
|
func
|
|
89
97
|
The function to wrap as a tool.
|
|
98
|
+
name
|
|
99
|
+
The name of the tool. If not provided, the name will be inferred from the
|
|
100
|
+
function's name.
|
|
90
101
|
model
|
|
91
102
|
A Pydantic model that describes the input parameters for the function.
|
|
92
103
|
If not provided, the model will be inferred from the function's type hints.
|
|
@@ -94,8 +105,7 @@ class Tool:
|
|
|
94
105
|
Note that the name and docstring of the model takes precedence over the
|
|
95
106
|
name and docstring of the function.
|
|
96
107
|
annotations
|
|
97
|
-
Additional properties that describe the tool and its behavior.
|
|
98
|
-
a `from mcp.types import ToolAnnotations` instance.
|
|
108
|
+
Additional properties that describe the tool and its behavior.
|
|
99
109
|
|
|
100
110
|
Returns
|
|
101
111
|
-------
|
|
@@ -114,7 +124,8 @@ class Tool:
|
|
|
114
124
|
# Throw if there is a mismatch between the model and the function parameters
|
|
115
125
|
params = inspect.signature(func).parameters
|
|
116
126
|
fields = model.model_fields
|
|
117
|
-
|
|
127
|
+
fields_alias = [val.alias if val.alias else key for key, val in fields.items()]
|
|
128
|
+
diff = set(params) ^ set(fields_alias)
|
|
118
129
|
if diff:
|
|
119
130
|
raise ValueError(
|
|
120
131
|
f"`model` fields must match tool function parameters exactly. "
|
|
@@ -125,7 +136,7 @@ class Tool:
|
|
|
125
136
|
|
|
126
137
|
return cls(
|
|
127
138
|
func=func,
|
|
128
|
-
name=model.__name__ or func.__name__,
|
|
139
|
+
name=name or model.__name__ or func.__name__,
|
|
129
140
|
description=model.__doc__ or func.__doc__ or "",
|
|
130
141
|
parameters=params,
|
|
131
142
|
annotations=annotations,
|
|
@@ -203,12 +214,17 @@ class Tool:
|
|
|
203
214
|
|
|
204
215
|
params = mcp_tool_input_schema_to_param_schema(mcp_tool.inputSchema)
|
|
205
216
|
|
|
217
|
+
# Convert MCP ToolAnnotations to our TypedDict format
|
|
218
|
+
annotations = None
|
|
219
|
+
if mcp_tool.annotations:
|
|
220
|
+
annotations = cast(ToolAnnotations, mcp_tool.annotations.model_dump())
|
|
221
|
+
|
|
206
222
|
return cls(
|
|
207
223
|
func=_utils.wrap_async(_call),
|
|
208
224
|
name=mcp_tool.name,
|
|
209
225
|
description=mcp_tool.description or "",
|
|
210
226
|
parameters=params,
|
|
211
|
-
annotations=
|
|
227
|
+
annotations=annotations,
|
|
212
228
|
)
|
|
213
229
|
|
|
214
230
|
|
|
@@ -13,7 +13,7 @@ else:
|
|
|
13
13
|
# Even though TypedDict is available in Python 3.8, because it's used with NotRequired,
|
|
14
14
|
# they should both come from the same typing module.
|
|
15
15
|
# https://peps.python.org/pep-0655/#usage-in-python-3-11
|
|
16
|
-
if sys.version_info >= (3,
|
|
16
|
+
if sys.version_info >= (3, 12):
|
|
17
17
|
from typing import NotRequired, Required, TypedDict
|
|
18
18
|
else:
|
|
19
19
|
from typing_extensions import NotRequired, Required, TypedDict
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.11.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 11,
|
|
31
|
+
__version__ = version = '0.11.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 11, 1)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -13,6 +13,8 @@ from .._content import (
|
|
|
13
13
|
ContentToolRequest,
|
|
14
14
|
ContentToolResult,
|
|
15
15
|
ImageContentTypes,
|
|
16
|
+
ToolAnnotations,
|
|
17
|
+
ToolInfo,
|
|
16
18
|
)
|
|
17
19
|
from .._provider import ModelInfo
|
|
18
20
|
from .._tokens import TokenUsage
|
|
@@ -32,6 +34,8 @@ __all__ = (
|
|
|
32
34
|
"ImageContentTypes",
|
|
33
35
|
"SubmitInputArgsT",
|
|
34
36
|
"TokenUsage",
|
|
37
|
+
"ToolAnnotations",
|
|
38
|
+
"ToolInfo",
|
|
35
39
|
"MISSING_TYPE",
|
|
36
40
|
"MISSING",
|
|
37
41
|
"ModelInfo",
|
|
@@ -90,11 +90,7 @@ def assert_tools_simple(chat_fun: ChatFun, stream: bool = True):
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def assert_tools_simple_stream_content(chat_fun: ChatFun):
|
|
93
|
-
|
|
94
|
-
from mcp.types import ToolAnnotations
|
|
95
|
-
except ImportError:
|
|
96
|
-
pytest.skip("mcp is not installed")
|
|
97
|
-
return
|
|
93
|
+
from chatlas._content import ToolAnnotations
|
|
98
94
|
|
|
99
95
|
chat = chat_fun(system_prompt="Be very terse, not even punctuation.")
|
|
100
96
|
|
|
@@ -114,7 +110,7 @@ def assert_tools_simple_stream_content(chat_fun: ChatFun):
|
|
|
114
110
|
assert request[0].tool is not None
|
|
115
111
|
assert request[0].tool.name == "get_date"
|
|
116
112
|
assert request[0].tool.annotations is not None
|
|
117
|
-
assert request[0].tool.annotations
|
|
113
|
+
assert request[0].tool.annotations["title"] == "Get Date"
|
|
118
114
|
|
|
119
115
|
# Emits a response (with a reference to the request)
|
|
120
116
|
response = [x for x in chunks if isinstance(x, ContentToolResult)]
|
|
@@ -3,6 +3,7 @@ from typing import Any, Optional, Union
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
5
|
from chatlas import ChatOpenAI
|
|
6
|
+
from chatlas._content import ToolInfo
|
|
6
7
|
from chatlas.types import ContentToolRequest, ContentToolResult
|
|
7
8
|
|
|
8
9
|
|
|
@@ -113,11 +114,12 @@ def test_invoke_tool_returns_tool_result():
|
|
|
113
114
|
name: str = "tool",
|
|
114
115
|
args: Optional[dict[str, Any]] = None,
|
|
115
116
|
):
|
|
117
|
+
tool_obj = chat._tools.get(name)
|
|
116
118
|
return ContentToolRequest(
|
|
117
119
|
id="id",
|
|
118
120
|
name=name,
|
|
119
121
|
arguments=args or {},
|
|
120
|
-
tool=
|
|
122
|
+
tool=ToolInfo.from_tool(tool_obj) if tool_obj else None,
|
|
121
123
|
)
|
|
122
124
|
|
|
123
125
|
req1 = new_tool_request()
|
|
@@ -178,11 +180,12 @@ async def test_invoke_tool_returns_tool_result_async():
|
|
|
178
180
|
name: str = "tool",
|
|
179
181
|
args: Optional[dict[str, Any]] = None,
|
|
180
182
|
):
|
|
183
|
+
tool_obj = chat._tools.get(name)
|
|
181
184
|
return ContentToolRequest(
|
|
182
185
|
id="id",
|
|
183
186
|
name=name,
|
|
184
187
|
arguments=args or {},
|
|
185
|
-
tool=
|
|
188
|
+
tool=ToolInfo.from_tool(tool_obj) if tool_obj else None,
|
|
186
189
|
)
|
|
187
190
|
|
|
188
191
|
req1 = new_tool_request()
|
|
@@ -254,18 +257,20 @@ def test_tool_custom_result():
|
|
|
254
257
|
chat.register_tool(custom_tool)
|
|
255
258
|
chat.register_tool(custom_tool_err)
|
|
256
259
|
|
|
260
|
+
tool_obj = chat._tools.get("custom_tool")
|
|
257
261
|
req = ContentToolRequest(
|
|
258
262
|
id="id",
|
|
259
263
|
name="custom_tool",
|
|
260
264
|
arguments={},
|
|
261
|
-
tool=
|
|
265
|
+
tool=ToolInfo.from_tool(tool_obj) if tool_obj else None,
|
|
262
266
|
)
|
|
263
267
|
|
|
268
|
+
tool_err_obj = chat._tools.get("custom_tool_err")
|
|
264
269
|
req_err = ContentToolRequest(
|
|
265
270
|
id="id",
|
|
266
271
|
name="custom_tool_err",
|
|
267
272
|
arguments={},
|
|
268
|
-
tool=
|
|
273
|
+
tool=ToolInfo.from_tool(tool_err_obj) if tool_err_obj else None,
|
|
269
274
|
)
|
|
270
275
|
|
|
271
276
|
results = list(chat._invoke_tool(req))
|
|
@@ -316,18 +321,20 @@ async def test_tool_custom_result_async():
|
|
|
316
321
|
chat.register_tool(custom_tool)
|
|
317
322
|
chat.register_tool(custom_tool_err)
|
|
318
323
|
|
|
324
|
+
tool_obj = chat._tools.get("custom_tool")
|
|
319
325
|
req = ContentToolRequest(
|
|
320
326
|
id="id",
|
|
321
327
|
name="custom_tool",
|
|
322
328
|
arguments={},
|
|
323
|
-
tool=
|
|
329
|
+
tool=ToolInfo.from_tool(tool_obj) if tool_obj else None,
|
|
324
330
|
)
|
|
325
331
|
|
|
332
|
+
tool_err_obj = chat._tools.get("custom_tool_err")
|
|
326
333
|
req_err = ContentToolRequest(
|
|
327
334
|
id="id",
|
|
328
335
|
name="custom_tool_err",
|
|
329
336
|
arguments={},
|
|
330
|
-
tool=
|
|
337
|
+
tool=ToolInfo.from_tool(tool_err_obj) if tool_err_obj else None,
|
|
331
338
|
)
|
|
332
339
|
|
|
333
340
|
results = []
|
|
@@ -361,3 +368,38 @@ async def test_tool_custom_result_async():
|
|
|
361
368
|
assert res_err.name == req_err.name
|
|
362
369
|
assert res_err.arguments == req_err.arguments
|
|
363
370
|
|
|
371
|
+
|
|
372
|
+
def test_content_tool_request_serializable():
|
|
373
|
+
"""Test that ContentToolRequest with Tool instance is JSON serializable"""
|
|
374
|
+
chat = ChatOpenAI()
|
|
375
|
+
|
|
376
|
+
def add(x: int, y: int) -> int:
|
|
377
|
+
"""Add two numbers"""
|
|
378
|
+
return x + y
|
|
379
|
+
|
|
380
|
+
chat.register_tool(add)
|
|
381
|
+
|
|
382
|
+
# Create a ContentToolRequest with the Tool info
|
|
383
|
+
tool = chat._tools["add"]
|
|
384
|
+
request = ContentToolRequest(
|
|
385
|
+
id="test-123",
|
|
386
|
+
name="add",
|
|
387
|
+
arguments={"x": 1, "y": 2},
|
|
388
|
+
tool=ToolInfo.from_tool(tool),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Test that it can be serialized to JSON
|
|
392
|
+
json_data = request.model_dump_json()
|
|
393
|
+
assert isinstance(json_data, str)
|
|
394
|
+
|
|
395
|
+
# Test that the JSON can be parsed
|
|
396
|
+
parsed = ContentToolRequest.model_validate_json(json_data)
|
|
397
|
+
assert parsed.id == "test-123"
|
|
398
|
+
assert parsed.name == "add"
|
|
399
|
+
assert parsed.arguments == {"x": 1, "y": 2}
|
|
400
|
+
assert parsed.content_type == "tool_request"
|
|
401
|
+
|
|
402
|
+
# Test that the tool is serialized (func is excluded from serialization)
|
|
403
|
+
assert parsed.tool is not None
|
|
404
|
+
assert parsed.tool.name == "add"
|
|
405
|
+
assert parsed.tool.description == "Add two numbers"
|