agentscope-runtime 1.0.0b2__py3-none-any.whl → 1.0.2__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.
Files changed (71) hide show
  1. agentscope_runtime/adapters/agentscope/message.py +78 -10
  2. agentscope_runtime/adapters/agentscope/stream.py +155 -101
  3. agentscope_runtime/adapters/agentscope/tool/tool.py +1 -3
  4. agentscope_runtime/adapters/agno/__init__.py +0 -0
  5. agentscope_runtime/adapters/agno/message.py +30 -0
  6. agentscope_runtime/adapters/agno/stream.py +122 -0
  7. agentscope_runtime/adapters/langgraph/__init__.py +12 -0
  8. agentscope_runtime/adapters/langgraph/message.py +257 -0
  9. agentscope_runtime/adapters/langgraph/stream.py +205 -0
  10. agentscope_runtime/cli/__init__.py +7 -0
  11. agentscope_runtime/cli/cli.py +63 -0
  12. agentscope_runtime/cli/commands/__init__.py +2 -0
  13. agentscope_runtime/cli/commands/chat.py +815 -0
  14. agentscope_runtime/cli/commands/deploy.py +1062 -0
  15. agentscope_runtime/cli/commands/invoke.py +58 -0
  16. agentscope_runtime/cli/commands/list_cmd.py +103 -0
  17. agentscope_runtime/cli/commands/run.py +176 -0
  18. agentscope_runtime/cli/commands/sandbox.py +128 -0
  19. agentscope_runtime/cli/commands/status.py +60 -0
  20. agentscope_runtime/cli/commands/stop.py +185 -0
  21. agentscope_runtime/cli/commands/web.py +166 -0
  22. agentscope_runtime/cli/loaders/__init__.py +6 -0
  23. agentscope_runtime/cli/loaders/agent_loader.py +295 -0
  24. agentscope_runtime/cli/state/__init__.py +10 -0
  25. agentscope_runtime/cli/utils/__init__.py +18 -0
  26. agentscope_runtime/cli/utils/console.py +378 -0
  27. agentscope_runtime/cli/utils/validators.py +118 -0
  28. agentscope_runtime/engine/app/agent_app.py +15 -5
  29. agentscope_runtime/engine/deployers/__init__.py +1 -0
  30. agentscope_runtime/engine/deployers/agentrun_deployer.py +154 -24
  31. agentscope_runtime/engine/deployers/base.py +27 -2
  32. agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
  33. agentscope_runtime/engine/deployers/local_deployer.py +188 -25
  34. agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
  35. agentscope_runtime/engine/deployers/state/__init__.py +9 -0
  36. agentscope_runtime/engine/deployers/state/manager.py +388 -0
  37. agentscope_runtime/engine/deployers/state/schema.py +96 -0
  38. agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
  39. agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
  40. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
  41. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
  42. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
  43. agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
  44. agentscope_runtime/engine/deployers/utils/package.py +56 -6
  45. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +68 -9
  46. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
  47. agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
  48. agentscope_runtime/engine/runner.py +32 -12
  49. agentscope_runtime/engine/schemas/agent_schemas.py +21 -7
  50. agentscope_runtime/engine/schemas/exception.py +580 -0
  51. agentscope_runtime/engine/services/agent_state/__init__.py +2 -0
  52. agentscope_runtime/engine/services/agent_state/state_service_factory.py +55 -0
  53. agentscope_runtime/engine/services/memory/__init__.py +2 -0
  54. agentscope_runtime/engine/services/memory/memory_service_factory.py +126 -0
  55. agentscope_runtime/engine/services/sandbox/__init__.py +2 -0
  56. agentscope_runtime/engine/services/sandbox/sandbox_service_factory.py +49 -0
  57. agentscope_runtime/engine/services/service_factory.py +119 -0
  58. agentscope_runtime/engine/services/session_history/__init__.py +2 -0
  59. agentscope_runtime/engine/services/session_history/session_history_service_factory.py +73 -0
  60. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +35 -10
  61. agentscope_runtime/engine/tracing/wrapper.py +49 -31
  62. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
  64. agentscope_runtime/sandbox/utils.py +2 -0
  65. agentscope_runtime/version.py +1 -1
  66. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +82 -11
  67. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +71 -36
  68. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
  69. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
  70. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
  71. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
@@ -22,12 +22,15 @@ import sys
22
22
  import time
23
23
  import uuid
24
24
  from pathlib import Path
