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,62 @@
|
|
|
1
|
+
"""OpenTool SDK for Python."""
|
|
2
|
+
|
|
3
|
+
from .dto import (
|
|
4
|
+
CLI_ARGUMENT_APIKEYS,
|
|
5
|
+
CLI_ARGUMENT_HOST,
|
|
6
|
+
CLI_ARGUMENT_PORT,
|
|
7
|
+
CLI_ARGUMENT_TAG,
|
|
8
|
+
DEFAULT_PREFIX,
|
|
9
|
+
DEFAULT_TOOL_HOST,
|
|
10
|
+
DEFAULT_TOOL_PORT,
|
|
11
|
+
DEFAULT_TOOL_TAG,
|
|
12
|
+
HostType,
|
|
13
|
+
JsonRPCHttpRequestBody,
|
|
14
|
+
JsonRPCHttpResponseBody,
|
|
15
|
+
JsonRPCHttpResponseBodyError,
|
|
16
|
+
StatusInfo,
|
|
17
|
+
StatusType,
|
|
18
|
+
Version,
|
|
19
|
+
)
|
|
20
|
+
from .json_loader import OpenToolJsonLoader
|
|
21
|
+
from .llm.model import FunctionCall, ToolReturn
|
|
22
|
+
from .model.data_type import DataType
|
|
23
|
+
from .model.function_model import FunctionModel
|
|
24
|
+
from .model.info import Info
|
|
25
|
+
from .model.opentool import OpenTool
|
|
26
|
+
from .model.parameter import Parameter
|
|
27
|
+
from .model.return_model import Return
|
|
28
|
+
from .model.schema import Schema, SchemaReferenceResolver, SchemaResolver, SchemaType, SchemaValidationResult
|
|
29
|
+
from .model.server import ServerConfig
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"HostType",
|
|
33
|
+
"Version",
|
|
34
|
+
"JsonRPCHttpRequestBody",
|
|
35
|
+
"JsonRPCHttpResponseBody",
|
|
36
|
+
"JsonRPCHttpResponseBodyError",
|
|
37
|
+
"StatusInfo",
|
|
38
|
+
"StatusType",
|
|
39
|
+
"DEFAULT_PREFIX",
|
|
40
|
+
"DEFAULT_TOOL_TAG",
|
|
41
|
+
"DEFAULT_TOOL_HOST",
|
|
42
|
+
"DEFAULT_TOOL_PORT",
|
|
43
|
+
"CLI_ARGUMENT_TAG",
|
|
44
|
+
"CLI_ARGUMENT_HOST",
|
|
45
|
+
"CLI_ARGUMENT_PORT",
|
|
46
|
+
"CLI_ARGUMENT_APIKEYS",
|
|
47
|
+
"FunctionCall",
|
|
48
|
+
"ToolReturn",
|
|
49
|
+
"DataType",
|
|
50
|
+
"FunctionModel",
|
|
51
|
+
"Info",
|
|
52
|
+
"OpenTool",
|
|
53
|
+
"Parameter",
|
|
54
|
+
"Return",
|
|
55
|
+
"Schema",
|
|
56
|
+
"SchemaType",
|
|
57
|
+
"SchemaReferenceResolver",
|
|
58
|
+
"SchemaResolver",
|
|
59
|
+
"SchemaValidationResult",
|
|
60
|
+
"ServerConfig",
|
|
61
|
+
"OpenToolJsonLoader",
|
|
62
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .exceptions import (
|
|
2
|
+
ErrorNullException,
|
|
3
|
+
OpenToolServerCallException,
|
|
4
|
+
OpenToolServerNoAccessException,
|
|
5
|
+
OpenToolServerUnauthorizedException,
|
|
6
|
+
ResponseNullException,
|
|
7
|
+
)
|
|
8
|
+
from .opentool_client import Client, OpenToolClient
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Client",
|
|
12
|
+
"OpenToolClient",
|
|
13
|
+
"ResponseNullException",
|
|
14
|
+
"ErrorNullException",
|
|
15
|
+
"OpenToolServerUnauthorizedException",
|
|
16
|
+
"OpenToolServerNoAccessException",
|
|
17
|
+
"OpenToolServerCallException",
|
|
18
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ResponseNullException(Exception):
|
|
9
|
+
code: int
|
|
10
|
+
message: str = "Response is null"
|
|
11
|
+
|
|
12
|
+
def to_json(self) -> dict[str, Any]:
|
|
13
|
+
return {"code": self.code, "message": self.message}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ErrorNullException(Exception):
|
|
18
|
+
code: int
|
|
19
|
+
message: str = "Error is null"
|
|
20
|
+
|
|
21
|
+
def to_json(self) -> dict[str, Any]:
|
|
22
|
+
return {"code": self.code, "message": self.message}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class OpenToolServerUnauthorizedException(Exception):
|
|
27
|
+
code: int = 401
|
|
28
|
+
message: str = "Please check API Key is VALID or NOT"
|
|
29
|
+
|
|
30
|
+
def to_json(self) -> dict[str, Any]:
|
|
31
|
+
return {"code": self.code, "message": self.message}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class OpenToolServerNoAccessException(Exception):
|
|
36
|
+
code: int = 404
|
|
37
|
+
message: str = "Please check OpenTool Server is RUNNING or NOT"
|
|
38
|
+
|
|
39
|
+
def to_json(self) -> dict[str, Any]:
|
|
40
|
+
return {"code": self.code, "message": self.message}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class OpenToolServerCallException(Exception):
|
|
45
|
+
message: str
|
|
46
|
+
|
|
47
|
+
def to_json(self) -> dict[str, Any]:
|
|
48
|
+
return {"message": self.message}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import socket
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Callable, Iterator
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib import error, parse, request
|
|
9
|
+
|
|
10
|
+
from ..dto import (
|
|
11
|
+
DEFAULT_PREFIX,
|
|
12
|
+
LOAD_METADATA_NOT_FOUND_CODE,
|
|
13
|
+
JsonRPCHttpRequestBody,
|
|
14
|
+
JsonRPCHttpResponseBody,
|
|
15
|
+
StatusInfo,
|
|
16
|
+
Version,
|
|
17
|
+
)
|
|
18
|
+
from ..llm.model import FunctionCall, ToolReturn
|
|
19
|
+
from ..model.opentool import OpenTool
|
|
20
|
+
from ..model.schema import SchemaReferenceResolver
|
|
21
|
+
from ..tool.model import EventType
|
|
22
|
+
from .exceptions import (
|
|
23
|
+
OpenToolServerCallException,
|
|
24
|
+
OpenToolServerNoAccessException,
|
|
25
|
+
OpenToolServerUnauthorizedException,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Client(ABC):
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def version(self) -> Version:
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def call(self, function_call: FunctionCall) -> ToolReturn:
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def stream_call(
|
|
40
|
+
self,
|
|
41
|
+
function_call: FunctionCall,
|
|
42
|
+
on_stream: Callable[[str, ToolReturn], None],
|
|
43
|
+
) -> None:
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
def load(self) -> OpenTool | None:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def stop(self) -> StatusInfo | None:
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class OpenToolClient(Client):
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
tool_host: str,
|
|
59
|
+
tool_port: int,
|
|
60
|
+
tool_api_key: str | None = None,
|
|
61
|
+
is_ssl: bool = False,
|
|
62
|
+
schema_reference_resolver: SchemaReferenceResolver | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
self.schema_reference_resolver = schema_reference_resolver
|
|
65
|
+
self.is_ssl = is_ssl
|
|
66
|
+
self.host = tool_host
|
|
67
|
+
self.port = tool_port
|
|
68
|
+
self.prefix = DEFAULT_PREFIX
|
|
69
|
+
self.tool_api_key = tool_api_key
|
|
70
|
+
protocol = "https" if self.is_ssl else "http"
|
|
71
|
+
self.base_url = f"{protocol}://{self.host}:{self.port}{self.prefix}"
|
|
72
|
+
|
|
73
|
+
def version(self) -> Version:
|
|
74
|
+
data = self._request_json("GET", "/version")
|
|
75
|
+
return Version.from_json(data)
|
|
76
|
+
|
|
77
|
+
def call(self, function_call: FunctionCall) -> ToolReturn:
|
|
78
|
+
result = self._call_json_rpc_http(
|
|
79
|
+
function_call.id,
|
|
80
|
+
function_call.name,
|
|
81
|
+
function_call.arguments,
|
|
82
|
+
)
|
|
83
|
+
return ToolReturn(id=function_call.id, result=result)
|
|
84
|
+
|
|
85
|
+
def _call_json_rpc_http(
|
|
86
|
+
self,
|
|
87
|
+
request_id: str,
|
|
88
|
+
method: str,
|
|
89
|
+
params: dict[str, Any],
|
|
90
|
+
) -> dict[str, Any]:
|
|
91
|
+
body = JsonRPCHttpRequestBody(id=request_id, method=method, params=params)
|
|
92
|
+
data = self._request_json("POST", "/call", body.to_json())
|
|
93
|
+
response_body = JsonRPCHttpResponseBody.from_json(data)
|
|
94
|
+
if response_body.error is not None:
|
|
95
|
+
raise OpenToolServerCallException(response_body.error.message)
|
|
96
|
+
return response_body.result
|
|
97
|
+
|
|
98
|
+
def stream_call(
|
|
99
|
+
self,
|
|
100
|
+
function_call: FunctionCall,
|
|
101
|
+
on_stream: Callable[[str, ToolReturn], None],
|
|
102
|
+
) -> None:
|
|
103
|
+
body = JsonRPCHttpRequestBody(
|
|
104
|
+
id=function_call.id,
|
|
105
|
+
method=function_call.name,
|
|
106
|
+
params=function_call.arguments,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
for event_name, event_data in self._stream_call_json_rpc_http(body.to_json()):
|
|
110
|
+
event_name = event_name.lower()
|
|
111
|
+
if event_name == EventType.START:
|
|
112
|
+
on_stream(event_name, ToolReturn(id=function_call.id, result=event_data))
|
|
113
|
+
elif event_name == EventType.DONE:
|
|
114
|
+
break
|
|
115
|
+
elif event_name in (EventType.DATA, EventType.ERROR):
|
|
116
|
+
response_body = JsonRPCHttpResponseBody.from_json(event_data)
|
|
117
|
+
if response_body.error is not None:
|
|
118
|
+
raise OpenToolServerCallException(response_body.error.message)
|
|
119
|
+
if event_name == EventType.ERROR:
|
|
120
|
+
raise OpenToolServerCallException(str(event_data.get("message", event_data)))
|
|
121
|
+
on_stream(event_name, ToolReturn(id=function_call.id, result=response_body.result))
|
|
122
|
+
|
|
123
|
+
on_stream(
|
|
124
|
+
EventType.DONE,
|
|
125
|
+
ToolReturn(id=function_call.id, result={EventType.DONE: function_call.name}),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _stream_call_json_rpc_http(
|
|
129
|
+
self, payload: dict[str, Any]
|
|
130
|
+
) -> Iterator[tuple[str, dict[str, Any]]]:
|
|
131
|
+
url = f"{self.base_url}/streamCall"
|
|
132
|
+
request_data = json.dumps(payload).encode("utf-8")
|
|
133
|
+
headers = {
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
"Accept": "text/event-stream",
|
|
136
|
+
}
|
|
137
|
+
if self.tool_api_key:
|
|
138
|
+
headers["Authorization"] = f"Bearer {self.tool_api_key}"
|
|
139
|
+
|
|
140
|
+
req = request.Request(url=url, method="POST", data=request_data, headers=headers)
|
|
141
|
+
try:
|
|
142
|
+
with request.urlopen(req, timeout=60) as response:
|
|
143
|
+
if response.status == 401:
|
|
144
|
+
raise OpenToolServerUnauthorizedException()
|
|
145
|
+
buffer: list[str] = []
|
|
146
|
+
for raw_line in response:
|
|
147
|
+
line = raw_line.decode("utf-8").rstrip("\r\n")
|
|
148
|
+
if line == "":
|
|
149
|
+
event_name, event_data = _parse_sse_block(buffer)
|
|
150
|
+
buffer = []
|
|
151
|
+
if event_name is not None and event_data is not None:
|
|
152
|
+
yield event_name, event_data
|
|
153
|
+
continue
|
|
154
|
+
buffer.append(line)
|
|
155
|
+
if buffer:
|
|
156
|
+
event_name, event_data = _parse_sse_block(buffer)
|
|
157
|
+
if event_name is not None and event_data is not None:
|
|
158
|
+
yield event_name, event_data
|
|
159
|
+
except error.HTTPError as exc:
|
|
160
|
+
if exc.code == 401:
|
|
161
|
+
raise OpenToolServerUnauthorizedException() from exc
|
|
162
|
+
message = exc.read().decode("utf-8", errors="ignore")
|
|
163
|
+
raise OpenToolServerCallException(f"HTTP {exc.code} {message}") from exc
|
|
164
|
+
except (socket.timeout, error.URLError) as exc:
|
|
165
|
+
raise OpenToolServerNoAccessException() from exc
|
|
166
|
+
|
|
167
|
+
def load(self) -> OpenTool | None:
|
|
168
|
+
data = self._request_json("GET", "/load")
|
|
169
|
+
if data.get("code") == LOAD_METADATA_NOT_FOUND_CODE or (
|
|
170
|
+
data.get("code") == 404 and data.get("message") == "Json Parser NOT implement"
|
|
171
|
+
):
|
|
172
|
+
return None
|
|
173
|
+
return OpenTool.from_json(data, schema_reference_resolver=self.schema_reference_resolver)
|
|
174
|
+
|
|
175
|
+
def stop(self) -> StatusInfo | None:
|
|
176
|
+
try:
|
|
177
|
+
data = self._request_json("POST", "/stop")
|
|
178
|
+
return StatusInfo.from_json(data)
|
|
179
|
+
except Exception:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
def _request_json(
|
|
183
|
+
self,
|
|
184
|
+
method: str,
|
|
185
|
+
path: str,
|
|
186
|
+
body: dict[str, Any] | None = None,
|
|
187
|
+
) -> dict[str, Any]:
|
|
188
|
+
url = parse.urljoin(f"{self.base_url}/", path.lstrip("/"))
|
|
189
|
+
headers = {
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
"Accept": "application/json",
|
|
192
|
+
}
|
|
193
|
+
if self.tool_api_key:
|
|
194
|
+
headers["Authorization"] = f"Bearer {self.tool_api_key}"
|
|
195
|
+
|
|
196
|
+
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
197
|
+
req = request.Request(url=url, method=method, data=data, headers=headers)
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
with request.urlopen(req, timeout=20) as response:
|
|
201
|
+
payload = response.read().decode("utf-8")
|
|
202
|
+
return json.loads(payload) if payload else {}
|
|
203
|
+
except error.HTTPError as exc:
|
|
204
|
+
if exc.code == 401:
|
|
205
|
+
raise OpenToolServerUnauthorizedException() from exc
|
|
206
|
+
message = exc.read().decode("utf-8", errors="replace")
|
|
207
|
+
if path == "/load" and exc.code == 404:
|
|
208
|
+
try:
|
|
209
|
+
data = json.loads(message)
|
|
210
|
+
except json.JSONDecodeError:
|
|
211
|
+
data = None
|
|
212
|
+
if isinstance(data, dict) and data.get("code") == LOAD_METADATA_NOT_FOUND_CODE:
|
|
213
|
+
return data
|
|
214
|
+
raise OpenToolServerCallException(f"HTTP {exc.code} {message}") from exc
|
|
215
|
+
except (socket.timeout, error.URLError) as exc:
|
|
216
|
+
raise OpenToolServerNoAccessException() from exc
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _parse_sse_block(lines: list[str]) -> tuple[str | None, dict[str, Any] | None]:
|
|
220
|
+
event_name: str | None = None
|
|
221
|
+
data_lines: list[str] = []
|
|
222
|
+
|
|
223
|
+
for line in lines:
|
|
224
|
+
if line.startswith("event:"):
|
|
225
|
+
event_name = line[len("event:") :].strip()
|
|
226
|
+
elif line.startswith("data:"):
|
|
227
|
+
data_lines.append(line[len("data:") :].removeprefix(" "))
|
|
228
|
+
|
|
229
|
+
if event_name is None or not data_lines:
|
|
230
|
+
return None, None
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
payload = json.loads("\n".join(data_lines))
|
|
234
|
+
except json.JSONDecodeError as exc:
|
|
235
|
+
raise OpenToolServerCallException(f"Invalid SSE JSON: {exc}") from exc
|
|
236
|
+
if not isinstance(payload, dict):
|
|
237
|
+
raise OpenToolServerCallException("Expected an SSE JSON object")
|
|
238
|
+
return event_name, payload
|
opentool_python/dto.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
JSONRPC_VERSION = "2.0"
|
|
7
|
+
LOAD_METADATA_NOT_FOUND_CODE = "TOOL_NOT_FOUND"
|
|
8
|
+
LOAD_METADATA_NOT_FOUND_MESSAGE = "Tool metadata not found"
|
|
9
|
+
DEFAULT_PREFIX = "/opentool"
|
|
10
|
+
DEFAULT_TOOL_TAG = "N/A"
|
|
11
|
+
DEFAULT_TOOL_HOST = "0.0.0.0"
|
|
12
|
+
DEFAULT_TOOL_PORT = 17001
|
|
13
|
+
CLI_ARGUMENT_TAG = "opentoolServerTag"
|
|
14
|
+
CLI_ARGUMENT_HOST = "opentoolServerHost"
|
|
15
|
+
CLI_ARGUMENT_PORT = "opentoolServerPort"
|
|
16
|
+
CLI_ARGUMENT_APIKEYS = "opentoolServerApiKeys"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HostType:
|
|
20
|
+
ANY = "0.0.0.0"
|
|
21
|
+
LOCALHOST = "127.0.0.1"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Version:
|
|
26
|
+
version: str
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_json(cls, data: dict[str, Any]) -> "Version":
|
|
30
|
+
return cls(version=str(data["version"]))
|
|
31
|
+
|
|
32
|
+
def to_json(self) -> dict[str, Any]:
|
|
33
|
+
return {"version": self.version}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class JsonRPCHttpRequestBody:
|
|
38
|
+
method: str
|
|
39
|
+
params: dict[str, Any] | None
|
|
40
|
+
id: str
|
|
41
|
+
jsonrpc: str = JSONRPC_VERSION
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_json(cls, data: dict[str, Any]) -> "JsonRPCHttpRequestBody":
|
|
45
|
+
method, request_id = data["method"], data["id"]
|
|
46
|
+
params = data.get("params")
|
|
47
|
+
version = data.get("jsonrpc", JSONRPC_VERSION)
|
|
48
|
+
if not isinstance(method, str) or not isinstance(request_id, str):
|
|
49
|
+
raise ValueError("method and id must be strings")
|
|
50
|
+
if params is not None and not isinstance(params, dict):
|
|
51
|
+
raise ValueError("params must be an object or null")
|
|
52
|
+
if not isinstance(version, str):
|
|
53
|
+
raise ValueError("jsonrpc must be a string")
|
|
54
|
+
return cls(method=method, params=params, id=request_id, jsonrpc=version)
|
|
55
|
+
|
|
56
|
+
def to_json(self) -> dict[str, Any]:
|
|
57
|
+
return {
|
|
58
|
+
"jsonrpc": self.jsonrpc,
|
|
59
|
+
"method": self.method,
|
|
60
|
+
"id": self.id,
|
|
61
|
+
**({"params": self.params} if self.params is not None else {}),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class JsonRPCHttpResponseBodyError:
|
|
67
|
+
code: int
|
|
68
|
+
message: str
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_json(cls, data: dict[str, Any]) -> "JsonRPCHttpResponseBodyError":
|
|
72
|
+
return cls(code=int(data["code"]), message=str(data["message"]))
|
|
73
|
+
|
|
74
|
+
def to_json(self) -> dict[str, Any]:
|
|
75
|
+
return {"code": self.code, "message": self.message}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class JsonRPCHttpResponseBody:
|
|
80
|
+
result: dict[str, Any] = field(default_factory=dict)
|
|
81
|
+
id: str = ""
|
|
82
|
+
error: JsonRPCHttpResponseBodyError | None = None
|
|
83
|
+
jsonrpc: str = JSONRPC_VERSION
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_json(cls, data: dict[str, Any]) -> "JsonRPCHttpResponseBody":
|
|
87
|
+
error_data = data.get("error")
|
|
88
|
+
error = (
|
|
89
|
+
JsonRPCHttpResponseBodyError.from_json(error_data)
|
|
90
|
+
if isinstance(error_data, dict)
|
|
91
|
+
else None
|
|
92
|
+
)
|
|
93
|
+
return cls(
|
|
94
|
+
result=data.get("result") or {},
|
|
95
|
+
id=str(data.get("id", "")),
|
|
96
|
+
error=error,
|
|
97
|
+
jsonrpc=str(data.get("jsonrpc", JSONRPC_VERSION)),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def to_json(self) -> dict[str, Any]:
|
|
101
|
+
payload: dict[str, Any] = {
|
|
102
|
+
"jsonrpc": self.jsonrpc,
|
|
103
|
+
"result": self.result,
|
|
104
|
+
"id": self.id,
|
|
105
|
+
}
|
|
106
|
+
if self.error is not None:
|
|
107
|
+
payload["error"] = self.error.to_json()
|
|
108
|
+
return payload
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class StatusType:
|
|
112
|
+
STOPPED = "stopped"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class StatusInfo:
|
|
117
|
+
status: str
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def from_json(cls, data: dict[str, Any]) -> "StatusInfo":
|
|
121
|
+
return cls(status=str(data["status"]))
|
|
122
|
+
|
|
123
|
+
def to_json(self) -> dict[str, Any]:
|
|
124
|
+
return {"status": self.status}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .model.opentool import OpenTool
|
|
7
|
+
from .model.schema import SchemaReferenceResolver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenToolJsonLoader:
|
|
11
|
+
def __init__(self, *, schema_reference_resolver: SchemaReferenceResolver | None = None) -> None:
|
|
12
|
+
self.schema_reference_resolver = schema_reference_resolver
|
|
13
|
+
self.schemas_json: dict[str, object] | None = None
|
|
14
|
+
|
|
15
|
+
def load(self, json_string: str) -> OpenTool:
|
|
16
|
+
opentool_map = json.loads(json_string)
|
|
17
|
+
self.schemas_json = opentool_map.get("schemas")
|
|
18
|
+
return OpenTool.from_json(
|
|
19
|
+
opentool_map, schema_reference_resolver=self.schema_reference_resolver
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def load_from_file(self, json_path: str) -> OpenTool:
|
|
23
|
+
return self.load(Path(json_path).read_text(encoding="utf-8"))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FunctionCall:
|
|
9
|
+
id: str
|
|
10
|
+
name: str
|
|
11
|
+
arguments: dict[str, Any]
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def from_json(cls, data: dict[str, Any]) -> "FunctionCall":
|
|
15
|
+
return cls(
|
|
16
|
+
id=str(data["id"]),
|
|
17
|
+
name=str(data["name"]),
|
|
18
|
+
arguments=data.get("arguments") or {},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def to_json(self) -> dict[str, Any]:
|
|
22
|
+
return {"id": self.id, "name": self.name, "arguments": self.arguments}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ToolReturn:
|
|
27
|
+
id: str
|
|
28
|
+
result: dict[str, Any]
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_json(cls, data: dict[str, Any]) -> "ToolReturn":
|
|
32
|
+
return cls(id=str(data["id"]), result=data.get("result") or {})
|
|
33
|
+
|
|
34
|
+
def to_json(self) -> dict[str, Any]:
|
|
35
|
+
return {"id": self.id, "result": self.result}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .data_type import DataType
|
|
2
|
+
from .function_model import FunctionModel
|
|
3
|
+
from .info import Info
|
|
4
|
+
from .opentool import OpenTool
|
|
5
|
+
from .parameter import Parameter
|
|
6
|
+
from .return_model import Return
|
|
7
|
+
from .schema import Schema, SchemaReferenceResolver, SchemaResolver, SchemaType, SchemaValidationResult
|
|
8
|
+
from .server import ServerConfig
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DataType",
|
|
12
|
+
"FunctionModel",
|
|
13
|
+
"Info",
|
|
14
|
+
"OpenTool",
|
|
15
|
+
"Parameter",
|
|
16
|
+
"Return",
|
|
17
|
+
"Schema",
|
|
18
|
+
"SchemaType",
|
|
19
|
+
"SchemaReferenceResolver",
|
|
20
|
+
"SchemaResolver",
|
|
21
|
+
"SchemaValidationResult",
|
|
22
|
+
"ServerConfig",
|
|
23
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .parameter import Parameter
|
|
7
|
+
from .return_model import Return
|
|
8
|
+
from .schema import SchemaResolver
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class FunctionModel:
|
|
13
|
+
name: str
|
|
14
|
+
description: str
|
|
15
|
+
parameters: list[Parameter]
|
|
16
|
+
return_: Return | None = None
|
|
17
|
+
stream: dict[str, Return] | None = None
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_json(cls, data: dict[str, Any], *, schema_resolver: SchemaResolver | None = None) -> "FunctionModel":
|
|
21
|
+
parameters = data["parameters"]
|
|
22
|
+
if not isinstance(parameters, list) or any(not isinstance(item, dict) for item in parameters):
|
|
23
|
+
raise ValueError("parameters: expected an array of objects")
|
|
24
|
+
|
|
25
|
+
raw_return = data.get("return")
|
|
26
|
+
if raw_return is not None and not isinstance(raw_return, dict):
|
|
27
|
+
raise ValueError("return: expected an object")
|
|
28
|
+
|
|
29
|
+
raw_stream = data.get("stream")
|
|
30
|
+
stream: dict[str, Return] | None = None
|
|
31
|
+
if raw_stream is not None:
|
|
32
|
+
if not isinstance(raw_stream, dict) or any(not isinstance(item, dict) for item in raw_stream.values()):
|
|
33
|
+
raise ValueError("stream: expected a map of return objects")
|
|
34
|
+
stream = {
|
|
35
|
+
name: Return.from_json(value, schema_resolver=schema_resolver)
|
|
36
|
+
for name, value in raw_stream.items()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return cls(
|
|
40
|
+
name=str(data["name"]),
|
|
41
|
+
description=str(data["description"]),
|
|
42
|
+
parameters=[
|
|
43
|
+
Parameter.from_json(item, schema_resolver=schema_resolver)
|
|
44
|
+
for item in parameters
|
|
45
|
+
],
|
|
46
|
+
return_=(
|
|
47
|
+
Return.from_json(raw_return, schema_resolver=schema_resolver)
|
|
48
|
+
if raw_return is not None else None
|
|
49
|
+
),
|
|
50
|
+
stream=stream,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def to_json(self) -> dict[str, Any]:
|
|
54
|
+
payload: dict[str, Any] = {
|
|
55
|
+
"name": self.name,
|
|
56
|
+
"description": self.description,
|
|
57
|
+
"parameters": [parameter.to_json() for parameter in self.parameters],
|
|
58
|
+
}
|
|
59
|
+
if self.return_ is not None:
|
|
60
|
+
payload["return"] = self.return_.to_json()
|
|
61
|
+
if self.stream is not None:
|
|
62
|
+
payload["stream"] = {
|
|
63
|
+
event_name: return_model.to_json()
|
|
64
|
+
for event_name, return_model in self.stream.items()
|
|
65
|
+
}
|
|
66
|
+
return payload
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Info:
|
|
9
|
+
title: str
|
|
10
|
+
version: str
|
|
11
|
+
description: str | None = None
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def from_json(cls, data: dict[str, Any]) -> "Info":
|
|
15
|
+
return cls(
|
|
16
|
+
title=str(data["title"]),
|
|
17
|
+
version=str(data["version"]),
|
|
18
|
+
description=data.get("description"),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def to_json(self) -> dict[str, Any]:
|
|
22
|
+
payload: dict[str, Any] = {"title": self.title, "version": self.version}
|
|
23
|
+
if self.description is not None:
|
|
24
|
+
payload["description"] = self.description
|
|
25
|
+
return payload
|