agentscope-runtime 1.0.2__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. agentscope_runtime/cli/commands/deploy.py +12 -0
  2. agentscope_runtime/common/collections/redis_mapping.py +4 -1
  3. agentscope_runtime/engine/app/agent_app.py +48 -5
  4. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +56 -1
  5. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +449 -41
  6. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +273 -0
  7. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +640 -0
  8. agentscope_runtime/engine/deployers/kubernetes_deployer.py +3 -0
  9. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +8 -2
  10. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +5 -0
  11. agentscope_runtime/engine/deployers/utils/net_utils.py +65 -0
  12. agentscope_runtime/engine/runner.py +5 -3
  13. agentscope_runtime/engine/schemas/exception.py +24 -0
  14. agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
  15. agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
  16. agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
  17. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
  18. agentscope_runtime/sandbox/build.py +50 -57
  19. agentscope_runtime/version.py +1 -1
  20. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/METADATA +9 -3
  21. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/RECORD +25 -22
  22. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/WHEEL +0 -0
  23. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/entry_points.txt +0 -0
  24. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/licenses/LICENSE +0 -0
  25. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,640 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Nacos Registry Implementation for A2A Protocol
4
+
5
+ This module provides a Nacos-based registry implementation for A2A protocol
6
+ agent service registration. It serves as the default registry implementation
7
+ for A2A protocol adapters.
8
+ """
9
+ import asyncio
10
+ import logging
11
+ from enum import Enum
12
+ from typing import Optional, TYPE_CHECKING, List
13
+ import threading
14
+
15
+ from a2a.types import AgentCard
16
+
17
+ # Make the v2.nacos imports optional to avoid hard dependency at
18
+ # module import time.
19
+ if TYPE_CHECKING:
20
+ from v2.nacos import ClientConfig, ClientConfigBuilder
21
+ from v2.nacos.ai.model.ai_param import (
22
+ RegisterAgentEndpointParam,
23
+ ReleaseAgentCardParam,
24
+ )
25
+ from v2.nacos.ai.nacos_ai_service import NacosAIService
26
+
27
+ _NACOS_SDK_AVAILABLE = True
28
+ else:
29
+ try:
30
+ from v2.nacos import ClientConfig, ClientConfigBuilder
31
+ from v2.nacos.ai.model.ai_param import (
32
+ RegisterAgentEndpointParam,
33
+ ReleaseAgentCardParam,
34
+ )
35
+ from v2.nacos.ai.nacos_ai_service import NacosAIService
36
+
37
+ _NACOS_SDK_AVAILABLE = True
38
+ except Exception:
39
+
40
+ class ClientConfig:
41
+ pass
42
+
43
+ class ClientConfigBuilder:
44
+ pass
45
+
46
+ class RegisterAgentEndpointParam:
47
+ pass
48
+
49
+ class ReleaseAgentCardParam:
50
+ pass
51
+
52
+ class NacosAIService:
53
+ pass
54
+
55
+ _NACOS_SDK_AVAILABLE = False
56
+
57
+ # Import after conditional imports to avoid circular dependencies
58
+ from .a2a_registry import ( # pylint: disable=wrong-import-position
59
+ A2ARegistry,
60
+ A2ATransportsProperties,
61
+ )
62
+
63
+ logger = logging.getLogger(__name__)
64
+
65
+
66
+ class RegistrationStatus(Enum):
67
+ """Registration task status."""
68
+
69
+ PENDING = "pending"
70
+ IN_PROGRESS = "in_progress"
71
+ COMPLETED = "completed"
72
+ FAILED = "failed"
73
+ CANCELLED = "cancelled"
74
+
75
+
76
+ class NacosRegistry(A2ARegistry):
77
+ """Nacos-based registry implementation for A2A protocol.
78
+
79
+ This registry registers A2A agent services to Nacos service
80
+ discovery system. It performs two-step registration:
81
+ 1. Publishes agent card to Nacos (agent metadata)
82
+ 2. Registers agent endpoint with host/port information (service location)
83
+
84
+ Args:
85
+ nacos_client_config: Optional Nacos client configuration.
86
+ If None, creates default config from environment variables.
87
+ """
88
+
89
+ def __init__(
90
+ self,
91
+ nacos_client_config: Optional[ClientConfig] = None,
92
+ ):
93
+ self._nacos_client_config = nacos_client_config
94
+ self._nacos_ai_service: Optional[NacosAIService] = None
95
+ self._register_task: Optional[asyncio.Task] = None
96
+ self._register_thread: Optional[threading.Thread] = None
97
+ self._registration_status: RegistrationStatus = (
98
+ RegistrationStatus.PENDING
99
+ )
100
+ self._registration_lock = threading.Lock()
101
+ self._shutdown_event = threading.Event()
102
+
103
+ def registry_name(self) -> str:
104
+ """Get the name of this registry implementation.
105
+
106
+ Returns:
107
+ Registry name: "nacos"
108
+ """
109
+ return "nacos"
110
+
111
+ def register(
112
+ self,
113
+ agent_card: AgentCard,
114
+ a2a_transports_properties: Optional[
115
+ List[A2ATransportsProperties]
116
+ ] = None,
117
+ ) -> None:
118
+ """Register an A2A agent service to Nacos.
119
+
120
+ This method initiates registration asynchronously in the
121
+ background. Exceptions during registration are caught and
122
+ logged, but not raised. Use `get_registration_status()` or
123
+ `wait_for_registration()` to check the result.
124
+
125
+ Args:
126
+ agent_card: The complete A2A agent card generated by runtime
127
+ a2a_transports_properties: List of transport configurations.
128
+ Each transport will be registered separately.
129
+ """
130
+ # If Nacos SDK is not available, log and return
131
+ if not _NACOS_SDK_AVAILABLE:
132
+ logger.debug(
133
+ "[NacosRegistry] Nacos SDK is not available; "
134
+ "skipping registration",
135
+ )
136
+ return
137
+
138
+ self._start_register_task(
139
+ agent_card=agent_card,
140
+ a2a_transports_properties=a2a_transports_properties,
141
+ )
142
+
143
+ def _start_register_task(
144
+ self,
145
+ agent_card: AgentCard,
146
+ a2a_transports_properties: Optional[
147
+ List[A2ATransportsProperties]
148
+ ] = None,
149
+ ) -> None:
150
+ """Start background Nacos registration task.
151
+
152
+ If there is an active asyncio event loop, schedule the
153
+ registration as a task on that loop. Otherwise, spawn a daemon
154
+ thread and run the registration using asyncio.run so
155
+ registration still occurs in synchronous contexts.
156
+ """
157
+ # All status checks and updates must be within the lock to
158
+ # ensure atomicity
159
+ with self._registration_lock:
160
+ # Check if shutdown was already requested (inside lock
161
+ # for atomicity)
162
+ if self._shutdown_event.is_set():
163
+ logger.info(
164
+ "[NacosRegistry] Shutdown already requested, "
165
+ "skipping registration",
166
+ )
167
+ self._registration_status = RegistrationStatus.CANCELLED
168
+ return
169
+
170
+ # Check if registration is already in progress or completed
171
+ if self._registration_status in (
172
+ RegistrationStatus.IN_PROGRESS,
173
+ RegistrationStatus.COMPLETED,
174
+ ):
175
+ logger.warning(
176
+ "[NacosRegistry] Registration already in "
177
+ "progress or completed, skipping",
178
+ )
179
+ return
180
+ self._registration_status = RegistrationStatus.PENDING
181
+
182
+ try:
183
+ loop = None
184
+ try:
185
+ loop = asyncio.get_running_loop()
186
+ except RuntimeError:
187
+ # No running loop in this thread; we'll fall back to a
188
+ # background thread
189
+ loop = None
190
+
191
+ if loop is not None:
192
+ with self._registration_lock:
193
+ self._registration_status = RegistrationStatus.IN_PROGRESS
194
+ self._register_task = loop.create_task(
195
+ self._register_to_nacos(
196
+ agent_card=agent_card,
197
+ a2a_transports_properties=a2a_transports_properties,
198
+ ),
199
+ )
200
+ logger.info(
201
+ "[NacosRegistry] Registration task scheduled on "
202
+ "running event loop",
203
+ )
204
+ return
205
+
206
+ # No running loop: use a background thread to run asyncio.run
207
+ def _thread_runner():
208
+ try:
209
+ with self._registration_lock:
210
+ if self._shutdown_event.is_set():
211
+ logger.info(
212
+ "[NacosRegistry] Shutdown requested "
213
+ "before registration started",
214
+ )
215
+ self._registration_status = (
216
+ RegistrationStatus.CANCELLED
217
+ )
218
+ return
219
+ self._registration_status = (
220
+ RegistrationStatus.IN_PROGRESS
221
+ )
222
+
223
+ asyncio.run(
224
+ self._register_to_nacos(
225
+ agent_card=agent_card,
226
+ a2a_transports_properties=(
227
+ a2a_transports_properties
228
+ ),
229
+ ),
230
+ )
231
+ with self._registration_lock:
232
+ if (
233
+ self._registration_status
234
+ == RegistrationStatus.IN_PROGRESS
235
+ ):
236
+ self._registration_status = (
237
+ RegistrationStatus.COMPLETED
238
+ )
239
+ except asyncio.CancelledError:
240
+ with self._registration_lock:
241
+ self._registration_status = (
242
+ RegistrationStatus.CANCELLED
243
+ )
244
+ logger.info("[NacosRegistry] Registration cancelled")
245
+ except Exception:
246
+ with self._registration_lock:
247
+ self._registration_status = RegistrationStatus.FAILED
248
+ logger.error(
249
+ "[NacosRegistry] Registration failed in "
250
+ "background thread",
251
+ exc_info=True,
252
+ )
253
+
254
+ thread = threading.Thread(
255
+ target=_thread_runner,
256
+ name="nacos-registry-register",
257
+ daemon=True,
258
+ )
259
+ thread.start()
260
+ # Store thread reference after successful start for
261
+ # proper tracking and cleanup
262
+ with self._registration_lock:
263
+ self._register_thread = thread
264
+ logger.info(
265
+ "[NacosRegistry] Registration task started in "
266
+ "background thread",
267
+ )
268
+ except Exception:
269
+ with self._registration_lock:
270
+ self._registration_status = RegistrationStatus.FAILED
271
+ logger.warning(
272
+ "[NacosRegistry] Error starting registration task",
273
+ exc_info=True,
274
+ )
275
+
276
+ def _get_client_config(self) -> ClientConfig:
277
+ """Get Nacos client configuration.
278
+
279
+ Returns:
280
+ ClientConfig: Nacos client configuration
281
+ """
282
+ if self._nacos_client_config is not None:
283
+ return self._nacos_client_config
284
+
285
+ # Use centralized config builder from a2a_registry module
286
+ # This ensures consistent behavior with env-based registry creation
287
+ from .a2a_registry import (
288
+ get_registry_settings,
289
+ _build_nacos_client_config,
290
+ )
291
+
292
+ settings = get_registry_settings()
293
+ return _build_nacos_client_config(settings)
294
+
295
+ # pylint: disable=too-many-branches,too-many-statements
296
+ async def _register_to_nacos(
297
+ self,
298
+ agent_card: AgentCard,
299
+ a2a_transports_properties: Optional[
300
+ List[A2ATransportsProperties]
301
+ ] = None,
302
+ ) -> None:
303
+ """Register agent to Nacos.
304
+
305
+ Performs two-step registration:
306
+ 1. Release agent card to Nacos
307
+ 2. Register agent endpoint with host/port information
308
+
309
+ On successful registration, the NacosAIService connection is kept
310
+ alive to maintain heartbeat and health checks. The service will be
311
+ closed in cleanup() during application shutdown.
312
+
313
+ Most exceptions are caught and logged without re-raising to prevent
314
+ background tasks from crashing the host. Only `asyncio.CancelledError`
315
+ is re-raised.
316
+
317
+ Args:
318
+ agent_card: The A2A agent card to register
319
+ a2a_transports_properties: Optional list of transport
320
+ configurations
321
+
322
+ Raises:
323
+ asyncio.CancelledError: If the registration task is cancelled
324
+ """
325
+ # Check if shutdown was requested
326
+ if self._shutdown_event.is_set():
327
+ with self._registration_lock:
328
+ self._registration_status = RegistrationStatus.CANCELLED
329
+ logger.info(
330
+ "[NacosRegistry] Registration cancelled due to shutdown",
331
+ )
332
+ return
333
+
334
+ client_config = self._get_client_config()
335
+ try:
336
+ self._nacos_ai_service = await NacosAIService.create_ai_service(
337
+ client_config,
338
+ )
339
+
340
+ # Check again after service creation
341
+ if self._shutdown_event.is_set():
342
+ with self._registration_lock:
343
+ self._registration_status = RegistrationStatus.CANCELLED
344
+ logger.info(
345
+ "[NacosRegistry] Registration cancelled after "
346
+ "service creation",
347
+ )
348
+ return
349
+
350
+ # Publish agent card
351
+ await self._nacos_ai_service.release_agent_card(
352
+ ReleaseAgentCardParam(agent_card=agent_card),
353
+ )
354
+ logger.info(
355
+ "[NacosRegistry] Agent card published: agent=%s",
356
+ agent_card.name,
357
+ )
358
+
359
+ # Check again before endpoint registration
360
+ if self._shutdown_event.is_set():
361
+ with self._registration_lock:
362
+ self._registration_status = RegistrationStatus.CANCELLED
363
+ logger.warning(
364
+ "[NacosRegistry] Registration cancelled after "
365
+ "card publish, endpoint may be partially "
366
+ "registered",
367
+ )
368
+ return
369
+
370
+ # Register agent endpoint
371
+ if a2a_transports_properties:
372
+ for transport in a2a_transports_properties:
373
+ host = transport.host
374
+ port = transport.port
375
+ endpoint_param = RegisterAgentEndpointParam(
376
+ agent_name=agent_card.name,
377
+ version=agent_card.version,
378
+ address=host,
379
+ port=port,
380
+ path=transport.path,
381
+ support_tls=transport.support_tls,
382
+ transport=transport.transport_type,
383
+ )
384
+
385
+ await self._nacos_ai_service.register_agent_endpoint(
386
+ endpoint_param,
387
+ )
388
+ logger.info(
389
+ "[NacosRegistry] Agent endpoint registered: "
390
+ "agent=%s, address=%s:%s, path=%s",
391
+ agent_card.name,
392
+ host,
393
+ port,
394
+ transport.path,
395
+ )
396
+
397
+ with self._registration_lock:
398
+ if self._registration_status == RegistrationStatus.IN_PROGRESS:
399
+ self._registration_status = RegistrationStatus.COMPLETED
400
+ # Service remains alive to maintain heartbeat and health checks.
401
+ # It will be closed in cleanup() during application shutdown.
402
+
403
+ except asyncio.CancelledError:
404
+ with self._registration_lock:
405
+ self._registration_status = RegistrationStatus.CANCELLED
406
+ logger.info("[NacosRegistry] Registration task cancelled")
407
+ try:
408
+ svc = self._nacos_ai_service
409
+ if svc is not None:
410
+ await svc.shutdown()
411
+ logger.debug(
412
+ "[NacosRegistry] NacosAIService closed due to "
413
+ "cancellation",
414
+ )
415
+ except Exception:
416
+ logger.debug(
417
+ "[NacosRegistry] Error closing NacosAIService on "
418
+ "cancellation",
419
+ exc_info=True,
420
+ )
421
+ raise
422
+ except Exception as e:
423
+ with self._registration_lock:
424
+ self._registration_status = RegistrationStatus.FAILED
425
+ # Log errors but don't re-raise; background tasks should
426
+ # not crash the host
427
+ logger.error(
428
+ "[NacosRegistry] Failed to register agent=%s: %s",
429
+ agent_card.name,
430
+ str(e),
431
+ exc_info=True,
432
+ )
433
+ try:
434
+ svc = self._nacos_ai_service
435
+ if svc is not None:
436
+ await svc.shutdown()
437
+ logger.debug(
438
+ "[NacosRegistry] NacosAIService closed due to "
439
+ "registration failure",
440
+ )
441
+ except Exception:
442
+ logger.debug(
443
+ "[NacosRegistry] Error closing NacosAIService on failure",
444
+ exc_info=True,
445
+ )
446
+
447
+ def get_registration_status(self) -> RegistrationStatus:
448
+ """Get the current registration status.
449
+
450
+ Returns:
451
+ RegistrationStatus: Current status of the registration task
452
+ """
453
+ with self._registration_lock:
454
+ return self._registration_status
455
+
456
+ async def wait_for_registration(
457
+ self,
458
+ timeout: Optional[float] = None,
459
+ ) -> RegistrationStatus:
460
+ """Wait for registration to complete, fail, or be cancelled.
461
+
462
+ Args:
463
+ timeout: Optional timeout in seconds. If None, waits indefinitely.
464
+
465
+ Returns:
466
+ RegistrationStatus: Final status of the registration
467
+ """
468
+ if self._register_task is not None:
469
+ # Task-based registration: wait for the task
470
+ try:
471
+ if timeout is not None:
472
+ await asyncio.wait_for(
473
+ self._register_task,
474
+ timeout=timeout,
475
+ )
476
+ else:
477
+ await self._register_task
478
+ except asyncio.TimeoutError:
479
+ logger.warning(
480
+ "[NacosRegistry] Wait for registration timed "
481
+ "out after %s seconds",
482
+ timeout,
483
+ )
484
+ except (asyncio.CancelledError, Exception):
485
+ pass # Task may have been cancelled or failed
486
+ elif self._register_thread is not None:
487
+ # Thread-based registration: wait for the thread (matching
488
+ # LocalDeployManager pattern)
489
+ self._register_thread.join(timeout=timeout)
490
+ if self._register_thread.is_alive():
491
+ logger.warning(
492
+ "[NacosRegistry] Registration thread did not "
493
+ "complete within timeout",
494
+ )
495
+
496
+ return self.get_registration_status()
497
+
498
+ # pylint: disable=too-many-branches,too-many-statements
499
+ async def cleanup(
500
+ self,
501
+ wait_for_completion: bool = True,
502
+ timeout: Optional[float] = 5.0,
503
+ ) -> None:
504
+ """Clean up registration resources and cancel ongoing
505
+ registration if needed.
506
+
507
+ This method should be called during shutdown to ensure proper cleanup.
508
+ It will:
509
+ 1. Signal shutdown to prevent new registration attempts
510
+ 2. Optionally wait for the registration to complete or timeout
511
+ 3. Cancel the asyncio task if running
512
+ 4. Wait for background thread if running
513
+ 5. Clean up service connections
514
+
515
+ Args:
516
+ wait_for_completion: If True, wait for registration to complete
517
+ before cancelling. If False, cancel immediately.
518
+ timeout: Maximum time to wait for completion (if
519
+ wait_for_completion=True). Default is 5 seconds.
520
+ """
521
+ logger.info("[NacosRegistry] Starting cleanup")
522
+
523
+ # Signal shutdown first to prevent new registration attempts
524
+ self._shutdown_event.set()
525
+
526
+ with self._registration_lock:
527
+ current_status = self._registration_status
528
+
529
+ # If registration is in progress or pending, handle cancellation
530
+ if current_status in (
531
+ RegistrationStatus.IN_PROGRESS,
532
+ RegistrationStatus.PENDING,
533
+ ):
534
+ if (
535
+ wait_for_completion
536
+ and current_status == RegistrationStatus.IN_PROGRESS
537
+ ):
538
+ logger.info(
539
+ "[NacosRegistry] Waiting for registration to "
540
+ "complete (timeout=%s)",
541
+ timeout,
542
+ )
543
+ try:
544
+ await self.wait_for_registration(timeout=timeout)
545
+ current_status = self.get_registration_status()
546
+ if current_status == RegistrationStatus.COMPLETED:
547
+ logger.info(
548
+ "[NacosRegistry] Registration completed "
549
+ "before shutdown",
550
+ )
551
+ elif current_status == RegistrationStatus.FAILED:
552
+ logger.warning(
553
+ "[NacosRegistry] Registration failed "
554
+ "before shutdown",
555
+ )
556
+ elif current_status == RegistrationStatus.CANCELLED:
557
+ logger.info(
558
+ "[NacosRegistry] Registration was cancelled",
559
+ )
560
+ except Exception as e:
561
+ logger.warning(
562
+ "[NacosRegistry] Error waiting for registration: %s",
563
+ e,
564
+ exc_info=True,
565
+ )
566
+ else:
567
+ if current_status == RegistrationStatus.PENDING:
568
+ logger.info(
569
+ "[NacosRegistry] Registration was pending, "
570
+ "marking as cancelled",
571
+ )
572
+ with self._registration_lock:
573
+ self._registration_status = (
574
+ RegistrationStatus.CANCELLED
575
+ )
576
+ else:
577
+ logger.info(
578
+ "[NacosRegistry] Skipping wait, cancelling "
579
+ "immediately",
580
+ )
581
+
582
+ # Cancel the task if it's still running (matching
583
+ # env_service pattern)
584
+ if (
585
+ self._register_task is not None
586
+ and not self._register_task.done()
587
+ ):
588
+ logger.info("[NacosRegistry] Cancelling registration task")
589
+ self._register_task.cancel()
590
+ try:
591
+ await self._register_task
592
+ except asyncio.CancelledError:
593
+ # Expected when task is cancelled: await on a
594
+ # cancelled task raises CancelledError
595
+ pass
596
+ except Exception as e:
597
+ logger.debug(
598
+ "[NacosRegistry] Error cancelling task: %s",
599
+ e,
600
+ exc_info=True,
601
+ )
602
+
603
+ # Wait for thread if it's still running (matching
604
+ # LocalDeployManager pattern)
605
+ if (
606
+ self._register_thread is not None
607
+ and self._register_thread.is_alive()
608
+ ):
609
+ logger.info(
610
+ "[NacosRegistry] Waiting for registration thread "
611
+ "to finish",
612
+ )
613
+ thread_timeout = (
614
+ timeout
615
+ if (
616
+ wait_for_completion
617
+ and current_status == RegistrationStatus.IN_PROGRESS
618
+ )
619
+ else 1.0
620
+ )
621
+ self._register_thread.join(timeout=thread_timeout)
622
+ if self._register_thread.is_alive():
623
+ logger.warning(
624
+ "[NacosRegistry] Registration thread did not "
625
+ "terminate, potential resource leak",
626
+ )
627
+
628
+ # Always clean up service connection, regardless of status
629
+ if self._nacos_ai_service is not None:
630
+ try:
631
+ await self._nacos_ai_service.shutdown()
632
+ except Exception:
633
+ logger.debug(
634
+ "[NacosRegistry] Error closing service during cleanup",
635
+ exc_info=True,
636
+ )
637
+ finally:
638
+ self._nacos_ai_service = None
639
+
640
+ logger.info("[NacosRegistry] Cleanup completed")
@@ -144,6 +144,7 @@ class KubernetesDeployManager(DeployManager):
144
144
  image_tag: str = "latest",
145
145
  push_to_registry: bool = False,
146
146
  use_cache: bool = True,
147
+ pypi_mirror: Optional[str] = None,
147
148
  **kwargs,
148
149
  ) -> Dict[str, Any]:
149
150
  """
