bohr-agent-sdk 0.1.19__py3-none-any.whl → 0.1.21__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bohr-agent-sdk
3
- Version: 0.1.19
3
+ Version: 0.1.21
4
4
  Summary: SDK for scientific agents
5
5
  Home-page: https://github.com/dptech-corp/bohr-agent-sdk/
6
6
  Author: DP Technology
@@ -20,21 +20,21 @@ dp/agent/device/device/__init__.py,sha256=w7_1S16S1vWUq0RGl0GFgjq2vFkc5oNvy8cQTn
20
20
  dp/agent/device/device/device.py,sha256=9ZRIJth-4qMO-i-u_b_cO3d6a4eTbTQjPaxFsV_zEkc,9643
21
21
  dp/agent/device/device/types.py,sha256=JuxB-hjf1CjjvfBxCLwRAXVFlYS-nPEdiJpBWLFVCzo,1924
22
22
  dp/agent/server/__init__.py,sha256=rckaYd8pbYyB4ENEhgjXKeGMXjdnrgcJpdM1gu5u1Wc,508
23
- dp/agent/server/calculation_mcp_server.py,sha256=omPcrjVT3k8ek3BbB5Dc3TRjdSC_0IEqjCrK2Ka0YqE,10888
23
+ dp/agent/server/calculation_mcp_server.py,sha256=oumU2xtqrrgp8BKiuF9YsqMNevXba2JACyYsvNy4nQo,11008
24
24
  dp/agent/server/preprocessor.py,sha256=XUWu7QOwo_sIDMYS2b1OTrM33EXEVH_73vk-ju1Ok8A,1264
25
25
  dp/agent/server/utils.py,sha256=8jgYZEW4XBp86AF2Km6QkwHltBmrnS-soTpHov7ZEJw,4501
26
26
  dp/agent/server/executor/__init__.py,sha256=s95M5qKQk39Yi9qaVJZhk_nfj54quSf7EDghR3OCFUA,248
27
27
  dp/agent/server/executor/base_executor.py,sha256=EFJBsYVYAvuRbiLAbLOwLTw3h7ScjN025xnSP4uJHrQ,2052
28
- dp/agent/server/executor/dispatcher_executor.py,sha256=zp9RDUoT2RYZGbKXsUsk4HbS0lyM9IEt1zTwOTCQ1YY,9239
29
- dp/agent/server/executor/local_executor.py,sha256=alNpEPicZQOQMtXc1vqGSo4El_9agXAJ5ldqi-OJGL4,3887
28
+ dp/agent/server/executor/dispatcher_executor.py,sha256=urpzmKH_tBOgblBdJEa3y8eEhXqUDrdcdWCnUdJpfZk,9420
29
+ dp/agent/server/executor/local_executor.py,sha256=9JEtZTEbKsmHAY5nsdLZ36gH5M_rSG6En0W9IrCUwm0,5803
30
30
  dp/agent/server/storage/__init__.py,sha256=Sgsyp5hb0_hhIGugAPfQFzBHt_854rS_MuMuE3sn8Gs,389
31
31
  dp/agent/server/storage/base_storage.py,sha256=728-oNG6N8isV95gZVnyi4vTznJPJhSjxw9Gl5Y_y5o,2356
32
32
  dp/agent/server/storage/bohrium_storage.py,sha256=EsKX4dWWvZTn2TEhZv4zsvihfDK0mmPFecrln-Ytk40,10488
33
33
  dp/agent/server/storage/http_storage.py,sha256=KiySq7g9-iJr12XQCKKyJLn8wJoDnSRpQAR5_qPJ1ZU,1471
34
34
  dp/agent/server/storage/local_storage.py,sha256=t1wfjByjXew9ws3PuUxWxmZQ0-Wt1a6t4wmj3fW62GI,1352
35
35
  dp/agent/server/storage/oss_storage.py,sha256=pgjmi7Gir3Y5wkMDCvU4fvSls15fXT7Ax-h9MYHFPK0,3359
