agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.2.0b1__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/__init__.py +0 -0
- agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
- agentscope_runtime/common/collections/redis_mapping.py +42 -0
- agentscope_runtime/common/container_clients/__init__.py +0 -0
- agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
- agentscope_runtime/common/container_clients/docker_client.py +250 -0
- agentscope_runtime/engine/__init__.py +12 -0
- agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
- agentscope_runtime/engine/agents/agno_agent.py +19 -18
- agentscope_runtime/engine/agents/autogen_agent.py +13 -8
- agentscope_runtime/engine/agents/utils.py +53 -0
- 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/base.py +1 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
- agentscope_runtime/engine/deployers/local_deployer.py +61 -3
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +10 -11
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
- agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
- agentscope_runtime/engine/helpers/helper.py +60 -41
- agentscope_runtime/engine/runner.py +35 -24
- agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
- agentscope_runtime/engine/services/sandbox_service.py +62 -70
- agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
- agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
- agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
- agentscope_runtime/engine/services/utils/__init__.py +0 -0
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
- 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/__init__.py +2 -0
- agentscope_runtime/sandbox/box/base/__init__.py +4 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
- agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
- agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
- agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
- agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
- agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
- agentscope_runtime/sandbox/box/sandbox.py +5 -2
- agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
- agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
- agentscope_runtime/sandbox/build.py +143 -58
- agentscope_runtime/sandbox/client/http_client.py +87 -59
- agentscope_runtime/sandbox/client/training_client.py +0 -1
- agentscope_runtime/sandbox/constant.py +27 -1
- agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
- agentscope_runtime/sandbox/custom/example.py +4 -3
- agentscope_runtime/sandbox/enums.py +1 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
- agentscope_runtime/sandbox/manager/server/app.py +82 -14
- agentscope_runtime/sandbox/manager/server/config.py +50 -3
- agentscope_runtime/sandbox/model/container.py +12 -23
- agentscope_runtime/sandbox/model/manager_config.py +93 -5
- agentscope_runtime/sandbox/registry.py +1 -1
- agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
- agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
- agentscope_runtime/sandbox/tools/tool.py +4 -0
- agentscope_runtime/sandbox/utils.py +124 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/METADATA +209 -101
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/RECORD +94 -78
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
- agentscope_runtime/engine/agents/llm_agent.py +0 -51
- agentscope_runtime/engine/llms/__init__.py +0 -3
- agentscope_runtime/engine/llms/base_llm.py +0 -60
- agentscope_runtime/engine/llms/qwen_llm.py +0 -47
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
- /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_queue.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.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/base_client.py +0 -0
- /agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b1.dist-info}/top_level.txt +0 -0
|
@@ -29,7 +29,8 @@ class LocalDeployManager(DeployManager):
|
|
|
29
29
|
self,
|
|
30
30
|
host: str = "127.0.0.1",
|
|
31
31
|
port: int = 8000,
|
|
32
|
-
shutdown_timeout: int =
|
|
32
|
+
shutdown_timeout: int = 30,
|
|
33
|
+
startup_timeout: int = 30,
|
|
33
34
|
logger: Optional[logging.Logger] = None,
|
|
34
35
|
):
|
|
35
36
|
"""Initialize LocalDeployManager.
|
|
@@ -44,6 +45,7 @@ class LocalDeployManager(DeployManager):
|
|
|
44
45
|
self.host = host
|
|
45
46
|
self.port = port
|
|
46
47
|
self._shutdown_timeout = shutdown_timeout
|
|
48
|
+
self._startup_timeout = startup_timeout
|
|
47
49
|
self._logger = logger or logging.getLogger(__name__)
|
|
48
50
|
|
|
49
51
|
# State management
|
|
@@ -66,6 +68,7 @@ class LocalDeployManager(DeployManager):
|
|
|
66
68
|
|
|
67
69
|
async def deploy(
|
|
68
70
|
self,
|
|
71
|
+
app=None,
|
|
69
72
|
runner: Optional[Any] = None,
|
|
70
73
|
endpoint_path: str = "/process",
|
|
71
74
|
request_model: Optional[Type] = None,
|
|
@@ -75,7 +78,11 @@ class LocalDeployManager(DeployManager):
|
|
|
75
78
|
after_finish: Optional[Callable] = None,
|
|
76
79
|
mode: DeploymentMode = DeploymentMode.DAEMON_THREAD,
|
|
77
80
|
services_config: Optional[ServicesConfig] = None,
|
|
81
|
+
custom_endpoints: Optional[List[Dict]] = None,
|
|
78
82
|
protocol_adapters: Optional[list[ProtocolAdapter]] = None,
|
|
83
|
+
broker_url: Optional[str] = None,
|
|
84
|
+
backend_url: Optional[str] = None,
|
|
85
|
+
enable_embedded_worker: bool = False,
|
|
79
86
|
**kwargs: Any,
|
|
80
87
|
) -> Dict[str, str]:
|
|
81
88
|
"""Deploy using unified FastAPI architecture.
|
|
@@ -90,7 +97,12 @@ class LocalDeployManager(DeployManager):
|
|
|
90
97
|
after_finish: Callback function called after server finishes
|
|
91
98
|
mode: Deployment mode
|
|
92
99
|
services_config: Services configuration
|
|
100
|
+
custom_endpoints: Custom endpoints from agent app
|
|
93
101
|
protocol_adapters: Protocol adapters
|
|
102
|
+
broker_url: Celery broker URL for background task processing
|
|
103
|
+
backend_url: Celery backend URL for result storage
|
|
104
|
+
enable_embedded_worker: Whether to run Celery worker
|
|
105
|
+
embedded in the app
|
|
94
106
|
**kwargs: Additional keyword arguments
|
|
95
107
|
|
|
96
108
|
Returns:
|
|
@@ -102,6 +114,19 @@ class LocalDeployManager(DeployManager):
|
|
|
102
114
|
if self.is_running:
|
|
103
115
|
raise RuntimeError("Service is already running")
|
|
104
116
|
|
|
117
|
+
self._app = app
|
|
118
|
+
if self._app is not None:
|
|
119
|
+
runner = self._app._runner
|
|
120
|
+
endpoint_path = self._app.endpoint_path
|
|
121
|
+
response_type = self._app.response_type
|
|
122
|
+
stream = self._app.stream
|
|
123
|
+
request_model = self._app.request_model
|
|
124
|
+
before_start = self._app.before_start
|
|
125
|
+
after_finish = self._app.after_finish
|
|
126
|
+
backend_url = self._app.backend_url
|
|
127
|
+
broker_url = self._app.broker_url
|
|
128
|
+
custom_endpoints = self._app.custom_endpoints
|
|
129
|
+
protocol_adapters = self._app.protocol_adapters
|
|
105
130
|
try:
|
|
106
131
|
if mode == DeploymentMode.DAEMON_THREAD:
|
|
107
132
|
return await self._deploy_daemon_thread(
|
|
@@ -113,7 +138,11 @@ class LocalDeployManager(DeployManager):
|
|
|
113
138
|
before_start=before_start,
|
|
114
139
|
after_finish=after_finish,
|
|
115
140
|
services_config=services_config,
|
|
141
|
+
custom_endpoints=custom_endpoints,
|
|
116
142
|
protocol_adapters=protocol_adapters,
|
|
143
|
+
broker_url=broker_url,
|
|
144
|
+
backend_url=backend_url,
|
|
145
|
+
enable_embedded_worker=enable_embedded_worker,
|
|
117
146
|
**kwargs,
|
|
118
147
|
)
|
|
119
148
|
elif mode == DeploymentMode.DETACHED_PROCESS:
|
|
@@ -126,6 +155,7 @@ class LocalDeployManager(DeployManager):
|
|
|
126
155
|
before_start=before_start,
|
|
127
156
|
after_finish=after_finish,
|
|
128
157
|
services_config=services_config,
|
|
158
|
+
custom_endpoints=custom_endpoints,
|
|
129
159
|
protocol_adapters=protocol_adapters,
|
|
130
160
|
**kwargs,
|
|
131
161
|
)
|
|
@@ -143,16 +173,22 @@ class LocalDeployManager(DeployManager):
|
|
|
143
173
|
self,
|
|
144
174
|
runner: Optional[Any] = None,
|
|
145
175
|
protocol_adapters: Optional[list[ProtocolAdapter]] = None,
|
|
176
|
+
broker_url: Optional[str] = None,
|
|
177
|
+
backend_url: Optional[str] = None,
|
|
178
|
+
enable_embedded_worker: bool = False,
|
|
146
179
|
**kwargs,
|
|
147
180
|
) -> Dict[str, str]:
|
|
148
181
|
"""Deploy in daemon thread mode."""
|
|
149
182
|
self._logger.info("Deploying FastAPI service in daemon thread mode...")
|
|
150
183
|
|
|
151
|
-
# Create FastAPI app using factory
|
|
184
|
+
# Create FastAPI app using factory with Celery support
|
|
152
185
|
app = FastAPIAppFactory.create_app(
|
|
153
186
|
runner=runner,
|
|
154
187
|
mode=DeploymentMode.DAEMON_THREAD,
|
|
155
188
|
protocol_adapters=protocol_adapters,
|
|
189
|
+
broker_url=broker_url,
|
|
190
|
+
backend_url=backend_url,
|
|
191
|
+
enable_embedded_worker=enable_embedded_worker,
|
|
156
192
|
**kwargs,
|
|
157
193
|
)
|
|
158
194
|
|
|
@@ -174,7 +210,7 @@ class LocalDeployManager(DeployManager):
|
|
|
174
210
|
self._server_thread.start()
|
|
175
211
|
|
|
176
212
|
# Wait for server to start
|
|
177
|
-
await self._wait_for_server_ready()
|
|
213
|
+
await self._wait_for_server_ready(self._startup_timeout)
|
|
178
214
|
|
|
179
215
|
self.is_running = True
|
|
180
216
|
self.deploy_id = f"daemon_{self.host}_{self.port}"
|
|
@@ -207,6 +243,8 @@ class LocalDeployManager(DeployManager):
|
|
|
207
243
|
)
|
|
208
244
|
|
|
209
245
|
agent = runner._agent
|
|
246
|
+
if "agent" in kwargs:
|
|
247
|
+
kwargs.pop("agent")
|
|
210
248
|
|
|
211
249
|
# Create package project for detached deployment
|
|
212
250
|
project_dir = await self.create_detached_project(
|
|
@@ -272,6 +310,13 @@ class LocalDeployManager(DeployManager):
|
|
|
272
310
|
extra_packages: Optional[List[str]] = None,
|
|
273
311
|
services_config: Optional[ServicesConfig] = None,
|
|
274
312
|
protocol_adapters: Optional[list[ProtocolAdapter]] = None,
|
|
313
|
+
custom_endpoints: Optional[
|
|
314
|
+
List[Dict]
|
|
315
|
+
] = None, # New parameter for custom endpoints
|
|
316
|
+
# Celery parameters
|
|
317
|
+
broker_url: Optional[str] = None,
|
|
318
|
+
backend_url: Optional[str] = None,
|
|
319
|
+
enable_embedded_worker: bool = False,
|
|
275
320
|
**kwargs, # pylint: disable=unused-argument
|
|
276
321
|
) -> str:
|
|
277
322
|
"""Create detached project using package_project method."""
|
|
@@ -288,6 +333,11 @@ class LocalDeployManager(DeployManager):
|
|
|
288
333
|
extra_packages=extra_packages,
|
|
289
334
|
protocol_adapters=protocol_adapters,
|
|
290
335
|
services_config=services_config,
|
|
336
|
+
custom_endpoints=custom_endpoints, # Add custom endpoints
|
|
337
|
+
# Celery configuration
|
|
338
|
+
broker_url=broker_url,
|
|
339
|
+
backend_url=backend_url,
|
|
340
|
+
enable_embedded_worker=enable_embedded_worker,
|
|
291
341
|
requirements=requirements
|
|
292
342
|
+ (
|
|
293
343
|
["redis"]
|
|
@@ -301,6 +351,14 @@ class LocalDeployManager(DeployManager):
|
|
|
301
351
|
if config
|
|
302
352
|
)
|
|
303
353
|
else []
|
|
354
|
+
)
|
|
355
|
+
+ (
|
|
356
|
+
[
|
|
357
|
+
"celery",
|
|
358
|
+
"redis",
|
|
359
|
+
] # Add Celery and Redis if Celery is configured
|
|
360
|
+
if broker_url or backend_url
|
|
361
|
+
else []
|
|
304
362
|
),
|
|
305
363
|
)
|
|
306
364
|
|
|
@@ -201,16 +201,6 @@ async def _oss_create_bucket_if_not_exists(client, bucket_name: str) -> None:
|
|
|
201
201
|
)
|
|
202
202
|
|
|
203
203
|
|
|
204
|
-
def _create_bucket_name(prefix: str, base_name: str) -> str:
|
|
205
|
-
import re as _re
|
|
206
|
-
|
|
207
|
-
ts = time.strftime("%Y%m%d-%H%M%S", time.gmtime())
|
|
208
|
-
base = _re.sub(r"\s+", "-", base_name)
|
|
209
|
-
base = _re.sub(r"[^a-zA-Z0-9-]", "", base).lower().strip("-")
|
|
210
|
-
name = f"{prefix}-{base}-{ts}"
|
|
211
|
-
return name[:63]
|
|
212
|
-
|
|
213
|
-
|
|
214
204
|
async def _oss_put_and_presign(
|
|
215
205
|
client,
|
|
216
206
|
bucket_name: str,
|
|
@@ -569,6 +559,9 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
569
559
|
external_whl_path: Optional[str] = None,
|
|
570
560
|
agent_id: Optional[str] = None,
|
|
571
561
|
agent_desc: Optional[str] = None,
|
|
562
|
+
custom_endpoints: Optional[
|
|
563
|
+
List[Dict]
|
|
564
|
+
] = None, # New parameter for custom endpoints
|
|
572
565
|
**kwargs,
|
|
573
566
|
) -> Dict[str, str]:
|
|
574
567
|
"""
|
|
@@ -579,7 +572,10 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
579
572
|
"""
|
|
580
573
|
if not agent_id:
|
|
581
574
|
if not runner and not project_dir and not external_whl_path:
|
|
582
|
-
raise ValueError(
|
|
575
|
+
raise ValueError(
|
|
576
|
+
"Either runner, project_dir, "
|
|
577
|
+
"or external_whl_path must be provided.",
|
|
578
|
+
)
|
|
583
579
|
|
|
584
580
|
# convert services_config to Model body
|
|
585
581
|
if services_config and isinstance(services_config, dict):
|
|
@@ -588,6 +584,8 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
588
584
|
try:
|
|
589
585
|
if runner:
|
|
590
586
|
agent = runner._agent
|
|
587
|
+
if "agent" in kwargs:
|
|
588
|
+
kwargs.pop("agent")
|
|
591
589
|
|
|
592
590
|
# Create package project for detached deployment
|
|
593
591
|
project_dir = await LocalDeployManager.create_detached_project(
|
|
@@ -595,6 +593,7 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
595
593
|
endpoint_path=endpoint_path,
|
|
596
594
|
services_config=services_config, # type: ignore[arg-type]
|
|
597
595
|
protocol_adapters=protocol_adapters,
|
|
596
|
+
custom_endpoints=custom_endpoints, # Pass custom endpoints
|
|
598
597
|
requirements=requirements,
|
|
599
598
|
extra_packages=extra_packages,
|
|
600
599
|
**kwargs,
|
|
@@ -39,6 +39,9 @@ class RunnerImageConfig(BaseModel):
|
|
|
39
39
|
endpoint_path: str = "/process"
|
|
40
40
|
protocol_adapters: Optional[List] = None # New: protocol adapters
|
|
41
41
|
services_config: Optional[ServicesConfig] = None
|
|
42
|
+
custom_endpoints: Optional[
|
|
43
|
+
List[Dict]
|
|
44
|
+
] = None # New: custom endpoints configuration
|
|
42
45
|
|
|
43
46
|
# Docker configuration
|
|
44
47
|
base_image: str = "python:3.10-slim-bookworm"
|
|
@@ -184,6 +187,7 @@ class RunnerImageFactory:
|
|
|
184
187
|
endpoint_path=config.endpoint_path,
|
|
185
188
|
protocol_adapters=config.protocol_adapters,
|
|
186
189
|
services_config=config.services_config,
|
|
190
|
+
custom_endpoints=config.custom_endpoints,
|
|
187
191
|
),
|
|
188
192
|
dockerfile_path=dockerfile_path,
|
|
189
193
|
# caller_depth is no longer needed due to automatic
|
|
@@ -245,6 +249,9 @@ class RunnerImageFactory:
|
|
|
245
249
|
push_to_registry: bool = False,
|
|
246
250
|
services_config: Optional[ServicesConfig] = None,
|
|
247
251
|
protocol_adapters: Optional[List] = None, # New: protocol adapters
|
|
252
|
+
custom_endpoints: Optional[
|
|
253
|
+
List[Dict]
|
|
254
|
+
] = None, # New parameter for custom endpoints
|
|
248
255
|
**kwargs,
|
|
249
256
|
) -> str:
|
|
250
257
|
"""
|
|
@@ -261,6 +268,7 @@ class RunnerImageFactory:
|
|
|
261
268
|
push_to_registry: Whether to push to registry
|
|
262
269
|
services_config: Optional services config
|
|
263
270
|
protocol_adapters: Protocol adapters
|
|
271
|
+
custom_endpoints: Custom endpoints from agent app
|
|
264
272
|
**kwargs: Additional configuration options
|
|
265
273
|
|
|
266
274
|
Returns:
|
|
@@ -276,6 +284,7 @@ class RunnerImageFactory:
|
|
|
276
284
|
push_to_registry=push_to_registry,
|
|
277
285
|
protocol_adapters=protocol_adapters,
|
|
278
286
|
services_config=services_config,
|
|
287
|
+
custom_endpoints=custom_endpoints,
|
|
279
288
|
**kwargs,
|
|
280
289
|
)
|
|
281
290
|
|
|
@@ -12,7 +12,7 @@ import shutil
|
|
|
12
12
|
import tarfile
|
|
13
13
|
import tempfile
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import List, Optional, Any, Tuple
|
|
15
|
+
from typing import List, Optional, Any, Tuple, Dict
|
|
16
16
|
|
|
17
17
|
from pydantic import BaseModel
|
|
18
18
|
|
|
@@ -61,6 +61,173 @@ def _get_package_version() -> str:
|
|
|
61
61
|
return ""
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def _prepare_custom_endpoints_for_template(
|
|
65
|
+
custom_endpoints: Optional[List[Dict]],
|
|
66
|
+
temp_dir: str,
|
|
67
|
+
) -> Tuple[Optional[List[Dict]], List[str]]:
|
|
68
|
+
"""
|
|
69
|
+
Prepare custom endpoints for template rendering.
|
|
70
|
+
Copy handler source directories to ensure all dependencies are available.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
custom_endpoints: List of custom endpoint configurations
|
|
74
|
+
temp_dir: Temporary directory where files will be copied
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Tuple of:
|
|
78
|
+
- Prepared endpoint configurations with file information
|
|
79
|
+
- List of copied directory names (for sys.path setup)
|
|
80
|
+
"""
|
|
81
|
+
if not custom_endpoints:
|
|
82
|
+
return None, []
|
|
83
|
+
|
|
84
|
+
prepared_endpoints = []
|
|
85
|
+
handler_dirs_copied = set() # Track copied directories to avoid duplicates
|
|
86
|
+
copied_dir_names = [] # Track directory names for sys.path
|
|
87
|
+
|
|
88
|
+
for endpoint in custom_endpoints:
|
|
89
|
+
prepared_endpoint = {
|
|
90
|
+
"path": endpoint.get("path", "/unknown"),
|
|
91
|
+
"methods": endpoint.get("methods", ["POST"]),
|
|
92
|
+
"module": endpoint.get("module"),
|
|
93
|
+
"function_name": endpoint.get("function_name"),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Try to get handler source file if handler is provided
|
|
97
|
+
handler = endpoint.get("handler")
|
|
98
|
+
if handler and callable(handler):
|
|
99
|
+
try:
|
|
100
|
+
# Get the source file of the handler
|
|
101
|
+
handler_file = inspect.getfile(handler)
|
|
102
|
+
handler_name = handler.__name__
|
|
103
|
+
|
|
104
|
+
# Skip if it's a built-in or from site-packages
|
|
105
|
+
if (
|
|
106
|
+
not handler_file.endswith(".py")
|
|
107
|
+
or "site-packages" in handler_file
|
|
108
|
+
):
|
|
109
|
+
raise ValueError("Handler from non-user code")
|
|
110
|
+
|
|
111
|
+
# Get the directory containing the handler file
|
|
112
|
+
handler_dir = os.path.dirname(os.path.abspath(handler_file))
|
|
113
|
+
|
|
114
|
+
# Copy the entire working directory if not already copied
|
|
115
|
+
if handler_dir not in handler_dirs_copied:
|
|
116
|
+
# Create a subdirectory name for this handler's context
|
|
117
|
+
dir_name = os.path.basename(handler_dir)
|
|
118
|
+
if not dir_name or dir_name == ".":
|
|
119
|
+
dir_name = "handler_context"
|
|
120
|
+
|
|
121
|
+
# Sanitize directory name
|
|
122
|
+
dir_name = re.sub(r"[^a-zA-Z0-9_]", "_", dir_name)
|
|
123
|
+
|
|
124
|
+
# Ensure unique directory name
|
|
125
|
+
counter = 1
|
|
126
|
+
base_dir_name = dir_name
|
|
127
|
+
dest_context_dir = os.path.join(temp_dir, dir_name)
|
|
128
|
+
while os.path.exists(dest_context_dir):
|
|
129
|
+
dir_name = f"{base_dir_name}_{counter}"
|
|
130
|
+
dest_context_dir = os.path.join(temp_dir, dir_name)
|
|
131
|
+
counter += 1
|
|
132
|
+
|
|
133
|
+
# Copy entire directory structure
|
|
134
|
+
# Exclude common non-essential directories
|
|
135
|
+
ignore_patterns = shutil.ignore_patterns(
|
|
136
|
+
"__pycache__",
|
|
137
|
+
"*.pyc",
|
|
138
|
+
"*.pyo",
|
|
139
|
+
".git",
|
|
140
|
+
".gitignore",
|
|
141
|
+
".pytest_cache",
|
|
142
|
+
".mypy_cache",
|
|
143
|
+
".tox",
|
|
144
|
+
"venv",
|
|
145
|
+
"env",
|
|
146
|
+
".venv",
|
|
147
|
+
".env",
|
|
148
|
+
"node_modules",
|
|
149
|
+
".DS_Store",
|
|
150
|
+
"*.egg-info",
|
|
151
|
+
"build",
|
|
152
|
+
"dist",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
shutil.copytree(
|
|
156
|
+
handler_dir,
|
|
157
|
+
dest_context_dir,
|
|
158
|
+
ignore=ignore_patterns,
|
|
159
|
+
dirs_exist_ok=True,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
handler_dirs_copied.add(handler_dir)
|
|
163
|
+
copied_dir_names.append(dir_name)
|
|
164
|
+
else:
|
|
165
|
+
# Find the existing copied directory name
|
|
166
|
+
for existing_dir in os.listdir(temp_dir):
|
|
167
|
+
existing_path = os.path.join(temp_dir, existing_dir)
|
|
168
|
+
if os.path.isdir(existing_path):
|
|
169
|
+
# Check if this is the directory we already copied
|
|
170
|
+
original_handler_basename = os.path.basename(
|
|
171
|
+
handler_dir,
|
|
172
|
+
)
|
|
173
|
+
if existing_dir.startswith(
|
|
174
|
+
re.sub(
|
|
175
|
+
r"[^a-zA-Z0-9_]",
|
|
176
|
+
"_",
|
|
177
|
+
original_handler_basename,
|
|
178
|
+
),
|
|
179
|
+
):
|
|
180
|
+
dir_name = existing_dir
|
|
181
|
+
break
|
|
182
|
+
else:
|
|
183
|
+
# Fallback if not found
|
|
184
|
+
dir_name = re.sub(
|
|
185
|
+
r"[^a-zA-Z0-9_]",
|
|
186
|
+
"_",
|
|
187
|
+
os.path.basename(handler_dir),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Calculate the module path relative to the handler directory
|
|
191
|
+
handler_file_rel = os.path.relpath(handler_file, handler_dir)
|
|
192
|
+
# Convert file path to module path
|
|
193
|
+
module_parts = os.path.splitext(handler_file_rel)[0].split(
|
|
194
|
+
os.sep,
|
|
195
|
+
)
|
|
196
|
+
if module_parts[-1] == "__init__":
|
|
197
|
+
module_parts = module_parts[:-1]
|
|
198
|
+
|
|
199
|
+
# Construct the full import path
|
|
200
|
+
if module_parts:
|
|
201
|
+
module_path = f"{dir_name}.{'.'.join(module_parts)}"
|
|
202
|
+
else:
|
|
203
|
+
module_path = dir_name
|
|
204
|
+
|
|
205
|
+
# Set the module and function name for template
|
|
206
|
+
prepared_endpoint["handler_module"] = module_path
|
|
207
|
+
prepared_endpoint["function_name"] = handler_name
|
|
208
|
+
|
|
209
|
+
except (OSError, TypeError, ValueError) as e:
|
|
210
|
+
# If source file extraction fails, try module/function_name
|
|
211
|
+
import traceback
|
|
212
|
+
|
|
213
|
+
print(f"Warning: Failed to copy handler directory: {e}")
|
|
214
|
+
traceback.print_exc()
|
|
215
|
+
|
|
216
|
+
# Add inline code if no handler module/function available
|
|
217
|
+
if not prepared_endpoint.get("handler_module") and (
|
|
218
|
+
not prepared_endpoint["module"]
|
|
219
|
+
or not prepared_endpoint["function_name"]
|
|
220
|
+
):
|
|
221
|
+
prepared_endpoint["inline_code"] = endpoint.get(
|
|
222
|
+
"inline_code",
|
|
223
|
+
'lambda request: {"error": "Handler not available"}',
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
prepared_endpoints.append(prepared_endpoint)
|
|
227
|
+
|
|
228
|
+
return prepared_endpoints, copied_dir_names
|
|
229
|
+
|
|
230
|
+
|
|
64
231
|
class PackageConfig(BaseModel):
|
|
65
232
|
"""Configuration for project packaging"""
|
|
66
233
|
|
|
@@ -73,6 +240,13 @@ class PackageConfig(BaseModel):
|
|
|
73
240
|
ServicesConfig
|
|
74
241
|
] = None # New: services configuration
|
|
75
242
|
protocol_adapters: Optional[List[Any]] = None # New: protocol adapters
|
|
243
|
+
custom_endpoints: Optional[
|
|
244
|
+
List[Dict]
|
|
245
|
+
] = None # New: custom endpoints configuration
|
|
246
|
+
# Celery configuration parameters
|
|
247
|
+
broker_url: Optional[str] = None
|
|
248
|
+
backend_url: Optional[str] = None
|
|
249
|
+
enable_embedded_worker: bool = False
|
|
76
250
|
|
|
77
251
|
|
|
78
252
|
def _find_agent_source_file(
|
|
@@ -742,6 +916,41 @@ def package_project(
|
|
|
742
916
|
f"# Protocol adapters\nprotocol_adapters = {instances_str}"
|
|
743
917
|
)
|
|
744
918
|
|
|
919
|
+
# Convert celery_config to string representation for template
|
|
920
|
+
celery_config_str = None
|
|
921
|
+
config_lines = []
|
|
922
|
+
|
|
923
|
+
# Generate celery configuration code
|
|
924
|
+
config_lines.append("# Celery configuration")
|
|
925
|
+
|
|
926
|
+
if config.broker_url:
|
|
927
|
+
config_lines.append(
|
|
928
|
+
f'celery_config["broker_url"] = "{config.broker_url}"',
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
if config.backend_url:
|
|
932
|
+
config_lines.append(
|
|
933
|
+
f'celery_config["backend_url"] = "{config.backend_url}"',
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
if config.enable_embedded_worker:
|
|
937
|
+
config_lines.append(
|
|
938
|
+
f'celery_config["enable_embedded_worker"] = '
|
|
939
|
+
f"{config.enable_embedded_worker}",
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
if config_lines:
|
|
943
|
+
celery_config_str = "\n".join(config_lines)
|
|
944
|
+
|
|
945
|
+
# Prepare custom endpoints and get copied directory names
|
|
946
|
+
(
|
|
947
|
+
custom_endpoints_data,
|
|
948
|
+
handler_dirs,
|
|
949
|
+
) = _prepare_custom_endpoints_for_template(
|
|
950
|
+
config.custom_endpoints,
|
|
951
|
+
temp_dir,
|
|
952
|
+
)
|
|
953
|
+
|
|
745
954
|
# Render template - use template file by default,
|
|
746
955
|
# or user-provided string
|
|
747
956
|
if template is None:
|
|
@@ -751,6 +960,9 @@ def package_project(
|
|
|
751
960
|
endpoint_path=config.endpoint_path or "/process",
|
|
752
961
|
deployment_mode=config.deployment_mode or "standalone",
|
|
753
962
|
protocol_adapters=protocol_adapters_str,
|
|
963
|
+
celery_config=celery_config_str,
|
|
964
|
+
custom_endpoints=custom_endpoints_data,
|
|
965
|
+
handler_dirs=handler_dirs,
|
|
754
966
|
)
|
|
755
967
|
else:
|
|
756
968
|
# Use user-provided template string
|
|
@@ -760,6 +972,9 @@ def package_project(
|
|
|
760
972
|
endpoint_path=config.endpoint_path,
|
|
761
973
|
deployment_mode=config.deployment_mode or "standalone",
|
|
762
974
|
protocol_adapters=protocol_adapters_str,
|
|
975
|
+
celery_config=celery_config_str,
|
|
976
|
+
custom_endpoints=custom_endpoints_data,
|
|
977
|
+
handler_dirs=handler_dirs,
|
|
763
978
|
)
|
|
764
979
|
|
|
765
980
|
# Write main.py
|
|
@@ -802,9 +1017,25 @@ def package_project(
|
|
|
802
1017
|
if not config.requirements:
|
|
803
1018
|
config.requirements = []
|
|
804
1019
|
|
|
805
|
-
#
|
|
1020
|
+
# Add Celery requirements if Celery is configured
|
|
1021
|
+
celery_requirements = []
|
|
1022
|
+
if (
|
|
1023
|
+
config.broker_url
|
|
1024
|
+
or config.backend_url
|
|
1025
|
+
or config.enable_embedded_worker
|
|
1026
|
+
):
|
|
1027
|
+
celery_requirements = ["celery", "redis"]
|
|
1028
|
+
|
|
1029
|
+
# Combine base requirements with user requirements and Celery
|
|
1030
|
+
# requirements
|
|
806
1031
|
all_requirements = sorted(
|
|
807
|
-
list(
|
|
1032
|
+
list(
|
|
1033
|
+
set(
|
|
1034
|
+
base_requirements
|
|
1035
|
+
+ config.requirements
|
|
1036
|
+
+ celery_requirements,
|
|
1037
|
+
),
|
|
1038
|
+
),
|
|
808
1039
|
)
|
|
809
1040
|
for req in all_requirements:
|
|
810
1041
|
f.write(f"{req}\n")
|