libentry 1.23.4__py3-none-any.whl → 1.24.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.
- libentry/mcp/service.py +97 -32
- libentry/resource.py +78 -0
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/METADATA +1 -1
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/RECORD +9 -8
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/LICENSE +0 -0
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/WHEEL +0 -0
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/entry_points.txt +0 -0
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/top_level.txt +0 -0
- {libentry-1.23.4.dist-info → libentry-1.24.1.dist-info}/zip-safe +0 -0
libentry/mcp/service.py
CHANGED
@@ -57,11 +57,24 @@ class SubroutineAdapter:
|
|
57
57
|
|
58
58
|
def __call__(
|
59
59
|
self,
|
60
|
-
request:
|
60
|
+
request: Any,
|
61
|
+
args: Optional[Dict[str, str]] = None
|
61
62
|
) -> Union[SubroutineResponse, Iterable[SubroutineResponse]]:
|
62
63
|
if isinstance(request, BaseModel):
|
63
64
|
request = request.model_dump()
|
64
65
|
|
66
|
+
if not isinstance(request, Dict):
|
67
|
+
raise ValueError(
|
68
|
+
f"Subroutine only accepts JSON object as input. "
|
69
|
+
f"Expect \"dict\", got \"{type(request)}\"."
|
70
|
+
)
|
71
|
+
|
72
|
+
if args is not None:
|
73
|
+
conflicts = args.keys() & request.keys()
|
74
|
+
if len(conflicts) > 0:
|
75
|
+
raise ValueError(f"Duplicated fields: \"{conflicts}\".")
|
76
|
+
request = {**args, **request}
|
77
|
+
|
65
78
|
try:
|
66
79
|
input_model = self.api_signature.input_model
|
67
80
|
if input_model is not None:
|
@@ -117,16 +130,29 @@ class JSONRPCAdapter:
|
|
117
130
|
|
118
131
|
def __call__(
|
119
132
|
self,
|
120
|
-
request: Union[JSONRPCRequest, JSONRPCNotification, Dict[str, Any]]
|
133
|
+
request: Union[JSONRPCRequest, JSONRPCNotification, Dict[str, Any]],
|
134
|
+
args: Optional[Dict[str, str]] = None
|
121
135
|
) -> Union[JSONRPCResponse, Iterable[JSONRPCResponse], None]:
|
122
136
|
if isinstance(request, Dict):
|
137
|
+
if args is not None:
|
138
|
+
conflicts = args.keys() & request.keys()
|
139
|
+
if len(conflicts) > 0:
|
140
|
+
raise ValueError(f"Duplicated fields: \"{conflicts}\".")
|
141
|
+
request = {**args, **request}
|
123
142
|
request = self.type_adapter.validate_python(request)
|
124
143
|
|
144
|
+
if isinstance(request, JSONRPCRequest):
|
145
|
+
fn = self._apply_request
|
146
|
+
elif isinstance(request, JSONRPCNotification):
|
147
|
+
fn = self._apply_notification
|
148
|
+
else:
|
149
|
+
raise ValueError(
|
150
|
+
f"JSONRPC only accepts JSONRPCRequest, JSONRPCNotification as input. "
|
151
|
+
f"Expect \"JSONRPCRequest\", \"JSONRPCNotification\" or \"dict\", got \"{type(request)}\"."
|
152
|
+
)
|
153
|
+
|
125
154
|
try:
|
126
|
-
|
127
|
-
return self._apply_request(request)
|
128
|
-
else:
|
129
|
-
return self._apply_notification(request)
|
155
|
+
return fn(request)
|
130
156
|
except SystemExit as e:
|
131
157
|
raise e
|
132
158
|
except KeyboardInterrupt as e:
|
@@ -251,26 +277,20 @@ class FlaskHandler:
|
|
251
277
|
data = flask_request.data
|
252
278
|
content_type = flask_request.content_type
|
253
279
|
|
254
|
-
|
280
|
+
args = {**args}
|
255
281
|
if data:
|
256
282
|
if (not content_type) or content_type == MIME.json.value:
|
257
|
-
|
283
|
+
raw_request = json.loads(data)
|
258
284
|
else:
|
259
285
|
return self.app.error(f"Unsupported Content-Type: \"{content_type}\".")
|
260
286
|
else:
|
261
|
-
|
262
|
-
|
263
|
-
conflicts = json_from_url.keys() & json_from_data.keys()
|
264
|
-
if len(conflicts) > 0:
|
265
|
-
return self.app.error(f"Duplicated fields: \"{conflicts}\".")
|
266
|
-
|
267
|
-
input_json = {**json_from_url, **json_from_data}
|
287
|
+
raw_request = {}
|
268
288
|
|
269
289
|
################################################################################
|
270
290
|
# Call method as MCP
|
271
291
|
################################################################################
|
272
292
|
try:
|
273
|
-
mcp_response = self.default_adapter(
|
293
|
+
mcp_response = self.default_adapter(raw_request, args)
|
274
294
|
except Exception as e:
|
275
295
|
error = json.dumps(SubroutineError.from_exception(e))
|
276
296
|
return self.app.error(error, mimetype=MIME.json.value)
|
@@ -370,7 +390,11 @@ class SSEService:
|
|
370
390
|
|
371
391
|
# noinspection PyUnusedLocal
|
372
392
|
@api.get("/sse", tag=api.TAG_ENDPOINT)
|
373
|
-
def sse(
|
393
|
+
def sse(
|
394
|
+
self,
|
395
|
+
raw_request: Any,
|
396
|
+
args: Optional[Dict[str, str]] = None
|
397
|
+
) -> Iterable[SSE]:
|
374
398
|
session_id = str(uuid.uuid4())
|
375
399
|
queue = Queue(8)
|
376
400
|
with self.lock:
|
@@ -396,7 +420,23 @@ class SSEService:
|
|
396
420
|
return _stream()
|
397
421
|
|
398
422
|
@api.route("/sse/message", tag=api.TAG_ENDPOINT)
|
399
|
-
def sse_message(
|
423
|
+
def sse_message(
|
424
|
+
self,
|
425
|
+
raw_request: Any,
|
426
|
+
args: Optional[Dict[str, str]] = None
|
427
|
+
) -> None:
|
428
|
+
if not isinstance(raw_request, Dict):
|
429
|
+
raise ValueError(
|
430
|
+
f"/message only accepts JSON object as input. "
|
431
|
+
f"Expect \"dict\", got \"{type(raw_request)}\"."
|
432
|
+
)
|
433
|
+
|
434
|
+
if args is not None:
|
435
|
+
conflicts = args.keys() & raw_request.keys()
|
436
|
+
if len(conflicts) > 0:
|
437
|
+
raise ValueError(f"Duplicated fields: \"{conflicts}\".")
|
438
|
+
raw_request = {**args, **raw_request}
|
439
|
+
|
400
440
|
################################################################################
|
401
441
|
# session validation
|
402
442
|
################################################################################
|
@@ -472,7 +512,26 @@ class JSONRPCService:
|
|
472
512
|
self.type_adapter = TypeAdapter(Union[JSONRPCRequest, JSONRPCNotification])
|
473
513
|
|
474
514
|
@api.route(tag=api.TAG_ENDPOINT)
|
475
|
-
def message(
|
515
|
+
def message(
|
516
|
+
self,
|
517
|
+
raw_request: Any,
|
518
|
+
args: Optional[Dict[str, str]] = None
|
519
|
+
) -> Union[JSONRPCResponse, Iterable[JSONRPCResponse], None]:
|
520
|
+
if isinstance(raw_request, List):
|
521
|
+
raise ValueError("Batching RCP requests is not supported yet.")
|
522
|
+
|
523
|
+
if not isinstance(raw_request, Dict):
|
524
|
+
raise ValueError(
|
525
|
+
f"/message only accepts JSON object as input. "
|
526
|
+
f"Expect \"dict\", got \"{type(raw_request)}\"."
|
527
|
+
)
|
528
|
+
|
529
|
+
if args is not None:
|
530
|
+
conflicts = args.keys() & raw_request.keys()
|
531
|
+
if len(conflicts) > 0:
|
532
|
+
raise ValueError(f"Duplicated fields: \"{conflicts}\".")
|
533
|
+
raw_request = {**args, **raw_request}
|
534
|
+
|
476
535
|
request = self.type_adapter.validate_python(raw_request)
|
477
536
|
path = f"/{request.method}"
|
478
537
|
route = self.service_routes.get(path, self.builtin_routes.get(path))
|
@@ -502,6 +561,10 @@ class LifeCycleService:
|
|
502
561
|
serverInfo=Implementation(name="python-libentry", version="1.0.0")
|
503
562
|
)
|
504
563
|
|
564
|
+
@api.route()
|
565
|
+
def ping(self):
|
566
|
+
return None
|
567
|
+
|
505
568
|
|
506
569
|
class NotificationsService:
|
507
570
|
|
@@ -531,24 +594,26 @@ class ToolsService:
|
|
531
594
|
tools = []
|
532
595
|
for name, route in self.get_tool_routes().items():
|
533
596
|
api_info = route.api_info
|
597
|
+
api_models = route.fn if isinstance(route.fn, APISignature) else get_api_signature(route.fn)
|
598
|
+
args_model = api_models.input_model or api_models.bundled_model
|
534
599
|
tool = Tool(
|
535
600
|
name=api_info.name,
|
536
601
|
description=api_info.description,
|
537
|
-
inputSchema=ToolSchema()
|
602
|
+
inputSchema=ToolSchema.model_validate(args_model.model_json_schema())
|
538
603
|
)
|
539
604
|
tools.append(tool)
|
540
|
-
schema = query_api(route.fn)
|
541
|
-
input_schema = schema.context[schema.input_schema]
|
542
|
-
for field in input_schema.fields:
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
605
|
+
# schema = query_api(route.fn)
|
606
|
+
# input_schema = schema.context[schema.input_schema]
|
607
|
+
# for field in input_schema.fields:
|
608
|
+
# type_ = field.type
|
609
|
+
# if isinstance(type_, List):
|
610
|
+
# type_ = "|".join(type_)
|
611
|
+
# tool.inputSchema.properties[field.name] = ToolProperty(
|
612
|
+
# type=type_,
|
613
|
+
# description=field.description
|
614
|
+
# )
|
615
|
+
# if field.is_required:
|
616
|
+
# tool.inputSchema.required.append(field.name)
|
552
617
|
return ListToolsResult(tools=tools)
|
553
618
|
|
554
619
|
@api.route("/tools/call")
|
libentry/resource.py
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
__author__ = "xi"
|
4
|
+
__all__ = [
|
5
|
+
"AbstractResourceManager",
|
6
|
+
"JSONResourceManager",
|
7
|
+
"RemoteResourceManager",
|
8
|
+
"ResourceManager",
|
9
|
+
]
|
10
|
+
|
11
|
+
import abc
|
12
|
+
from typing import Any, Dict, List
|
13
|
+
from urllib.parse import urlparse
|
14
|
+
|
15
|
+
import yaml
|
16
|
+
|
17
|
+
|
18
|
+
class AbstractResourceManager(abc.ABC):
|
19
|
+
|
20
|
+
@abc.abstractmethod
|
21
|
+
def list(self) -> List[str]:
|
22
|
+
pass
|
23
|
+
|
24
|
+
@abc.abstractmethod
|
25
|
+
def get(self, name: str) -> Any:
|
26
|
+
pass
|
27
|
+
|
28
|
+
@abc.abstractmethod
|
29
|
+
def set(self, name: str, value: Any) -> Any:
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class JSONResourceManager(AbstractResourceManager):
|
34
|
+
|
35
|
+
def __init__(self, path: str):
|
36
|
+
with open(path, "r") as f:
|
37
|
+
self.resource_obj = yaml.load(f, yaml.SafeLoader)
|
38
|
+
|
39
|
+
if not isinstance(self.resource_obj, Dict):
|
40
|
+
raise RuntimeError(
|
41
|
+
f"Expect a JSON object, "
|
42
|
+
f"got {type(self.resource_obj)}."
|
43
|
+
)
|
44
|
+
|
45
|
+
def list(self) -> List[str]:
|
46
|
+
return [*self.resource_obj.keys()]
|
47
|
+
|
48
|
+
def get(self, name: str) -> Any:
|
49
|
+
return self.resource_obj.get(name)
|
50
|
+
|
51
|
+
def set(self, name: str, value: Any) -> Any:
|
52
|
+
raise NotImplementedError("\"set\" is not supported by JSONResourceClient.")
|
53
|
+
|
54
|
+
|
55
|
+
class RemoteResourceManager(AbstractResourceManager):
|
56
|
+
|
57
|
+
def __init__(self, url: str):
|
58
|
+
pass
|
59
|
+
|
60
|
+
|
61
|
+
class ResourceManager:
|
62
|
+
|
63
|
+
def __new__(cls, uri: str) -> AbstractResourceManager:
|
64
|
+
parsed_uri = urlparse(uri)
|
65
|
+
if parsed_uri.scheme in {"", "json", "yaml", "yml"}:
|
66
|
+
return JSONResourceManager(parsed_uri.path)
|
67
|
+
else:
|
68
|
+
raise NotImplementedError(f"Scheme \"{parsed_uri.scheme}\" is not supported.")
|
69
|
+
|
70
|
+
|
71
|
+
def _test():
|
72
|
+
client = ResourceManager("config_test.yml")
|
73
|
+
print(client.get("qwen_2.5"))
|
74
|
+
return 0
|
75
|
+
|
76
|
+
|
77
|
+
if __name__ == "__main__":
|
78
|
+
raise SystemExit(_test())
|
@@ -6,13 +6,14 @@ libentry/executor.py,sha256=cTV0WxJi0nU1TP-cOwmeodN8DD6L1691M2HIQsJtGrU,6582
|
|
6
6
|
libentry/experiment.py,sha256=ejgAHDXWIe9x4haUzIFuz1WasLY0_aD1z_vyEVGjTu8,4922
|
7
7
|
libentry/json.py,sha256=CubUUu29h7idLaC4d66vKhjBgVHKN1rZOv-Tw2qM17k,1916
|
8
8
|
libentry/logging.py,sha256=IiYoCUzm8XTK1fduA-NA0FI2Qz_m81NEPV3d3tEfgdI,1349
|
9
|
+
libentry/resource.py,sha256=qAPXF7RzRYeCM4xWft6XR3SYk7lEiQ6ymOHbpNoi0mQ,1821
|
9
10
|
libentry/schema.py,sha256=40SOhCF_eytWOF47MWKCRHKHl_lCaQVetx1Af62PkiI,10439
|
10
11
|
libentry/test_api.py,sha256=Xw7B7sH6g1iCTV5sFzyBF3JAJzeOr9xg0AyezTNsnIk,4452
|
11
12
|
libentry/utils.py,sha256=O7P6GadtUIjq0N2IZH7PhHZDUM3NebzcqyDqytet7CM,683
|
12
13
|
libentry/mcp/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
|
13
14
|
libentry/mcp/api.py,sha256=KSvz6_TNGKQQoFJPNH1XIc8CMji0I2_CqC9VCSOPRfc,2491
|
14
15
|
libentry/mcp/client.py,sha256=tEJbMpXiMKQ0qDgLGyavE2A4jpoXliqbrVj_TMfOGXI,22488
|
15
|
-
libentry/mcp/service.py,sha256=
|
16
|
+
libentry/mcp/service.py,sha256=qA6I9Mi-eudRYwVGhff1_aAImK424bhASlsWaHP8XNI,34971
|
16
17
|
libentry/mcp/types.py,sha256=xTQCnKAgeJNss4klJ33MrWHGCzG_LeR3urizO_Z9q9U,12239
|
17
18
|
libentry/service/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
|
18
19
|
libentry/service/common.py,sha256=OVaW2afgKA6YqstJmtnprBCqQEUZEWotZ6tHavmJJeU,42
|
@@ -21,10 +22,10 @@ libentry/service/list.py,sha256=ElHWhTgShGOhaxMUEwVbMXos0NQKjHsODboiQ-3AMwE,1397
|
|
21
22
|
libentry/service/running.py,sha256=FrPJoJX6wYxcHIysoatAxhW3LajCCm0Gx6l7__6sULQ,5105
|
22
23
|
libentry/service/start.py,sha256=mZT7b9rVULvzy9GTZwxWnciCHgv9dbGN2JbxM60OMn4,1270
|
23
24
|
libentry/service/stop.py,sha256=wOpwZgrEJ7QirntfvibGq-XsTC6b3ELhzRW2zezh-0s,1187
|
24
|
-
libentry-1.
|
25
|
-
libentry-1.
|
26
|
-
libentry-1.
|
27
|
-
libentry-1.
|
28
|
-
libentry-1.
|
29
|
-
libentry-1.
|
30
|
-
libentry-1.
|
25
|
+
libentry-1.24.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
26
|
+
libentry-1.24.1.dist-info/METADATA,sha256=5jCSisDg0Wwi72S051ebnGnvtzgnZwK0plwLrd4oMO0,1135
|
27
|
+
libentry-1.24.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
28
|
+
libentry-1.24.1.dist-info/entry_points.txt,sha256=1v_nLVDsjvVJp9SWhl4ef2zZrsLTBtFWgrYFgqvQBgc,61
|
29
|
+
libentry-1.24.1.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
|
30
|
+
libentry-1.24.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
31
|
+
libentry-1.24.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|