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.
- agentscope_runtime/common/container_clients/__init__.py +0 -0
- agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +546 -6
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +130 -10
- agentscope_runtime/engine/agents/agno_agent.py +8 -10
- agentscope_runtime/engine/agents/langgraph_agent.py +52 -9
- agentscope_runtime/engine/app/__init__.py +6 -0
- agentscope_runtime/engine/app/agent_app.py +239 -0
- agentscope_runtime/engine/app/base_app.py +181 -0
- agentscope_runtime/engine/app/celery_mixin.py +92 -0
- agentscope_runtime/engine/deployers/__init__.py +13 -0
- agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2890 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
- agentscope_runtime/engine/deployers/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +203 -0
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +272 -0
- agentscope_runtime/engine/deployers/local_deployer.py +414 -501
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +838 -0
- agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
- agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +306 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +1163 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +1064 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
- agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
- agentscope_runtime/engine/runner.py +76 -35
- agentscope_runtime/engine/schemas/agent_schemas.py +112 -2
- agentscope_runtime/engine/schemas/embedding.py +37 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
- agentscope_runtime/engine/schemas/oai_llm.py +538 -0
- agentscope_runtime/engine/schemas/realtime.py +254 -0
- agentscope_runtime/engine/services/tablestore_memory_service.py +4 -1
- agentscope_runtime/engine/tracing/__init__.py +9 -3
- agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
- agentscope_runtime/engine/tracing/base.py +66 -34
- agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
- agentscope_runtime/engine/tracing/message_util.py +528 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
- agentscope_runtime/engine/tracing/tracing_util.py +130 -0
- agentscope_runtime/engine/tracing/wrapper.py +794 -169
- agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/training_box/training_box.py +0 -42
- agentscope_runtime/sandbox/client/http_client.py +52 -18
- agentscope_runtime/sandbox/constant.py +3 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +2 -1
- agentscope_runtime/sandbox/custom/example.py +2 -1
- agentscope_runtime/sandbox/enums.py +0 -1
- agentscope_runtime/sandbox/manager/sandbox_manager.py +29 -22
- agentscope_runtime/sandbox/model/container.py +6 -0
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/METADATA +103 -59
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/RECORD +87 -52
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/entry_points.txt +1 -0
- /agentscope_runtime/{sandbox/manager/container_clients → common}/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_mapping.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/agentrun_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/docker_client.py +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.6.dist-info → agentscope_runtime-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
)
|