bohr-agent-sdk 0.1.109__py3-none-any.whl → 0.1.111__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.109.dist-info → bohr_agent_sdk-0.1.111.dist-info}/METADATA +1 -1
- {bohr_agent_sdk-0.1.109.dist-info → bohr_agent_sdk-0.1.111.dist-info}/RECORD +10 -10
- dp/agent/client/mcp_client.py +3 -3
- dp/agent/server/calculation_mcp_server.py +102 -1
- dp/agent/server/executor/base_executor.py +22 -0
- dp/agent/server/executor/dispatcher_executor.py +17 -3
- dp/agent/server/executor/local_executor.py +12 -2
- {bohr_agent_sdk-0.1.109.dist-info → bohr_agent_sdk-0.1.111.dist-info}/WHEEL +0 -0
- {bohr_agent_sdk-0.1.109.dist-info → bohr_agent_sdk-0.1.111.dist-info}/entry_points.txt +0 -0
- {bohr_agent_sdk-0.1.109.dist-info → bohr_agent_sdk-0.1.111.dist-info}/top_level.txt +0 -0
|
@@ -51,7 +51,7 @@ dp/agent/cli/templates/ui/server/session_manager.py,sha256=ZbNHGCFvswa-LKWn6c6RM
|
|
|
51
51
|
dp/agent/cli/templates/ui/server/user_files.py,sha256=khkiyY2UOOysHqO6JgCPUDqtrInp83G1M62i3Lj-0aY,2995
|
|
52
52
|
dp/agent/cli/templates/ui/server/utils.py,sha256=f4NfwFBq_RdZyFn_KCW6ZThYW8TvQyVruK7PJZ-DA80,1530
|
|
53
53
|
dp/agent/client/__init__.py,sha256=yu7HYZwAkD7g5dL9JttLkGmspBcyOf-6OoCjci4oPDA,59
|
|
54
|
-
dp/agent/client/mcp_client.py,sha256=
|
|
54
|
+
dp/agent/client/mcp_client.py,sha256=glbQa-fv2aOBhv_GC2ldwyWHOVcSIEwtVLAjzLZvp0c,6289
|
|
55
55
|
dp/agent/cloud/__init__.py,sha256=e16ymCZX2f-S8DyGB5jSK8gnQqVObRIsvtLXLALIKxQ,441
|
|
56
56
|
dp/agent/cloud/main.py,sha256=5QIEjpZ1RxWnR8wyLf-vlgz1bn9oOnxCYn158LBaLN4,727
|
|
57
57
|
dp/agent/cloud/mcp.py,sha256=tsAwC3doVMLYr6Oh8PxVqF-qCygYkDZJTIhoF_h8eGQ,4537
|
|
@@ -62,21 +62,21 @@ 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=vvsf58aKbBtH0AqrG5_qhGqg5g2nEhmXgabDzZKpa6o,18534
|
|
66
66
|
dp/agent/server/preprocessor.py,sha256=XUWu7QOwo_sIDMYS2b1OTrM33EXEVH_73vk-ju1Ok8A,1264
|
|
67
67
|
dp/agent/server/utils.py,sha256=ui3lca9EagcGqmYf8BKLsPARIzXxJ3jgN98yuEO3OSQ,1668
|
|
68
68
|
dp/agent/server/executor/__init__.py,sha256=s95M5qKQk39Yi9qaVJZhk_nfj54quSf7EDghR3OCFUA,248
|
|
69
|
-
dp/agent/server/executor/base_executor.py,sha256=
|
|
70
|
-
dp/agent/server/executor/dispatcher_executor.py,sha256=
|
|
71
|
-
dp/agent/server/executor/local_executor.py,sha256=
|
|
69
|
+
dp/agent/server/executor/base_executor.py,sha256=nR2jI-wFvKoOk8QaK11pnSAkHj2MsE6uyzPWDx-vgJA,3018
|
|
70
|
+
dp/agent/server/executor/dispatcher_executor.py,sha256=CZRxbVkLaDvStXhNaMKrKcx2Z0tPPVzIxkU1ufqWgYc,12081
|
|
71
|
+
dp/agent/server/executor/local_executor.py,sha256=pGXlDOrfjfP40hSMzbF-Wls3OnOTsH8PdkQjcDjP6_w,6580
|
|
72
72
|
dp/agent/server/storage/__init__.py,sha256=Sgsyp5hb0_hhIGugAPfQFzBHt_854rS_MuMuE3sn8Gs,389
|
|
73
73
|
dp/agent/server/storage/base_storage.py,sha256=728-oNG6N8isV95gZVnyi4vTznJPJhSjxw9Gl5Y_y5o,2356
|
|
74
74
|
dp/agent/server/storage/bohrium_storage.py,sha256=EsKX4dWWvZTn2TEhZv4zsvihfDK0mmPFecrln-Ytk40,10488
|
|
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.111.dist-info/METADATA,sha256=savZekEjjj6ToO6eqTYjuaEqgq7jJF-KH4Xzf_XSukM,11070
|
|
79
|
+
bohr_agent_sdk-0.1.111.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
80
|
+
bohr_agent_sdk-0.1.111.dist-info/entry_points.txt,sha256=5n5kneF5IbDQtoQ2WfF-QuBjDtsimJte9Rv9baSGgc0,86
|
|
81
|
+
bohr_agent_sdk-0.1.111.dist-info/top_level.txt,sha256=87xLUDhu_1nQHoGLwlhJ6XlO7OsjILh6i1nX6ljFzDo,3
|
|
82
|
+
bohr_agent_sdk-0.1.111.dist-info/RECORD,,
|
dp/agent/client/mcp_client.py
CHANGED
|
@@ -125,7 +125,7 @@ class MCPClient:
|
|
|
125
125
|
|
|
126
126
|
executor = arguments.get("executor")
|
|
127
127
|
storage = arguments.get("storage")
|
|
128
|
-
res = await self.
|
|
128
|
+
res = await self.call_tool("submit_" + tool_name, arguments)
|
|
129
129
|
if res.isError:
|
|
130
130
|
logger.error("Failed to submit %s: %s" % (
|
|
131
131
|
tool_name, res.content[0].text))
|
|
@@ -137,7 +137,7 @@ class MCPClient:
|
|
|
137
137
|
logger.info(job_info["extra_info"])
|
|
138
138
|
|
|
139
139
|
while True:
|
|
140
|
-
res = await self.
|
|
140
|
+
res = await self.call_tool("query_job_status", {
|
|
141
141
|
"job_id": job_id, "executor": executor})
|
|
142
142
|
if res.isError:
|
|
143
143
|
logger.error(res.content[0].text)
|
|
@@ -148,7 +148,7 @@ class MCPClient:
|
|
|
148
148
|
break
|
|
149
149
|
await asyncio.sleep(self.query_interval)
|
|
150
150
|
|
|
151
|
-
res = await self.
|
|
151
|
+
res = await self.call_tool("get_job_results", {
|
|
152
152
|
"job_id": job_id, "executor": executor, "storage": storage})
|
|
153
153
|
if res.isError:
|
|
154
154
|
logger.error("Job %s failed: %s" % (job_id, res.content[0].text))
|
|
@@ -7,7 +7,7 @@ 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 Any, Literal, Optional, TypedDict
|
|
10
|
+
from typing import Any, Literal, Optional, TypedDict, List, Dict
|
|
11
11
|
|
|
12
12
|
import mcp
|
|
13
13
|
from mcp.server.fastmcp import FastMCP
|
|
@@ -133,6 +133,69 @@ def handle_input_artifacts(fn, kwargs, storage):
|
|
|
133
133
|
"storage_type": scheme,
|
|
134
134
|
"uri": uri,
|
|
135
135
|
}
|
|
136
|
+
elif param.annotation is List[Path] or (
|
|
137
|
+
param.annotation is Optional[List[Path]] and
|
|
138
|
+
kwargs.get(name) is not None):
|
|
139
|
+
uris = kwargs[name]
|
|
140
|
+
new_paths = []
|
|
141
|
+
for uri in uris:
|
|
142
|
+
scheme, key = parse_uri(uri)
|
|
143
|
+
if scheme == storage_type:
|
|
144
|
+
s = storage
|
|
145
|
+
else:
|
|
146
|
+
s = storage_dict[scheme]()
|
|
147
|
+
path = s.download(key, "inputs/%s" % name)
|
|
148
|
+
new_paths.append(Path(path))
|
|
149
|
+
logger.info("Artifact %s downloaded to %s" % (
|
|
150
|
+
uri, path))
|
|
151
|
+
kwargs[name] = new_paths
|
|
152
|
+
input_artifacts[name] = {
|
|
153
|
+
"storage_type": storage_type,
|
|
154
|
+
"uri": uris,
|
|
155
|
+
}
|
|
156
|
+
elif param.annotation is Dict[str, Path] or (
|
|
157
|
+
param.annotation is Optional[Dict[str, Path]] and
|
|
158
|
+
kwargs.get(name) is not None):
|
|
159
|
+
uris_dict = kwargs[name]
|
|
160
|
+
new_paths_dict = {}
|
|
161
|
+
for key_name, uri in uris_dict.items():
|
|
162
|
+
scheme, key = parse_uri(uri)
|
|
163
|
+
if scheme == storage_type:
|
|
164
|
+
s = storage
|
|
165
|
+
else:
|
|
166
|
+
s = storage_dict[scheme]()
|
|
167
|
+
path = s.download(key, f"inputs/{name}/{key_name}")
|
|
168
|
+
new_paths_dict[key_name] = Path(path)
|
|
169
|
+
logger.info("Artifact %s (key=%s) downloaded to %s" % (
|
|
170
|
+
uri, key_name, path))
|
|
171
|
+
kwargs[name] = new_paths_dict
|
|
172
|
+
input_artifacts[name] = {
|
|
173
|
+
"storage_type": storage_type,
|
|
174
|
+
"uri": uris_dict,
|
|
175
|
+
}
|
|
176
|
+
elif param.annotation is Dict[str, List[Path]] or (
|
|
177
|
+
param.annotation is Optional[Dict[str, List[Path]]] and
|
|
178
|
+
kwargs.get(name) is not None):
|
|
179
|
+
uris_dict = kwargs[name]
|
|
180
|
+
new_paths_dict = {}
|
|
181
|
+
for key_name, uris in uris_dict.items():
|
|
182
|
+
new_paths = []
|
|
183
|
+
for uri in uris:
|
|
184
|
+
scheme, key = parse_uri(uri)
|
|
185
|
+
if scheme == storage_type:
|
|
186
|
+
s = storage
|
|
187
|
+
else:
|
|
188
|
+
s = storage_dict[scheme]()
|
|
189
|
+
path = s.download(key, f"inputs/{name}/{key_name}")
|
|
190
|
+
new_paths.append(Path(path))
|
|
191
|
+
logger.info("Artifact %s (key=%s) downloaded to %s" % (
|
|
192
|
+
uri, key_name, path))
|
|
193
|
+
new_paths_dict[key_name] = new_paths
|
|
194
|
+
kwargs[name] = new_paths_dict
|
|
195
|
+
input_artifacts[name] = {
|
|
196
|
+
"storage_type": storage_type,
|
|
197
|
+
"uri": uris_dict,
|
|
198
|
+
}
|
|
136
199
|
return kwargs, input_artifacts
|
|
137
200
|
|
|
138
201
|
|
|
@@ -152,6 +215,20 @@ def handle_output_artifacts(results, exec_id, storage):
|
|
|
152
215
|
"storage_type": storage_type,
|
|
153
216
|
"uri": uri,
|
|
154
217
|
}
|
|
218
|
+
elif isinstance(results[name], list) and all(isinstance(item, Path) for item in results[name]):
|
|
219
|
+
new_uris = []
|
|
220
|
+
for item in results[name]:
|
|
221
|
+
key = storage.upload("%s/outputs/%s" % (exec_id, name),
|
|
222
|
+
item)
|
|
223
|
+
uri = storage_type + "://" + key
|
|
224
|
+
logger.info("Artifact %s uploaded to %s" % (
|
|
225
|
+
item, uri))
|
|
226
|
+
new_uris.append(uri)
|
|
227
|
+
results[name] = new_uris
|
|
228
|
+
output_artifacts[name] = {
|
|
229
|
+
"storage_type": storage_type,
|
|
230
|
+
"uri": new_uris,
|
|
231
|
+
}
|
|
155
232
|
return results, output_artifacts
|
|
156
233
|
|
|
157
234
|
|
|
@@ -211,6 +288,30 @@ class CalculationMCPServer:
|
|
|
211
288
|
parameters.append(inspect.Parameter(
|
|
212
289
|
name=param.name, default=param.default,
|
|
213
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))
|
|
214
315
|
else:
|
|
215
316
|
parameters.append(param)
|
|
216
317
|
for param in new_typed_signature.parameters.values():
|
|
@@ -5,6 +5,8 @@ from collections.abc import Callable
|
|
|
5
5
|
from typing import Any, Literal, TypedDict
|
|
6
6
|
|
|
7
7
|
from mcp.server.fastmcp.server import Context
|
|
8
|
+
from mcp.shared.context import RequestContext
|
|
9
|
+
from starlette.requests import Request
|
|
8
10
|
logger = logging.getLogger(__name__)
|
|
9
11
|
|
|
10
12
|
|
|
@@ -27,6 +29,26 @@ class BaseExecutor(ABC):
|
|
|
27
29
|
def get_results(self, job_id: str) -> dict:
|
|
28
30
|
pass
|
|
29
31
|
|
|
32
|
+
def prune_context(self, kwargs: dict):
|
|
33
|
+
for key, value in kwargs.items():
|
|
34
|
+
if isinstance(value, Context):
|
|
35
|
+
context = Context(request_context=RequestContext(
|
|
36
|
+
request_id=value.request_context.request_id,
|
|
37
|
+
meta=value.request_context.meta,
|
|
38
|
+
session=None,
|
|
39
|
+
lifespan_context=value.request_context.lifespan_context,
|
|
40
|
+
request=Request(
|
|
41
|
+
scope={
|
|
42
|
+
k: v for k, v in
|
|
43
|
+
value.request_context.request.scope.items()
|
|
44
|
+
if k not in ["app", "router", "endpoint",
|
|
45
|
+
"starlette.exception_handlers"]
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
))
|
|
49
|
+
kwargs[key] = context
|
|
50
|
+
return kwargs
|
|
51
|
+
|
|
30
52
|
async def async_run(
|
|
31
53
|
self, fn: Callable, kwargs: dict, context: Context,
|
|
32
54
|
trace_id: str) -> TypedDict(
|
|
@@ -129,12 +129,13 @@ class DispatcherExecutor(BaseExecutor):
|
|
|
129
129
|
self.resources["envs"]["DP_AGENT_RUNNING_MODE"] = "1"
|
|
130
130
|
|
|
131
131
|
def submit(self, fn, kwargs):
|
|
132
|
+
kwargs = self.prune_context(kwargs)
|
|
132
133
|
script = ""
|
|
133
134
|
fn_name = fn.__name__
|
|
134
135
|
func_def_script, packages = get_func_def_script(fn)
|
|
135
136
|
self.python_packages.extend(packages)
|
|
136
137
|
|
|
137
|
-
script += "import asyncio, jsonpickle, os\n"
|
|
138
|
+
script += "import asyncio, jsonpickle, os, shutil\n"
|
|
138
139
|
script += "from pathlib import Path\n\n"
|
|
139
140
|
script += "if __name__ == \"__main__\":\n"
|
|
140
141
|
script += " cwd = os.getcwd()\n"
|
|
@@ -148,11 +149,24 @@ class DispatcherExecutor(BaseExecutor):
|
|
|
148
149
|
script += " results = asyncio.run(%s(**kwargs))\n" % fn_name
|
|
149
150
|
else:
|
|
150
151
|
script += " results = %s(**kwargs)\n" % fn_name
|
|
152
|
+
script += " result_dir = None\n"
|
|
153
|
+
script += " import uuid\n"
|
|
151
154
|
script += " if isinstance(results, dict):\n"
|
|
152
155
|
script += " for name in results:\n"
|
|
153
156
|
script += " if isinstance(results[name], Path):\n"
|
|
154
|
-
script += " results[name]
|
|
155
|
-
|
|
157
|
+
script += " if not results[name].absolute().is_relative_to(cwd):\n"
|
|
158
|
+
script += " if result_dir is None:\n"
|
|
159
|
+
script += " result_dir = Path('result_files_dir_' + str(uuid.uuid4()))\n"
|
|
160
|
+
script += " result_dir.mkdir(parents=True, exist_ok=True)\n"
|
|
161
|
+
script += " dest_path = result_dir / results[name].absolute().relative_to('/')\n"
|
|
162
|
+
script += " dest_path.parent.mkdir(parents=True, exist_ok=True)\n"
|
|
163
|
+
script += " if results[name].is_file():\n"
|
|
164
|
+
script += " shutil.copy2(results[name], dest_path)\n"
|
|
165
|
+
script += " elif results[name].is_dir():\n"
|
|
166
|
+
script += " shutil.copytree(results[name], dest_path, dirs_exist_ok=True)\n"
|
|
167
|
+
script += " results[name] = dest_path.absolute().relative_to(cwd)\n"
|
|
168
|
+
script += " else:\n"
|
|
169
|
+
script += " results[name] = results[name].absolute().relative_to(cwd)\n"
|
|
156
170
|
script += " except Exception as e:\n"
|
|
157
171
|
script += " os.chdir(cwd)\n"
|
|
158
172
|
script += " with open('err', 'w') as f:\n"
|
|
@@ -88,6 +88,7 @@ class LocalExecutor(BaseExecutor):
|
|
|
88
88
|
"""
|
|
89
89
|
self.env = env or {}
|
|
90
90
|
self.dflow = dflow
|
|
91
|
+
self.workflow_id = None
|
|
91
92
|
|
|
92
93
|
def set_env(self):
|
|
93
94
|
old_env = {}
|
|
@@ -105,6 +106,7 @@ class LocalExecutor(BaseExecutor):
|
|
|
105
106
|
del os.environ[k]
|
|
106
107
|
|
|
107
108
|
def submit(self, fn, kwargs):
|
|
109
|
+
kwargs = self.prune_context(kwargs)
|
|
108
110
|
os.environ["DP_AGENT_RUNNING_MODE"] = "1"
|
|
109
111
|
old_env = self.set_env()
|
|
110
112
|
params = {"fn": fn, "kwargs": kwargs}
|
|
@@ -123,6 +125,7 @@ class LocalExecutor(BaseExecutor):
|
|
|
123
125
|
match_link = re.search(DFLOW_LINK_PATTERN, log)
|
|
124
126
|
if match_id and match_link:
|
|
125
127
|
wf_id = match_id.group(1)
|
|
128
|
+
self.workflow_id = wf_id
|
|
126
129
|
wf_uid = match_id.group(2)
|
|
127
130
|
wf_link = match_link.group(1)
|
|
128
131
|
extra_info["workflow_id"] = wf_id
|
|
@@ -155,11 +158,18 @@ class LocalExecutor(BaseExecutor):
|
|
|
155
158
|
return "Failed"
|
|
156
159
|
|
|
157
160
|
def terminate(self, job_id):
|
|
161
|
+
if self.workflow_id is not None:
|
|
162
|
+
try:
|
|
163
|
+
from dflow import Workflow
|
|
164
|
+
wf = Workflow(id=self.workflow_id)
|
|
165
|
+
wf.terminate()
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.error(f"Failed to terminate workflow: {e}")
|
|
158
168
|
try:
|
|
159
169
|
p = psutil.Process(int(job_id))
|
|
160
170
|
p.terminate()
|
|
161
|
-
except Exception:
|
|
162
|
-
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error(f"Failed to terminate process: {e}")
|
|
163
173
|
|
|
164
174
|
def get_results(self, job_id):
|
|
165
175
|
if os.path.isfile("%s.txt" % job_id):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|