25
- from typing import List, Tuple
25
+ import os
26
+ from typing import List, Tuple, Optional, Union
27
+ from .detached_app import _parse_pyproject_toml, append_project_requirements
26
28
 
27
- try:
28
- import tomllib # Python 3.11+
29
- except ImportError: # pragma: no cover - fallback on older Pythons
30
- tomllib = None # type: ignore
29
+
30
+ def get_user_bundle_appdir(build_root: Path, user_project_dir: Path) -> Path:
31
+ return (
32
+ build_root / "deploy_starter" / "user_bundle" / user_project_dir.name
33
+ )
31
34
 
32
35
 
33
36
  def _read_text_file_lines(file_path: Path) -> List[str]:
@@ -64,72 +67,6 @@ def _parse_requirements_txt(req_path: Path) -> Tuple[List[str], List[str]]:
64
67
  return standard_requirements, local_wheel_paths
65
68
 
66
69
 
67
- def _parse_pyproject_toml(pyproject_path: Path) -> List[str]:
68
- deps: List[str] = []
69
- if not pyproject_path.is_file():
70
- return deps
71
- text = pyproject_path.read_text(encoding="utf-8")
72
-
73
- try:
74
- # Prefer stdlib tomllib (Python 3.11+)
75
- if tomllib is None:
76
- raise RuntimeError("tomllib not available")
77
- data = tomllib.loads(text)
78
- # PEP 621
79
- proj = data.get("project") or {}
80
- deps.extend(proj.get("dependencies") or [])
81
- # Poetry fallback
82
- poetry = (data.get("tool") or {}).get("poetry") or {}
83
- poetry_deps = poetry.get("dependencies") or {}
84
- for name, spec in poetry_deps.items():
85
- if name.lower() == "python":
86
- continue
87
- if isinstance(spec, str):
88
- deps.append(f"{name}{spec if spec.strip() else ''}")
89
- elif isinstance(spec, dict):
90
- version = spec.get("version")
91
- if version:
92
- deps.append(f"{name}{version}")
93
- else:
94
- deps.append(name)
95
- except Exception:
96
- # Minimal non-toml parser fallback: try to extract a dependencies = [ ... ] list
97
- block_match = re.search(
98
- r"dependencies\s*=\s*\[(.*?)\]",
99
- text,
100
- re.S | re.I,
101
- )
102
- if block_match:
103
- block = block_match.group(1)
104
- for m in re.finditer(r"['\"]([^'\"]+)['\"]", block):
105
- deps.append(m.group(1))
106
- # Poetry fallback: very limited, heuristic
107
- poetry_block = re.search(
108
- r"\[tool\.poetry\.dependencies\](.*?)\n\[",
109
- text,
110
- re.S,
111
- )
112
- if poetry_block:
113
- for line in poetry_block.group(1).splitlines():
114
- line = line.strip()
115
- if not line or line.startswith("#"):
116
- continue
117
- if ":" in line:
118
- # name = "^1.2.3"
119
- m = re.match(
120
- r"([A-Za-z0-9_.-]+)\s*=\s*['\"]([^'\"]+)['\"]",
121
- line,
122
- )
123
- if m and m.group(1).lower() != "python":
124
- deps.append(f"{m.group(1)}{m.group(2)}")
125
- else:
126
- # name without version
127
- name = line.split("#")[0].strip()
128
- if name and name.lower() != "python":
129
- deps.append(name)
130
- return deps
131
-
132
-
133
70
  def _gather_user_dependencies(
134
71
  project_dir: Path,
135
72
  ) -> Tuple[List[str], List[Path]]:
@@ -211,6 +148,7 @@ def generate_wrapper_project(
211
148
  start_cmd: str,
212
149
  deploy_name: str,
213
150
  telemetry_enabled: bool = True,
151
+ requirements: Optional[Union[str, List[str]]] = None,
214
152
  ) -> Tuple[Path, Path]:
