opentool-python 0.1.0__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.
- opentool_python/__init__.py +62 -0
- opentool_python/client/__init__.py +18 -0
- opentool_python/client/exceptions.py +48 -0
- opentool_python/client/opentool_client.py +238 -0
- opentool_python/dto.py +124 -0
- opentool_python/json_loader.py +23 -0
- opentool_python/llm/__init__.py +3 -0
- opentool_python/llm/model.py +35 -0
- opentool_python/model/__init__.py +23 -0
- opentool_python/model/data_type.py +8 -0
- opentool_python/model/function_model.py +66 -0
- opentool_python/model/info.py +25 -0
- opentool_python/model/opentool.py +59 -0
- opentool_python/model/parameter.py +36 -0
- opentool_python/model/return_model.py +27 -0
- opentool_python/model/schema.py +251 -0
- opentool_python/model/server.py +20 -0
- opentool_python/opentool_client.py +24 -0
- opentool_python/opentool_schema.py +27 -0
- opentool_python/opentool_server.py +17 -0
- opentool_python/server/__init__.py +5 -0
- opentool_python/server/cli_arguments.py +41 -0
- opentool_python/server/cli_tool.py +10 -0
- opentool_python/server/exceptions.py +31 -0
- opentool_python/server/opentool_server.py +310 -0
- opentool_python/tool/__init__.py +18 -0
- opentool_python/tool/exceptions.py +61 -0
- opentool_python/tool/model.py +24 -0
- opentool_python/tool/tool.py +30 -0
- opentool_python-0.1.0.dist-info/METADATA +180 -0
- opentool_python-0.1.0.dist-info/RECORD +34 -0
- opentool_python-0.1.0.dist-info/WHEEL +5 -0
- opentool_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- opentool_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .function_model import FunctionModel
|
|
7
|
+
from .info import Info
|
|
8
|
+
from .schema import Schema, SchemaReferenceResolver, SchemaResolver
|
|
9
|
+
from .server import ServerConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class OpenTool:
|
|
14
|
+
opentool: str
|
|
15
|
+
info: Info
|
|
16
|
+
functions: list[FunctionModel]
|
|
17
|
+
server: ServerConfig | None = None
|
|
18
|
+
schemas: dict[str, Schema] | None = None
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_json(
|
|
22
|
+
cls,
|
|
23
|
+
data: dict[str, Any],
|
|
24
|
+
*,
|
|
25
|
+
schema_reference_resolver: SchemaReferenceResolver | None = None,
|
|
26
|
+
) -> "OpenTool":
|
|
27
|
+
raw_schemas = data.get("schemas")
|
|
28
|
+
if raw_schemas is not None and not isinstance(raw_schemas, dict):
|
|
29
|
+
raise ValueError("schemas: expected an object")
|
|
30
|
+
schema_resolver = SchemaResolver(raw_schemas, schema_reference_resolver)
|
|
31
|
+
schemas = schema_resolver.resolve_all() if raw_schemas is not None else None
|
|
32
|
+
functions = data["functions"]
|
|
33
|
+
if not isinstance(functions, list) or any(not isinstance(item, dict) for item in functions):
|
|
34
|
+
raise ValueError("functions: expected an array of objects")
|
|
35
|
+
|
|
36
|
+
return cls(
|
|
37
|
+
opentool=str(data["opentool"]),
|
|
38
|
+
info=Info.from_json(data["info"]),
|
|
39
|
+
server=ServerConfig.from_json(data["server"]) if isinstance(data.get("server"), dict) else None,
|
|
40
|
+
functions=[
|
|
41
|
+
FunctionModel.from_json(item, schema_resolver=schema_resolver)
|
|
42
|
+
for item in functions
|
|
43
|
+
],
|
|
44
|
+
schemas=schemas,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def to_json(self) -> dict[str, Any]:
|
|
48
|
+
payload: dict[str, Any] = {
|
|
49
|
+
"opentool": self.opentool,
|
|
50
|
+
"info": self.info.to_json(),
|
|
51
|
+
"functions": [function.to_json() for function in self.functions],
|
|
52
|
+
}
|
|
53
|
+
if self.server is not None:
|
|
54
|
+
payload["server"] = self.server.to_json()
|
|
55
|
+
if self.schemas is not None:
|
|
56
|
+
payload["schemas"] = {
|
|
57
|
+
name: schema.to_json() for name, schema in self.schemas.items()
|
|
58
|
+
}
|
|
59
|
+
return payload
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .schema import Schema, SchemaResolver
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Parameter:
|
|
11
|
+
name: str
|
|
12
|
+
schema: Schema
|
|
13
|
+
required: bool = False
|
|
14
|
+
description: str | None = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_json(cls, data: dict[str, Any], *, schema_resolver: SchemaResolver | None = None) -> "Parameter":
|
|
18
|
+
required = data.get("required")
|
|
19
|
+
if required is not None and not isinstance(required, bool):
|
|
20
|
+
raise ValueError("required: expected a boolean")
|
|
21
|
+
return cls(
|
|
22
|
+
name=str(data["name"]),
|
|
23
|
+
schema=Schema.from_json(data["schema"], resolver=schema_resolver),
|
|
24
|
+
required=required if required is not None else False,
|
|
25
|
+
description=data.get("description"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def to_json(self) -> dict[str, Any]:
|
|
29
|
+
payload: dict[str, Any] = {
|
|
30
|
+
"name": self.name,
|
|
31
|
+
"schema": self.schema.to_json(),
|
|
32
|
+
"required": self.required,
|
|
33
|
+
}
|
|
34
|
+
if self.description is not None:
|
|
35
|
+
payload["description"] = self.description
|
|
36
|
+
return payload
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .schema import Schema, SchemaResolver
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Return:
|
|
11
|
+
name: str
|
|
12
|
+
schema: Schema
|
|
13
|
+
description: str | None = None
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_json(cls, data: dict[str, Any], *, schema_resolver: SchemaResolver | None = None) -> "Return":
|
|
17
|
+
return cls(
|
|
18
|
+
name=str(data["name"]),
|
|
19
|
+
schema=Schema.from_json(data["schema"], resolver=schema_resolver),
|
|
20
|
+
description=data.get("description"),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def to_json(self) -> dict[str, Any]:
|
|
24
|
+
payload: dict[str, Any] = {"name": self.name, "schema": self.schema.to_json()}
|
|
25
|
+
if self.description is not None:
|
|
26
|
+
payload["description"] = self.description
|
|
27
|
+
return payload
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import quote, unquote
|
|
8
|
+
|
|
9
|
+
from jsonschema import Draft202012Validator, FormatChecker
|
|
10
|
+
from jsonschema.validators import validator_for
|
|
11
|
+
from referencing import Registry, Resource
|
|
12
|
+
from referencing.exceptions import NoSuchResource
|
|
13
|
+
from referencing.jsonschema import DRAFT202012
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SchemaValue = dict[str, Any] | bool
|
|
17
|
+
SchemaReferenceResolver = Callable[[str], SchemaValue | None]
|
|
18
|
+
_LOCAL_PREFIX = "https://opentool.invalid/schemas/"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SchemaType:
|
|
22
|
+
BOOLEAN = "boolean"
|
|
23
|
+
INTEGER = "integer"
|
|
24
|
+
NUMBER = "number"
|
|
25
|
+
STRING = "string"
|
|
26
|
+
ARRAY = "array"
|
|
27
|
+
OBJECT = "object"
|
|
28
|
+
NULL = "null"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class SchemaValidationResult:
|
|
33
|
+
valid: bool
|
|
34
|
+
errors: list[str]
|
|
35
|
+
warnings: list[str] = field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def is_valid(self) -> bool:
|
|
39
|
+
return self.valid
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SchemaResolver:
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
definitions: dict[str, SchemaValue] | None = None,
|
|
46
|
+
reference_resolver: SchemaReferenceResolver | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
self._definitions = deepcopy(definitions) if definitions is not None else {}
|
|
49
|
+
|
|
50
|
+
def retrieve(uri: str) -> Resource:
|
|
51
|
+
value = reference_resolver(uri) if reference_resolver is not None else None
|
|
52
|
+
if value is None:
|
|
53
|
+
raise NoSuchResource(ref=uri)
|
|
54
|
+
return self._resource(value)
|
|
55
|
+
|
|
56
|
+
self.registry = Registry(retrieve=retrieve).with_resources(
|
|
57
|
+
(_LOCAL_PREFIX + quote(name, safe=""), self._resource(value))
|
|
58
|
+
for name, value in self._definitions.items()
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def resolve_all(self) -> dict[str, Schema]:
|
|
62
|
+
return {name: Schema.from_json(value, resolver=self) for name, value in self._definitions.items()}
|
|
63
|
+
|
|
64
|
+
def _resource(self, value: SchemaValue) -> Resource:
|
|
65
|
+
if not isinstance(value, (dict, bool)):
|
|
66
|
+
raise ValueError("Expected a JSON Schema object or boolean")
|
|
67
|
+
return Resource.from_contents(self.normalize(value), default_specification=DRAFT202012)
|
|
68
|
+
|
|
69
|
+
def keyword_view(self, value: dict[str, Any]) -> dict[str, Any]:
|
|
70
|
+
chain = [value]
|
|
71
|
+
seen = set()
|
|
72
|
+
while isinstance(ref := value.get("$ref"), str) and ref.startswith("#/schemas/"):
|
|
73
|
+
token = ref[len("#/schemas/"):]
|
|
74
|
+
if "/" in token or ref in seen:
|
|
75
|
+
break
|
|
76
|
+
seen.add(ref)
|
|
77
|
+
name = unquote(token).replace("~1", "/").replace("~0", "~")
|
|
78
|
+
target = self._definitions.get(name)
|
|
79
|
+
if not isinstance(target, dict):
|
|
80
|
+
break
|
|
81
|
+
chain.append(target)
|
|
82
|
+
value = target
|
|
83
|
+
view: dict[str, Any] = {}
|
|
84
|
+
for node in reversed(chain):
|
|
85
|
+
view.update(node)
|
|
86
|
+
return view
|
|
87
|
+
|
|
88
|
+
def normalize(self, value: SchemaValue) -> SchemaValue:
|
|
89
|
+
if not isinstance(value, dict):
|
|
90
|
+
return value
|
|
91
|
+
normalized = dict(value)
|
|
92
|
+
# Only visit schema positions, leaving literal values in enum, const and annotations intact.
|
|
93
|
+
for key in ("$ref", "$dynamicRef", "$recursiveRef"):
|
|
94
|
+
ref = value.get(key)
|
|
95
|
+
if isinstance(ref, str) and ref.startswith("#/schemas/"):
|
|
96
|
+
token, separator, rest = ref[len("#/schemas/"):].partition("/")
|
|
97
|
+
name = unquote(token).replace("~1", "/").replace("~0", "~")
|
|
98
|
+
if name not in self._definitions:
|
|
99
|
+
raise ValueError(f"Unknown OpenTool schema reference: {ref}")
|
|
100
|
+
normalized[key] = _LOCAL_PREFIX + quote(name, safe="") + (f"#/{rest}" if separator else "")
|
|
101
|
+
for key in ("$defs", "definitions", "properties", "patternProperties", "dependentSchemas", "dependencies"):
|
|
102
|
+
if isinstance(value.get(key), dict):
|
|
103
|
+
normalized[key] = {name: self.normalize(node) for name, node in value[key].items()}
|
|
104
|
+
for key in ("allOf", "anyOf", "oneOf", "prefixItems", "items"):
|
|
105
|
+
if isinstance(value.get(key), list):
|
|
106
|
+
normalized[key] = [self.normalize(node) for node in value[key]]
|
|
107
|
+
for key in (
|
|
108
|
+
"additionalProperties", "unevaluatedProperties", "items", "additionalItems",
|
|
109
|
+
"unevaluatedItems", "contains", "propertyNames", "not", "if", "then", "else", "contentSchema",
|
|
110
|
+
):
|
|
111
|
+
if isinstance(value.get(key), (dict, bool)):
|
|
112
|
+
normalized[key] = self.normalize(value[key])
|
|
113
|
+
return normalized
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class SchemasSingleton:
|
|
117
|
+
"""Legacy registry for standalone Schema.from_json callers."""
|
|
118
|
+
|
|
119
|
+
_resolver = SchemaResolver()
|
|
120
|
+
_schemas: dict[str, Schema] = {}
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def init_instance(cls, schemas_json: dict[str, SchemaValue]) -> None:
|
|
124
|
+
resolver = SchemaResolver(schemas_json)
|
|
125
|
+
schemas = resolver.resolve_all()
|
|
126
|
+
cls._resolver = resolver
|
|
127
|
+
cls._schemas = schemas
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def get_instance(cls) -> dict[str, Schema]:
|
|
131
|
+
return dict(cls._schemas)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Schema:
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
type: str | list[str],
|
|
138
|
+
description: str | None = None,
|
|
139
|
+
properties: dict[str, Schema] | None = None,
|
|
140
|
+
items: Schema | None = None,
|
|
141
|
+
enum_: list[Any] | None = None,
|
|
142
|
+
required: list[str] | None = None,
|
|
143
|
+
**keywords: Any,
|
|
144
|
+
) -> None:
|
|
145
|
+
value = {"type": type, **keywords}
|
|
146
|
+
if description is not None:
|
|
147
|
+
value["description"] = description
|
|
148
|
+
if properties is not None:
|
|
149
|
+
value["properties"] = {key: schema.to_json() for key, schema in properties.items()}
|
|
150
|
+
if items is not None:
|
|
151
|
+
value["items"] = items.to_json()
|
|
152
|
+
if enum_ is not None:
|
|
153
|
+
value["enum"] = enum_
|
|
154
|
+
if required is not None:
|
|
155
|
+
value["required"] = required
|
|
156
|
+
self._initialize(value, SchemaResolver())
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def from_json(cls, data: SchemaValue, *, resolver: SchemaResolver | None = None) -> Schema:
|
|
160
|
+
schema = cls.__new__(cls)
|
|
161
|
+
schema._initialize(data, resolver if resolver is not None else SchemasSingleton._resolver)
|
|
162
|
+
return schema
|
|
163
|
+
|
|
164
|
+
def _initialize(self, data: SchemaValue, resolver: SchemaResolver) -> None:
|
|
165
|
+
if not isinstance(data, (dict, bool)):
|
|
166
|
+
raise ValueError("Expected a JSON Schema object or boolean")
|
|
167
|
+
self._json = deepcopy(data)
|
|
168
|
+
self._resolver = resolver
|
|
169
|
+
normalized = resolver.normalize(self._json)
|
|
170
|
+
validator_class = validator_for(normalized, default=Draft202012Validator)
|
|
171
|
+
validator_class.check_schema(normalized)
|
|
172
|
+
self._validator = validator_class(normalized, registry=resolver.registry)
|
|
173
|
+
self._format_validator = self._validator.evolve(format_checker=validator_class.FORMAT_CHECKER or FormatChecker())
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def is_boolean_schema(self) -> bool:
|
|
177
|
+
return isinstance(self._json, bool)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def raw_schema(self) -> SchemaValue:
|
|
181
|
+
return self.to_json()
|
|
182
|
+
|
|
183
|
+
def __getitem__(self, keyword: str) -> Any:
|
|
184
|
+
if not isinstance(self._json, dict):
|
|
185
|
+
return None
|
|
186
|
+
if keyword in self._json:
|
|
187
|
+
return deepcopy(self._json[keyword])
|
|
188
|
+
return deepcopy(self._resolver.keyword_view(self._json).get(keyword))
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def has_type(self) -> bool:
|
|
192
|
+
return self["type"] is not None
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def type(self) -> str:
|
|
196
|
+
value = self["type"]
|
|
197
|
+
if isinstance(value, list):
|
|
198
|
+
return value[0] if value else ""
|
|
199
|
+
return value if isinstance(value, str) else ""
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def type_union(self) -> list[str] | None:
|
|
203
|
+
value = self["type"]
|
|
204
|
+
return value if isinstance(value, list) else None
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def description(self) -> str | None:
|
|
208
|
+
return self["description"]
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def enum_(self) -> list[Any] | None:
|
|
212
|
+
return self["enum"]
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def required(self) -> list[str] | None:
|
|
216
|
+
return self["required"]
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def properties(self) -> dict[str, Schema] | None:
|
|
220
|
+
properties = self["properties"]
|
|
221
|
+
if properties is None:
|
|
222
|
+
return None
|
|
223
|
+
return {key: self._child(node) for key, node in properties.items()}
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def items(self) -> Schema | list[Schema] | None:
|
|
227
|
+
items = self["items"]
|
|
228
|
+
if isinstance(items, list):
|
|
229
|
+
return [self._child(node) for node in items]
|
|
230
|
+
return self._child(items) if items is not None else None
|
|
231
|
+
|
|
232
|
+
def _child(self, node: SchemaValue) -> Schema:
|
|
233
|
+
child = self.__class__.__new__(self.__class__)
|
|
234
|
+
child._json = node
|
|
235
|
+
child._resolver = self._resolver
|
|
236
|
+
normalized = self._resolver.normalize(node)
|
|
237
|
+
child._validator = self._validator.evolve(schema=normalized)
|
|
238
|
+
child._format_validator = self._format_validator.evolve(schema=normalized)
|
|
239
|
+
return child
|
|
240
|
+
|
|
241
|
+
def to_json(self) -> SchemaValue:
|
|
242
|
+
return deepcopy(self._json)
|
|
243
|
+
|
|
244
|
+
def validate(self, instance: Any, *, validate_formats: bool = False) -> SchemaValidationResult:
|
|
245
|
+
validator = self._format_validator if validate_formats else self._validator
|
|
246
|
+
errors = [f"{error.json_path}: {error.message}" for error in validator.iter_errors(instance)]
|
|
247
|
+
return SchemaValidationResult(valid=not errors, errors=errors)
|
|
248
|
+
|
|
249
|
+
def is_valid(self, instance: Any, *, validate_formats: bool = False) -> bool:
|
|
250
|
+
validator = self._format_validator if validate_formats else self._validator
|
|
251
|
+
return validator.is_valid(instance)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ServerConfig:
|
|
9
|
+
url: str
|
|
10
|
+
description: str | None = None
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def from_json(cls, data: dict[str, Any]) -> "ServerConfig":
|
|
14
|
+
return cls(url=str(data["url"]), description=data.get("description"))
|
|
15
|
+
|
|
16
|
+
def to_json(self) -> dict[str, Any]:
|
|
17
|
+
payload: dict[str, Any] = {"url": self.url}
|
|
18
|
+
if self.description is not None:
|
|
19
|
+
payload["description"] = self.description
|
|
20
|
+
return payload
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Client exports for OpenTool Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .client.exceptions import (
|
|
4
|
+
ErrorNullException,
|
|
5
|
+
OpenToolServerCallException,
|
|
6
|
+
OpenToolServerNoAccessException,
|
|
7
|
+
OpenToolServerUnauthorizedException,
|
|
8
|
+
ResponseNullException,
|
|
9
|
+
)
|
|
10
|
+
from .client.opentool_client import Client, OpenToolClient
|
|
11
|
+
from .dto import * # noqa: F403
|
|
12
|
+
from .llm.model import * # noqa: F403
|
|
13
|
+
from .tool.exceptions import * # noqa: F403
|
|
14
|
+
from .tool.model import * # noqa: F403
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Client",
|
|
18
|
+
"OpenToolClient",
|
|
19
|
+
"ResponseNullException",
|
|
20
|
+
"ErrorNullException",
|
|
21
|
+
"OpenToolServerUnauthorizedException",
|
|
22
|
+
"OpenToolServerNoAccessException",
|
|
23
|
+
"OpenToolServerCallException",
|
|
24
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Schema exports for OpenTool Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .json_loader import OpenToolJsonLoader
|
|
4
|
+
from .model.data_type import DataType
|
|
5
|
+
from .model.function_model import FunctionModel
|
|
6
|
+
from .model.info import Info
|
|
7
|
+
from .model.opentool import OpenTool
|
|
8
|
+
from .model.parameter import Parameter
|
|
9
|
+
from .model.return_model import Return
|
|
10
|
+
from .model.schema import Schema, SchemaReferenceResolver, SchemaResolver, SchemaType, SchemaValidationResult
|
|
11
|
+
from .model.server import ServerConfig
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"FunctionModel",
|
|
15
|
+
"Info",
|
|
16
|
+
"OpenTool",
|
|
17
|
+
"Parameter",
|
|
18
|
+
"Return",
|
|
19
|
+
"Schema",
|
|
20
|
+
"SchemaType",
|
|
21
|
+
"SchemaReferenceResolver",
|
|
22
|
+
"SchemaResolver",
|
|
23
|
+
"SchemaValidationResult",
|
|
24
|
+
"DataType",
|
|
25
|
+
"ServerConfig",
|
|
26
|
+
"OpenToolJsonLoader",
|
|
27
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Server exports for OpenTool Python SDK."""
|
|
2
|
+
|
|
3
|
+
from .dto import * # noqa: F403
|
|
4
|
+
from .llm.model import * # noqa: F403
|
|
5
|
+
from .opentool_schema import * # noqa: F403
|
|
6
|
+
from .server.cli_arguments import CliArguments
|
|
7
|
+
from .server.opentool_server import OpenToolServer, Server
|
|
8
|
+
from .server.cli_tool import CliTool
|
|
9
|
+
from .tool.exceptions import * # noqa: F403
|
|
10
|
+
from .tool.model import * # noqa: F403
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Server",
|
|
14
|
+
"OpenToolServer",
|
|
15
|
+
"CliArguments",
|
|
16
|
+
"CliTool",
|
|
17
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..dto import (
|
|
7
|
+
CLI_ARGUMENT_APIKEYS,
|
|
8
|
+
CLI_ARGUMENT_HOST,
|
|
9
|
+
CLI_ARGUMENT_PORT,
|
|
10
|
+
CLI_ARGUMENT_TAG,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CliArguments:
|
|
15
|
+
def __init__(self, args: list[str]) -> None:
|
|
16
|
+
self._args = args
|
|
17
|
+
self._parser = argparse.ArgumentParser(add_help=False)
|
|
18
|
+
self._parser.add_argument(f"--{CLI_ARGUMENT_TAG}")
|
|
19
|
+
self._parser.add_argument(f"--{CLI_ARGUMENT_HOST}")
|
|
20
|
+
self._parser.add_argument(f"--{CLI_ARGUMENT_PORT}")
|
|
21
|
+
self._parser.add_argument(f"--{CLI_ARGUMENT_APIKEYS}", action="append")
|
|
22
|
+
|
|
23
|
+
def add_custom_option(self, name: str, **kwargs: Any) -> "CliArguments":
|
|
24
|
+
argument_name = f"--{name}"
|
|
25
|
+
self._parser.add_argument(argument_name, **kwargs)
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def add_custom_multi_option(self, name: str, **kwargs: Any) -> "CliArguments":
|
|
29
|
+
argument_name = f"--{name}"
|
|
30
|
+
if "action" not in kwargs:
|
|
31
|
+
kwargs["action"] = "append"
|
|
32
|
+
self._parser.add_argument(argument_name, **kwargs)
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def parse(self) -> dict[str, Any]:
|
|
36
|
+
namespace, _ = self._parser.parse_known_args(self._args)
|
|
37
|
+
data: dict[str, Any] = {}
|
|
38
|
+
for key, value in vars(namespace).items():
|
|
39
|
+
if value is not None:
|
|
40
|
+
data[key] = value
|
|
41
|
+
return data
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ServerException(Exception):
|
|
9
|
+
code: int
|
|
10
|
+
message: str
|
|
11
|
+
|
|
12
|
+
def to_json(self) -> dict[str, Any]:
|
|
13
|
+
return {"code": self.code, "message": self.message}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class JsonParseException(ServerException):
|
|
18
|
+
message: str = "Parse error"
|
|
19
|
+
code: int = -32700
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return json.dumps(self.to_json(), ensure_ascii=False)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class InvalidJsonPayloadException(ServerException):
|
|
27
|
+
message: str = "Invalid JSON payload: expected object"
|
|
28
|
+
code: int = -32700
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
return json.dumps(self.to_json(), ensure_ascii=False)
|