agentscope-runtime 0.1.3__py3-none-any.whl → 0.1.5b1__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/engine/agents/agentscope_agent/agent.py +3 -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 +2886 -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/cli_fc_deploy.py +143 -0
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +265 -0
- agentscope_runtime/engine/deployers/local_deployer.py +356 -501
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +626 -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 +297 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +932 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +504 -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/wheel_packager.py +389 -0
- agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
- agentscope_runtime/engine/runner.py +36 -10
- agentscope_runtime/engine/schemas/agent_schemas.py +70 -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/context_manager.py +2 -0
- agentscope_runtime/engine/services/mem0_memory_service.py +124 -0
- agentscope_runtime/engine/services/memory_service.py +2 -1
- agentscope_runtime/engine/services/redis_session_history_service.py +4 -3
- agentscope_runtime/engine/services/session_history_service.py +4 -3
- agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +555 -10
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5b1.dist-info}/METADATA +25 -5
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5b1.dist-info}/RECORD +44 -17
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5b1.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5b1.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5b1.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5b1.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint:disable=line-too-long
|
|
3
|
+
|
|
4
|
+
from typing import Dict, Type, Any
|
|
5
|
+
from .service_config import (
|
|
6
|
+
ServiceType,
|
|
7
|
+
ServiceProvider,
|
|
8
|
+
ServiceConfig,
|
|
9
|
+
ServicesConfig,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ServiceFactory:
|
|
14
|
+
"""Factory for creating service instances based on configuration."""
|
|
15
|
+
|
|
16
|
+
# Service registry mapping service types and providers to
|
|
17
|
+
# implementation classes
|
|
18
|
+
_service_registry: Dict[str, Dict[str, Type]] = {}
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def _populate_registry(cls):
|
|
22
|
+
"""Populate the service registry with available implementations."""
|
|
23
|
+
if cls._service_registry:
|
|
24
|
+
return # Already populated
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# Import service implementations
|
|
28
|
+
from agentscope_runtime.engine.services.memory_service import (
|
|
29
|
+
InMemoryMemoryService,
|
|
30
|
+
)
|
|
31
|
+
from agentscope_runtime.engine.services.session_history_service import ( # noqa E501
|
|
32
|
+
InMemorySessionHistoryService,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Register memory services
|
|
36
|
+
cls._service_registry[ServiceType.MEMORY] = {
|
|
37
|
+
ServiceProvider.IN_MEMORY: InMemoryMemoryService,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Register session history services
|
|
41
|
+
cls._service_registry[ServiceType.SESSION_HISTORY] = {
|
|
42
|
+
ServiceProvider.IN_MEMORY: InMemorySessionHistoryService,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Try to register Redis services if available
|
|
46
|
+
|
|
47
|
+
from agentscope_runtime.engine.services.redis_memory_service import ( # noqa E501
|
|
48
|
+
RedisMemoryService,
|
|
49
|
+
)
|
|
50
|
+
from agentscope_runtime.engine.services.redis_session_history_service import ( # noqa E501
|
|
51
|
+
RedisSessionHistoryService,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
cls._service_registry[ServiceType.MEMORY][
|
|
55
|
+
ServiceProvider.REDIS
|
|
56
|
+
] = RedisMemoryService
|
|
57
|
+
cls._service_registry[ServiceType.SESSION_HISTORY][
|
|
58
|
+
ServiceProvider.REDIS
|
|
59
|
+
] = RedisSessionHistoryService
|
|
60
|
+
|
|
61
|
+
# Try to register other services if available
|
|
62
|
+
from agentscope_runtime.engine.services.sandbox_service import (
|
|
63
|
+
SandboxService,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Assuming default implementation
|
|
67
|
+
cls._service_registry[ServiceType.SANDBOX] = {
|
|
68
|
+
ServiceProvider.IN_MEMORY: SandboxService,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
from agentscope_runtime.engine.services.rag_service import (
|
|
72
|
+
RAGService,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Assuming default implementation
|
|
76
|
+
cls._service_registry[ServiceType.RAG] = {
|
|
77
|
+
ServiceProvider.IN_MEMORY: RAGService,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
except ImportError as e:
|
|
81
|
+
raise RuntimeError(
|
|
82
|
+
f"Failed to import required service classes: {e}",
|
|
83
|
+
) from e
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def create_service(
|
|
87
|
+
cls,
|
|
88
|
+
service_type: ServiceType,
|
|
89
|
+
config: ServiceConfig,
|
|
90
|
+
) -> Any:
|
|
91
|
+
"""Create a service instance based on type and configuration.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
service_type: Type of service to create
|
|
95
|
+
config: Configuration for the service
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Service instance
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If service type or provider is unknown
|
|
102
|
+
RuntimeError: If service creation fails
|
|
103
|
+
"""
|
|
104
|
+
cls._populate_registry()
|
|
105
|
+
|
|
106
|
+
if service_type not in cls._service_registry:
|
|
107
|
+
raise ValueError(f"Unknown service type: {service_type}")
|
|
108
|
+
|
|
109
|
+
providers = cls._service_registry[service_type]
|
|
110
|
+
if config.provider not in providers:
|
|
111
|
+
available_providers = list(providers.keys())
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Unknown provider '{config.provider}' for service '"
|
|
114
|
+
f"{service_type}'. Available providers: {available_providers}",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
service_class = providers[config.provider]
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# Create service instance with configuration parameters
|
|
121
|
+
return service_class(**config.config)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
f"Failed to create {service_type} service with provider "
|
|
125
|
+
f"'{config.provider}': {e}",
|
|
126
|
+
) from e
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def register_service(
|
|
130
|
+
cls,
|
|
131
|
+
service_type: ServiceType,
|
|
132
|
+
provider: ServiceProvider,
|
|
133
|
+
service_class: Type,
|
|
134
|
+
):
|
|
135
|
+
"""Register a new service implementation.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
service_type: Type of service
|
|
139
|
+
provider: Service provider
|
|
140
|
+
service_class: Implementation class
|
|
141
|
+
"""
|
|
142
|
+
cls._populate_registry()
|
|
143
|
+
|
|
144
|
+
if service_type not in cls._service_registry:
|
|
145
|
+
cls._service_registry[service_type] = {}
|
|
146
|
+
|
|
147
|
+
cls._service_registry[service_type][provider] = service_class
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def create_services_from_config(
|
|
151
|
+
cls,
|
|
152
|
+
config: ServicesConfig,
|
|
153
|
+
) -> Dict[str, Any]:
|
|
154
|
+
"""Create all services from a services configuration.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
config: Services configuration
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Dict mapping service names to service instances
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
RuntimeError: If any required service creation fails
|
|
164
|
+
"""
|
|
165
|
+
services = {}
|
|
166
|
+
|
|
167
|
+
# Create required services
|
|
168
|
+
try:
|
|
169
|
+
services["memory"] = cls.create_service(
|
|
170
|
+
ServiceType.MEMORY,
|
|
171
|
+
config.memory,
|
|
172
|
+
)
|
|
173
|
+
services["session_history"] = cls.create_service(
|
|
174
|
+
ServiceType.SESSION_HISTORY,
|
|
175
|
+
config.session_history,
|
|
176
|
+
)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
raise RuntimeError(
|
|
179
|
+
f"Failed to create required services: {e}",
|
|
180
|
+
) from e
|
|
181
|
+
|
|
182
|
+
# Create optional services
|
|
183
|
+
if config.sandbox:
|
|
184
|
+
try:
|
|
185
|
+
services["sandbox"] = cls.create_service(
|
|
186
|
+
ServiceType.SANDBOX,
|
|
187
|
+
config.sandbox,
|
|
188
|
+
)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
# Log warning but don't fail
|
|
191
|
+
print(f"Warning: Failed to create sandbox service: {e}")
|
|
192
|
+
|
|
193
|
+
if config.rag:
|
|
194
|
+
try:
|
|
195
|
+
services["rag"] = cls.create_service(
|
|
196
|
+
ServiceType.RAG,
|
|
197
|
+
config.rag,
|
|
198
|
+
)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
# Log warning but don't fail
|
|
201
|
+
print(f"Warning: Failed to create RAG service: {e}")
|
|
202
|
+
|
|
203
|
+
return services
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def get_available_providers(cls, service_type: ServiceType) -> list:
|
|
207
|
+
"""Get list of available providers for a service type.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
service_type: Type of service
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
List of available provider names
|
|
214
|
+
"""
|
|
215
|
+
cls._populate_registry()
|
|
216
|
+
|
|
217
|
+
if service_type not in cls._service_registry:
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
return list(cls._service_registry[service_type].keys())
|