215
153
  """
216
154
  Create a wrapper project under build_root, embedding user project under
@@ -221,9 +159,8 @@ def generate_wrapper_project(
221
159
  # 1) Copy user project into wrapper under deploy_starter/user_bundle/<project_basename>
222
160
  # Put user code inside the deploy_starter package so wheel includes it and preserves project folder name
223
161
  project_basename = user_project_dir.name
224
- bundle_app_dir = (
225
- wrapper_dir / "deploy_starter" / "user_bundle" / project_basename
226
- )
162
+ bundle_app_dir = get_user_bundle_appdir(wrapper_dir, user_project_dir)
163
+
227
164
  ignore = shutil.ignore_patterns(
228
165
  ".git",
229
166
  ".venv",
@@ -245,16 +182,6 @@ def generate_wrapper_project(
245
182
  )
246
183
 
247
184
  # 2) Dependencies
248
- _, local_wheels = _gather_user_dependencies(user_project_dir)
249
-
250
- # Copy local wheel files to wrapper project
251
- if local_wheels:
252
- wheels_dir = wrapper_dir / "deploy_starter" / "wheels"
253
- wheels_dir.mkdir(parents=True, exist_ok=True)
254
- for wheel_file in local_wheels:
255
- dest = wheels_dir / wheel_file.name
256
- shutil.copy2(wheel_file, dest)
257
-
258
185
  wrapper_deps = [
259
186
  "pyyaml",
260
187
  "alibabacloud-oss-v2",
@@ -267,6 +194,30 @@ def generate_wrapper_project(
267
194
  "jinja2",
268
195
  "psutil",
269
196
  ]
197
+
198
+ _, local_wheels = _gather_user_dependencies(user_project_dir)
199
+
200
+ # gather
201
+ append_project_requirements(
202
+ build_root,
203
+ additional_requirements=requirements,
204
+ use_local_runtime=os.getenv("USE_LOCAL_RUNTIME", "False") == "True",
205
+ )
206
+ _, project_wheels = _gather_user_dependencies(build_root)
207
+
208
+ local_wheels.extend(project_wheels)
209
+
210
+ # Copy local wheel files to wrapper project
211
+ if local_wheels:
212
+ wheels_dir = wrapper_dir / "deploy_starter" / "wheels"
213
+ wheels_dir.mkdir(parents=True, exist_ok=True)
214
+ for wheel_file in local_wheels:
215
+ dest = wheels_dir / wheel_file.name
216
+ shutil.copy2(wheel_file, dest)
217
+ else:
218
+ # if not use local wheel, make sure the agentscope-runtime will be installed
219
+ wrapper_deps.append("agentscope_runtime")
220
+
270
221
  # De-duplicate while preserving order
271
222
  seen = set()
272
223
  standard_reqs, local_wheel_paths = _parse_requirements_txt(
@@ -280,9 +231,6 @@ def generate_wrapper_project(
280
231
  seen.add(key)
281
232
  install_requires.append(pkg)
282
233
 
283
- req_content = "\n".join(install_requires) + "\n"
284
- req_content += "\n".join(local_wheel_paths) + "\n"
285
-
286
234
  # 3) Packaging metadata
287
235
  unique_suffix = uuid.uuid4().hex[:8]
288
236
  package_name = f"agentscope_runtime_{unique_suffix}"
@@ -309,28 +257,7 @@ def read_config():
309
257
  return yaml.safe_load(f) or {{}}
310
258
 
311
259
 
312
- def install_local_wheels():
313
- \"\"\"Install local wheel files if they exist.\"\"\"
314
- wheels_dir = Path(__file__).resolve().parent / 'wheels'
315
- if wheels_dir.exists() and wheels_dir.is_dir():
316
- wheel_files = list(wheels_dir.glob('*.whl'))
317
- if wheel_files:
318
- print(f'[deploy_starter] Installing {{len(wheel_files)}} local wheel(s)...')
319
- for wheel in wheel_files:
320
- try:
321
- subprocess.run(
322
- ['pip', 'install', '--no-deps', str(wheel)],
323
- check=True,
324
- capture_output=True,
325
- )
326
- print(f'[deploy_starter] Installed: {{wheel.name}}')
327
- except subprocess.CalledProcessError as e:
328
- print(f'[deploy_starter] Warning: Failed to install {{wheel.name}}: {{e}}', file=sys.stderr)
329
-
330
-
331
260
  def main():
332
- # Install local wheels first
333
- install_local_wheels()
334
261
 
335
262
  cfg = read_config()
336
263
  subdir = cfg.get('APP_SUBDIR_NAME')
@@ -419,22 +346,76 @@ APP_SUBDIR_NAME: "{project_basename}"
419
346
 
420
347
  setup_py = f"""
421
348
  from setuptools import setup, find_packages
422
- import os
423
- import subprocess
424
- import sys
349
+ from setuptools.command.build_py import build_py
350
+ import zipfile
351
+ import shutil
425
352
  from pathlib import Path
426
- from setuptools import setup, find_packages
427
- from setuptools.command.install import install
353
+ from email.parser import Parser
354
+ import tempfile
355
+
356
+
357
+ class BuildPyWithWheelMerge(build_py):
358
+ \"\"\"Merge bundled wheel packages into the final wheel at build time\"\"\"
359
+
360
+ def run(self):
361
+ build_py.run(self)
362
+ self._merge_wheels()
428
363
 
