agentscope-runtime 1.0.3__py3-none-any.whl → 1.0.4__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/adapters/agentscope/stream.py +2 -9
- agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
- agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
- agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
- agentscope_runtime/adapters/utils.py +6 -0
- agentscope_runtime/cli/commands/deploy.py +371 -0
- agentscope_runtime/common/container_clients/knative_client.py +466 -0
- agentscope_runtime/engine/__init__.py +4 -0
- agentscope_runtime/engine/constant.py +1 -0
- agentscope_runtime/engine/deployers/__init__.py +12 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +19 -10
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
- agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +134 -25
- agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
- agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
- agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
- agentscope_runtime/engine/runner.py +12 -0
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +2 -2
- agentscope_runtime/engine/services/memory/redis_memory_service.py +2 -2
- agentscope_runtime/engine/services/session_history/redis_session_history_service.py +2 -2
- agentscope_runtime/engine/tracing/wrapper.py +18 -4
- agentscope_runtime/sandbox/__init__.py +14 -6
- agentscope_runtime/sandbox/box/base/__init__.py +2 -2
- agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
- agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
- agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
- agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
- agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
- agentscope_runtime/sandbox/box/sandbox.py +98 -65
- agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
- agentscope_runtime/sandbox/client/__init__.py +6 -1
- agentscope_runtime/sandbox/client/async_http_client.py +339 -0
- agentscope_runtime/sandbox/client/base.py +74 -0
- agentscope_runtime/sandbox/client/http_client.py +108 -329
- agentscope_runtime/sandbox/enums.py +7 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +264 -4
- agentscope_runtime/sandbox/manager/server/app.py +7 -1
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/METADATA +102 -28
- {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/RECORD +49 -40
- {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=too-many-branches,too-many-statements
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
import traceback
|
|
7
|
+
from typing import Optional, Dict, Tuple
|
|
8
|
+
from kubernetes import client
|
|
9
|
+
from kubernetes import config as k8s_config
|
|
10
|
+
from kubernetes.client.rest import ApiException
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class KnativeClient:
|
|
16
|
+
"""
|
|
17
|
+
A client for interacting with Knative Services in a Kubernetes cluster.
|
|
18
|
+
|
|
19
|
+
This client wraps the Kubernetes CustomObjectsApi to manage
|
|
20
|
+
Knative Service resources (serving.knative.dev/v1).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
config=None,
|
|
26
|
+
image_registry: Optional[str] = None,
|
|
27
|
+
):
|
|
28
|
+
self.config = config
|
|
29
|
+
namespace = self.config.k8s_namespace
|
|
30
|
+
kubeconfig = self.config.kubeconfig_path
|
|
31
|
+
self.image_registry = image_registry
|
|
32
|
+
try:
|
|
33
|
+
if kubeconfig:
|
|
34
|
+
k8s_config.load_kube_config(config_file=kubeconfig)
|
|
35
|
+
else:
|
|
36
|
+
# Try to load in-cluster config first, then fall back to
|
|
37
|
+
# kubeconfig
|
|
38
|
+
try:
|
|
39
|
+
k8s_config.load_incluster_config()
|
|
40
|
+
except k8s_config.ConfigException:
|
|
41
|
+
k8s_config.load_kube_config()
|
|
42
|
+
self._custom_api = client.CustomObjectsApi() # For Knative Service
|
|
43
|
+
self.v1 = client.CoreV1Api()
|
|
44
|
+
self.namespace = namespace
|
|
45
|
+
# Test connection
|
|
46
|
+
self.v1.list_namespace()
|
|
47
|
+
logger.debug("Kubernetes client initialized successfully")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise RuntimeError(
|
|
50
|
+
f"Kubernetes client initialization failed: {str(e)}\n"
|
|
51
|
+
"Solutions:\n"
|
|
52
|
+
"• Ensure kubectl is configured\n"
|
|
53
|
+
"• Check kubeconfig file permissions\n"
|
|
54
|
+
"• Verify cluster connectivity\n"
|
|
55
|
+
"• For in-cluster: ensure proper RBAC permissions",
|
|
56
|
+
) from e
|
|
57
|
+
|
|
58
|
+
def _is_local_cluster(self):
|
|
59
|
+
"""
|
|
60
|
+
Determine if we're connected to a local Kubernetes cluster.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
bool: True if connected to a local cluster, False otherwise
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
# Get the current context configuration
|
|
67
|
+
contexts, current_context = k8s_config.list_kube_config_contexts(
|
|
68
|
+
config_file=self.config.kubeconfig_path
|
|
69
|
+
if hasattr(self.config, "kubeconfig_path")
|
|
70
|
+
and self.config.kubeconfig_path
|
|
71
|
+
else None,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if current_context and current_context.get("context"):
|
|
75
|
+
cluster_name = current_context["context"].get("cluster", "")
|
|
76
|
+
server = None
|
|
77
|
+
|
|
78
|
+
# Get cluster server URL
|
|
79
|
+
for cluster in contexts.get("clusters", []):
|
|
80
|
+
if cluster["name"] == cluster_name:
|
|
81
|
+
server = cluster.get("cluster", {}).get("server", "")
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
if server:
|
|
85
|
+
# Check for common local cluster patterns
|
|
86
|
+
local_patterns = [
|
|
87
|
+
"localhost",
|
|
88
|
+
"127.0.0.1",
|
|
89
|
+
"0.0.0.0",
|
|
90
|
+
"docker-desktop",
|
|
91
|
+
"kind-", # kind clusters
|
|
92
|
+
"minikube", # minikube
|
|
93
|
+
"k3d-", # k3d clusters
|
|
94
|
+
"colima", # colima
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
server_lower = server.lower()
|
|
98
|
+
cluster_lower = cluster_name.lower()
|
|
99
|
+
|
|
100
|
+
for pattern in local_patterns:
|
|
101
|
+
if pattern in server_lower or pattern in cluster_lower:
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(
|
|
108
|
+
f"Could not determine cluster type, assuming remote: {e}",
|
|
109
|
+
)
|
|
110
|
+
# If we can't determine, assume remote for safety
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
def create_kservice(
|
|
114
|
+
self,
|
|
115
|
+
name: str,
|
|
116
|
+
image: str,
|
|
117
|
+
ports=None,
|
|
118
|
+
volumes=None,
|
|
119
|
+
environment=None,
|
|
120
|
+
runtime_config=None,
|
|
121
|
+
annotations: Optional[Dict[str, str]] = None,
|
|
122
|
+
labels: Optional[Dict[str, str]] = None,
|
|
123
|
+
namespace: Optional[str] = None,
|
|
124
|
+
) -> Tuple[str, str] | Tuple[None, None]:
|
|
125
|
+
"""
|
|
126
|
+
Create a Knative Service.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
name (str): Name of the KService.
|
|
130
|
+
image (str): Container image.
|
|
131
|
+
ports: List of ports to expose
|
|
132
|
+
volumes: Volume mounts dictionary
|
|
133
|
+
environment: Environment variables dictionary
|
|
134
|
+
runtime_config: Runtime configuration dictionary
|
|
135
|
+
annotations (dict): KService annotations.
|
|
136
|
+
labels (dict): KService labels.
|
|
137
|
+
namespace (str): Override default namespace.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict: Created Knative Service object.
|
|
141
|
+
"""
|
|
142
|
+
ns = namespace or self.namespace
|
|
143
|
+
|
|
144
|
+
# Create kservice pod specification
|
|
145
|
+
pod_spec = self._create_kservice_podspec(
|
|
146
|
+
image,
|
|
147
|
+
name,
|
|
148
|
+
ports,
|
|
149
|
+
volumes,
|
|
150
|
+
environment,
|
|
151
|
+
runtime_config,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
kservice_manifest = {
|
|
155
|
+
"apiVersion": "serving.knative.dev/v1",
|
|
156
|
+
"kind": "Service",
|
|
157
|
+
"metadata": {
|
|
158
|
+
"name": name,
|
|
159
|
+
"namespace": ns,
|
|
160
|
+
"labels": labels or {},
|
|
161
|
+
"annotations": annotations or {},
|
|
162
|
+
},
|
|
163
|
+
"spec": {
|
|
164
|
+
"template": {
|
|
165
|
+
"metadata": {
|
|
166
|
+
"labels": labels or {},
|
|
167
|
+
"annotations": annotations or {},
|
|
168
|
+
},
|
|
169
|
+
"spec": pod_spec,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.info(f"Creating Knative Service '{name}' in namespace '{ns}'")
|
|
175
|
+
try:
|
|
176
|
+
self._custom_api.create_namespaced_custom_object(
|
|
177
|
+
group="serving.knative.dev",
|
|
178
|
+
version="v1",
|
|
179
|
+
namespace=ns,
|
|
180
|
+
plural="services",
|
|
181
|
+
body=kservice_manifest,
|
|
182
|
+
)
|
|
183
|
+
logger.debug("Knative Service created successfully.")
|
|
184
|
+
# Wait for kservice to be ready
|
|
185
|
+
if not self.wait_for_ready(name, timeout=120):
|
|
186
|
+
logger.warning(f"KService '{name}' may not be fully ready")
|
|
187
|
+
ksvc = self.get_kservice(name, ns)
|
|
188
|
+
|
|
189
|
+
url = ksvc.get("status", {}).get("url")
|
|
190
|
+
return (
|
|
191
|
+
name,
|
|
192
|
+
url or "",
|
|
193
|
+
)
|
|
194
|
+
except ApiException as e:
|
|
195
|
+
logger.error(
|
|
196
|
+
f"Failed to create KService: {e}, {traceback.format_exc()}",
|
|
197
|
+
)
|
|
198
|
+
return None, None
|
|
199
|
+
|
|
200
|
+
def _create_kservice_podspec(
|
|
201
|
+
self,
|
|
202
|
+
image,
|
|
203
|
+
name,
|
|
204
|
+
ports=None,
|
|
205
|
+
volumes=None,
|
|
206
|
+
environment=None,
|
|
207
|
+
runtime_config=None,
|
|
208
|
+
):
|
|
209
|
+
"""Create a Knative Service Pod specification."""
|
|
210
|
+
if runtime_config is None:
|
|
211
|
+
runtime_config = {}
|
|
212
|
+
|
|
213
|
+
container_name = name or "main-container"
|
|
214
|
+
|
|
215
|
+
# Use image registry if configured
|
|
216
|
+
if not self.image_registry:
|
|
217
|
+
full_image = image
|
|
218
|
+
else:
|
|
219
|
+
full_image = f"{self.image_registry}/{image}"
|
|
220
|
+
|
|
221
|
+
# Create container spec (reuse the existing pod spec logic)
|
|
222
|
+
container = client.V1Container(
|
|
223
|
+
name=container_name,
|
|
224
|
+
image=full_image,
|
|
225
|
+
image_pull_policy=runtime_config.get(
|
|
226
|
+
"image_pull_policy",
|
|
227
|
+
"IfNotPresent",
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Configure ports
|
|
232
|
+
if ports:
|
|
233
|
+
container_ports = []
|
|
234
|
+
for port_spec in ports:
|
|
235
|
+
port_info = self._parse_port_spec(port_spec)
|
|
236
|
+
if port_info:
|
|
237
|
+
container_ports.append(
|
|
238
|
+
client.V1ContainerPort(
|
|
239
|
+
container_port=port_info["port"],
|
|
240
|
+
protocol=port_info["protocol"],
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
if container_ports:
|
|
244
|
+
container.ports = container_ports
|
|
245
|
+
|
|
246
|
+
# Configure environment variables
|
|
247
|
+
if environment:
|
|
248
|
+
env_vars = []
|
|
249
|
+
for key, value in environment.items():
|
|
250
|
+
env_vars.append(client.V1EnvVar(name=key, value=str(value)))
|
|
251
|
+
container.env = env_vars
|
|
252
|
+
|
|
253
|
+
# Configure volume mounts and volumes
|
|
254
|
+
volume_mounts = []
|
|
255
|
+
pod_volumes = []
|
|
256
|
+
if volumes:
|
|
257
|
+
for volume_idx, (host_path, mount_info) in enumerate(
|
|
258
|
+
volumes.items(),
|
|
259
|
+
):
|
|
260
|
+
if isinstance(mount_info, dict):
|
|
261
|
+
container_path = mount_info["bind"]
|
|
262
|
+
mode = mount_info.get("mode", "rw")
|
|
263
|
+
else:
|
|
264
|
+
container_path = mount_info
|
|
265
|
+
mode = "rw"
|
|
266
|
+
volume_name = f"vol-{volume_idx}"
|
|
267
|
+
|
|
268
|
+
# Create volume mount
|
|
269
|
+
volume_mounts.append(
|
|
270
|
+
client.V1VolumeMount(
|
|
271
|
+
name=volume_name,
|
|
272
|
+
mount_path=container_path,
|
|
273
|
+
read_only=(mode == "ro"),
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
# Create host path volume
|
|
277
|
+
pod_volumes.append(
|
|
278
|
+
client.V1Volume(
|
|
279
|
+
name=volume_name,
|
|
280
|
+
host_path=client.V1HostPathVolumeSource(
|
|
281
|
+
path=host_path,
|
|
282
|
+
),
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if volume_mounts:
|
|
287
|
+
container.volume_mounts = volume_mounts
|
|
288
|
+
|
|
289
|
+
# Apply runtime config to container
|
|
290
|
+
if "resources" in runtime_config:
|
|
291
|
+
container.resources = client.V1ResourceRequirements(
|
|
292
|
+
**runtime_config["resources"],
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if "security_context" in runtime_config:
|
|
296
|
+
container.security_context = client.V1SecurityContext(
|
|
297
|
+
**runtime_config["security_context"],
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Pod template specification
|
|
301
|
+
pod_spec = client.V1PodSpec(
|
|
302
|
+
containers=[container],
|
|
303
|
+
restart_policy=runtime_config.get(
|
|
304
|
+
"restart_policy",
|
|
305
|
+
"Always",
|
|
306
|
+
), # KService typically use Always
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if pod_volumes:
|
|
310
|
+
pod_spec.volumes = pod_volumes
|
|
311
|
+
|
|
312
|
+
if "node_selector" in runtime_config:
|
|
313
|
+
pod_spec.node_selector = runtime_config["node_selector"]
|
|
314
|
+
|
|
315
|
+
if "tolerations" in runtime_config:
|
|
316
|
+
pod_spec.tolerations = runtime_config["tolerations"]
|
|
317
|
+
|
|
318
|
+
# Handle image pull secrets
|
|
319
|
+
image_pull_secrets = runtime_config.get("image_pull_secrets", [])
|
|
320
|
+
if image_pull_secrets:
|
|
321
|
+
secrets = []
|
|
322
|
+
for secret_name in image_pull_secrets:
|
|
323
|
+
secrets.append(client.V1LocalObjectReference(name=secret_name))
|
|
324
|
+
pod_spec.image_pull_secrets = secrets
|
|
325
|
+
|
|
326
|
+
return pod_spec
|
|
327
|
+
|
|
328
|
+
def _parse_port_spec(self, port_spec):
|
|
329
|
+
"""
|
|
330
|
+
Parse port specification.
|
|
331
|
+
- "80/tcp" -> {"port": 80, "protocol": "TCP"}
|
|
332
|
+
- "80" -> {"port": 80, "protocol": "TCP"}
|
|
333
|
+
- 80 -> {"port": 80, "protocol": "TCP"}
|
|
334
|
+
"""
|
|
335
|
+
try:
|
|
336
|
+
if isinstance(port_spec, int):
|
|
337
|
+
return {"port": port_spec, "protocol": "TCP"}
|
|
338
|
+
|
|
339
|
+
if isinstance(port_spec, str):
|
|
340
|
+
if "/" in port_spec:
|
|
341
|
+
port_str, protocol = port_spec.split("/", 1)
|
|
342
|
+
else:
|
|
343
|
+
port_str = port_spec
|
|
344
|
+
protocol = "TCP"
|
|
345
|
+
|
|
346
|
+
port = int(port_str)
|
|
347
|
+
protocol = protocol.upper()
|
|
348
|
+
|
|
349
|
+
return {"port": port, "protocol": protocol}
|
|
350
|
+
|
|
351
|
+
# Log a warning if the port_spec is neither int nor str
|
|
352
|
+
logger.warning(f"Unsupported port specification: {port_spec}")
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
except ValueError as e:
|
|
356
|
+
logger.error(f"Failed to parse port spec '{port_spec}': {e}")
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
def delete_kservice(
|
|
360
|
+
self,
|
|
361
|
+
name: str,
|
|
362
|
+
namespace: Optional[str] = None,
|
|
363
|
+
) -> bool:
|
|
364
|
+
"""Delete a Knative Service."""
|
|
365
|
+
ns = namespace or self.namespace
|
|
366
|
+
logger.info(f"Deleting Knative Service '{name}' in namespace '{ns}'")
|
|
367
|
+
try:
|
|
368
|
+
self._custom_api.delete_namespaced_custom_object(
|
|
369
|
+
group="serving.knative.dev",
|
|
370
|
+
version="v1",
|
|
371
|
+
namespace=ns,
|
|
372
|
+
plural="services",
|
|
373
|
+
name=name,
|
|
374
|
+
body=client.V1DeleteOptions(),
|
|
375
|
+
)
|
|
376
|
+
logger.debug("Knative Service deleted.")
|
|
377
|
+
return True
|
|
378
|
+
except ApiException as e:
|
|
379
|
+
if e.status == 404:
|
|
380
|
+
logger.warning(f"Knative Service '{name}' not found.")
|
|
381
|
+
return False
|
|
382
|
+
logger.error(f"Failed to delete Knative Service: {e.body}")
|
|
383
|
+
raise
|
|
384
|
+
|
|
385
|
+
def get_kservice(self, name: str, namespace: Optional[str] = None):
|
|
386
|
+
"""Get a Knative Service by name."""
|
|
387
|
+
try:
|
|
388
|
+
svc = self._custom_api.get_namespaced_custom_object(
|
|
389
|
+
group="serving.knative.dev",
|
|
390
|
+
version="v1",
|
|
391
|
+
namespace=namespace,
|
|
392
|
+
plural="services",
|
|
393
|
+
name=name,
|
|
394
|
+
)
|
|
395
|
+
return svc
|
|
396
|
+
except ApiException as e:
|
|
397
|
+
if e.status == 404:
|
|
398
|
+
return None
|
|
399
|
+
raise
|
|
400
|
+
|
|
401
|
+
def wait_for_ready(
|
|
402
|
+
self,
|
|
403
|
+
name: str,
|
|
404
|
+
timeout: int = 300,
|
|
405
|
+
poll_interval: int = 5,
|
|
406
|
+
) -> bool:
|
|
407
|
+
"""
|
|
408
|
+
Wait until the Knative Service is ready.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
bool: True if ready within timeout, False otherwise.
|
|
412
|
+
"""
|
|
413
|
+
logger.info(
|
|
414
|
+
f"Waiting for Knative Service '{name}' "
|
|
415
|
+
"to become ready (timeout={timeout}s)",
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
start_time = time.time()
|
|
419
|
+
while time.time() - start_time < timeout:
|
|
420
|
+
svc = self.get_kservice(name, self.namespace)
|
|
421
|
+
if svc:
|
|
422
|
+
conditions = svc.get("status", {}).get("conditions", [])
|
|
423
|
+
ready_cond = next(
|
|
424
|
+
(c for c in conditions if c.get("type") == "Ready"),
|
|
425
|
+
None,
|
|
426
|
+
)
|
|
427
|
+
if ready_cond and ready_cond.get("status") == "True":
|
|
428
|
+
logger.info(f"Knative Service '{name}' is ready.")
|
|
429
|
+
return True
|
|
430
|
+
time.sleep(poll_interval)
|
|
431
|
+
|
|
432
|
+
logger.error(
|
|
433
|
+
f"Knative Service '{name}' did not "
|
|
434
|
+
"become ready within {timeout} seconds.",
|
|
435
|
+
)
|
|
436
|
+
return False
|
|
437
|
+
|
|
438
|
+
def get_kservice_status(self, name):
|
|
439
|
+
"""Get the current status of the specified kservice."""
|
|
440
|
+
try:
|
|
441
|
+
ksvc = self.get_kservice(name, self.namespace)
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
"name": name,
|
|
445
|
+
"url": ksvc.get("status", {}).get("url"),
|
|
446
|
+
"conditions": [
|
|
447
|
+
{
|
|
448
|
+
"type": condition.get("type"),
|
|
449
|
+
"status": condition.get("status"),
|
|
450
|
+
"reason": condition.get("reason"),
|
|
451
|
+
"message": condition.get("message"),
|
|
452
|
+
}
|
|
453
|
+
for condition in (
|
|
454
|
+
ksvc.get("status", {}).get("conditions", [])
|
|
455
|
+
)
|
|
456
|
+
],
|
|
457
|
+
}
|
|
458
|
+
except ApiException as e:
|
|
459
|
+
if e.status == 404:
|
|
460
|
+
logger.warning(f"KService '{name}' not found")
|
|
461
|
+
else:
|
|
462
|
+
logger.error(f"Failed to get ksvc status: {e.reason}")
|
|
463
|
+
return None
|
|
464
|
+
except Exception as e:
|
|
465
|
+
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
|
|
466
|
+
return None
|
|
@@ -11,8 +11,10 @@ if TYPE_CHECKING:
|
|
|
11
11
|
DeployManager,
|
|
12
12
|
LocalDeployManager,
|
|
13
13
|
KubernetesDeployManager,
|
|
14
|
+
KnativeDeployManager,
|
|
14
15
|
ModelstudioDeployManager,
|
|
15
16
|
AgentRunDeployManager,
|
|
17
|
+
FCDeployManager,
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
|
|
@@ -22,7 +24,9 @@ install_lazy_loader(
|
|
|
22
24
|
"DeployManager": ".deployers",
|
|
23
25
|
"LocalDeployManager": ".deployers",
|
|
24
26
|
"KubernetesDeployManager": ".deployers",
|
|
27
|
+
"KnativeDeployManager": ".deployers",
|
|
25
28
|
"ModelstudioDeployManager": ".deployers",
|
|
26
29
|
"AgentRunDeployManager": ".deployers",
|
|
30
|
+
"FCDeployManager": ".deployers",
|
|
27
31
|
},
|
|
28
32
|
)
|
|
@@ -8,6 +8,9 @@ from .kubernetes_deployer import (
|
|
|
8
8
|
from .modelstudio_deployer import (
|
|
9
9
|
ModelstudioDeployManager,
|
|
10
10
|
)
|
|
11
|
+
from .knative_deployer import (
|
|
12
|
+
KnativeDeployManager,
|
|
13
|
+
)
|
|
11
14
|
|
|
12
15
|
try:
|
|
13
16
|
from .agentrun_deployer import (
|
|
@@ -16,10 +19,19 @@ try:
|
|
|
16
19
|
except ImportError:
|
|
17
20
|
AgentRunDeployManager = None # type: ignore
|
|
18
21
|
|
|
22
|
+
try:
|
|
23
|
+
from .fc_deployer import (
|
|
24
|
+
FCDeployManager,
|
|
25
|
+
)
|
|
26
|
+
except ImportError:
|
|
27
|
+
FCDeployManager = None # type: ignore
|
|
28
|
+
|
|
19
29
|
__all__ = [
|
|
20
30
|
"DeployManager",
|
|
21
31
|
"LocalDeployManager",
|
|
22
32
|
"KubernetesDeployManager",
|
|
23
33
|
"ModelstudioDeployManager",
|
|
24
34
|
"AgentRunDeployManager",
|
|
35
|
+
"KnativeDeployManager",
|
|
36
|
+
"FCDeployManager",
|
|
25
37
|
]
|
|
@@ -1,57 +1,32 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from .....common.utils.lazy_loader import install_lazy_loader
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .a2a_protocol_adapter import (
|
|
7
|
+
A2AFastAPIDefaultAdapter,
|
|
8
|
+
AgentCardWithRuntimeConfig,
|
|
9
|
+
extract_a2a_config,
|
|
10
|
+
)
|
|
11
|
+
from .a2a_registry import A2ARegistry
|
|
12
|
+
from .nacos_a2a_registry import NacosRegistry
|
|
13
13
|
|
|
14
14
|
# NOTE: NacosRegistry is NOT imported at module import time to avoid forcing
|
|
15
15
|
# an optional dependency on environments that don't have nacos SDK installed.
|
|
16
|
-
# Instead, NacosRegistry is imported lazily via
|
|
16
|
+
# Instead, NacosRegistry is imported lazily via install_lazy_loader when
|
|
17
17
|
# actually needed (e.g., when user does: from ... import NacosRegistry).
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Lazy import for NacosRegistry to avoid forcing optional nacos dependency.
|
|
34
|
-
|
|
35
|
-
This function is called by Python when an attribute is accessed
|
|
36
|
-
that doesn't exist at module level. This allows NacosRegistry
|
|
37
|
-
to be imported only when actually needed, rather than at module
|
|
38
|
-
import time.
|
|
39
|
-
|
|
40
|
-
If the nacos SDK is not installed, provides a helpful error message
|
|
41
|
-
instead of a confusing ImportError.
|
|
42
|
-
"""
|
|
43
|
-
if name == "NacosRegistry":
|
|
44
|
-
try:
|
|
45
|
-
from .nacos_a2a_registry import NacosRegistry
|
|
46
|
-
|
|
47
|
-
return NacosRegistry
|
|
48
|
-
except ImportError as e:
|
|
49
|
-
# Check if it's the v2.nacos dependency that's missing
|
|
50
|
-
if "v2.nacos" in str(e) or "nacos" in str(e).lower():
|
|
51
|
-
raise ImportError(
|
|
52
|
-
"NacosRegistry requires the 'v2-nacos' package. "
|
|
53
|
-
"Install it with: pip install v2-nacos",
|
|
54
|
-
) from e
|
|
55
|
-
# Re-raise other import errors as-is
|
|
56
|
-
raise
|
|
57
|
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
19
|
+
install_lazy_loader(
|
|
20
|
+
globals(),
|
|
21
|
+
{
|
|
22
|
+
"A2AFastAPIDefaultAdapter": ".a2a_protocol_adapter",
|
|
23
|
+
"AgentCardWithRuntimeConfig": ".a2a_protocol_adapter",
|
|
24
|
+
"extract_a2a_config": ".a2a_protocol_adapter",
|
|
25
|
+
"A2ARegistry": ".a2a_registry",
|
|
26
|
+
"NacosRegistry": {
|
|
27
|
+
"module": ".nacos_a2a_registry",
|
|
28
|
+
"hint": "NacosRegistry requires the 'nacos-sdk-python' package. "
|
|
29
|
+
"Install it with: pip install nacos-sdk-python",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
)
|
|
@@ -19,19 +19,18 @@ from a2a.types import (
|
|
|
19
19
|
AgentCard,
|
|
20
20
|
AgentSkill,
|
|
21
21
|
)
|
|
22
|
+
from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
|
|
22
23
|
from fastapi import FastAPI
|
|
23
24
|
from pydantic import ConfigDict, BaseModel, field_validator
|
|
24
25
|
|
|
25
26
|
from agentscope_runtime.engine.deployers.utils.net_utils import (
|
|
26
27
|
get_first_non_loopback_ip,
|
|
27
28
|
)
|
|
28
|
-
from agentscope_runtime.version import __version__ as runtime_version
|
|
29
29
|
|
|
30
30
|
from .a2a_agent_adapter import A2AExecutor
|
|
31
31
|
from .a2a_registry import (
|
|
32
32
|
A2ARegistry,
|
|
33
33
|
A2ATransportsProperties,
|
|
34
|
-
create_registry_from_env,
|
|
35
34
|
)
|
|
36
35
|
|
|
37
36
|
# NOTE: Do NOT import NacosRegistry at module import time to avoid
|
|
@@ -44,7 +43,7 @@ from ..protocol_adapter import ProtocolAdapter
|
|
|
44
43
|
logger = logging.getLogger(__name__)
|
|
45
44
|
|
|
46
45
|
A2A_JSON_RPC_URL = "/a2a"
|
|
47
|
-
DEFAULT_WELLKNOWN_PATH =
|
|
46
|
+
DEFAULT_WELLKNOWN_PATH = AGENT_CARD_WELL_KNOWN_PATH
|
|
48
47
|
DEFAULT_TASK_TIMEOUT = 60
|
|
49
48
|
DEFAULT_TASK_EVENT_TIMEOUT = 10
|
|
50
49
|
DEFAULT_TRANSPORT = "JSONRPC"
|
|
@@ -58,8 +57,12 @@ def extract_a2a_config(
|
|
|
58
57
|
) -> "AgentCardWithRuntimeConfig":
|
|
59
58
|
"""Normalize a2a_config to AgentCardWithRuntimeConfig object.
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
Registry resolution priority:
|
|
61
|
+
1. Use registry from a2a_config if provided
|
|
62
|
+
2. Fallback to environment variables if a2a_config.registry is
|
|
63
|
+
None
|
|
64
|
+
3. If neither is available, registry remains None (user doesn't
|
|
65
|
+
want registry)
|
|
63
66
|
|
|
64
67
|
Args:
|
|
65
68
|
a2a_config: Optional AgentCardWithRuntimeConfig instance.
|
|
@@ -70,12 +73,18 @@ def extract_a2a_config(
|
|
|
70
73
|
if a2a_config is None:
|
|
71
74
|
a2a_config = AgentCardWithRuntimeConfig()
|
|
72
75
|
|
|
73
|
-
#
|
|
76
|
+
# Try environment variables only if registry is not explicitly provided
|
|
74
77
|
if a2a_config.registry is None:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
try:
|
|
79
|
+
from .nacos_a2a_registry import create_nacos_registry_from_env
|
|
80
|
+
|
|
81
|
+
env_registry = create_nacos_registry_from_env()
|
|
82
|
+
if env_registry is not None:
|
|
83
|
+
a2a_config.registry = env_registry
|
|
84
|
+
logger.debug("[A2A] Using registry from environment variables")
|
|
85
|
+
except ImportError:
|
|
86
|
+
# Nacos SDK not available, registry remains None
|
|
87
|
+
logger.debug("[A2A] Nacos registry not available")
|
|
79
88
|
|
|
80
89
|
return a2a_config
|
|
81
90
|
|