schemez 1.4.0__py3-none-any.whl → 1.4.4__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 schemez might be problematic. Click here for more details.
- schemez/code_generation/tool_code_generator.py +51 -23
- schemez/code_generation/toolset_code_generator.py +19 -3
- schemez/executable.py +11 -9
- schemez/functionschema.py +13 -31
- {schemez-1.4.0.dist-info → schemez-1.4.4.dist-info}/METADATA +1 -1
- {schemez-1.4.0.dist-info → schemez-1.4.4.dist-info}/RECORD +8 -8
- {schemez-1.4.0.dist-info → schemez-1.4.4.dist-info}/WHEEL +0 -0
- {schemez-1.4.0.dist-info → schemez-1.4.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
6
|
import inspect
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
-
|
|
9
|
-
from pydantic_ai import RunContext
|
|
7
|
+
from typing import TYPE_CHECKING, Any, get_origin
|
|
10
8
|
|
|
11
9
|
from schemez import create_schema
|
|
12
10
|
from schemez.code_generation.route_helpers import (
|
|
@@ -38,22 +36,29 @@ TYPE_MAP = {
|
|
|
38
36
|
class ToolCodeGenerator:
|
|
39
37
|
"""Generates code artifacts for a single tool."""
|
|
40
38
|
|
|
41
|
-
schema: OpenAIFunctionTool
|
|
42
|
-
"""Schema of the tool."""
|
|
43
|
-
|
|
44
39
|
callable: Callable
|
|
45
40
|
"""Tool to generate code for."""
|
|
46
41
|
|
|
42
|
+
schema: OpenAIFunctionTool
|
|
43
|
+
"""Schema of the tool."""
|
|
44
|
+
|
|
47
45
|
name_override: str | None = None
|
|
48
|
-
"""Name
|
|
46
|
+
"""Name override for the function to generate code for."""
|
|
47
|
+
|
|
48
|
+
exclude_types: list[type] = field(default_factory=list)
|
|
49
|
+
"""Exclude parameters from generated code (like context types)."""
|
|
49
50
|
|
|
50
51
|
@classmethod
|
|
51
|
-
def from_callable(
|
|
52
|
+
def from_callable(
|
|
53
|
+
cls,
|
|
54
|
+
fn: Callable,
|
|
55
|
+
exclude_types: list[type] | None = None,
|
|
56
|
+
) -> ToolCodeGenerator:
|
|
52
57
|
"""Create a ToolCodeGenerator from a Tool."""
|
|
53
58
|
schema = create_schema(fn).model_dump_openai()
|
|
54
59
|
schema["function"]["name"] = fn.__name__
|
|
55
60
|
schema["function"]["description"] = fn.__doc__ or ""
|
|
56
|
-
return cls(schema=schema, callable=
|
|
61
|
+
return cls(schema=schema, callable=fn, exclude_types=exclude_types or [])
|
|
57
62
|
|
|
58
63
|
@property
|
|
59
64
|
def name(self) -> str:
|
|
@@ -162,7 +167,7 @@ class ToolCodeGenerator:
|
|
|
162
167
|
self.callable
|
|
163
168
|
)
|
|
164
169
|
|
|
165
|
-
def _is_context_parameter(self, param_name: str) -> bool:
|
|
170
|
+
def _is_context_parameter(self, param_name: str) -> bool:
|
|
166
171
|
"""Check if a parameter is a context parameter that should be hidden."""
|
|
167
172
|
try:
|
|
168
173
|
sig = self._get_callable_signature()
|
|
@@ -173,26 +178,49 @@ class ToolCodeGenerator:
|
|
|
173
178
|
if param.annotation == inspect.Parameter.empty:
|
|
174
179
|
return False
|
|
175
180
|
|
|
176
|
-
# Check if parameter is RunContext or AgentContext
|
|
177
181
|
annotation = param.annotation
|
|
178
|
-
annotation_str = str(annotation)
|
|
179
182
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
for typ in self.exclude_types:
|
|
184
|
+
if self._types_match(annotation, typ):
|
|
185
|
+
return True
|
|
186
|
+
except Exception: # noqa: BLE001
|
|
187
|
+
pass
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
if "RunContext" in annotation_str:
|
|
186
|
-
return True
|
|
189
|
+
return False
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
def _types_match(self, annotation: Any, exclude_type: type) -> bool:
|
|
192
|
+
"""Check if annotation matches exclude_type using various strategies."""
|
|
193
|
+
try:
|
|
194
|
+
# Direct type match
|
|
195
|
+
if annotation is exclude_type:
|
|
190
196
|
return True
|
|
191
197
|
|
|
192
|
-
#
|
|
193
|
-
|
|
198
|
+
# Handle generic types - get origin for comparison
|
|
199
|
+
origin_annotation = get_origin(annotation)
|
|
200
|
+
if origin_annotation is exclude_type:
|
|
194
201
|
return True
|
|
195
202
|
|
|
203
|
+
# String-based comparison for forward references and __future__.annotations
|
|
204
|
+
annotation_str = str(annotation)
|
|
205
|
+
exclude_type_name = exclude_type.__name__
|
|
206
|
+
exclude_type_full_name = f"{exclude_type.__module__}.{exclude_type.__name__}"
|
|
207
|
+
|
|
208
|
+
# Check various string representations
|
|
209
|
+
if (
|
|
210
|
+
exclude_type_name in annotation_str
|
|
211
|
+
or exclude_type_full_name in annotation_str
|
|
212
|
+
):
|
|
213
|
+
# Be more specific to avoid false positives
|
|
214
|
+
# Check if it's the exact type name, not just a substring
|
|
215
|
+
import re
|
|
216
|
+
|
|
217
|
+
patterns = [
|
|
218
|
+
rf"\b{re.escape(exclude_type_name)}\b",
|
|
219
|
+
rf"\b{re.escape(exclude_type_full_name)}\b",
|
|
220
|
+
]
|
|
221
|
+
if any(re.search(pattern, annotation_str) for pattern in patterns):
|
|
222
|
+
return True
|
|
223
|
+
|
|
196
224
|
except Exception: # noqa: BLE001
|
|
197
225
|
pass
|
|
198
226
|
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
import inspect
|
|
7
8
|
from typing import TYPE_CHECKING, Any
|
|
8
9
|
|
|
9
10
|
from schemez.code_generation.namespace_callable import NamespaceCallable
|
|
@@ -56,6 +57,7 @@ class ToolsetCodeGenerator:
|
|
|
56
57
|
callables: Sequence[Callable],
|
|
57
58
|
include_signatures: bool = True,
|
|
58
59
|
include_docstrings: bool = True,
|
|
60
|
+
exclude_types: list[type] | None = None,
|
|
59
61
|
) -> ToolsetCodeGenerator:
|
|
60
62
|
"""Create a ToolsetCodeGenerator from a sequence of Tools.
|
|
61
63
|
|
|
@@ -63,11 +65,16 @@ class ToolsetCodeGenerator:
|
|
|
63
65
|
callables: Callables to generate code for
|
|
64
66
|
include_signatures: Include function signatures in documentation
|
|
65
67
|
include_docstrings: Include function docstrings in documentation
|
|
68
|
+
exclude_types: Parameter Types to exclude from the generated code
|
|
69
|
+
Often used for context parameters.
|
|
66
70
|
|
|
67
71
|
Returns:
|
|
68
72
|
ToolsetCodeGenerator instance
|
|
69
73
|
"""
|
|
70
|
-
generators = [
|
|
74
|
+
generators = [
|
|
75
|
+
ToolCodeGenerator.from_callable(i, exclude_types=exclude_types)
|
|
76
|
+
for i in callables
|
|
77
|
+
]
|
|
71
78
|
return cls(generators, include_signatures, include_docstrings)
|
|
72
79
|
|
|
73
80
|
def generate_tool_description(self) -> str:
|
|
@@ -101,6 +108,13 @@ class ToolsetCodeGenerator:
|
|
|
101
108
|
indented_desc = " " + generator.callable.__doc__.replace(
|
|
102
109
|
"\n", "\n "
|
|
103
110
|
)
|
|
111
|
+
|
|
112
|
+
# Add warning for async functions without proper return type hints
|
|
113
|
+
if inspect.iscoroutinefunction(generator.callable):
|
|
114
|
+
sig = inspect.signature(generator.callable)
|
|
115
|
+
if sig.return_annotation == inspect.Signature.empty:
|
|
116
|
+
indented_desc += "\n \n Note: This async function should explicitly return a value." # noqa: E501
|
|
117
|
+
|
|
104
118
|
parts.append(f' """{indented_desc}"""')
|
|
105
119
|
parts.append("")
|
|
106
120
|
|
|
@@ -142,9 +156,11 @@ class ToolsetCodeGenerator:
|
|
|
142
156
|
|
|
143
157
|
|
|
144
158
|
if __name__ == "__main__":
|
|
145
|
-
import webbrowser
|
|
146
159
|
|
|
147
|
-
|
|
160
|
+
async def no_annotations_func(test):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
generator = ToolsetCodeGenerator.from_callables([no_annotations_func])
|
|
148
164
|
models = generator.generate_return_models()
|
|
149
165
|
print(models)
|
|
150
166
|
namespace = generator.generate_execution_namespace()
|
schemez/executable.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from collections.abc import AsyncIterator, Callable # noqa: TC003
|
|
5
|
-
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeVar, assert_never, overload
|
|
6
6
|
|
|
7
7
|
from schemez.functionschema import FunctionType, create_schema
|
|
8
8
|
|
|
@@ -34,8 +34,11 @@ class ExecutableFunction[T_co]:
|
|
|
34
34
|
schema: OpenAI function schema
|
|
35
35
|
func: The actual function to execute
|
|
36
36
|
"""
|
|
37
|
+
from schemez.functionschema import _determine_function_type
|
|
38
|
+
|
|
37
39
|
self.schema = schema
|
|
38
40
|
self.func = func
|
|
41
|
+
self.function_type = _determine_function_type(self.func)
|
|
39
42
|
|
|
40
43
|
def run(self, *args: Any, **kwargs: Any) -> T_co | list[T_co]: # noqa: PLR0911
|
|
41
44
|
"""Run the function synchronously.
|
|
@@ -47,7 +50,7 @@ class ExecutableFunction[T_co]:
|
|
|
47
50
|
Returns:
|
|
48
51
|
Either a single result or list of results for generators
|
|
49
52
|
"""
|
|
50
|
-
match self.
|
|
53
|
+
match self.function_type:
|
|
51
54
|
case FunctionType.SYNC:
|
|
52
55
|
return self.func(*args, **kwargs) # type: ignore
|
|
53
56
|
case FunctionType.ASYNC:
|
|
@@ -86,9 +89,8 @@ class ExecutableFunction[T_co]:
|
|
|
86
89
|
return loop.run_until_complete(
|
|
87
90
|
self._collect_async_gen(*args, **kwargs),
|
|
88
91
|
)
|
|
89
|
-
case _:
|
|
90
|
-
|
|
91
|
-
raise ValueError(msg)
|
|
92
|
+
case _ as unreachable:
|
|
93
|
+
assert_never(unreachable)
|
|
92
94
|
|
|
93
95
|
async def _collect_async_gen(self, *args: Any, **kwargs: Any) -> list[T_co]:
|
|
94
96
|
"""Collect async generator results into a list.
|
|
@@ -115,7 +117,7 @@ class ExecutableFunction[T_co]:
|
|
|
115
117
|
Raises:
|
|
116
118
|
ValueError: If the function type is unknown
|
|
117
119
|
"""
|
|
118
|
-
match self.
|
|
120
|
+
match self.function_type:
|
|
119
121
|
case FunctionType.SYNC:
|
|
120
122
|
return self.func(*args, **kwargs) # type: ignore
|
|
121
123
|
case FunctionType.ASYNC:
|
|
@@ -125,7 +127,7 @@ class ExecutableFunction[T_co]:
|
|
|
125
127
|
case FunctionType.ASYNC_GENERATOR:
|
|
126
128
|
return [x async for x in self.func(*args, **kwargs)] # type: ignore
|
|
127
129
|
case _:
|
|
128
|
-
msg = f"Unknown function type: {self.
|
|
130
|
+
msg = f"Unknown function type: {self.function_type}"
|
|
129
131
|
raise ValueError(msg)
|
|
130
132
|
|
|
131
133
|
async def astream(self, *args: Any, **kwargs: Any) -> AsyncIterator[T_co]:
|
|
@@ -141,7 +143,7 @@ class ExecutableFunction[T_co]:
|
|
|
141
143
|
Raises:
|
|
142
144
|
ValueError: If the function type is unknown
|
|
143
145
|
"""
|
|
144
|
-
match self.
|
|
146
|
+
match self.function_type:
|
|
145
147
|
case FunctionType.SYNC_GENERATOR:
|
|
146
148
|
for x in self.func(*args, **kwargs): # type: ignore
|
|
147
149
|
yield x
|
|
@@ -153,7 +155,7 @@ class ExecutableFunction[T_co]:
|
|
|
153
155
|
case FunctionType.ASYNC:
|
|
154
156
|
yield await self.func(*args, **kwargs) # type: ignore
|
|
155
157
|
case _:
|
|
156
|
-
msg = f"Unknown function type: {self.
|
|
158
|
+
msg = f"Unknown function type: {self.function_type}"
|
|
157
159
|
raise ValueError(msg)
|
|
158
160
|
|
|
159
161
|
|
schemez/functionschema.py
CHANGED
|
@@ -23,11 +23,7 @@ import docstring_parser
|
|
|
23
23
|
import pydantic
|
|
24
24
|
|
|
25
25
|
from schemez import log
|
|
26
|
-
from schemez.typedefs import
|
|
27
|
-
OpenAIFunctionDefinition,
|
|
28
|
-
OpenAIFunctionTool,
|
|
29
|
-
ToolParameters,
|
|
30
|
-
)
|
|
26
|
+
from schemez.typedefs import OpenAIFunctionDefinition, OpenAIFunctionTool, ToolParameters
|
|
31
27
|
|
|
32
28
|
|
|
33
29
|
if typing.TYPE_CHECKING:
|
|
@@ -77,38 +73,22 @@ class FunctionSchema(pydantic.BaseModel):
|
|
|
77
73
|
"""The name of the function as it will be presented to the OpenAI API."""
|
|
78
74
|
|
|
79
75
|
description: str | None = None
|
|
80
|
-
"""
|
|
81
|
-
Optional description of what the function does. This helps the AI understand
|
|
82
|
-
when and how to use the function.
|
|
83
|
-
"""
|
|
76
|
+
"""Optional description of what the function does."""
|
|
84
77
|
|
|
85
78
|
parameters: ToolParameters = pydantic.Field(
|
|
86
79
|
default_factory=lambda: ToolParameters(type="object", properties={}),
|
|
87
80
|
)
|
|
88
|
-
"""
|
|
89
|
-
JSON Schema object describing the function's parameters. Contains type information,
|
|
90
|
-
descriptions, and constraints for each parameter.
|
|
91
|
-
"""
|
|
81
|
+
"""JSON Schema object describing the function's parameters."""
|
|
92
82
|
|
|
93
83
|
required: list[str] = pydantic.Field(default_factory=list)
|
|
94
84
|
"""
|
|
95
85
|
List of parameter names that are required (do not have default values).
|
|
96
|
-
These parameters must be provided when calling the function.
|
|
97
86
|
"""
|
|
98
87
|
|
|
99
88
|
returns: dict[str, Any] = pydantic.Field(
|
|
100
89
|
default_factory=lambda: {"type": "object"},
|
|
101
90
|
)
|
|
102
|
-
"""
|
|
103
|
-
JSON Schema object describing the function's return type. Used for type checking
|
|
104
|
-
and documentation purposes.
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
function_type: FunctionType = FunctionType.SYNC
|
|
108
|
-
"""
|
|
109
|
-
The execution pattern of the function (sync, async, generator, or async generator).
|
|
110
|
-
Used to determine how to properly invoke the function.
|
|
111
|
-
"""
|
|
91
|
+
"""JSON Schema object describing the function's return type."""
|
|
112
92
|
|
|
113
93
|
model_config = pydantic.ConfigDict(frozen=True)
|
|
114
94
|
|
|
@@ -377,7 +357,6 @@ class FunctionSchema(pydantic.BaseModel):
|
|
|
377
357
|
parameters=parameters,
|
|
378
358
|
required=required,
|
|
379
359
|
returns={"type": "object"},
|
|
380
|
-
function_type=FunctionType.SYNC,
|
|
381
360
|
)
|
|
382
361
|
|
|
383
362
|
|
|
@@ -647,22 +626,26 @@ def _determine_function_type(func: Callable[..., Any]) -> FunctionType:
|
|
|
647
626
|
def create_schema(
|
|
648
627
|
func: Callable[..., Any],
|
|
649
628
|
name_override: str | None = None,
|
|
629
|
+
description_override: str | None = None,
|
|
650
630
|
) -> FunctionSchema:
|
|
651
631
|
"""Create an OpenAI function schema from a Python function.
|
|
652
632
|
|
|
633
|
+
If an iterator is passed, the schema return type is a list of the iterator's
|
|
634
|
+
element type.
|
|
635
|
+
Variable arguments (*args) and keyword arguments (**kwargs) are not
|
|
636
|
+
supported in OpenAI function schemas and will be ignored with a warning.
|
|
637
|
+
|
|
653
638
|
Args:
|
|
654
639
|
func: Function to create schema for
|
|
655
640
|
name_override: Optional name override (otherwise the function name)
|
|
641
|
+
description_override: Optional description override
|
|
642
|
+
(otherwise the function docstring)
|
|
656
643
|
|
|
657
644
|
Returns:
|
|
658
645
|
Schema representing the function
|
|
659
646
|
|
|
660
647
|
Raises:
|
|
661
648
|
TypeError: If input is not callable
|
|
662
|
-
|
|
663
|
-
Note:
|
|
664
|
-
Variable arguments (*args) and keyword arguments (**kwargs) are not
|
|
665
|
-
supported in OpenAI function schemas and will be ignored with a warning.
|
|
666
649
|
"""
|
|
667
650
|
if not callable(func):
|
|
668
651
|
msg = f"Expected callable, got {type(func)}"
|
|
@@ -737,11 +720,10 @@ def create_schema(
|
|
|
737
720
|
|
|
738
721
|
return FunctionSchema(
|
|
739
722
|
name=name_override or getattr(func, "__name__", "unknown") or "unknown",
|
|
740
|
-
description=docstring.short_description,
|
|
723
|
+
description=description_override or docstring.short_description,
|
|
741
724
|
parameters=parameters, # Now includes required fields
|
|
742
725
|
required=required,
|
|
743
726
|
returns=returns_dct,
|
|
744
|
-
function_type=function_type,
|
|
745
727
|
)
|
|
746
728
|
|
|
747
729
|
|
|
@@ -4,13 +4,13 @@ schemez/code.py,sha256=usZLov9i5KpK1W2VJxngUzeetgrINtodiooG_AxN-y4,2072
|
|
|
4
4
|
schemez/code_generation/__init__.py,sha256=KV2ETgN8sKHlAOGnktURAHDJbz8jImBNputaxhdlin8,286
|
|
5
5
|
schemez/code_generation/namespace_callable.py,sha256=LiQHsS3J46snBj4uhKNrI-dboeQNPO2QwdbhSk3-gyE,2382
|
|
6
6
|
schemez/code_generation/route_helpers.py,sha256=YZfD0BUZ6_0iLnSzf2vKa8Fu2kJNfC-8oYzaQ--x8fA,4056
|
|
7
|
-
schemez/code_generation/tool_code_generator.py,sha256=
|
|
8
|
-
schemez/code_generation/toolset_code_generator.py,sha256=
|
|
7
|
+
schemez/code_generation/tool_code_generator.py,sha256=6j6F6dDhXruInaZeu3ReK0x7wA56oAGRfzRCytnlaQk,10766
|
|
8
|
+
schemez/code_generation/toolset_code_generator.py,sha256=0L3Wrqc4XUbtbptHFLWARogBWMy50iVpQzzvYQlzNe0,5806
|
|
9
9
|
schemez/convert.py,sha256=3sOxOgDaFzV7uiOUSM6_Sy0YlafIlZRSevs5y2vT1Kw,4403
|
|
10
10
|
schemez/create_type.py,sha256=wrdqdzXtfxZfsgp9IroldoYGTJs_Rdli8TiscqhV2bI,11647
|
|
11
11
|
schemez/docstrings.py,sha256=kmd660wcomXzKac0SSNYxPRNbVCUovrpmE9jwnVRS6c,4115
|
|
12
|
-
schemez/executable.py,sha256=
|
|
13
|
-
schemez/functionschema.py,sha256=
|
|
12
|
+
schemez/executable.py,sha256=ZvZJdUMI_EWjTqp-wUTzob3-cZDPmHgA03W756gSzKc,7190
|
|
13
|
+
schemez/functionschema.py,sha256=LCNHn4ZCitUIoiEylfI59i0Em5TmnJ5N0rfxUWWlE38,25803
|
|
14
14
|
schemez/helpers.py,sha256=YFx7UKYDI_sn5sVGAzzl5rcB26iBvLatvZlEFsQM5rc,7874
|
|
15
15
|
schemez/log.py,sha256=i0SDbIfWmuC_nfJdQOYAdUYaR0TBk3Mhu-K3M-lnAM4,364
|
|
16
16
|
schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -24,7 +24,7 @@ schemez/tool_executor/executor.py,sha256=0VOY9N4Epqdv_MYU-BoDiTKbFiyIwGk_pfe5Jcx
|
|
|
24
24
|
schemez/tool_executor/helpers.py,sha256=zxfI9tUkUx8Dy9fNP89-2kqfV8eZwQ3re2Gmd-oekb0,1476
|
|
25
25
|
schemez/tool_executor/types.py,sha256=l2DxUIEHP9bjLnEaXZ6X428cSviicTDJsc3wfSNqKxg,675
|
|
26
26
|
schemez/typedefs.py,sha256=3OAUQ1nin9nlsOcTPAO5xrsOqVUfwsH_7_cexQYREus,6091
|
|
27
|
-
schemez-1.4.
|
|
28
|
-
schemez-1.4.
|
|
29
|
-
schemez-1.4.
|
|
30
|
-
schemez-1.4.
|
|
27
|
+
schemez-1.4.4.dist-info/licenses/LICENSE,sha256=AteGCH9r177TxxrOFEiOARrastASsf7yW6MQxlAHdwA,1078
|
|
28
|
+
schemez-1.4.4.dist-info/WHEEL,sha256=DpNsHFUm_gffZe1FgzmqwuqiuPC6Y-uBCzibcJcdupM,78
|
|
29
|
+
schemez-1.4.4.dist-info/METADATA,sha256=3iBXsicqp4iXZ3nwDFvwHU_akqL17f0_zt5jqyCbNOE,11616
|
|
30
|
+
schemez-1.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|