chatlas 0.11.0__py3-none-any.whl → 0.11.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of chatlas might be problematic. Click here for more details.

chatlas/_chat.py CHANGED
@@ -34,6 +34,7 @@ from ._content import (
34
34
  ContentText,
35
35
  ContentToolRequest,
36
36
  ContentToolResult,
37
+ ToolInfo,
37
38
  )
38
39
  from ._display import (
39
40
  EchoDisplayOptions,
@@ -52,7 +53,7 @@ from ._typing_extensions import TypedDict, TypeGuard
52
53
  from ._utils import MISSING, MISSING_TYPE, html_escape, wrap_async
53
54
 
54
55
  if TYPE_CHECKING:
55
- from mcp.types import ToolAnnotations
56
+ from ._content import ToolAnnotations
56
57
 
57
58
 
58
59
  class TokensDict(TypedDict):
@@ -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
- x.tool = self._tools.get(x.name)
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
- x.tool = self._tools.get(x.name)
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.tool
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.tool
2211
+ tool = self._tools.get(request.name)
2204
2212
 
2205
2213
  if tool is None:
2206
2214
  yield self._handle_tool_error_result(
chatlas/_content.py CHANGED
@@ -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
- The tool/function to be called. This is set internally by chatlas's tool
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["Tool"] = None
275
+ tool: Optional[ToolInfo] = None
186
276
 
187
277
  content_type: ContentTypeEnum = "tool_request"
188
278
 
chatlas/_tools.py CHANGED
@@ -2,7 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  import warnings
5
- from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Optional
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. Should be
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. Should be
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
- diff = set(params) ^ set(fields)
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=mcp_tool.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, 11):
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
chatlas/_version.py CHANGED
@@ -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.0'
32
- __version_tuple__ = version_tuple = (0, 11, 0)
31
+ __version__ = version = '0.11.1'
32
+ __version_tuple__ = version_tuple = (0, 11, 1)
33
33
 
34
34
  __commit_id__ = commit_id = None
chatlas/types/__init__.py CHANGED
@@ -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",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatlas
3
- Version: 0.11.0
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
@@ -1,8 +1,8 @@
1
1
  chatlas/__init__.py,sha256=CyViGMiz50clcVu3vpZgOq_qP4hmoYGOlcHKlRPcLJo,2416
2
2
  chatlas/_auto.py,sha256=-s7XGzsKLX4RipWtk4WOE8iKbOBhXPUPtI0-63PpXCY,5660
3
3
  chatlas/_callbacks.py,sha256=3RpPaOQonTqScjXbaShgKJ1Rc-YxzWerxKRBjVssFnc,1838
4
- chatlas/_chat.py,sha256=HDNH_UA604sfyda-bVBs05GGs8-ISBwU4c2nM5bOd40,84997
5
- chatlas/_content.py,sha256=Hg4IQwoOXC72MaL3H-zpuN0JcBluEmsp2vstfSoBn_k,19984
4
+ chatlas/_chat.py,sha256=2AKkKHOJz6eV2Xulxle3_sl4PteP-_mj2YXaEbCd0gU,85375
5
+ chatlas/_content.py,sha256=RJuJaTkyOjlXPmQYUww_a-lLV1LKU2C25tuTOCvb9vA,22756
6
6
  chatlas/_content_image.py,sha256=EUK6wAint-JatLsiwvaPDu4D3W-NcIsDCkzABkXgfDg,8304
7
7
  chatlas/_content_pdf.py,sha256=cffeuJxzhUDukQ-Srkmpy62M8X12skYpU_FVq-Wvya4,2420
8
8
  chatlas/_display.py,sha256=wyQzSc6z1VqrJfkTLkw1wQcti9s1Pr4qT8UxFJESn4U,4664
@@ -29,14 +29,14 @@ chatlas/_provider_portkey.py,sha256=6wKrLZmKVxOqyO6P3HBgWqPe7y1N8une_1wp0aJq7pU,
29
29
  chatlas/_provider_snowflake.py,sha256=G66tG_zs_cIlrHaHY96GvBreTNHHk1O5012Y0BtYRqI,24578
30
30
  chatlas/_tokens.py,sha256=QUsBLNJPgXk8vovcG5JdQU8NarCv7FRpOVBdgFkBgHs,5388
31
31
  chatlas/_tokens_old.py,sha256=L9d9oafrXvEx2u4nIn_Jjn7adnQyLBnYBuPwJUE8Pl8,5005
32
- chatlas/_tools.py,sha256=SCmGP9bLHvVxQPznWfagG7GdzQamnyrPwYwDJ6EaWpw,11692
32
+ chatlas/_tools.py,sha256=Nm-6FPUc5s58ocToc1vNgBzCqenUOgnvHW52K9Ychoc,12029
33
33
  chatlas/_turn.py,sha256=yK7alUxeP8d2iBc7amyz20BtEqcpvX6BCwWZsnlQ5R4,4515
34
- chatlas/_typing_extensions.py,sha256=MB9vWMWlm-IF8uOQfrTcfb66MV6gYXn3zgnbdwAC7BQ,1076
34
+ chatlas/_typing_extensions.py,sha256=BXmbhywjm5ssmyVLGwyP_5TWZMAobzrrgZLYkB6_etE,1076
35
35
  chatlas/_utils.py,sha256=Kku2fa1mvTYCr5D28VxE6-fwfy2e2doCi-eKQkLEg4Y,4686
36
- chatlas/_version.py,sha256=9eKRDJ72C44i2IPiti-C7phzF429SwV2Nogzt0etpr0,706
36
+ chatlas/_version.py,sha256=2oVGHl_4oC5-CorjiyQuPRViZiNckFg-qYTqNnQsPWE,706
37
37
  chatlas/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  chatlas/data/prices.json,sha256=X6qALp-dWc4nfus9lIqHoKzk3PZDPHTLoxxcN2m6fXc,62645
39
- chatlas/types/__init__.py,sha256=4KWksOfX87xtkPWqTEV0qkCFij0UPJM39VNc00baiLc,776
39
+ chatlas/types/__init__.py,sha256=1n0xrJ7TRIKsZ2z06FLFgGqfKMFtXSIxxPvJ2j0hvPw,850
40
40
  chatlas/types/anthropic/__init__.py,sha256=OwubA-DPHYpYo0XyRyAFwftOI0mOxtHzAyhUSLcDx54,417
41
41
  chatlas/types/anthropic/_client.py,sha256=t_tnOzzsW1xWNADkNoAuZJYoE9QJ8ie7DQNnFO1pvoM,697
42
42
  chatlas/types/anthropic/_client_bedrock.py,sha256=2J6U1QcSx1KwiiHfXs3i4YEXDXw11sp-x3iLOuESrgQ,792
@@ -48,7 +48,7 @@ chatlas/types/openai/__init__.py,sha256=Q2RAr1bSH1nHsxICK05nAmKmxdhKmhbBkWD_XHiV
48
48
  chatlas/types/openai/_client.py,sha256=SttisELwAd52_Je_5q3RfWGdX5wbg2CoGbxhS8ThS0A,792
49
49
  chatlas/types/openai/_client_azure.py,sha256=b8Hr7iKYA5-sq9r7uEqbBFv9yo3itppmHIgkEGvChMs,896
50
50
  chatlas/types/openai/_submit.py,sha256=rhft1h7zy6eSlSBLkt7ZAySFh-8WnR5UEG-BXaFTxag,7815
51
- chatlas-0.11.0.dist-info/METADATA,sha256=E10nf0f9IXxzwtqdX2cOyY4iygNcpmC5met5iL7ng5k,5594
52
- chatlas-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
- chatlas-0.11.0.dist-info/licenses/LICENSE,sha256=zyuGzPOC7CcbOaBHsQ3UEyKYRO56KDUkor0OA4LqqDg,1081
54
- chatlas-0.11.0.dist-info/RECORD,,
51
+ chatlas-0.11.1.dist-info/METADATA,sha256=auxRj9RSC9kjVBSANlwfWWoOTQQ1MfroR59IoCWVI_M,5594
52
+ chatlas-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ chatlas-0.11.1.dist-info/licenses/LICENSE,sha256=zyuGzPOC7CcbOaBHsQ3UEyKYRO56KDUkor0OA4LqqDg,1081
54
+ chatlas-0.11.1.dist-info/RECORD,,