36
- bohr_agent_sdk-0.1.19.dist-info/METADATA,sha256=tDUixjAnwCjzdtcuXXc-71c2ZhUiJgKmRlXVMQguDEE,6329
37
- bohr_agent_sdk-0.1.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- bohr_agent_sdk-0.1.19.dist-info/entry_points.txt,sha256=5n5kneF5IbDQtoQ2WfF-QuBjDtsimJte9Rv9baSGgc0,86
39
- bohr_agent_sdk-0.1.19.dist-info/top_level.txt,sha256=87xLUDhu_1nQHoGLwlhJ6XlO7OsjILh6i1nX6ljFzDo,3
40
- bohr_agent_sdk-0.1.19.dist-info/RECORD,,
36
+ bohr_agent_sdk-0.1.21.dist-info/METADATA,sha256=nHfB7MVnAuzZs1EUND-29vdVA3KDSzpuJmQxTswFT_I,6329
37
+ bohr_agent_sdk-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ bohr_agent_sdk-0.1.21.dist-info/entry_points.txt,sha256=5n5kneF5IbDQtoQ2WfF-QuBjDtsimJte9Rv9baSGgc0,86
39
+ bohr_agent_sdk-0.1.21.dist-info/top_level.txt,sha256=87xLUDhu_1nQHoGLwlhJ6XlO7OsjILh6i1nX6ljFzDo,3
40
+ bohr_agent_sdk-0.1.21.dist-info/RECORD,,
@@ -29,7 +29,7 @@ def parse_uri(uri):
29
29
 
30
30
 
31
31
  def init_storage(storage_config: Optional[dict] = None):
32
- if storage_config is None:
32
+ if not storage_config:
33
33
  storage_config = {"type": "local"}
34
34
  storage_config = deepcopy(storage_config)
35
35
  storage_type = storage_config.pop("type")
@@ -38,7 +38,7 @@ def init_storage(storage_config: Optional[dict] = None):
38
38
 
39
39
 
40
40
  def init_executor(executor_config: Optional[dict] = None):
41
- if executor_config is None:
41
+ if not executor_config:
42
42
  executor_config = {"type": "local"}
43
43
  executor_config = deepcopy(executor_config)
44
44
  executor_type = executor_config.pop("type")
@@ -227,7 +227,11 @@ class CalculationMCPServer:
227
227
  exec_id = res["job_id"]
228
228
  job_id = "%s/%s" % (trace_id, exec_id)
229
229
  logger.info("Job submitted (ID: %s)" % job_id)