429
- def run():
430
- # Step 1: Install all .whl files in the ./wheel/ directory (relative to setup.py)
431
- wheel_dir = Path(__file__).parent / "wheel"
432
- if wheel_dir.is_dir():
433
- whl_files = sorted(wheel_dir.glob("*.whl"))
434
- if whl_files:
435
- for whl in whl_files:
436
- # Use the same Python interpreter to avoid env issues
437
- subprocess.check_call([sys.executable, "-m", "pip", "install", str(whl)])
364
+ def _merge_wheels(self):
365
+ \"\"\"Extract and merge all wheel files from the wheels directory\"\"\"
366
+ wheels_dir = Path("deploy_starter/wheels")
367
+ if not wheels_dir.exists():
368
+ return
369
+
370
+ whl_files = list(wheels_dir.glob("*.whl"))
371
+ if not whl_files:
372
+ return
373
+
374
+ print(f"\\n{{'='*60}}\\nMerging {{len(whl_files)}} wheel(s)...\\n{{'='*60}}\\n")
375
+
376
+ for whl_file in whl_files:
377
+ self._extract_wheel(whl_file, Path(self.build_lib))
378
+
379
+ print(f"{{'='*60}}\\nMerge completed!\\n{{'='*60}}\\n")
380
+
381
+ def _extract_wheel(self, whl_path, build_lib):
382
+ \"\"\"Extract wheel contents to build directory\"\"\"
383
+ with tempfile.TemporaryDirectory() as tmpdir, zipfile.ZipFile(whl_path) as zf:
384
+ zf.extractall(tmpdir)
385
+
386
+ for item in Path(tmpdir).iterdir():
387
+ if item.suffix in ['.dist-info', '.egg-info']:
388
+ continue
389
+
390
+ dest = build_lib / item.name
391
+ if dest.exists():
392
+ shutil.rmtree(dest) if dest.is_dir() else dest.unlink()
393
+
394
+ shutil.copytree(item, dest) if item.is_dir() else shutil.copy2(item, dest)
395
+ print(f" Merged: {{item.name}}")
396
+
397
+
398
+ def extract_wheel_dependencies():
399
+ \"\"\"Extract dependency declarations from wheel files\"\"\"
400
+ deps = []
401
+ wheels_dir = Path("deploy_starter/wheels")
402
+
403
+ if not wheels_dir.exists():
404
+ return deps
405
+
406
+ for whl in wheels_dir.glob("*.whl"):
407
+ try:
408
+ with zipfile.ZipFile(whl) as zf:
409
+ metadata_file = next((f for f in zf.namelist() if f.endswith('/METADATA')), None)
410
+ if metadata_file:
411
+ content = zf.read(metadata_file).decode('utf-8')
412
+ metadata = Parser().parsestr(content)
413
+ deps.extend([v.split(';')[0].strip() for k, v in metadata.items()
414
+ if k == 'Requires-Dist' and v.split(';')[0].strip() not in deps])
415
+ except Exception as e:
416
+ print(f"Warning: Failed to extract deps from {{whl.name}}: {{e}}")
417
+
418
+ return deps
438
419
 
439
420
 
