返回 JoyAI-Echo
schema.py
1 """JSON Schema fragment types: all subclass :class:`~nanobot.agent.tools.base.Schema` for descriptions and constraints on tool parameters.
2
3 - ``to_json_schema()``: returns a dict compatible with :meth:`~nanobot.agent.tools.base.Schema.validate_json_schema_value` /
4 :class:`~nanobot.agent.tools.base.Tool`.
5 - ``validate_value(value, path)``: validates a single value against this schema; returns a list of error messages (empty means valid).
6
7 Shared validation and fragment normalization are on the class methods of :class:`~nanobot.agent.tools.base.Schema`.
8
9 Note: Python does not allow subclassing ``bool``, so booleans use :class:`BooleanSchema`.
10 """
11
12 from __future__ import annotations
13
14 from collections.abc import Mapping
15 from typing import Any
16
17 from nanobot.agent.tools.base import Schema
18
19
20 class StringSchema(Schema):
21 """String parameter: ``description`` documents the field; optional length bounds and enum."""
22
23 def __init__(
24 self,
25 description: str = "",
26 *,
27 min_length: int | None = None,
28 max_length: int | None = None,
29 enum: tuple[Any, ...] | list[Any] | None = None,
30 nullable: bool = False,
31 ) -> None:
32 self._description = description
33 self._min_length = min_length
34 self._max_length = max_length
35 self._enum = tuple(enum) if enum is not None else None
36 self._nullable = nullable
37
38 def to_json_schema(self) -> dict[str, Any]:
39 t: Any = "string"
40 if self._nullable:
41 t = ["string", "null"]
42 d: dict[str, Any] = {"type": t}
43 if self._description:
44 d["description"] = self._description
45 if self._min_length is not None:
46 d["minLength"] = self._min_length
47 if self._max_length is not None:
48 d["maxLength"] = self._max_length
49 if self._enum is not None:
50 d["enum"] = list(self._enum)
51 return d
52
53
54 class IntegerSchema(Schema):
55 """Integer parameter: optional placeholder int (legacy ctor signature), description, and bounds."""
56
57 def __init__(
58 self,
59 value: int = 0,
60 *,
61 description: str = "",
62 minimum: int | None = None,
63 maximum: int | None = None,
64 enum: tuple[int, ...] | list[int] | None = None,
65 nullable: bool = False,
66 ) -> None:
67 self._value = value
68 self._description = description
69 self._minimum = minimum
70 self._maximum = maximum
71 self._enum = tuple(enum) if enum is not None else None
72 self._nullable = nullable
73
74 def to_json_schema(self) -> dict[str, Any]:
75 t: Any = "integer"
76 if self._nullable:
77 t = ["integer", "null"]
78 d: dict[str, Any] = {"type": t}
79 if self._description:
80 d["description"] = self._description
81 if self._minimum is not None:
82 d["minimum"] = self._minimum
83 if self._maximum is not None:
84 d["maximum"] = self._maximum
85 if self._enum is not None:
86 d["enum"] = list(self._enum)
87 return d
88
89
90 class NumberSchema(Schema):
91 """Numeric parameter (JSON number): description and optional bounds."""
92
93 def __init__(
94 self,
95 value: float = 0.0,
96 *,
97 description: str = "",
98 minimum: float | None = None,
99 maximum: float | None = None,
100 enum: tuple[float, ...] | list[float] | None = None,
101 nullable: bool = False,
102 ) -> None:
103 self._value = value
104 self._description = description
105 self._minimum = minimum
106 self._maximum = maximum
107 self._enum = tuple(enum) if enum is not None else None
108 self._nullable = nullable
109
110 def to_json_schema(self) -> dict[str, Any]:
111 t: Any = "number"
112 if self._nullable:
113 t = ["number", "null"]
114 d: dict[str, Any] = {"type": t}
115 if self._description:
116 d["description"] = self._description
117 if self._minimum is not None:
118 d["minimum"] = self._minimum
119 if self._maximum is not None:
120 d["maximum"] = self._maximum
121 if self._enum is not None:
122 d["enum"] = list(self._enum)
123 return d
124
125
126 class BooleanSchema(Schema):
127 """Boolean parameter (standalone class because Python forbids subclassing ``bool``)."""
128
129 def __init__(
130 self,
131 *,
132 description: str = "",
133 default: bool | None = None,
134 nullable: bool = False,
135 ) -> None:
136 self._description = description
137 self._default = default
138 self._nullable = nullable
139
140 def to_json_schema(self) -> dict[str, Any]:
141 t: Any = "boolean"
142 if self._nullable:
143 t = ["boolean", "null"]
144 d: dict[str, Any] = {"type": t}
145 if self._description:
146 d["description"] = self._description
147 if self._default is not None:
148 d["default"] = self._default
149 return d
150
151
152 class ArraySchema(Schema):
153 """Array parameter: element schema is given by ``items``."""
154
155 def __init__(
156 self,
157 items: Any | None = None,
158 *,
159 description: str = "",
160 min_items: int | None = None,
161 max_items: int | None = None,
162 nullable: bool = False,
163 ) -> None:
164 self._items_schema: Any = items if items is not None else StringSchema("")
165 self._description = description
166 self._min_items = min_items
167 self._max_items = max_items
168 self._nullable = nullable
169
170 def to_json_schema(self) -> dict[str, Any]:
171 t: Any = "array"
172 if self._nullable:
173 t = ["array", "null"]
174 d: dict[str, Any] = {
175 "type": t,
176 "items": Schema.fragment(self._items_schema),
177 }
178 if self._description:
179 d["description"] = self._description
180 if self._min_items is not None:
181 d["minItems"] = self._min_items
182 if self._max_items is not None:
183 d["maxItems"] = self._max_items
184 return d
185
186
187 class ObjectSchema(Schema):
188 """Object parameter: ``properties`` or keyword args are field names; values are child Schema or JSON Schema dicts."""
189
190 def __init__(
191 self,
192 properties: Mapping[str, Any] | None = None,
193 *,
194 required: list[str] | None = None,
195 description: str = "",
196 additional_properties: bool | dict[str, Any] | None = None,
197 nullable: bool = False,
198 **kwargs: Any,
199 ) -> None:
200 self._properties = dict(properties or {}, **kwargs)
201 self._required = list(required or [])
202 self._root_description = description
203 self._additional_properties = additional_properties
204 self._nullable = nullable
205
206 def to_json_schema(self) -> dict[str, Any]:
207 t: Any = "object"
208 if self._nullable:
209 t = ["object", "null"]
210 props = {k: Schema.fragment(v) for k, v in self._properties.items()}
211 out: dict[str, Any] = {"type": t, "properties": props}
212 if self._required:
213 out["required"] = self._required
214 if self._root_description:
215 out["description"] = self._root_description
216 if self._additional_properties is not None:
217 out["additionalProperties"] = self._additional_properties
218 return out
219
220
221 def tool_parameters_schema(
222 *,
223 required: list[str] | None = None,
224 description: str = "",
225 **properties: Any,
226 ) -> dict[str, Any]:
227 """Build root tool parameters ``{"type": "object", "properties": ...}`` for :meth:`Tool.parameters`."""
228 return ObjectSchema(
229 required=required,
230 description=description,
231 **properties,
232 ).to_json_schema()
233
233 lines PYTHON