230
- return convert_to_content({"job_id": job_id}, job_info={
230
+ result = {
231
+ "job_id": job_id,
232
+ "extra_info": res.get("extra_info"),
233
+ }
234
+ return convert_to_content(result, job_info={
231
235
  "trace_id": trace_id,
232
236
  "executor_type": executor_type,
233
237
  "job_id": job_id,
@@ -12,12 +12,15 @@ from dpdispatcher import Machine, Resources, Task, Submission
12
12
 
13
13
  from .base_executor import BaseExecutor
14
14
  from .... import __path__
15
+
15
16
  config = {
16
17
  "username": os.environ.get("BOHRIUM_USERNAME", ""),
17
18
  "password": os.environ.get("BOHRIUM_PASSWORD", ""),
18
19
  "project_id": os.environ.get("BOHRIUM_PROJECT_ID", ""),
19
20
  "access_key": os.environ.get("BOHRIUM_ACCESS_KEY", ""),
20
21
  "app_key": os.environ.get("BOHRIUM_APP_KEY", "agent"),
22
+ "bohrium_url": os.environ.get("BOHRIUM_BOHRIUM_URL",
23
+ "https://bohrium.dp.tech"),
21
24
  }
22
25
  logger = logging.getLogger(__name__)
23
26
 
@@ -26,17 +29,17 @@ def get_source_code(fn):
26
29
  source_lines, start_line = inspect.getsourcelines(fn)
27
30
  source_file = inspect.getsourcefile(fn)
28
31
  with open(source_file, "r", encoding="utf-8") as fd:
29
- pre_lines = fd.readlines()[:start_line-1]
32
+ pre_lines = fd.readlines()[:start_line - 1]
30
33
  return "".join(pre_lines + source_lines) + "\n"
31
34
 
32
35
 
33
36
  class DispatcherExecutor(BaseExecutor):
34
37
  def __init__(
35
- self,
36
- machine=None,
37
- resources=None,
38
- python_packages=None,
39
- python_executable="python3",
38
+ self,
39
+ machine=None,
40
+ resources=None,
41
+ python_packages=None,
42
+ python_executable="python3",
40
43
  ):
41
44
  """Use DPDispatcher to execute the tool
42
45
  Refer to https://docs.deepmodeling.com/projects/dpdispatcher.
@@ -107,7 +110,7 @@ class DispatcherExecutor(BaseExecutor):
107
110
  script += "if __name__ == \"__main__\":\n"
108
111
  script += " cwd = os.getcwd()\n"
109
112
  script += " kwargs = jsonpickle.loads(r'''%s''')\n" % \
110
- jsonpickle.dumps(kwargs)
113
+ jsonpickle.dumps(kwargs)
111
114
  script += " try:\n"
112
115
  if import_func_line is not None:
113
116
  script += " " + import_func_line
@@ -118,8 +121,8 @@ class DispatcherExecutor(BaseExecutor):
118
121
  script += " if isinstance(results, dict):\n"
119
122
  script += " for name in results:\n"
120
123
  script += " if isinstance(results[name], Path):\n"
121
- script += " results[name] = "\
122
- "results[name].absolute().relative_to(cwd)\n"
124
+ script += " results[name] = " \
125
+ "results[name].absolute().relative_to(cwd)\n"
123
126
  script += " except Exception as e:\n"
124
127
  script += " os.chdir(cwd)\n"
125
128
  script += " with open('err', 'w') as f:\n"
@@ -173,8 +176,8 @@ class DispatcherExecutor(BaseExecutor):
173
176
  extra_info = {
174
177
  "bohr_job_id": bohr_job_id,
175
178
  "bohr_group_id": bohr_group_id,
176
- "job_link": "https://bohrium.dp.tech/jobs/detail/%s" %
177
- bohr_job_id,
179
+ "job_link": f"{config['bohrium_url']}/jobs/detail/%s" %
180
+ bohr_job_id,
178
181
  }
179
182
  logger.info(extra_info)
180
183
  res["extra_info"] = extra_info
@@ -182,8 +185,8 @@ class DispatcherExecutor(BaseExecutor):
182
185
  bohr_job_id = submission.belonging_jobs[0].job_id
183
186
  extra_info = {
184
187
  "bohr_job_id": bohr_job_id,
185
- "job_link": "https://bohrium.dp.tech/jobs/detail/%s" %
186
- bohr_job_id,
188
+ "job_link": f"{config['bohrium_url']}/jobs/detail/%s" %
189
+ bohr_job_id,
187
190
  }
188
191
  logger.info(extra_info)
189
192
  res["extra_info"] = extra_info
@@ -1,19 +1,43 @@
1
1
  import asyncio
2
2
  import importlib
3
3
  import inspect
4
+ import io
4
5
  import jsonpickle
5
6
  import os
6
7
  import psutil
8
+ import re
7
9
  import sys
10
+ import time
8
11
  import uuid
9
12
  from multiprocessing import Process
10
13
  from typing import Dict, Optional
11
14
 
12
15
  from .base_executor import BaseExecutor
16
+ from ..utils import get_logger
13
17
 
18
+ DFLOW_ID_PATTERN = r"Workflow has been submitted \(ID: ([^,]*), UID: ([^)]*)\)"
19
+ DFLOW_LINK_PATTERN = r"Workflow link: (.*)"
20
+ logger = get_logger(__name__)
14
21
 
15
- def wrapped_fn(fn, kwargs):
22
+
23
+ class Tee(io.TextIOBase):
24
+ def __init__(self, file, stdout):
25
+ self.file = file
26
+ self.stdout = stdout
27
+
28
+ def write(self, text):
29
+ self.stdout.write(text)
30
+ self.file.write(text)
31
+ self.file.flush()
32
+ return len(text)
33
+
34
+
35
+ def wrapped_fn(fn, kwargs, redirect_file=None):
16
36
  pid = os.getpid()
37
+ if redirect_file:
38
+ stdout = sys.stdout
39
+ flog = open(redirect_file, "w")
40
+ sys.stdout = Tee(flog, stdout)
17
41
  try:
18
42
  if inspect.iscoroutinefunction(fn):
19
43
  result = asyncio.run(fn(**kwargs))
@@ -23,6 +47,10 @@ def wrapped_fn(fn, kwargs):
23
47
  with open("err", "w") as f:
24
48
  f.write(str(e))
25
49
  raise e
50
+ finally:
51
+ if redirect_file:
52
+ sys.stdout = stdout
53
+ flog.close()
26
54
  with open("%s.txt" % pid, "w") as f:
27
55
  f.write(jsonpickle.dumps(result))
28
56
 
@@ -47,13 +75,16 @@ def reload_dflow_config():
47
75
 
48
76
 
49
77
  class LocalExecutor(BaseExecutor):
50
- def __init__(self, env: Optional[Dict[str, str]] = None):
78
+ def __init__(self, env: Optional[Dict[str, str]] = None,
79
+ dflow: bool = False):
51
80
  """
52
81
  Execute the tool locally
53
82
  Args:
54
83
  env: The environmental variables at run time
84
+ dflow: Wait until workflow submitted in submit method
55
85
  """
56
86
  self.env = env or {}
87
+ self.dflow = dflow
57
88
 
58
89
  def set_env(self):
59
90
  old_env = {}
@@ -73,10 +104,34 @@ class LocalExecutor(BaseExecutor):
73
104
  def submit(self, fn, kwargs):
74
105
  os.environ["DP_AGENT_RUNNING_MODE"] = "1"
75
106
  old_env = self.set_env()
76
- p = Process(target=wrapped_fn, kwargs={"fn": fn, "kwargs": kwargs})
107
+ params = {"fn": fn, "kwargs": kwargs}
108
+ if self.dflow:
109
+ params["redirect_file"] = "log.txt"
110
+ p = Process(target=wrapped_fn, kwargs=params)
77
111
  p.start()
112
+ extra_info = {}
113
+ if self.dflow:
114
+ while True:
115
+ alive = p.is_alive()
116
+ if os.path.isfile("log.txt"):
117
+ with open("log.txt", "r") as f:
118
+ log = f.read()
119
+ match_id = re.search(DFLOW_ID_PATTERN, log)
120
+ match_link = re.search(DFLOW_LINK_PATTERN, log)
121
+ if match_id and match_link:
122
+ wf_id = match_id.group(1)
123
+ wf_uid = match_id.group(2)
124
+ wf_link = match_link.group(1)
125
+ extra_info["workflow_id"] = wf_id
126
+ extra_info["workflow_uid"] = wf_uid
127
+ extra_info["workflow_link"] = wf_link
128
+ break
129
+ if not alive:
130
+ break
131
+ logger.info("Waiting workflow to be submitted")
132
+ time.sleep(1)
78
133
  self.recover_env(old_env)
79
- return {"job_id": str(p.pid)}
134
+ return {"job_id": str(p.pid), "extra_info": extra_info}
80
135
 
81
136
  def query_status(self, job_id):
82
137
  try: