bohr-agent-sdk 0.1.111__py3-none-any.whl → 0.1.112__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.
- {bohr_agent_sdk-0.1.111.dist-info → bohr_agent_sdk-0.1.112.dist-info}/METADATA +1 -1
- {bohr_agent_sdk-0.1.111.dist-info → bohr_agent_sdk-0.1.112.dist-info}/RECORD +7 -7
- dp/agent/server/calculation_mcp_server.py +80 -92
- dp/agent/server/utils.py +12 -1
- {bohr_agent_sdk-0.1.111.dist-info → bohr_agent_sdk-0.1.112.dist-info}/WHEEL +0 -0
- {bohr_agent_sdk-0.1.111.dist-info → bohr_agent_sdk-0.1.112.dist-info}/entry_points.txt +0 -0
- {bohr_agent_sdk-0.1.111.dist-info → bohr_agent_sdk-0.1.112.dist-info}/top_level.txt +0 -0
|
@@ -62,9 +62,9 @@ dp/agent/device/device/__init__.py,sha256=w7_1S16S1vWUq0RGl0GFgjq2vFkc5oNvy8cQTn
|
|
|
62
62
|
dp/agent/device/device/device.py,sha256=9ZRIJth-4qMO-i-u_b_cO3d6a4eTbTQjPaxFsV_zEkc,9643
|
|
63
63
|
dp/agent/device/device/types.py,sha256=JuxB-hjf1CjjvfBxCLwRAXVFlYS-nPEdiJpBWLFVCzo,1924
|
|
64
64
|
dp/agent/server/__init__.py,sha256=rckaYd8pbYyB4ENEhgjXKeGMXjdnrgcJpdM1gu5u1Wc,508
|
|
65
|
-
dp/agent/server/calculation_mcp_server.py,sha256=
|
|
65
|
+
dp/agent/server/calculation_mcp_server.py,sha256=dMIFUQttO7yZyivcuf4BWEZra0Fkh7M7fB2b8tpFix0,17203
|
|
66
66
|
dp/agent/server/preprocessor.py,sha256=XUWu7QOwo_sIDMYS2b1OTrM33EXEVH_73vk-ju1Ok8A,1264
|
|
67
|
-
dp/agent/server/utils.py,sha256=
|
|
67
|
+
dp/agent/server/utils.py,sha256=cIKaAg8UaP5yMwvIVTgUVBjy-B3S16bEdnucUf4UDIM,2055
|
|
68
68
|
dp/agent/server/executor/__init__.py,sha256=s95M5qKQk39Yi9qaVJZhk_nfj54quSf7EDghR3OCFUA,248
|
|
69
69
|
dp/agent/server/executor/base_executor.py,sha256=nR2jI-wFvKoOk8QaK11pnSAkHj2MsE6uyzPWDx-vgJA,3018
|
|
70
70
|
dp/agent/server/executor/dispatcher_executor.py,sha256=CZRxbVkLaDvStXhNaMKrKcx2Z0tPPVzIxkU1ufqWgYc,12081
|
|
@@ -75,8 +75,8 @@ dp/agent/server/storage/bohrium_storage.py,sha256=EsKX4dWWvZTn2TEhZv4zsvihfDK0mm
|
|
|
75
75
|
dp/agent/server/storage/http_storage.py,sha256=KiySq7g9-iJr12XQCKKyJLn8wJoDnSRpQAR5_qPJ1ZU,1471
|
|
76
76
|
dp/agent/server/storage/local_storage.py,sha256=t1wfjByjXew9ws3PuUxWxmZQ0-Wt1a6t4wmj3fW62GI,1352
|
|
77
77
|
dp/agent/server/storage/oss_storage.py,sha256=pgjmi7Gir3Y5wkMDCvU4fvSls15fXT7Ax-h9MYHFPK0,3359
|
|
78
|
-
bohr_agent_sdk-0.1.
|
|
79
|
-
bohr_agent_sdk-0.1.
|
|
80
|
-
bohr_agent_sdk-0.1.
|
|
81
|
-
bohr_agent_sdk-0.1.
|
|
82
|
-
bohr_agent_sdk-0.1.
|
|
78
|
+
bohr_agent_sdk-0.1.112.dist-info/METADATA,sha256=qnbHAl8Ey0kVMpxD_3Q6qWhOZCC0uocIZXwUQZk11_s,11070
|
|
79
|
+
bohr_agent_sdk-0.1.112.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
80
|
+
bohr_agent_sdk-0.1.112.dist-info/entry_points.txt,sha256=5n5kneF5IbDQtoQ2WfF-QuBjDtsimJte9Rv9baSGgc0,86
|
|
81
|
+
bohr_agent_sdk-0.1.112.dist-info/top_level.txt,sha256=87xLUDhu_1nQHoGLwlhJ6XlO7OsjILh6i1nX6ljFzDo,3
|
|
82
|
+
bohr_agent_sdk-0.1.112.dist-info/RECORD,,
|
|
@@ -7,17 +7,17 @@ from copy import deepcopy
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from urllib.parse import urlparse
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Annotated, Literal, Optional, List, Dict
|
|
11
11
|
|
|
12
|
-
import mcp
|
|
13
12
|
from mcp.server.fastmcp import FastMCP
|
|
14
13
|
from mcp.server.fastmcp.utilities.context_injection import (
|
|
15
14
|
find_context_parameter,
|
|
16
15
|
)
|
|
17
16
|
from mcp.server.fastmcp.utilities.func_metadata import (
|
|
18
|
-
|
|
17
|
+
ArgModelBase,
|
|
19
18
|
func_metadata,
|
|
20
19
|
)
|
|
20
|
+
from pydantic import BaseModel, Field, create_model
|
|
21
21
|
from starlette.responses import JSONResponse
|
|
22
22
|
from starlette.routing import Route
|
|
23
23
|
|
|
@@ -65,18 +65,9 @@ def set_directory(workdir: str):
|
|
|
65
65
|
os.chdir(cwd)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
executor = json.load(f)
|
|
72
|
-
return executor
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def load_storage(storage):
|
|
76
|
-
if not storage and os.path.exists("storage.json"):
|
|
77
|
-
with open("storage.json", "r") as f:
|
|
78
|
-
storage = json.load(f)
|
|
79
|
-
return storage
|
|
68
|
+
def load_job_info():
|
|
69
|
+
with open("job.json", "r") as f:
|
|
70
|
+
return json.load(f)
|
|
80
71
|
|
|
81
72
|
|
|
82
73
|
def query_job_status(job_id: str, executor: Optional[dict] = None
|
|
@@ -90,7 +81,7 @@ def query_job_status(job_id: str, executor: Optional[dict] = None
|
|
|
90
81
|
"""
|
|
91
82
|
trace_id, exec_id = job_id.split("/")
|
|
92
83
|
with set_directory(trace_id):
|
|
93
|
-
executor =
|
|
84
|
+
executor = load_job_info()["executor"] or executor
|
|
94
85
|
_, executor = init_executor(executor)
|
|
95
86
|
status = executor.query_status(exec_id)
|
|
96
87
|
logger.info("Job %s status is %s" % (job_id, status))
|
|
@@ -105,7 +96,7 @@ def terminate_job(job_id: str, executor: Optional[dict] = None):
|
|
|
105
96
|
"""
|
|
106
97
|
trace_id, exec_id = job_id.split("/")
|
|
107
98
|
with set_directory(trace_id):
|
|
108
|
-
executor =
|
|
99
|
+
executor = load_job_info()["executor"] or executor
|
|
109
100
|
_, executor = init_executor(executor)
|
|
110
101
|
executor.terminate(exec_id)
|
|
111
102
|
logger.info("Job %s is terminated" % job_id)
|
|
@@ -215,7 +206,8 @@ def handle_output_artifacts(results, exec_id, storage):
|
|
|
215
206
|
"storage_type": storage_type,
|
|
216
207
|
"uri": uri,
|
|
217
208
|
}
|
|
218
|
-
elif isinstance(results[name], list) and all(
|
|
209
|
+
elif isinstance(results[name], list) and all(
|
|
210
|
+
isinstance(item, Path) for item in results[name]):
|
|
219
211
|
new_uris = []
|
|
220
212
|
for item in results[name]:
|
|
221
213
|
key = storage.upload("%s/outputs/%s" % (exec_id, name),
|
|
@@ -235,7 +227,7 @@ def handle_output_artifacts(results, exec_id, storage):
|
|
|
235
227
|
# MCP does not regard Any as serializable in Python 3.12
|
|
236
228
|
# use Optional[Any] to work around
|
|
237
229
|
def get_job_results(job_id: str, executor: Optional[dict] = None,
|
|
238
|
-
storage: Optional[dict] = None)
|
|
230
|
+
storage: Optional[dict] = None):
|
|
239
231
|
"""
|
|
240
232
|
Get results of a calculation job
|
|
241
233
|
Args:
|
|
@@ -245,8 +237,9 @@ def get_job_results(job_id: str, executor: Optional[dict] = None,
|
|
|
245
237
|
"""
|
|
246
238
|
trace_id, exec_id = job_id.split("/")
|
|
247
239
|
with set_directory(trace_id):
|
|
248
|
-
|
|
249
|
-
|
|
240
|
+
job_info = load_job_info()
|
|
241
|
+
executor = job_info["executor"] or executor
|
|
242
|
+
storage = job_info["storage"] or storage
|
|
250
243
|
_, executor = init_executor(executor)
|
|
251
244
|
results = executor.get_results(exec_id)
|
|
252
245
|
results, output_artifacts = handle_output_artifacts(
|
|
@@ -254,7 +247,24 @@ def get_job_results(job_id: str, executor: Optional[dict] = None,
|
|
|
254
247
|
logger.info("Job %s result is %s" % (job_id, results))
|
|
255
248
|
return JobResult(result=results, job_info={
|
|
256
249
|
"output_artifacts": output_artifacts,
|
|
257
|
-
})
|
|
250
|
+
}, tool_name=job_info["tool_name"])
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
annotation_map = {
|
|
254
|
+
Path: str,
|
|
255
|
+
Optional[Path]: Optional[str],
|
|
256
|
+
List[Path]: List[str],
|
|
257
|
+
Optional[List[Path]]: Optional[List[str]],
|
|
258
|
+
Dict[str, Path]: Dict[str, str],
|
|
259
|
+
Optional[Dict[str, Path]]: Optional[Dict[str, str]],
|
|
260
|
+
Dict[str, List[Path]]: Dict[str, List[str]],
|
|
261
|
+
Optional[Dict[str, List[Path]]]: Optional[Dict[str, List[str]]],
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class SubmitResult(BaseModel):
|
|
266
|
+
job_id: str
|
|
267
|
+
extra_info: dict | None = None
|
|
258
268
|
|
|
259
269
|
|
|
260
270
|
class CalculationMCPServer:
|
|
@@ -268,71 +278,47 @@ class CalculationMCPServer:
|
|
|
268
278
|
self.preprocess_func = preprocess_func
|
|
269
279
|
self.fastmcp_mode = fastmcp_mode
|
|
270
280
|
self.mcp = FastMCP(*args, **kwargs)
|
|
281
|
+
self.fn_metadata_map = {}
|
|
271
282
|
|
|
272
283
|
def add_patched_tool(self, fn, new_fn, name, is_async=False, doc=None,
|
|
273
284
|
override_return_annotation=False):
|
|
274
285
|
"""patch the metadata of the tool"""
|
|
275
286
|
context_kwarg = find_context_parameter(fn)
|
|
276
|
-
|
|
277
|
-
def _get_typed_signature_patched(call):
|
|
278
|
-
"""patch parameters"""
|
|
279
|
-
typed_signature = _get_typed_signature(call)
|
|
280
|
-
new_typed_signature = _get_typed_signature(new_fn)
|
|
281
|
-
parameters = []
|
|
282
|
-
for param in typed_signature.parameters.values():
|
|
283
|
-
if param.annotation is Path:
|
|
284
|
-
parameters.append(inspect.Parameter(
|
|
285
|
-
name=param.name, default=param.default,
|
|
286
|
-
annotation=str, kind=param.kind))
|
|
287
|
-
elif param.annotation is Optional[Path]:
|
|
288
|
-
parameters.append(inspect.Parameter(
|
|
289
|
-
name=param.name, default=param.default,
|
|
290
|
-
annotation=Optional[str], kind=param.kind))
|
|
291
|
-
elif param.annotation is List[Path]:
|
|
292
|
-
parameters.append(inspect.Parameter(
|
|
293
|
-
name=param.name, default=param.default,
|
|
294
|
-
annotation=List[str], kind=param.kind))
|
|
295
|
-
elif param.annotation is Optional[List[Path]]:
|
|
296
|
-
parameters.append(inspect.Parameter(
|
|
297
|
-
name=param.name, default=param.default,
|
|
298
|
-
annotation=Optional[List[str]], kind=param.kind))
|
|
299
|
-
elif param.annotation is Dict[str, Path]:
|
|
300
|
-
parameters.append(inspect.Parameter(
|
|
301
|
-
name=param.name, default=param.default,
|
|
302
|
-
annotation=Dict[str, str], kind=param.kind))
|
|
303
|
-
elif param.annotation is Optional[Dict[str, Path]]:
|
|
304
|
-
parameters.append(inspect.Parameter(
|
|
305
|
-
name=param.name, default=param.default,
|
|
306
|
-
annotation=Optional[Dict[str, str]], kind=param.kind))
|
|
307
|
-
elif param.annotation is Dict[str, List[Path]]:
|
|
308
|
-
parameters.append(inspect.Parameter(
|
|
309
|
-
name=param.name, default=param.default,
|
|
310
|
-
annotation=Dict[str, List[str]], kind=param.kind))
|
|
311
|
-
elif param.annotation is Optional[Dict[str, List[Path]]]:
|
|
312
|
-
parameters.append(inspect.Parameter(
|
|
313
|
-
name=param.name, default=param.default,
|
|
314
|
-
annotation=Optional[Dict[str, List[str]]], kind=param.kind))
|
|
315
|
-
else:
|
|
316
|
-
parameters.append(param)
|
|
317
|
-
for param in new_typed_signature.parameters.values():
|
|
318
|
-
if param.name != "kwargs":
|
|
319
|
-
parameters.append(param)
|
|
320
|
-
return inspect.Signature(
|
|
321
|
-
parameters,
|
|
322
|
-
return_annotation=(new_typed_signature.return_annotation
|
|
323
|
-
if override_return_annotation
|
|
324
|
-
else typed_signature.return_annotation))
|
|
325
|
-
|
|
326
|
-
# Due to the frequent changes of MCP, we use a patching style here
|
|
327
|
-
mcp.server.fastmcp.utilities.func_metadata._get_typed_signature = \
|
|
328
|
-
_get_typed_signature_patched
|
|
329
287
|
func_arg_metadata = func_metadata(
|
|
330
288
|
fn,
|
|
331
289
|
skip_names=[context_kwarg] if context_kwarg is not None else [],
|
|
332
|
-
structured_output=None,
|
|
333
290
|
)
|
|
334
|
-
|
|
335
|
-
|
|
291
|
+
self.fn_metadata_map[name] = func_arg_metadata
|
|
292
|
+
model_params = {}
|
|
293
|
+
params = inspect.signature(fn, eval_str=True).parameters
|
|
294
|
+
for n, annotation in \
|
|
295
|
+
func_arg_metadata.arg_model.__annotations__.items():
|
|
296
|
+
param = params[n]
|
|
297
|
+
if param.annotation in annotation_map:
|
|
298
|
+
model_params[n] = Annotated[
|
|
299
|
+
(annotation_map[param.annotation], Field())]
|
|
300
|
+
else:
|
|
301
|
+
model_params[n] = annotation
|
|
302
|
+
if param.default is not inspect.Parameter.empty:
|
|
303
|
+
model_params[n] = (model_params[n], param.default)
|
|
304
|
+
for n, param in inspect.signature(new_fn).parameters.items():
|
|
305
|
+
if n == "kwargs":
|
|
306
|
+
continue
|
|
307
|
+
model_params[n] = Annotated[(param.annotation, Field())]
|
|
308
|
+
if param.default is not inspect.Parameter.empty:
|
|
309
|
+
model_params[n] = (model_params[n], param.default)
|
|
310
|
+
|
|
311
|
+
func_arg_metadata.arg_model = create_model(
|
|
312
|
+
f"{fn.__name__}Arguments",
|
|
313
|
+
__base__=ArgModelBase,
|
|
314
|
+
**model_params,
|
|
315
|
+
)
|
|
316
|
+
if override_return_annotation:
|
|
317
|
+
new_func_arg_metadata = func_metadata(new_fn)
|
|
318
|
+
func_arg_metadata.output_model = new_func_arg_metadata.output_model
|
|
319
|
+
func_arg_metadata.output_schema = \
|
|
320
|
+
new_func_arg_metadata.output_schema
|
|
321
|
+
func_arg_metadata.wrap_output = new_func_arg_metadata.wrap_output
|
|
336
322
|
if self.fastmcp_mode and func_arg_metadata.wrap_output:
|
|
337
323
|
# Only simulate behavior of fastmcp for output_schema
|
|
338
324
|
func_arg_metadata.output_schema["x-fastmcp-wrap-result"] = True
|
|
@@ -341,16 +327,18 @@ class CalculationMCPServer:
|
|
|
341
327
|
tool = Tool(
|
|
342
328
|
fn=new_fn,
|
|
343
329
|
name=name,
|
|
344
|
-
description=doc or fn.__doc__,
|
|
330
|
+
description=doc or fn.__doc__ or "",
|
|
345
331
|
parameters=parameters,
|
|
346
332
|
fn_metadata=func_arg_metadata,
|
|
347
333
|
is_async=is_async,
|
|
348
334
|
context_kwarg=context_kwarg,
|
|
335
|
+
fn_metadata_map=self.fn_metadata_map,
|
|
349
336
|
)
|
|
350
337
|
self.mcp._tool_manager._tools[name] = tool
|
|
351
338
|
|
|
352
339
|
def add_tool(self, fn, *args, **kwargs):
|
|
353
|
-
tool = Tool.from_function(
|
|
340
|
+
tool = Tool.from_function(
|
|
341
|
+
fn, *args, fn_metadata_map=self.fn_metadata_map, **kwargs)
|
|
354
342
|
self.mcp._tool_manager._tools[tool.name] = tool
|
|
355
343
|
return tool
|
|
356
344
|
|
|
@@ -361,20 +349,20 @@ class CalculationMCPServer:
|
|
|
361
349
|
def decorator(fn: Callable) -> Callable:
|
|
362
350
|
def submit_job(executor: Optional[dict] = None,
|
|
363
351
|
storage: Optional[dict] = None,
|
|
364
|
-
**kwargs) ->
|
|
365
|
-
"job_id": str, "extra_info": Optional[dict]}):
|
|
352
|
+
**kwargs) -> SubmitResult:
|
|
366
353
|
trace_id = datetime.today().strftime('%Y-%m-%d-%H:%M:%S.%f')
|
|
367
354
|
logger.info("Job processing (Trace ID: %s)" % trace_id)
|
|
368
355
|
with set_directory(trace_id):
|
|
369
356
|
if preprocess_func is not None:
|
|
370
357
|
executor, storage, kwargs = preprocess_func(
|
|
371
358
|
executor, storage, kwargs)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
359
|
+
job = {
|
|
360
|
+
"tool_name": fn.__name__,
|
|
361
|
+
"executor": executor,
|
|
362
|
+
"storage": storage,
|
|
363
|
+
}
|
|
364
|
+
with open("job.json", "w") as f:
|
|
365
|
+
json.dump(job, f, indent=4)
|
|
378
366
|
kwargs, input_artifacts = handle_input_artifacts(
|
|
379
367
|
fn, kwargs, storage)
|
|
380
368
|
executor_type, executor = init_executor(executor)
|
|
@@ -382,10 +370,10 @@ class CalculationMCPServer:
|
|
|
382
370
|
exec_id = res["job_id"]
|
|
383
371
|
job_id = "%s/%s" % (trace_id, exec_id)
|
|
384
372
|
logger.info("Job submitted (ID: %s)" % job_id)
|
|
385
|
-
result =
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
373
|
+
result = SubmitResult(
|
|
374
|
+
job_id=job_id,
|
|
375
|
+
extra_info=res.get("extra_info"),
|
|
376
|
+
)
|
|
389
377
|
return JobResult(result=result, job_info={
|
|
390
378
|
"trace_id": trace_id,
|
|
391
379
|
"executor_type": executor_type,
|
dp/agent/server/utils.py
CHANGED
|
@@ -21,6 +21,7 @@ def get_logger(name, level="INFO",
|
|
|
21
21
|
class JobResult(BaseModel):
|
|
22
22
|
result: Any
|
|
23
23
|
job_info: dict
|
|
24
|
+
tool_name: str | None = None
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class Tool(mcp.server.fastmcp.tools.Tool):
|
|
@@ -28,13 +29,23 @@ class Tool(mcp.server.fastmcp.tools.Tool):
|
|
|
28
29
|
Workaround MCP server cannot print traceback
|
|
29
30
|
Add job info to first unstructured content
|
|
30
31
|
"""
|
|
32
|
+
fn_metadata_map: dict | None = None
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_function(cls, *args, fn_metadata_map=None, **kwargs):
|
|
36
|
+
tool = super().from_function(*args, **kwargs)
|
|
37
|
+
tool.fn_metadata_map = fn_metadata_map
|
|
38
|
+
return tool
|
|
39
|
+
|
|
31
40
|
async def run(self, *args, **kwargs):
|
|
32
41
|
try:
|
|
33
42
|
kwargs["convert_result"] = False
|
|
34
43
|
result = await super().run(*args, **kwargs)
|
|
35
44
|
if isinstance(result, JobResult):
|
|
36
45
|
job_info = result.job_info
|
|
37
|
-
|
|
46
|
+
fn_metadata = self.fn_metadata_map.get(
|
|
47
|
+
result.tool_name, self.fn_metadata)
|
|
48
|
+
result = fn_metadata.convert_result(result.result)
|
|
38
49
|
if isinstance(result, tuple) and len(result) == 2:
|
|
39
50
|
unstructured_content, _ = result
|
|
40
51
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|