From 042f5ff520446efb60e3e63c0c489362abbf22e9 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:10:59 -0500 Subject: [PATCH 1/9] fix: Make sure Tool information attached to ContentToolRequest is serializable; require tool annotations to be a dictionary --- chatlas/__init__.py | 4 ++ chatlas/_chat.py | 16 ++++--- chatlas/_content.py | 96 +++++++++++++++++++++++++++++++++++-- chatlas/_tools.py | 25 +++++++--- tests/conftest.py | 8 +--- tests/test_content_tools.py | 19 +++++--- tests/test_tool_from_mcp.py | 8 ++-- 7 files changed, 144 insertions(+), 32 deletions(-) diff --git a/chatlas/__init__.py b/chatlas/__init__.py index ece25354..50f1c8cf 100644 --- a/chatlas/__init__.py +++ b/chatlas/__init__.py @@ -6,6 +6,8 @@ ContentToolResult, ContentToolResultImage, ContentToolResultResource, + ToolAnnotations, + ToolInfo, ) from ._content_image import content_image_file, content_image_plot, content_image_url from ._content_pdf import content_pdf_file, content_pdf_url @@ -65,6 +67,8 @@ "ContentToolResult", "ContentToolResultImage", "ContentToolResultResource", + "ToolAnnotations", + "ToolInfo", "interpolate", "interpolate_file", "Provider", diff --git a/chatlas/_chat.py b/chatlas/_chat.py index 5318903d..e0474e91 100644 --- a/chatlas/_chat.py +++ b/chatlas/_chat.py @@ -34,6 +34,7 @@ ContentText, ContentToolRequest, ContentToolResult, + ToolInfo, ) from ._display import ( EchoDisplayOptions, @@ -52,7 +53,7 @@ from ._utils import MISSING, MISSING_TYPE, html_escape, wrap_async if TYPE_CHECKING: - from mcp.types import ToolAnnotations + from ._content import ToolAnnotations class TokensDict(TypedDict): @@ -1622,7 +1623,6 @@ def add(a: int, b: int) -> int: name and docstring of the function. annotations Additional properties that describe the tool and its behavior. - Should be a `from mcp.types import ToolAnnotations` instance. Raises ------ @@ -1937,7 +1937,9 @@ def _chat_impl( all_results: list[ContentToolResult] = [] for x in turn.contents: if isinstance(x, ContentToolRequest): - x.tool = self._tools.get(x.name) + tool = self._tools.get(x.name) + if tool is not None: + x.tool = ToolInfo.from_tool(tool) if echo == "output": self._echo_content(f"\n\n{x}\n\n") if content == "all": @@ -1998,7 +2000,9 @@ async def _chat_impl_async( all_results: list[ContentToolResult] = [] for x in turn.contents: if isinstance(x, ContentToolRequest): - x.tool = self._tools.get(x.name) + tool = self._tools.get(x.name) + if tool is not None: + x.tool = ToolInfo.from_tool(tool) if echo == "output": self._echo_content(f"\n\n{x}\n\n") if content == "all": @@ -2156,7 +2160,7 @@ def emit(text: str | Content): self._turns.extend([user_turn, turn]) def _invoke_tool(self, request: ContentToolRequest): - tool = request.tool + tool = self._tools.get(request.name) func = tool.func if tool is not None else None if func is None: @@ -2204,7 +2208,7 @@ def _as_generator(res): yield self._handle_tool_error_result(request, e) async def _invoke_tool_async(self, request: ContentToolRequest): - tool = request.tool + tool = self._tools.get(request.name) if tool is None: yield self._handle_tool_error_result( diff --git a/chatlas/_content.py b/chatlas/_content.py index 9a2f6e79..c54c3856 100644 --- a/chatlas/_content.py +++ b/chatlas/_content.py @@ -6,9 +6,59 @@ import orjson from pydantic import BaseModel, ConfigDict +from ._typing_extensions import NotRequired, TypedDict + if TYPE_CHECKING: from ._tools import Tool + +class ToolAnnotations(TypedDict, total=False): + """ + Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ + + title: NotRequired[str] + """A human-readable title for the tool.""" + + readOnlyHint: NotRequired[bool] + """ + If true, the tool does not modify its environment. + Default: false + """ + + destructiveHint: NotRequired[bool] + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + (This property is meaningful only when `readOnlyHint == false`) + Default: true + """ + + idempotentHint: NotRequired[bool] + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on the its environment. + (This property is meaningful only when `readOnlyHint == false`) + Default: false + """ + + openWorldHint: NotRequired[bool] + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + Default: true + """ + + ImageContentTypes = Literal[ "image/png", "image/jpeg", @@ -19,6 +69,45 @@ Allowable content types for images. """ + +class ToolInfo(BaseModel): + """ + Serializable tool information + + This contains only the serializable parts of a Tool that are needed + for ContentToolRequest to be JSON-serializable. This allows tool + metadata to be preserved without including the non-serializable + function reference. + + Parameters + ---------- + name + The name of the tool. + description + A description of what the tool does. + parameters + A dictionary describing the input parameters and their types. + annotations + Additional properties that describe the tool and its behavior. + """ + + name: str + description: str + parameters: dict[str, Any] + annotations: Optional[ToolAnnotations] = None + + @classmethod + def from_tool(cls, tool: "Tool") -> "ToolInfo": + """Create a ToolInfo from a Tool instance.""" + func_schema = tool.schema["function"] + return cls( + name=tool.name, + description=func_schema.get("description", ""), + parameters=func_schema.get("parameters", {}), + annotations=tool.annotations, + ) + + ContentTypeEnum = Literal[ "text", "image_remote", @@ -175,14 +264,15 @@ class ContentToolRequest(Content): arguments The arguments to pass to the tool/function. tool - The tool/function to be called. This is set internally by chatlas's tool - calling loop. + Serializable information about the tool. This is set internally by + chatlas's tool calling loop and contains only the metadata needed + for serialization (name, description, parameters, annotations). """ id: str name: str arguments: object - tool: Optional["Tool"] = None + tool: Optional[ToolInfo] = None content_type: ContentTypeEnum = "tool_request" diff --git a/chatlas/_tools.py b/chatlas/_tools.py index 1aa18b3d..7470e25f 100644 --- a/chatlas/_tools.py +++ b/chatlas/_tools.py @@ -2,7 +2,15 @@ import inspect import warnings -from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Optional +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + Awaitable, + Callable, + Optional, + cast, +) import openai from pydantic import BaseModel, Field, create_model @@ -12,6 +20,7 @@ ContentToolResult, ContentToolResultImage, ContentToolResultResource, + ToolAnnotations, ) __all__ = ( @@ -22,7 +31,6 @@ if TYPE_CHECKING: from mcp import ClientSession as MCPClientSession from mcp import Tool as MCPTool - from mcp.types import ToolAnnotations from openai.types.chat import ChatCompletionToolParam @@ -44,8 +52,7 @@ class Tool: parameters A dictionary describing the input parameters and their types. annotations - Additional properties that describe the tool and its behavior. Should be - a `from mcp.types import ToolAnnotations` instance. + Additional properties that describe the tool and its behavior. """ func: Callable[..., Any] | Callable[..., Awaitable[Any]] @@ -98,8 +105,7 @@ def from_func( Note that the name and docstring of the model takes precedence over the name and docstring of the function. annotations - Additional properties that describe the tool and its behavior. Should be - a `from mcp.types import ToolAnnotations` instance. + Additional properties that describe the tool and its behavior. Returns ------- @@ -208,12 +214,17 @@ async def _call(**args: Any) -> AsyncGenerator[ContentToolResult, None]: params = mcp_tool_input_schema_to_param_schema(mcp_tool.inputSchema) + # Convert MCP ToolAnnotations to our TypedDict format + annotations: ToolAnnotations = {} + if mcp_tool.annotations: + annotations = cast(ToolAnnotations, mcp_tool.annotations.model_dump()) + return cls( func=_utils.wrap_async(_call), name=mcp_tool.name, description=mcp_tool.description or "", parameters=params, - annotations=mcp_tool.annotations, + annotations=annotations, ) diff --git a/tests/conftest.py b/tests/conftest.py index c759726b..984bb4a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,11 +90,7 @@ def get_date(): def assert_tools_simple_stream_content(chat_fun: ChatFun): - try: - from mcp.types import ToolAnnotations - except ImportError: - pytest.skip("mcp is not installed") - return + from chatlas._content import ToolAnnotations chat = chat_fun(system_prompt="Be very terse, not even punctuation.") @@ -114,7 +110,7 @@ def get_date(): assert request[0].tool is not None assert request[0].tool.name == "get_date" assert request[0].tool.annotations is not None - assert request[0].tool.annotations.title == "Get Date" + assert request[0].tool.annotations["title"] == "Get Date" # Emits a response (with a reference to the request) response = [x for x in chunks if isinstance(x, ContentToolResult)] diff --git a/tests/test_content_tools.py b/tests/test_content_tools.py index e7d89062..f3500e45 100644 --- a/tests/test_content_tools.py +++ b/tests/test_content_tools.py @@ -4,6 +4,7 @@ from chatlas import ChatOpenAI from chatlas.types import ContentToolRequest, ContentToolResult +from chatlas._content import ToolInfo def test_register_tool(): @@ -113,11 +114,12 @@ def new_tool_request( name: str = "tool", args: Optional[dict[str, Any]] = None, ): + tool_obj = chat._tools.get(name) return ContentToolRequest( id="id", name=name, arguments=args or {}, - tool=chat._tools.get(name), + tool=ToolInfo.from_tool(tool_obj) if tool_obj else None, ) req1 = new_tool_request() @@ -178,11 +180,12 @@ def new_tool_request( name: str = "tool", args: Optional[dict[str, Any]] = None, ): + tool_obj = chat._tools.get(name) return ContentToolRequest( id="id", name=name, arguments=args or {}, - tool=chat._tools.get(name), + tool=ToolInfo.from_tool(tool_obj) if tool_obj else None, ) req1 = new_tool_request() @@ -254,18 +257,20 @@ def custom_tool_err(): chat.register_tool(custom_tool) chat.register_tool(custom_tool_err) + tool_obj = chat._tools.get("custom_tool") req = ContentToolRequest( id="id", name="custom_tool", arguments={}, - tool=chat._tools.get("custom_tool"), + tool=ToolInfo.from_tool(tool_obj) if tool_obj else None, ) + tool_err_obj = chat._tools.get("custom_tool_err") req_err = ContentToolRequest( id="id", name="custom_tool_err", arguments={}, - tool=chat._tools.get("custom_tool_err"), + tool=ToolInfo.from_tool(tool_err_obj) if tool_err_obj else None, ) results = list(chat._invoke_tool(req)) @@ -316,18 +321,20 @@ async def custom_tool_err(): chat.register_tool(custom_tool) chat.register_tool(custom_tool_err) + tool_obj = chat._tools.get("custom_tool") req = ContentToolRequest( id="id", name="custom_tool", arguments={}, - tool=chat._tools.get("custom_tool"), + tool=ToolInfo.from_tool(tool_obj) if tool_obj else None, ) + tool_err_obj = chat._tools.get("custom_tool_err") req_err = ContentToolRequest( id="id", name="custom_tool_err", arguments={}, - tool=chat._tools.get("custom_tool_err"), + tool=ToolInfo.from_tool(tool_err_obj) if tool_err_obj else None, ) results = [] diff --git a/tests/test_tool_from_mcp.py b/tests/test_tool_from_mcp.py index f620a83d..3c830fe6 100644 --- a/tests/test_tool_from_mcp.py +++ b/tests/test_tool_from_mcp.py @@ -449,7 +449,7 @@ def test_mcp_tool_input_schema_conversion(self): def test_from_mcp_with_annotations(self): """Test creating a Tool from MCP tool with annotations.""" try: - from mcp.types import ToolAnnotations + from mcp.types import ToolAnnotations as MCPToolAnnotations except ImportError: pytest.skip("mcp is not installed") return @@ -462,7 +462,7 @@ def test_from_mcp_with_annotations(self): "properties": {"x": {"type": "integer"}}, "required": ["x"], }, - annotations=ToolAnnotations( + annotations=MCPToolAnnotations( title="Dangerous Tool", destructiveHint=True, ), @@ -473,8 +473,8 @@ def test_from_mcp_with_annotations(self): assert tool.name == "dangerous_tool" assert tool.annotations is not None - assert tool.annotations.title == "Dangerous Tool" - assert tool.annotations.destructiveHint is True + assert tool.annotations["title"] == "Dangerous Tool" + assert tool.annotations["destructiveHint"] is True def test_from_mcp_without_annotations(self): """Test creating a Tool from MCP tool without annotations.""" From 5b9a1ad434155e6fb02a26842570eacabc4d64c1 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:18:09 -0500 Subject: [PATCH 2/9] Add unit test --- tests/test_content_tools.py | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/test_content_tools.py b/tests/test_content_tools.py index f3500e45..707c3628 100644 --- a/tests/test_content_tools.py +++ b/tests/test_content_tools.py @@ -3,8 +3,8 @@ import pytest from chatlas import ChatOpenAI -from chatlas.types import ContentToolRequest, ContentToolResult from chatlas._content import ToolInfo +from chatlas.types import ContentToolRequest, ContentToolResult def test_register_tool(): @@ -368,3 +368,42 @@ async def custom_tool_err(): assert res_err.name == req_err.name assert res_err.arguments == req_err.arguments + +def test_content_tool_request_serializable(): + """Test that ContentToolRequest with Tool instance is JSON serializable""" + chat = ChatOpenAI() + + def add(x: int, y: int) -> int: + """Add two numbers""" + return x + y + + chat.register_tool(add) + + # Create a ContentToolRequest with the Tool info + tool = chat._tools["add"] + request = ContentToolRequest( + id="test-123", + name="add", + arguments={"x": 1, "y": 2}, + tool=ToolInfo.from_tool(tool), + ) + + # Test that it can be serialized to JSON + json_data = request.model_dump_json() + assert isinstance(json_data, str) + + # Test that the JSON can be parsed + parsed = ContentToolRequest.model_validate_json(json_data) + assert parsed.id == "test-123" + assert parsed.name == "add" + assert parsed.arguments == {"x": 1, "y": 2} + assert parsed.content_type == "tool_request" + + # Test that the tool is serialized (func is excluded from serialization) + assert parsed.tool is not None + assert parsed.tool.name == "add" + assert parsed.tool.description == "Add two numbers" + assert parsed.tool.description == "Add two numbers" + assert parsed.tool.description == "Add two numbers" + assert parsed.tool.description == "Add two numbers" + assert parsed.tool.description == "Add two numbers" From 6784bfed646d2eaf696ba513c75471a05336ff75 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:22:30 -0500 Subject: [PATCH 3/9] Make pydantic happy about where TypedDict gets imported from --- chatlas/_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatlas/_typing_extensions.py b/chatlas/_typing_extensions.py index 37f0c721..1cc1574f 100644 --- a/chatlas/_typing_extensions.py +++ b/chatlas/_typing_extensions.py @@ -13,7 +13,7 @@ # Even though TypedDict is available in Python 3.8, because it's used with NotRequired, # they should both come from the same typing module. # https://peps.python.org/pep-0655/#usage-in-python-3-11 -if sys.version_info >= (3, 11): +if sys.version_info > (3, 11): from typing import NotRequired, Required, TypedDict else: from typing_extensions import NotRequired, Required, TypedDict From f57ed675098f6b01b8ece8b14a906cdc4b9fedd0 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:32:57 -0500 Subject: [PATCH 4/9] not sure what's happening --- chatlas/_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatlas/_typing_extensions.py b/chatlas/_typing_extensions.py index 1cc1574f..6cb18922 100644 --- a/chatlas/_typing_extensions.py +++ b/chatlas/_typing_extensions.py @@ -13,7 +13,7 @@ # Even though TypedDict is available in Python 3.8, because it's used with NotRequired, # they should both come from the same typing module. # https://peps.python.org/pep-0655/#usage-in-python-3-11 -if sys.version_info > (3, 11): +if sys.version_info > (3, 12): from typing import NotRequired, Required, TypedDict else: from typing_extensions import NotRequired, Required, TypedDict From 3309ac0c3392c25592c6efaa01c205a167aed9c0 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:35:03 -0500 Subject: [PATCH 5/9] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d32b66f..53c379b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Bug fixes +* `ContentToolRequest` is (once again) serializable to/from JSON via Pydantic. (#164) * `.register_tool(model=model)` no longer unexpectedly errors when `model` contains `pydantic.Field(alias='_my_alias')`. (#161) +### Changes + +* `.register_tool(annotations=annotations)` drops support for `mcp.types.ToolAnnotations()` and instead expects a dictionary of the same info. (#164) + ## [0.11.0] - 2025-08-26 From deeaf2f41b58d43a4c75fd5af733176acfa1db2c Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:36:54 -0500 Subject: [PATCH 6/9] Minimize change --- chatlas/_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatlas/_typing_extensions.py b/chatlas/_typing_extensions.py index 6cb18922..fa290eb7 100644 --- a/chatlas/_typing_extensions.py +++ b/chatlas/_typing_extensions.py @@ -13,7 +13,7 @@ # Even though TypedDict is available in Python 3.8, because it's used with NotRequired, # they should both come from the same typing module. # https://peps.python.org/pep-0655/#usage-in-python-3-11 -if sys.version_info > (3, 12): +if sys.version_info >= (3, 12): from typing import NotRequired, Required, TypedDict else: from typing_extensions import NotRequired, Required, TypedDict From 4e4ae80e85cec3bdebf418bb8b33b15ba0978c21 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:43:27 -0500 Subject: [PATCH 7/9] Continue updating tests --- tests/test_content_tools.py | 4 ---- tests/test_tools_enhanced.py | 27 ++++++++++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/tests/test_content_tools.py b/tests/test_content_tools.py index 707c3628..4401250a 100644 --- a/tests/test_content_tools.py +++ b/tests/test_content_tools.py @@ -403,7 +403,3 @@ def add(x: int, y: int) -> int: assert parsed.tool is not None assert parsed.tool.name == "add" assert parsed.tool.description == "Add two numbers" - assert parsed.tool.description == "Add two numbers" - assert parsed.tool.description == "Add two numbers" - assert parsed.tool.description == "Add two numbers" - assert parsed.tool.description == "Add two numbers" diff --git a/tests/test_tools_enhanced.py b/tests/test_tools_enhanced.py index 490ced0d..c6ab42d8 100644 --- a/tests/test_tools_enhanced.py +++ b/tests/test_tools_enhanced.py @@ -1,9 +1,10 @@ import pytest +from pydantic import BaseModel, Field + from chatlas import ChatOpenAI -from chatlas._content import ContentToolResultImage, ContentToolResultResource +from chatlas._content import ContentToolResultImage, ContentToolResultResource, ToolInfo from chatlas._tools import Tool from chatlas.types import ContentToolRequest, ContentToolResult -from pydantic import BaseModel, Field class TestNewToolConstructor: @@ -449,11 +450,12 @@ def multi_result_tool(count: int): chat.register_tool(multi_result_tool) + tool = chat._tools["multi_result_tool"] request = ContentToolRequest( id="test-id", name="multi_result_tool", arguments={"count": 3}, - tool=chat._tools["multi_result_tool"], + tool=ToolInfo.from_tool(tool), ) results = list(chat._invoke_tool(request)) @@ -475,11 +477,12 @@ def single_result_tool(x: int) -> int: chat.register_tool(single_result_tool) + tool = chat._tools["single_result_tool"] request = ContentToolRequest( id="test-id", name="single_result_tool", arguments={"x": 5}, - tool=chat._tools["single_result_tool"], + tool=ToolInfo.from_tool(tool), ) results = list(chat._invoke_tool(request)) @@ -504,11 +507,12 @@ def custom_result_tool(count: int): chat.register_tool(custom_result_tool) + tool = chat._tools["custom_result_tool"] request = ContentToolRequest( id="test-id", name="custom_result_tool", arguments={"count": 2}, - tool=chat._tools["custom_result_tool"], + tool=ToolInfo.from_tool(tool), ) results = list(chat._invoke_tool(request)) @@ -532,11 +536,12 @@ async def async_multi_tool(count: int): chat.register_tool(async_multi_tool) + tool = chat._tools["async_multi_tool"] request = ContentToolRequest( id="test-id", name="async_multi_tool", arguments={"count": 2}, - tool=chat._tools["async_multi_tool"], + tool=ToolInfo.from_tool(tool), ) results = [] @@ -564,11 +569,12 @@ def error_after_yield_tool(count: int): chat.register_tool(error_after_yield_tool) + tool = chat._tools["error_after_yield_tool"] request = ContentToolRequest( id="test-id", name="error_after_yield_tool", arguments={"count": 5}, - tool=chat._tools["error_after_yield_tool"], + tool=ToolInfo.from_tool(tool), ) results = list(chat._invoke_tool(request)) @@ -601,11 +607,12 @@ def add(x: int, y: int) -> int: chat.register_tool(add) + tool = chat._tools["add"] request = ContentToolRequest( id="test-id", name="add", arguments={"x": 3, "y": 4}, - tool=chat._tools["add"], + tool=ToolInfo.from_tool(tool), ) results = list(chat._invoke_tool(request)) @@ -620,7 +627,9 @@ def test_unknown_tool_error_format_updated(self): chat = ChatOpenAI() request = ContentToolRequest( - id="test-id", name="nonexistent_tool", arguments={} + id="test-id", + name="nonexistent_tool", + arguments={}, ) results = list(chat._invoke_tool(request)) From 0505dc354b7befe097d545f4eec7f8f8f8474be1 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 08:56:27 -0500 Subject: [PATCH 8/9] Fix --- chatlas/_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatlas/_tools.py b/chatlas/_tools.py index 7470e25f..dd125eb3 100644 --- a/chatlas/_tools.py +++ b/chatlas/_tools.py @@ -215,7 +215,7 @@ async def _call(**args: Any) -> AsyncGenerator[ContentToolResult, None]: params = mcp_tool_input_schema_to_param_schema(mcp_tool.inputSchema) # Convert MCP ToolAnnotations to our TypedDict format - annotations: ToolAnnotations = {} + annotations = None if mcp_tool.annotations: annotations = cast(ToolAnnotations, mcp_tool.annotations.model_dump()) From 74764154342d796e26cbb2f3ac1f72c373023b1f Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 29 Aug 2025 09:00:42 -0500 Subject: [PATCH 9/9] Import from types module, not top-level -- add to docs --- chatlas/__init__.py | 4 ---- chatlas/types/__init__.py | 4 ++++ docs/_quarto.yml | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/chatlas/__init__.py b/chatlas/__init__.py index 50f1c8cf..ece25354 100644 --- a/chatlas/__init__.py +++ b/chatlas/__init__.py @@ -6,8 +6,6 @@ ContentToolResult, ContentToolResultImage, ContentToolResultResource, - ToolAnnotations, - ToolInfo, ) from ._content_image import content_image_file, content_image_plot, content_image_url from ._content_pdf import content_pdf_file, content_pdf_url @@ -67,8 +65,6 @@ "ContentToolResult", "ContentToolResultImage", "ContentToolResultResource", - "ToolAnnotations", - "ToolInfo", "interpolate", "interpolate_file", "Provider", diff --git a/chatlas/types/__init__.py b/chatlas/types/__init__.py index 1c746cde..5a53b5d8 100644 --- a/chatlas/types/__init__.py +++ b/chatlas/types/__init__.py @@ -13,6 +13,8 @@ ContentToolRequest, ContentToolResult, ImageContentTypes, + ToolAnnotations, + ToolInfo, ) from .._provider import ModelInfo from .._tokens import TokenUsage @@ -32,6 +34,8 @@ "ImageContentTypes", "SubmitInputArgsT", "TokenUsage", + "ToolAnnotations", + "ToolInfo", "MISSING_TYPE", "MISSING", "ModelInfo", diff --git a/docs/_quarto.yml b/docs/_quarto.yml index ec7911f5..94a7c434 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -188,6 +188,8 @@ quartodoc: - types.MISSING - types.SubmitInputArgsT - types.TokenUsage + - types.ToolAnnotations + - types.ToolInfo