agentscope-runtime 0.1.6__py3-none-any.whl → 0.2.0__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 (87) hide show
  1. agentscope_runtime/common/container_clients/__init__.py +0 -0
  2. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
  3. agentscope_runtime/engine/__init__.py +12 -0
  4. agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
  5. agentscope_runtime/engine/agents/agno_agent.py +8 -10
  6. agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
  7. agentscope_runtime/engine/app/__init__.py +6 -0
  8. agentscope_runtime/engine/app/agent_app.py +239 -0
  9. agentscope_runtime/engine/app/base_app.py +181 -0
  10. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  11. agentscope_runtime/engine/deployers/__init__.py +13 -0
  12. agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
  14. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
  15. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
  16. agentscope_runtime/engine/deployers/base.py +1 -0
  17. agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
  18. agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +414 -501
  20. agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
  21. agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
  22. agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
  23. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
  24. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
  25. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
  26. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
  27. agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
  28. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
  29. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
  30. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
  31. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
  32. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
  33. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
  34. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  35. agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
  36. agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
  37. agentscope_runtime/engine/runner.py +76 -35
  38. agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
  39. agentscope_runtime/engine/schemas/embedding.py +37 -0
  40. agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
  41. agentscope_runtime/engine/schemas/oai_llm.py +538 -0
  42. agentscope_runtime/engine/schemas/realtime.py +254 -0
  43. agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
  44. agentscope_runtime/engine/tracing/__init__.py +9 -3
  45. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  46. agentscope_runtime/engine/tracing/base.py +66 -34
  47. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  48. agentscope_runtime/engine/tracing/message_util.py +528 -0
  49. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  50. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  51. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  52. agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
  53. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
  54. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  55. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
  57. agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
  58. agentscope_runtime/sandbox/client/http_client.py +52 -18
  59. agentscope_runtime/sandbox/constant.py +3 -0
  60. agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
  61. agentscope_runtime/sandbox/custom/example.py +2 -1
  62. agentscope_runtime/sandbox/enums.py +0 -1
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
  64. agentscope_runtime/sandbox/model/container.py +6 -0
  65. agentscope_runtime/sandbox/registry.py +1 -1
  66. agentscope_runtime/sandbox/tools/tool.py +4 -0
  67. agentscope_runtime/version.py +1 -1
  68. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
  69. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
  70. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
  71. /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
  72. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  73. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  74. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  75. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  76. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
  77. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  78. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  79. /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
  80. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  81. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  82. /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
  83. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  84. /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
  85. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
  86. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
  87. {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,157 @@
1
+ # -*- coding: utf-8 -*-
2
+ """FastAPI templates management and rendering."""
3
+
4
+ import os
5
+ from typing import Dict, Any, Optional
6
+ from jinja2 import Template, Environment, FileSystemLoader
7
+ from ..deployment_modes import DeploymentMode
8
+
9
+
10
+ class FastAPITemplateManager:
11
+ """Manager for FastAPI deployment templates."""
12
+
13
+ def __init__(self):
14
+ """Initialize template manager."""
15
+ self.template_dir = os.path.join(
16
+ os.path.dirname(__file__),
17
+ )
18
+ self.env = Environment(
19
+ loader=FileSystemLoader(self.template_dir),
20
+ trim_blocks=True,
21
+ lstrip_blocks=True,
22
+ )
23
+
24
+ def render_standalone_template(
25
+ self,
26
+ agent_name: str,
27
+ endpoint_path: str = "/process",
28
+ deployment_mode: str = DeploymentMode.STANDALONE,
29
+ protocol_adapters: Optional[str] = None,
30
+ **kwargs,
31
+ ) -> str:
32
+ """Render the standalone deployment template.
33
+
34
+ Args:
35
+ agent_name: Name of the agent variable
36
+ endpoint_path: API endpoint path
37
+ deployment_mode: Deployment mode (standalone or detached_process)
38
+ protocol_adapters: Protocol adapters code string
39
+ **kwargs: Additional template variables
40
+
41
+ Returns:
42
+ Rendered template content
43
+ """
44
+ template = self.env.get_template("standalone_main.py.j2")
45
+ return template.render(
46
+ agent_name=agent_name,
47
+ endpoint_path=endpoint_path,
48
+ deployment_mode=deployment_mode,
49
+ protocol_adapters=protocol_adapters,
50
+ **kwargs,
51
+ )
52
+
53
+ def render_detached_script_template(
54
+ self,
55
+ endpoint_path: str = "/process",
56
+ host: str = "127.0.0.1",
57
+ port: int = 8000,
58
+ stream_enabled: bool = True,
59
+ response_type: str = "sse",
60
+ runner_code: str = "",
61
+ func_code: str = "",
62
+ services_config: str = "",
63
+ protocol_adapters: Optional[str] = None,
64
+ **kwargs,
65
+ ) -> str:
66
+ """Render the detached process script template.
67
+
68
+ Args:
69
+ endpoint_path: API endpoint path
70
+ host: Host to bind to
71
+ port: Port to bind to
72
+ stream_enabled: Enable streaming responses
73
+ response_type: Response type
74
+ runner_code: Code to setup runner
75
+ func_code: Code to setup custom function
76
+ services_config: Services configuration code
77
+ protocol_adapters: Protocol adapters code string
78
+ **kwargs: Additional template variables
79
+
80
+ Returns:
81
+ Rendered template content
82
+ """
83
+ template = self.env.get_template("detached_script.py.j2")
84
+ return template.render(
85
+ endpoint_path=endpoint_path,
86
+ host=host,
87
+ port=port,
88
+ stream_enabled=str(stream_enabled).lower(),
89
+ response_type=response_type,
90
+ runner_code=runner_code,
91
+ func_code=func_code,
92
+ services_config=services_config,
93
+ protocol_adapters=protocol_adapters,
94
+ **kwargs,
95
+ )
96
+
97
+ def render_template_from_string(
98
+ self,
99
+ template_string: str,
100
+ **variables,
101
+ ) -> str:
102
+ """Render a template from string.
103
+
104
+ Args:
105
+ template_string: Template content as string
106
+ **variables: Template variables
107
+
108
+ Returns:
109
+ Rendered template content
110
+ """
111
+ template = Template(template_string)
112
+ return template.render(**variables)
113
+
114
+ def get_template_list(self) -> list:
115
+ """Get list of available templates.
116
+
117
+ Returns:
118
+ List of template filenames
119
+ """
120
+ if not os.path.exists(self.template_dir):
121
+ return []
122
+
123
+ templates = []
124
+ for filename in os.listdir(self.template_dir):
125
+ if filename.endswith(".j2"):
126
+ templates.append(filename)
127
+
128
+ return templates
129
+
130
+ def validate_template_variables(
131
+ self,
132
+ template_name: str,
133
+ variables: Dict[str, Any],
134
+ ) -> Dict[str, list]:
135
+ """Validate template variables.
136
+
137
+ Args:
138
+ template_name: Name of the template
139
+ variables: Variables to validate
140
+
141
+ Returns:
142
+ Dictionary with 'missing' and 'extra' keys containing lists
143
+ """
144
+ # This is a basic implementation
145
+ # In practice, you might want to parse the template to find
146
+ # required variables
147
+ required_vars = {
148
+ "standalone_main.py.j2": ["agent_name", "endpoint_path"],
149
+ }
150
+
151
+ required = required_vars.get(template_name, [])
152
+ provided = list(variables.keys())
153
+
154
+ missing = [var for var in required if var not in provided]
155
+ extra = [var for var in provided if var not in required]
156
+
157
+ return {"missing": missing, "extra": extra}
@@ -0,0 +1,268 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint:disable=consider-using-with
3
+
4
+ import asyncio
5
+ import os
6
+ import subprocess
7
+ from typing import Optional
8
+
9
+ import psutil
10
+
11
+
12
+ class ProcessManager:
13
+ """Manager for detached process lifecycle."""
14
+
15
+ def __init__(self, shutdown_timeout: int = 30):
16
+ """Initialize process manager.
17
+
18
+ Args:
19
+ shutdown_timeout: Timeout in seconds for graceful shutdown
20
+ """
21
+ self.shutdown_timeout = shutdown_timeout
22
+
23
+ async def start_detached_process(
24
+ self,
25
+ script_path: str,
26
+ host: str = "127.0.0.1",
27
+ port: int = 8000,
28
+ env: Optional[dict] = None,
29
+ ) -> int:
30
+ """Start a detached process running the given script.
31
+
32
+ Args:
33
+ script_path: Path to the Python script to run
34
+ host: Host to bind to
35
+ port: Port to bind to
36
+ env: Additional environment variables
37
+
38
+ Returns:
39
+ Process PID
40
+
41
+ Raises:
42
+ RuntimeError: If process creation fails
43
+ """
44
+ try:
45
+ # Prepare environment
46
+ process_env = os.environ.copy()
47
+ if env:
48
+ process_env.update(env)
49
+
50
+ # Start detached process
51
+ process = subprocess.Popen(
52
+ [
53
+ "python",
54
+ script_path,
55
+ "--host",
56
+ host,
57
+ "--port",
58
+ str(port),
59
+ ],
60
+ stdout=subprocess.DEVNULL,
61
+ stderr=subprocess.DEVNULL,
62
+ stdin=subprocess.DEVNULL,
63
+ start_new_session=True, # Create new process group
64
+ env=process_env,
65
+ cwd=os.path.dirname(script_path),
66
+ )
67
+
68
+ # Verify process started successfully
69
+ await asyncio.sleep(0.1) # Give process time to start
70
+ if process.poll() is not None:
71
+ raise RuntimeError("Process failed to start")
72
+
73
+ return process.pid
74
+
75
+ except Exception as e:
76
+ raise RuntimeError(f"Failed to start detached process: {e}") from e
77
+
78
+ async def stop_process_gracefully(
79
+ self,
80
+ pid: int,
81
+ timeout: Optional[int] = None,
82
+ ) -> bool:
83
+ """Stop a process gracefully.
84
+
85
+ Args:
86
+ pid: Process ID to stop
87
+ timeout: Timeout for graceful shutdown (uses default if None)
88
+
89
+ Returns:
90
+ True if process was stopped successfully
91
+
92
+ Raises:
93
+ RuntimeError: If process termination fails
94
+ """
95
+ if timeout is None:
96
+ timeout = self.shutdown_timeout
97
+
98
+ try:
99
+ # Check if process exists
100
+ if not psutil.pid_exists(pid):
101
+ return True # Already terminated
102
+
103
+ process = psutil.Process(pid)
104
+
105
+ # Send SIGTERM for graceful shutdown
106
+ process.terminate()
107
+
108
+ # Wait for process to terminate
109
+ try:
110
+ process.wait(timeout=timeout)
111
+ return True
112
+ except psutil.TimeoutExpired:
113
+ # Force kill if graceful shutdown failed
114
+ process.kill()
115
+ process.wait(timeout=5) # Wait for kill to complete
116
+ return True
117
+
118
+ except psutil.NoSuchProcess:
119
+ return True # Process already terminated
120
+ except psutil.AccessDenied as e:
121
+ raise RuntimeError(
122
+ f"Access denied when terminating process {pid}: {e}",
123
+ ) from e
124
+ except Exception as e:
125
+ raise RuntimeError(
126
+ f"Failed to terminate process {pid}: {e}",
127
+ ) from e
128
+
129
+ def is_process_running(self, pid: int) -> bool:
130
+ """Check if a process is running.
131
+
132
+ Args:
133
+ pid: Process ID to check
134
+
135
+ Returns:
136
+ True if process is running
137
+ """
138
+ try:
139
+ return psutil.pid_exists(pid)
140
+ except Exception:
141
+ return False
142
+
143
+ def create_pid_file(self, pid: int, file_path: str):
144
+ """Create a PID file.
145
+
146
+ Args:
147
+ pid: Process ID
148
+ file_path: Path to PID file
149
+
150
+ Raises:
151
+ OSError: If file creation fails
152
+ """
153
+ try:
154
+ # Ensure directory exists
155
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
156
+
157
+ with open(file_path, "w", encoding="utf-8") as f:
158
+ f.write(str(pid))
159
+ except Exception as e:
160
+ raise OSError(f"Failed to create PID file {file_path}: {e}") from e
161
+
162
+ def read_pid_file(self, file_path: str) -> Optional[int]:
163
+ """Read PID from file.
164
+
165
+ Args:
166
+ file_path: Path to PID file
167
+
168
+ Returns:
169
+ Process ID or None if file doesn't exist or is invalid
170
+ """
171
+ try:
172
+ if not os.path.exists(file_path):
173
+ return None
174
+
175
+ with open(file_path, "r", encoding="utf-8") as f:
176
+ return int(f.read().strip())
177
+ except (ValueError, OSError):
178
+ return None
179
+
180
+ def cleanup_pid_file(self, file_path: str):
181
+ """Remove PID file.
182
+
183
+ Args:
184
+ file_path: Path to PID file
185
+ """
186
+ try:
187
+ if os.path.exists(file_path):
188
+ os.remove(file_path)
189
+ except OSError:
190
+ pass # Ignore cleanup errors
191
+
192
+ async def find_process_by_port(self, port: int) -> Optional[int]:
193
+ """Find process listening on a specific port.
194
+
195
+ Args:
196
+ port: Port number
197
+
198
+ Returns:
199
+ Process ID or None if not found
200
+ """
201
+ try:
202
+ for conn in psutil.net_connections(kind="inet"):
203
+ if conn.laddr.port == port and conn.status == "LISTEN":
204
+ if conn.pid:
205
+ return conn.pid
206
+ return None
207
+ except Exception:
208
+ return None
209
+
210
+ def get_process_info(self, pid: int) -> Optional[dict]:
211
+ """Get information about a process.
212
+
213
+ Args:
214
+ pid: Process ID
215
+
216
+ Returns:
217
+ Dictionary with process information or None if process not found
218
+ """
219
+ try:
220
+ if not psutil.pid_exists(pid):
221
+ return None
222
+
223
+ process = psutil.Process(pid)
224
+ return {
225
+ "pid": pid,
226
+ "name": process.name(),
227
+ "status": process.status(),
228
+ "cpu_percent": process.cpu_percent(),
229
+ "memory_percent": process.memory_percent(),
230
+ "create_time": process.create_time(),
231
+ "cmdline": process.cmdline(),
232
+ }
233
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
234
+ return None
235
+
236
+ async def wait_for_port(
237
+ self,
238
+ host: str,
239
+ port: int,
240
+ timeout: int = 30,
241
+ ) -> bool:
242
+ """Wait for a service to become available on a port.
243
+
244
+ Args:
245
+ host: Host to check
246
+ port: Port to check
247
+ timeout: Maximum time to wait
248
+
249
+ Returns:
250
+ True if service becomes available, False if timeout
251
+ """
252
+ import socket
253
+
254
+ end_time = asyncio.get_event_loop().time() + timeout
255
+
256
+ while asyncio.get_event_loop().time() < end_time:
257
+ try:
258
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
259
+ sock.settimeout(1)
260
+ result = sock.connect_ex((host, port))
261
+ if result == 0:
262
+ return True
263
+ except Exception:
264
+ pass
265
+
266
+ await asyncio.sleep(0.5)
267
+
268
+ return False
@@ -0,0 +1,75 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Service configuration models for dynamic service loading."""
3
+
4
+ from enum import Enum
5
+ from typing import Dict, Any, Optional
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class ServiceType(str, Enum):
11
+ """Types of services that can be configured."""
12
+
13
+ MEMORY = "memory"
14
+ SESSION_HISTORY = "session_history"
15
+ SANDBOX = "sandbox"
16
+ RAG = "rag"
17
+
18
+
19
+ class ServiceProvider(str, Enum):
20
+ """Service implementation providers."""
21
+
22
+ IN_MEMORY = "in_memory"
23
+ REDIS = "redis"
24
+ # Extensible for other providers
25
+
26
+
27
+ class ServiceConfig(BaseModel):
28
+ """Configuration for a single service."""
29
+
30
+ provider: ServiceProvider
31
+ config: Optional[Dict[str, Any]] = {}
32
+
33
+
34
+ class ServicesConfig(BaseModel):
35
+ """Configuration for all services."""
36
+
37
+ memory: ServiceConfig = ServiceConfig(provider=ServiceProvider.IN_MEMORY)
38
+ session_history: ServiceConfig = ServiceConfig(
39
+ provider=ServiceProvider.IN_MEMORY,
40
+ )
41
+ sandbox: Optional[ServiceConfig] = None
42
+ rag: Optional[ServiceConfig] = None
43
+
44
+
45
+ # Default configuration
46
+ DEFAULT_SERVICES_CONFIG = ServicesConfig()
47
+
48
+
49
+ def create_redis_services_config(
50
+ host: str = "localhost",
51
+ port: int = 6379,
52
+ memory_db: int = 0,
53
+ session_db: int = 1,
54
+ ) -> ServicesConfig:
55
+ """Create a ServicesConfig with Redis providers.
56
+
57
+ Args:
58
+ host: Redis host
59
+ port: Redis port
60
+ memory_db: Redis database for memory service
61
+ session_db: Redis database for session history service
62
+
63
+ Returns:
64
+ ServicesConfig: Configuration with Redis services
65
+ """
66
+ return ServicesConfig(
67
+ memory=ServiceConfig(
68
+ provider=ServiceProvider.REDIS,
69
+ config={"host": host, "port": port, "db": memory_db},
70
+ ),
71
+ session_history=ServiceConfig(
72
+ provider=ServiceProvider.REDIS,
73
+ config={"host": host, "port": port, "db": session_db},
74
+ ),
75
+ )