@@ -170,6 +171,7 @@ class KubernetesDeployManager(DeployManager):
170
171
  mount_dir: Mount directory
171
172
  runtime_config: K8s runtime configuration
172
173
  use_cache: Enable build cache (default: True)
174
+ pypi_mirror: PyPI mirror URL for pip package installation
173
175
  # Backward compatibility
174
176
  image_name: Image name
175
177
  image_tag: Image tag
@@ -209,6 +211,7 @@ class KubernetesDeployManager(DeployManager):
209
211
  protocol_adapters=protocol_adapters,
210
212
  custom_endpoints=custom_endpoints,
211
213
  use_cache=use_cache,
214
+ pypi_mirror=pypi_mirror,
212
215
  **kwargs,
213
216
  )
214
217
  if not built_image_name:
@@ -22,6 +22,7 @@ class DockerfileConfig(BaseModel):
22
22
  health_check_endpoint: str = "/health"
23
23
  custom_template: Optional[str] = None
24
24
  platform: Optional[str] = None
25
+ pypi_mirror: Optional[str] = None
25
26
 
26
27
 
27
28
  class DockerfileGenerator:
@@ -73,8 +74,7 @@ COPY . {working_dir}/
73
74
  # Install Python dependencies
74
75
  RUN pip install --no-cache-dir --upgrade pip
75
76
  RUN if [ -f requirements.txt ]; then \\
76
- pip install --no-cache-dir -r requirements.txt \\
77
- -i https://pypi.tuna.tsinghua.edu.cn/simple; fi
77
+ pip install --no-cache-dir -r requirements.txt{pypi_mirror_flag}; fi
78
78
 
79
79
  # Create non-root user for security
80
80
  RUN adduser --disabled-password --gecos '' {user} && \\
@@ -136,6 +136,11 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
136
136
  f'"--port", "{config.port}"]'
137
137
  )
138
138
 
139
+ # Prepare PyPI mirror flag
140
+ pypi_mirror_flag = ""
141
+ if config.pypi_mirror:
142
+ pypi_mirror_flag = f" -i {config.pypi_mirror}"
143
+
139
144
  # Format template with configuration values
140
145
  content = template.format(
141
146
  base_image=config.base_image,
@@ -147,6 +152,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
147
152
  env_vars_section=env_vars_section,
148
153
  startup_command_section=startup_command_section,
149
154
  platform=config.platform,
155
+ pypi_mirror_flag=pypi_mirror_flag,
150
156
  )
151
157
 
152
158
  return content