agentscope-runtime 1.0.0b2__py3-none-any.whl → 1.0.2__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 (71) hide show
  1. agentscope_runtime/adapters/agentscope/message.py +78 -10
  2. agentscope_runtime/adapters/agentscope/stream.py +155 -101
  3. agentscope_runtime/adapters/agentscope/tool/tool.py +1 -3
  4. agentscope_runtime/adapters/agno/__init__.py +0 -0
  5. agentscope_runtime/adapters/agno/message.py +30 -0
  6. agentscope_runtime/adapters/agno/stream.py +122 -0
  7. agentscope_runtime/adapters/langgraph/__init__.py +12 -0
  8. agentscope_runtime/adapters/langgraph/message.py +257 -0
  9. agentscope_runtime/adapters/langgraph/stream.py +205 -0
  10. agentscope_runtime/cli/__init__.py +7 -0
  11. agentscope_runtime/cli/cli.py +63 -0
  12. agentscope_runtime/cli/commands/__init__.py +2 -0
  13. agentscope_runtime/cli/commands/chat.py +815 -0
  14. agentscope_runtime/cli/commands/deploy.py +1062 -0
  15. agentscope_runtime/cli/commands/invoke.py +58 -0
  16. agentscope_runtime/cli/commands/list_cmd.py +103 -0
  17. agentscope_runtime/cli/commands/run.py +176 -0
  18. agentscope_runtime/cli/commands/sandbox.py +128 -0
  19. agentscope_runtime/cli/commands/status.py +60 -0
  20. agentscope_runtime/cli/commands/stop.py +185 -0
  21. agentscope_runtime/cli/commands/web.py +166 -0
  22. agentscope_runtime/cli/loaders/__init__.py +6 -0
  23. agentscope_runtime/cli/loaders/agent_loader.py +295 -0
  24. agentscope_runtime/cli/state/__init__.py +10 -0
  25. agentscope_runtime/cli/utils/__init__.py +18 -0
  26. agentscope_runtime/cli/utils/console.py +378 -0
  27. agentscope_runtime/cli/utils/validators.py +118 -0
  28. agentscope_runtime/engine/app/agent_app.py +15 -5
  29. agentscope_runtime/engine/deployers/__init__.py +1 -0
  30. agentscope_runtime/engine/deployers/agentrun_deployer.py +154 -24
  31. agentscope_runtime/engine/deployers/base.py +27 -2
  32. agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
  33. agentscope_runtime/engine/deployers/local_deployer.py +188 -25
  34. agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
  35. agentscope_runtime/engine/deployers/state/__init__.py +9 -0
  36. agentscope_runtime/engine/deployers/state/manager.py +388 -0
  37. agentscope_runtime/engine/deployers/state/schema.py +96 -0
  38. agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
  39. agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
  40. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
  41. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
  42. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
  43. agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
  44. agentscope_runtime/engine/deployers/utils/package.py +56 -6
  45. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +68 -9
  46. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
  47. agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
  48. agentscope_runtime/engine/runner.py +32 -12
  49. agentscope_runtime/engine/schemas/agent_schemas.py +21 -7
  50. agentscope_runtime/engine/schemas/exception.py +580 -0
  51. agentscope_runtime/engine/services/agent_state/__init__.py +2 -0
  52. agentscope_runtime/engine/services/agent_state/state_service_factory.py +55 -0
  53. agentscope_runtime/engine/services/memory/__init__.py +2 -0
  54. agentscope_runtime/engine/services/memory/memory_service_factory.py +126 -0
  55. agentscope_runtime/engine/services/sandbox/__init__.py +2 -0
  56. agentscope_runtime/engine/services/sandbox/sandbox_service_factory.py +49 -0
  57. agentscope_runtime/engine/services/service_factory.py +119 -0
  58. agentscope_runtime/engine/services/session_history/__init__.py +2 -0
  59. agentscope_runtime/engine/services/session_history/session_history_service_factory.py +73 -0
  60. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +35 -10
  61. agentscope_runtime/engine/tracing/wrapper.py +49 -31
  62. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
  64. agentscope_runtime/sandbox/utils.py +2 -0
  65. agentscope_runtime/version.py +1 -1
  66. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +82 -11
  67. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +71 -36
  68. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
  69. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
  70. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
  71. {agentscope_runtime-1.0.0b2.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,126 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from typing import Callable, Dict
4
+
5
+ from ..service_factory import ServiceFactory
6
+ from .memory_service import MemoryService, InMemoryMemoryService
7
+ from .redis_memory_service import RedisMemoryService
8
+ from .mem0_memory_service import Mem0MemoryService
9
+ from .reme_personal_memory_service import ReMePersonalMemoryService
10
+ from .reme_task_memory_service import ReMeTaskMemoryService
11
+
12
+ try:
13
+ from .tablestore_memory_service import (
14
+ TablestoreMemoryService,
15
+ SearchStrategy,
16
+ )
17
+
18
+ TABLESTORE_AVAILABLE = True
19
+ except ImportError:
20
+ TABLESTORE_AVAILABLE = False
21
+ SearchStrategy = None # type: ignore
22
+
23
+
24
+ class MemoryServiceFactory(ServiceFactory[MemoryService]):
25
+ """
26
+ Factory for MemoryService, supporting both environment variables and
27
+ kwargs.
28
+
29
+ Usage examples:
30
+ 1. Startup using only environment variables:
31
+ export MEMORY_BACKEND=redis
32
+ export MEMORY_REDIS_REDIS_URL="redis://localhost:6379/5"
33
+ service = await MemoryServiceFactory.create()
34
+
35
+ 2. Override environment variables using arguments:
36
+ export MEMORY_BACKEND=redis
37
+ export MEMORY_REDIS_REDIS_URL="redis://localhost:6379/5"
38
+ service = await MemoryServiceFactory.create(
39
+ redis_url="redis://otherhost:6379/1"
40
+ )
41
+
42
+ 3. Register a custom backend:
43
+ from my_backend import PostgresMemoryService
44
+ MemoryServiceFactory.register_backend(
45
+ "postgres",
46
+ PostgresMemoryService,
47
+ )
48
+ export MEMORY_BACKEND=postgres
49
+ export MEMORY_POSTGRES_DSN="postgresql://user:pass@localhost/db"
50
+ service = await MemoryServiceFactory.create()
51
+ """
52
+
53
+ _registry: Dict[str, Callable[..., MemoryService]] = {}
54
+ _env_prefix = "MEMORY_"
55
+ _default_backend = "in_memory"
56
+
57
+
58
+ # === Default built-in backend registration ===
59
+
60
+ MemoryServiceFactory.register_backend(
61
+ "in_memory",
62
+ lambda **kwargs: InMemoryMemoryService(),
63
+ )
64
+
65
+ MemoryServiceFactory.register_backend(
66
+ "redis",
67
+ RedisMemoryService,
68
+ )
69
+
70
+ MemoryServiceFactory.register_backend(
71
+ "mem0",
72
+ Mem0MemoryService,
73
+ )
74
+
75
+ MemoryServiceFactory.register_backend(
76
+ "reme_personal",
77
+ ReMePersonalMemoryService,
78
+ )
79
+
80
+ MemoryServiceFactory.register_backend(
81
+ "reme_task",
82
+ ReMeTaskMemoryService,
83
+ )
84
+
85
+ if TABLESTORE_AVAILABLE:
86
+
87
+ def _create_tablestore_memory_service(**kwargs):
88
+ """Helper function to create a TablestoreMemoryService instance."""
89
+ search_strategy = kwargs.get("search_strategy")
90
+ if isinstance(search_strategy, str):
91
+ search_strategy = SearchStrategy(search_strategy.lower())
92
+ elif search_strategy is None:
93
+ search_strategy = SearchStrategy.FULL_TEXT
94
+
95
+ return TablestoreMemoryService(
96
+ tablestore_client=kwargs["tablestore_client"],
97
+ search_strategy=search_strategy,
98
+ embedding_model=kwargs.get("embedding_model"),
99
+ vector_dimension=int(kwargs.get("vector_dimension", 1536)),
100
+ table_name=kwargs.get("table_name", "agentscope_runtime_memory"),
101
+ search_index_schema=kwargs.get("search_index_schema"),
102
+ text_field=kwargs.get("text_field", "text"),
103
+ embedding_field=kwargs.get("embedding_field", "embedding"),
104
+ vector_metric_type=kwargs.get("vector_metric_type"),
105
+ **{
106
+ k: v
107
+ for k, v in kwargs.items()
108
+ if k
109
+ not in [
110
+ "tablestore_client",
111
+ "search_strategy",
112
+ "embedding_model",
113
+ "vector_dimension",
114
+ "table_name",
115
+ "search_index_schema",
116
+ "text_field",
117
+ "embedding_field",
118
+ "vector_metric_type",
119
+ ]
120
+ },
121
+ )
122
+
123
+ MemoryServiceFactory.register_backend(
124
+ "tablestore",
125
+ _create_tablestore_memory_service,
126
+ )
@@ -4,10 +4,12 @@ from ....common.utils.lazy_loader import install_lazy_loader
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from .sandbox_service import SandboxService
7
+ from .sandbox_service_factory import SandboxServiceFactory
7
8
 
8
9
  install_lazy_loader(
9
10
  globals(),
10
11
  {
11
12
  "SandboxService": ".sandbox_service",
13
+ "SandboxServiceFactory": ".sandbox_service_factory",
12
14
  },
13
15
  )
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from typing import Callable, Dict
4
+
5
+ from ..service_factory import ServiceFactory
6
+ from .sandbox_service import SandboxService
7
+
8
+
9
+ class SandboxServiceFactory(ServiceFactory[SandboxService]):
10
+ """
11
+ Factory for SandboxService, supporting both environment variables and
12
+ kwargs.
13
+
14
+ Usage examples:
15
+ 1. Start with only environment variables:
16
+ export SANDBOX_BACKEND=default
17
+ export SANDBOX_DEFAULT_BASE_URL="http://localhost:8080"
18
+ export SANDBOX_DEFAULT_BEARER_TOKEN="token123"
19
+ service = await SandboxServiceFactory.create()
20
+
21
+ 2. Override environment variables with arguments:
22
+ export SANDBOX_DEFAULT_BASE_URL="http://localhost:8080"
23
+ service = await SandboxServiceFactory.create(
24
+ base_url="http://otherhost:8080",
25
+ bearer_token="custom_token"
26
+ )
27
+
28
+ 3. Register a custom backend:
29
+ from my_backend import CustomSandboxService
30
+ SandboxServiceFactory.register_backend(
31
+ "custom",
32
+ CustomSandboxService,
33
+ )
34
+ export SANDBOX_BACKEND=custom
35
+ export SANDBOX_CUSTOM_ENDPOINT="https://api.example.com"
36
+ export SANDBOX_CUSTOM_API_KEY="key123"
37
+ service = await SandboxServiceFactory.create()
38
+ """
39
+
40
+ _registry: Dict[str, Callable[..., SandboxService]] = {}
41
+ _env_prefix = "SANDBOX_"
42
+ _default_backend = "default"
43
+
44
+
45
+ # === Default backend registration ===
46
+ SandboxServiceFactory.register_backend(
47
+ "default",
48
+ SandboxService,
49
+ )
@@ -0,0 +1,119 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os
4
+ import inspect
5
+
6
+ from typing import Callable, Dict, Any, Optional, TypeVar, Generic
7
+
8
+ from .base import ServiceWithLifecycleManager
9
+
10
+ T = TypeVar("T", bound=ServiceWithLifecycleManager)
11
+
12
+
13
+ class ServiceFactory(Generic[T]):
14
+ """
15
+ Generic Service Factory base class that supports environment variables
16
+ and kwargs.
17
+
18
+ This base class provides a generic factory pattern that supports:
19
+ - Loading configuration from environment variables
20
+ - Overriding environment variables with kwargs
21
+ - Registering custom backends
22
+ """
23
+
24
+ _registry: Dict[str, Callable[..., T]] = {}
25
+ _env_prefix: str = ""
26
+ _default_backend: str = "in_memory"
27
+
28
+ @classmethod
29
+ def register_backend(
30
+ cls,
31
+ backend_type: str,
32
+ constructor: Callable[..., T],
33
+ ) -> None:
34
+ """Register a constructor function for a backend type.
35
+
36
+ Args:
37
+ backend_type: Backend type name (case-insensitive)
38
+ constructor: Constructor function used to create the service
39
+ instance
40
+ """
41
+ cls._registry[backend_type.lower()] = constructor
42
+
43
+ @classmethod
44
+ def _load_env_kwargs(cls, backend_type: str) -> Dict[str, Any]:
45
+ """Load backend-specific kwargs from environment variables.
46
+
47
+ Args:
48
+ backend_type: Backend type name
49
+
50
+ Returns:
51
+ A dictionary of parameters loaded from environment variables
52
+ """
53
+ result = {}
54
+ prefix = f"{cls._env_prefix}{backend_type.upper()}_"
55
+
56
+ for key, value in os.environ.items():
57
+ if key.startswith(prefix):
58
+ # env var: SERVICE_TYPE_BACKEND_PARAM -> param
59
+ param_name = key[len(prefix) :].lower()
60
+ result[param_name] = value
61
+
62
+ return result
63
+
64
+ @classmethod
65
+ async def create(
66
+ cls,
67
+ backend_type: Optional[str] = None,
68
+ **kwargs: Any,
69
+ ) -> T:
70
+ """
71
+ Create and start a service instance, supporting environment
72
+ variables and kwargs.
73
+
74
+ Args:
75
+ backend_type: Backend type, if None will read from environment
76
+ variables
77
+ **kwargs: Additional parameters, having higher priority than
78
+ environment variables
79
+
80
+ Returns:
81
+ A started service instance
82
+
83
+ Raises:
84
+ ValueError: If the backend_type is unsupported
85
+ """
86
+ if backend_type is None:
87
+ backend_type = os.getenv(
88
+ f"{cls._env_prefix}BACKEND",
89
+ cls._default_backend,
90
+ )
91
+ backend_type = backend_type.lower()
92
+
93
+ constructor = cls._registry.get(backend_type)
94
+ if constructor is None:
95
+ raise ValueError(f"Unsupported backend type: {backend_type}")
96
+
97
+ # 1. Load kwargs for this backend from environment variables
98
+ env_kwargs = cls._load_env_kwargs(backend_type)
99
+
100
+ # 2. Merge priority: kwargs > environment variables
101
+ final_kwargs = {**env_kwargs, **kwargs}
102
+
103
+ # 3. Filter out kwargs that are not accepted by the constructor
104
+ sig = inspect.signature(constructor)
105
+
106
+ has_var_kwargs = any(
107
+ p.kind == inspect.Parameter.VAR_KEYWORD
108
+ for p in sig.parameters.values()
109
+ )
110
+
111
+ if not has_var_kwargs:
112
+ accepted_keys = sig.parameters.keys()
113
+ final_kwargs = {
114
+ k: v for k, v in final_kwargs.items() if k in accepted_keys
115
+ }
116
+
117
+ # 4. Create instance
118
+ service = constructor(**final_kwargs)
119
+ return service
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
11
11
  from .tablestore_session_history_service import (
12
12
  TablestoreSessionHistoryService,
13
13
  )
14
+ from .session_history_service_factory import SessionHistoryServiceFactory
14
15
 
15
16
  install_lazy_loader(
16
17
  globals(),
@@ -19,5 +20,6 @@ install_lazy_loader(
19
20
  "InMemorySessionHistoryService": ".session_history_service",
20
21
  "RedisSessionHistoryService": ".redis_session_history_service",
21
22
  "TablestoreSessionHistoryService": ".tablestore_session_history_service", # noqa
23
+ "SessionHistoryServiceFactory": ".session_history_service_factory",
22
24
  },
23
25
  )
@@ -0,0 +1,73 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from typing import Callable, Dict
4
+
5
+ from ..service_factory import ServiceFactory
6
+ from .session_history_service import (
7
+ SessionHistoryService,
8
+ InMemorySessionHistoryService,
9
+ )
10
+ from .redis_session_history_service import RedisSessionHistoryService
11
+
12
+ try:
13
+ from .tablestore_session_history_service import (
14
+ TablestoreSessionHistoryService,
15
+ )
16
+
17
+ TABLESTORE_AVAILABLE = True
18
+ except ImportError:
19
+ TABLESTORE_AVAILABLE = False
20
+
21
+
22
+ class SessionHistoryServiceFactory(ServiceFactory[SessionHistoryService]):
23
+ """
24
+ Factory for SessionHistoryService, supporting both environment variables
25
+ and keyword arguments.
26
+
27
+ Usage examples:
28
+ 1. Start with only environment variables:
29
+ export SESSION_HISTORY_BACKEND=redis
30
+ export SESSION_HISTORY_REDIS_REDIS_URL="redis://localhost:6379/5"
31
+ service = await SessionHistoryServiceFactory.create()
32
+
33
+ 2. Override environment variables with arguments:
34
+ export SESSION_HISTORY_BACKEND=redis
35
+ export SESSION_HISTORY_REDIS_REDIS_URL="redis://localhost:6379/5"
36
+ service = await SessionHistoryServiceFactory.create(
37
+ redis_url="redis://otherhost:6379/1"
38
+ )
39
+
40
+ 3. Register a custom backend:
41
+ from my_backend import PostgresSessionHistoryService
42
+ SessionHistoryServiceFactory.register_backend(
43
+ "postgres",
44
+ PostgresSessionHistoryService,
45
+ )
46
+ export SESSION_HISTORY_BACKEND=postgres
47
+ export SESSION_HISTORY_POSTGRES_DSN="postgresql://user:pass@localhost/db" # noqa
48
+ export SESSION_HISTORY_POSTGRES_POOL_SIZE="20"
49
+ service = await SessionHistoryServiceFactory.create()
50
+ """
51
+
52
+ _registry: Dict[str, Callable[..., SessionHistoryService]] = {}
53
+ _env_prefix = "SESSION_HISTORY_"
54
+ _default_backend = "in_memory"
55
+
56
+
57
+ # === Default built-in backend registration ===
58
+
59
+ SessionHistoryServiceFactory.register_backend(
60
+ "in_memory",
61
+ InMemorySessionHistoryService,
62
+ )
63
+
64
+ SessionHistoryServiceFactory.register_backend(
65
+ "redis",
66
+ RedisSessionHistoryService,
67
+ )
68
+
69
+ if TABLESTORE_AVAILABLE:
70
+ SessionHistoryServiceFactory.register_backend(
71
+ "tablestore",
72
+ TablestoreSessionHistoryService,
73
+ )
@@ -56,6 +56,32 @@ def exclude_None_fields_in_place(obj: Dict):
56
56
  del obj[key]
57
57
 
58
58
 
59
+ def stringify_values(d: dict) -> dict:
60
+ for k, v in d.items():
61
+ if v is None:
62
+ continue
63
+ if isinstance(v, (dict, list)):
64
+ d[k] = json.dumps(v, ensure_ascii=False)
65
+ elif not isinstance(v, str):
66
+ d[k] = str(v)
67
+ return d
68
+
69
+
70
+ def _json_loads_if_str(v):
71
+ if isinstance(v, str):
72
+ try:
73
+ return json.loads(v)
74
+ except Exception:
75
+ return v
76
+ return v
77
+
78
+
79
+ def restore_json_strings(d: dict) -> dict:
80
+ for k, v in d.items():
81
+ d[k] = _json_loads_if_str(v)
82
+ return d
83
+
84
+
59
85
  def tablestore_log(msg: str):
60
86
  print(msg)
61
87
 
@@ -111,6 +137,7 @@ def convert_message_to_tablestore_message(
111
137
  """Convert Message to TablestoreMessage"""
112
138
  content, content_list = _generate_tablestore_content_from_message(message)
113
139
  tablestore_message_metadata = message.model_dump(exclude={"content", "id"})
140
+ tablestore_message_metadata = stringify_values(tablestore_message_metadata)
114
141
  tablestore_message_metadata[content_list_name] = json.dumps(
115
142
  content_list,
116
143
  ensure_ascii=False,
@@ -241,6 +268,9 @@ def _generate_init_json_from_tablestore_message(
241
268
  ) -> Dict[str, Any]:
242
269
  """Generate initialization JSON from TablestoreMessage"""
243
270
  tablestore_message = copy.deepcopy(tablestore_message)
271
+ tablestore_message.metadata = restore_json_strings(
272
+ tablestore_message.metadata,
273
+ )
244
274
  tablestore_message_content_list = tablestore_message.metadata.pop(
245
275
  content_list_name,
246
276
  None,
@@ -249,11 +279,7 @@ def _generate_init_json_from_tablestore_message(
249
279
  "id": tablestore_message.message_id,
250
280
  "content": _generate_content_from_tablestore_content(
251
281
  text=tablestore_message.content,
252
- content_list=(
253
- json.loads(tablestore_message_content_list)
254
- if tablestore_message_content_list
255
- else None
256
- ),
282
+ content_list=tablestore_message_content_list,
257
283
  ),
258
284
  }
259
285
  init_json.update(tablestore_message.metadata)
@@ -265,6 +291,9 @@ def _generate_init_json_from_tablestore_document(
265
291
  ) -> Dict[str, Any]:
266
292
  """Generate initialization JSON from TablestoreDocument"""
267
293
  tablestore_document = copy.deepcopy(tablestore_document)
294
+ tablestore_document.metadata = restore_json_strings(
295
+ tablestore_document.metadata,
296
+ )
268
297
  tablestore_document_content_list = tablestore_document.metadata.pop(
269
298
  content_list_name,
270
299
  None,
@@ -273,11 +302,7 @@ def _generate_init_json_from_tablestore_document(
273
302
  "id": tablestore_document.document_id,
274
303
  "content": _generate_content_from_tablestore_content(
275
304
  text=tablestore_document.text,
276
- content_list=(
277
- json.loads(tablestore_document_content_list)
278
- if tablestore_document_content_list
279
- else None
280
- ),
305
+ content_list=tablestore_document_content_list,
281
306
  ),
282
307
  }
283
308
  init_json.update(tablestore_document.metadata)
@@ -7,6 +7,7 @@ import json
7
7
  import os
8
8
  import re
9
9
  import time
10
+ import threading
10
11
  import uuid
11
12
  from collections.abc import Callable
12
13
  from copy import deepcopy
@@ -25,7 +26,7 @@ from typing import (
25
26
  from pydantic import BaseModel
26
27
  from opentelemetry.propagate import extract
27
28
  from opentelemetry.context import attach
28
- from opentelemetry.trace import StatusCode
29
+ from opentelemetry.trace import StatusCode, NoOpTracerProvider
29
30
  from opentelemetry import trace as ot_trace
30
31
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
31
32
  OTLPSpanExporter as OTLPSpanGrpcExporter,
@@ -177,7 +178,7 @@ def trace( # pylint: disable=too-many-statements
177
178
  **common_attrs,
178
179
  }
179
180
 
180
- with _ot_tracer.start_as_current_span(
181
+ with _get_ot_tracer().start_as_current_span(
181
182
  final_trace_name,
182
183
  context=parent_ctx,
183
184
  attributes=span_attributes,
@@ -280,7 +281,7 @@ def trace( # pylint: disable=too-many-statements
280
281
  **common_attrs,
281
282
  }
282
283
 
283
- with _ot_tracer.start_as_current_span(
284
+ with _get_ot_tracer().start_as_current_span(
284
285
  final_trace_name,
285
286
  context=parent_ctx,
286
287
  attributes=span_attributes,
@@ -385,7 +386,7 @@ def trace( # pylint: disable=too-many-statements
385
386
  **common_attrs,
386
387
  }
387
388
 
388
- with _ot_tracer.start_as_current_span(
389
+ with _get_ot_tracer().start_as_current_span(
389
390
  final_trace_name,
390
391
  context=parent_ctx,
391
392
  attributes=span_attributes,
@@ -518,7 +519,7 @@ def trace( # pylint: disable=too-many-statements
518
519
  **common_attrs,
519
520
  }
520
521
 
521
- with _ot_tracer.start_as_current_span(
522
+ with _get_ot_tracer().start_as_current_span(
522
523
  final_trace_name,
523
524
  context=parent_ctx,
524
525
  attributes=span_attributes,
@@ -897,13 +898,17 @@ def _get_service_name() -> str:
897
898
 
898
899
  def _get_tracer() -> Tracer:
899
900
  handlers: list[TracerHandler] = []
900
- if _str_to_bool(os.getenv("TRACE_ENABLE_LOG", "true")):
901
+ if _str_to_bool(os.getenv("TRACE_ENABLE_LOG", "false")):
901
902
  handlers.append(LocalLogHandler(enable_console=True))
902
903
 
903
904
  tracer = Tracer(handlers=handlers)
904
905
  return tracer
905
906
 
906
907
 
908
+ _otel_tracer_lock = threading.Lock()
909
+ _otel_tracer = None
910
+
911
+
907
912
  # TODO: support more tracing protocols and platforms
908
913
  def _get_ot_tracer() -> ot_trace.Tracer:
909
914
  """Get the OpenTelemetry tracer.
@@ -912,35 +917,48 @@ def _get_ot_tracer() -> ot_trace.Tracer:
912
917
  ot_trace.Tracer: The OpenTelemetry tracer instance.
913
918
  """
914
919
 
915
- resource = Resource(
916
- attributes={
917
- SERVICE_NAME: _get_service_name(),
918
- SERVICE_VERSION: os.getenv("SERVICE_VERSION", "1.0.0"),
919
- "source": "agentscope_runtime-source",
920
- },
921
- )
922
- provider = TracerProvider(resource=resource)
923
- if _str_to_bool(os.getenv("TRACE_ENABLE_REPORT", "false")):
924
- span_exporter = BatchSpanProcessor(
925
- OTLPSpanGrpcExporter(
926
- endpoint=os.getenv("TRACE_ENDPOINT", ""),
927
- headers=f"Authentication="
928
- f"{os.getenv('TRACE_AUTHENTICATION', '')}",
929
- ),
920
+ def _get_ot_tracer_inner() -> ot_trace.Tracer:
921
+ existing_provider = ot_trace.get_tracer_provider()
922
+
923
+ if not isinstance(existing_provider, NoOpTracerProvider):
924
+ return ot_trace.get_tracer("agentscope_runtime")
925
+
926
+ resource = Resource(
927
+ attributes={
928
+ SERVICE_NAME: _get_service_name(),
929
+ SERVICE_VERSION: os.getenv("SERVICE_VERSION", "1.0.0"),
930
+ "source": "agentscope_runtime-source",
931
+ },
930
932
  )
931
- provider.add_span_processor(span_exporter)
933
+ provider = TracerProvider(resource=resource)
934
+ if _str_to_bool(os.getenv("TRACE_ENABLE_REPORT", "false")):
935
+ span_exporter = BatchSpanProcessor(
936
+ OTLPSpanGrpcExporter(
937
+ endpoint=os.getenv("TRACE_ENDPOINT", ""),
938
+ headers=f"Authentication="
939
+ f"{os.getenv('TRACE_AUTHENTICATION', '')}",
940
+ ),
941
+ )
942
+ provider.add_span_processor(span_exporter)
932
943
 
933
- if _str_to_bool(os.getenv("TRACE_ENABLE_DEBUG", "false")):
934
- span_logger = BatchSpanProcessor(ConsoleSpanExporter())
935
- provider.add_span_processor(span_logger)
944
+ if _str_to_bool(os.getenv("TRACE_ENABLE_DEBUG", "false")):
945
+ span_logger = BatchSpanProcessor(ConsoleSpanExporter())
946
+ provider.add_span_processor(span_logger)
936
947
 
937
- tracer = ot_trace.get_tracer(
938
- "agentscope_runtime",
939
- tracer_provider=provider,
940
- )
941
- return tracer
948
+ tracer = ot_trace.get_tracer(
949
+ "agentscope_runtime",
950
+ tracer_provider=provider,
951
+ )
952
+ return tracer
953
+
954
+ global _otel_tracer
955
+
956
+ if _otel_tracer is None:
957
+ with _otel_tracer_lock:
958
+ if _otel_tracer is None:
959
+ _otel_tracer = _get_ot_tracer_inner()
942
960
 
961
+ return _otel_tracer
943
962
 
944
- _ot_tracer = _get_ot_tracer()
945
963
 
946
964
  _tracer = _get_tracer()