440
421
  setup(
@@ -442,7 +423,10 @@ setup(
442
423
  version='{version}',
443
424
  packages=find_packages(),
444
425
  include_package_data=True,
445
- install_requires={install_requires!r},
426
+ install_requires={install_requires!r} + extract_wheel_dependencies(),
427
+ cmdclass={{
428
+ 'build_py': BuildPyWithWheelMerge,
429
+ }},
446
430
  )
447
431
  """
448
432
  _write_file(wrapper_dir / "setup.py", setup_py)
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- # pylint: disable=not-callable
2
+ # pylint: disable=not-callable,too-many-statements,too-many-branches
3
3
  import asyncio
4
4
  import logging
5
5
  import inspect
@@ -29,6 +29,7 @@ from .schemas.agent_schemas import (
29
29
  SequenceNumberGenerator,
30
30
  Error,
31
31
  )
32
+ from .schemas.exception import AppBaseException, UnknownAgentException
32
33
  from .tracing import TraceType
33
34
  from .tracing.wrapper import trace
34
35
  from .tracing.message_util import (
@@ -216,22 +217,23 @@ class Runner:
216
217
  if isinstance(request, dict):
217
218
  request = AgentRequest(**request)
218
219
 
220
+ # Assign session ID
221
+ request.session_id = request.session_id or str(uuid.uuid4())
222
+
223
+ # Assign user ID
224
+ request.user_id = request.user_id or request.session_id
225
+
219
226
  seq_gen = SequenceNumberGenerator()
220
227
 
221
228
  # Initial response
222
229
  response = AgentResponse(id=request.id)
230
+ response.session_id = request.session_id
223
231
  yield seq_gen.yield_with_sequence(response)
224
232
 
225
233
  # Set to in-progress status
226
234
  response.in_progress()
227
235
  yield seq_gen.yield_with_sequence(response)
228
236
 
229
- # Assign session ID
230
- request.session_id = request.session_id or str(uuid.uuid4())
231
-
232
- # Assign user ID
233
- request.user_id = request.session_id or request.session_id
234
-
235
237
  query_kwargs = {
236
238
  "request": request,
237
239
  }
@@ -250,6 +252,26 @@ class Runner:
250
252
  kwargs.update(
251
253
  {"msgs": message_to_agentscope_msg(request.input)},
252
254
  )
255
+ elif self.framework_type == "langgraph":
256
+ from ..adapters.langgraph.stream import (
257
+ adapt_langgraph_message_stream,
258
+ )
259
+ from ..adapters.langgraph.message import message_to_langgraph_msg
260
+
261
+ stream_adapter = adapt_langgraph_message_stream
262
+ kwargs.update(
263
+ {"msgs": message_to_langgraph_msg(request.input)},
264
+ )
265
+ elif self.framework_type == "agno":
266
+ from ..adapters.agno.stream import (
267
+ adapt_agno_message_stream,
268
+ )
269
+ from ..adapters.agno.message import message_to_agno_message
270
+
271
+ stream_adapter = adapt_agno_message_stream
272
+ kwargs.update(
273
+ {"msgs": await message_to_agno_message(request.input)},
274
+ )
253
275
  # TODO: support other frameworks
254
276
  else:
255
277
 
@@ -275,11 +297,9 @@ class Runner:
275
297
  response.add_new_message(event)
276
298
  yield seq_gen.yield_with_sequence(event)
277
299
  except Exception as e:
278
- # TODO: fix code
279
- error = Error(
280
- code="500",
281
- message=f"Error happens in `query_handler`: {e}",
282
- )
300
+ if not isinstance(e, AppBaseException):
301
+ e = UnknownAgentException(original_exception=e)
302
+ error = Error(code=e.code, message=e.message)
283
303
  logger.error(f"{error.model_dump()}: {traceback.format_exc()}")
284
304
  yield seq_gen.yield_with_sequence(response.failed(error))
285
305
  return
@@ -27,6 +27,7 @@ class MessageType:
27
27
  MCP_APPROVAL_REQUEST = "mcp_approval_request"
28
28
  MCP_TOOL_CALL = "mcp_call"
29
29
  MCP_APPROVAL_RESPONSE = "mcp_approval_response"
30
+ MCP_TOOL_CALL_OUTPUT = "mcp_call_output"
30
31
  REASONING = "reasoning"
31
32
  HEARTBEAT = "heartbeat"
32
33
  ERROR = "error"
@@ -152,22 +153,35 @@ class FunctionCallOutput(BaseModel):
152
153
 
153
154
 
154
155
  class McpCall(BaseModel):
155
- id: str
156
+ """
157
+ MCP TOOL CALL MESSAGE BODY
158
+ """
159
+
160
+ call_id: Optional[str] = None
156
161
  """The unique ID of the tool call."""
157
162
 
158
- arguments: str
163
+ arguments: Optional[str] = None
159
164
  """A JSON string of the arguments passed to the tool."""
160
165
 
161
- name: str
166
+ name: Optional[str] = None
162
167
  """The name of the tool that was run."""
163
168
 
164
- server_label: str
169
+ server_label: Optional[str] = None
165
170
  """The label of the MCP server running the tool."""
166
171
 
167
- error: Optional[str] = None
168
- """The error from the tool call, if any."""
169
172
 
170
- output: Optional[str] = None
173
+ class McpCallOutput(BaseModel):
174
+ """
175
+ MCP TOOL CALL OUTPUT MESSAGE BODY
176
+ """
177
+
178
+ call_id: str
179
+ """The unique ID of the tool call."""
180
+
181
+ name: Optional[str] = None
182
+ """The name of the tool call."""
183
+
184
+ output: str
171
185
  """The output from the tool call."""
172
186
 
173
187