veadk-python 0.2.2__py3-none-any.whl → 0.2.5__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.

Potentially problematic release.


This version of veadk-python might be problematic. Click here for more details.

Files changed (149) hide show
  1. veadk/agent.py +31 -21
  2. veadk/agents/loop_agent.py +55 -0
  3. veadk/agents/parallel_agent.py +60 -0
  4. veadk/agents/sequential_agent.py +55 -0
  5. veadk/cli/cli_deploy.py +14 -1
  6. veadk/cli/cli_web.py +27 -0
  7. veadk/cloud/cloud_app.py +21 -6
  8. veadk/consts.py +14 -1
  9. veadk/database/viking/viking_database.py +3 -3
  10. veadk/evaluation/adk_evaluator/__init__.py +4 -0
  11. veadk/evaluation/adk_evaluator/adk_evaluator.py +170 -217
  12. veadk/evaluation/base_evaluator.py +26 -20
  13. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +8 -5
  14. veadk/{tracing/telemetry/metrics/__init__.py → integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/clean.py} +10 -0
  15. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +40 -7
  16. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +11 -5
  17. veadk/integrations/ve_faas/ve_faas.py +5 -1
  18. veadk/integrations/ve_tos/ve_tos.py +176 -0
  19. veadk/runner.py +162 -39
  20. veadk/tools/builtin_tools/image_edit.py +236 -0
  21. veadk/tools/builtin_tools/image_generate.py +236 -0
  22. veadk/tools/builtin_tools/video_generate.py +326 -0
  23. veadk/tools/sandbox/browser_sandbox.py +19 -9
  24. veadk/tools/sandbox/code_sandbox.py +21 -11
  25. veadk/tools/sandbox/computer_sandbox.py +16 -9
  26. veadk/tracing/base_tracer.py +6 -200
  27. veadk/tracing/telemetry/attributes/attributes.py +29 -0
  28. veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +71 -0
  29. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +451 -0
  30. veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +76 -0
  31. veadk/tracing/telemetry/attributes/extractors/types.py +75 -0
  32. veadk/tracing/telemetry/exporters/apmplus_exporter.py +97 -38
  33. veadk/tracing/telemetry/exporters/base_exporter.py +10 -10
  34. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +20 -13
  35. veadk/tracing/telemetry/exporters/inmemory_exporter.py +49 -32
  36. veadk/tracing/telemetry/exporters/tls_exporter.py +18 -12
  37. veadk/tracing/telemetry/opentelemetry_tracer.py +105 -102
  38. veadk/tracing/telemetry/telemetry.py +238 -0
  39. veadk/types.py +6 -1
  40. veadk/utils/misc.py +41 -1
  41. veadk/utils/patches.py +25 -0
  42. veadk/version.py +1 -1
  43. veadk_python-0.2.5.dist-info/METADATA +345 -0
  44. veadk_python-0.2.5.dist-info/RECORD +127 -0
  45. veadk/__pycache__/__init__.cpython-310.pyc +0 -0
  46. veadk/__pycache__/agent.cpython-310.pyc +0 -0
  47. veadk/__pycache__/config.cpython-310.pyc +0 -0
  48. veadk/__pycache__/consts.cpython-310.pyc +0 -0
  49. veadk/__pycache__/runner.cpython-310.pyc +0 -0
  50. veadk/__pycache__/types.cpython-310.pyc +0 -0
  51. veadk/__pycache__/version.cpython-310.pyc +0 -0
  52. veadk/a2a/__pycache__/__init__.cpython-310.pyc +0 -0
  53. veadk/a2a/__pycache__/agent_card.cpython-310.pyc +0 -0
  54. veadk/a2a/__pycache__/remote_ve_agent.cpython-310.pyc +0 -0
  55. veadk/a2a/__pycache__/ve_a2a_server.cpython-310.pyc +0 -0
  56. veadk/a2a/__pycache__/ve_agent_executor.cpython-310.pyc +0 -0
  57. veadk/cli/__pycache__/__init__.cpython-310.pyc +0 -0
  58. veadk/cli/__pycache__/cli.cpython-310.pyc +0 -0
  59. veadk/cli/__pycache__/cli_deploy.cpython-310.pyc +0 -0
  60. veadk/cli/__pycache__/cli_init.cpython-310.pyc +0 -0
  61. veadk/cli/__pycache__/cli_prompt.cpython-310.pyc +0 -0
  62. veadk/cli/__pycache__/cli_studio.cpython-310.pyc +0 -0
  63. veadk/cli/__pycache__/cli_web.cpython-310.pyc +0 -0
  64. veadk/cli/__pycache__/main.cpython-310.pyc +0 -0
  65. veadk/cloud/__pycache__/__init__.cpython-310.pyc +0 -0
  66. veadk/cloud/__pycache__/cloud_agent_engine.cpython-310.pyc +0 -0
  67. veadk/cloud/__pycache__/cloud_app.cpython-310.pyc +0 -0
  68. veadk/database/__pycache__/__init__.cpython-310.pyc +0 -0
  69. veadk/database/__pycache__/base_database.cpython-310.pyc +0 -0
  70. veadk/database/__pycache__/database_adapter.cpython-310.pyc +0 -0
  71. veadk/database/__pycache__/database_factory.cpython-310.pyc +0 -0
  72. veadk/database/__pycache__/local_database.cpython-310.pyc +0 -0
  73. veadk/database/kv/__pycache__/__init__.cpython-310.pyc +0 -0
  74. veadk/database/relational/__pycache__/__init__.cpython-310.pyc +0 -0
  75. veadk/database/vector/__pycache__/__init__.cpython-310.pyc +0 -0
  76. veadk/database/vector/__pycache__/opensearch_vector_database.cpython-310.pyc +0 -0
  77. veadk/database/vector/__pycache__/type.cpython-310.pyc +0 -0
  78. veadk/database/viking/__pycache__/__init__.cpython-310.pyc +0 -0
  79. veadk/evaluation/__pycache__/__init__.cpython-310.pyc +0 -0
  80. veadk/evaluation/__pycache__/base_evaluator.cpython-310.pyc +0 -0
  81. veadk/evaluation/__pycache__/eval_set_file_loader.cpython-310.pyc +0 -0
  82. veadk/evaluation/__pycache__/eval_set_recorder.cpython-310.pyc +0 -0
  83. veadk/evaluation/__pycache__/types.cpython-310.pyc +0 -0
  84. veadk/evaluation/adk_evaluator/__pycache__/__init__.cpython-310.pyc +0 -0
  85. veadk/evaluation/deepeval_evaluator/__pycache__/__init__.cpython-310.pyc +0 -0
  86. veadk/evaluation/deepeval_evaluator/__pycache__/deepeval_evaluator.cpython-310.pyc +0 -0
  87. veadk/evaluation/utils/__pycache__/prometheus.cpython-310.pyc +0 -0
  88. veadk/integrations/ve_apig/__pycache__/__init__.cpython-310.pyc +0 -0
  89. veadk/integrations/ve_apig/__pycache__/apig.cpython-310.pyc +0 -0
  90. veadk/integrations/ve_apig/__pycache__/ve_apig.cpython-310.pyc +0 -0
  91. veadk/integrations/ve_faas/__pycache__/__init__.cpython-310.pyc +0 -0
  92. veadk/integrations/ve_faas/__pycache__/types.cpython-310.pyc +0 -0
  93. veadk/integrations/ve_faas/__pycache__/ve_faas.cpython-310.pyc +0 -0
  94. veadk/integrations/ve_faas/__pycache__/ve_faas_utils.cpython-310.pyc +0 -0
  95. veadk/integrations/ve_faas/__pycache__/vefaas.cpython-310.pyc +0 -0
  96. veadk/integrations/ve_faas/__pycache__/vefaas_utils.cpython-310.pyc +0 -0
  97. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__pycache__/agent.cpython-310.pyc +0 -0
  98. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__pycache__/app.cpython-310.pyc +0 -0
  99. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__pycache__/studio_app.cpython-310.pyc +0 -0
  100. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name|replace('-', '_') }}/__pycache__/__init__.cpython-310.pyc +0 -0
  101. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name|replace('-', '_') }}/__pycache__/agent.cpython-310.pyc +0 -0
  102. veadk/integrations/ve_prompt_pilot/__pycache__/__init__.cpython-310.pyc +0 -0
  103. veadk/integrations/ve_prompt_pilot/__pycache__/agentpilot.cpython-310.pyc +0 -0
  104. veadk/knowledgebase/__pycache__/__init__.cpython-310.pyc +0 -0
  105. veadk/knowledgebase/__pycache__/knowledgebase.cpython-310.pyc +0 -0
  106. veadk/knowledgebase/__pycache__/knowledgebase_database_adapter.cpython-310.pyc +0 -0
  107. veadk/memory/__pycache__/__init__.cpython-310.pyc +0 -0
  108. veadk/memory/__pycache__/long_term_memory.cpython-310.pyc +0 -0
  109. veadk/memory/__pycache__/memory_database_adapter.cpython-310.pyc +0 -0
  110. veadk/memory/__pycache__/short_term_memory.cpython-310.pyc +0 -0
  111. veadk/memory/__pycache__/short_term_memory_processor.cpython-310.pyc +0 -0
  112. veadk/prompts/__pycache__/__init__.cpython-310.pyc +0 -0
  113. veadk/prompts/__pycache__/agent_default_prompt.cpython-310.pyc +0 -0
  114. veadk/prompts/__pycache__/prompt_memory_processor.cpython-310.pyc +0 -0
  115. veadk/prompts/__pycache__/prompt_optimization.cpython-310.pyc +0 -0
  116. veadk/tools/__pycache__/__init__.cpython-310.pyc +0 -0
  117. veadk/tools/__pycache__/demo_tools.cpython-310.pyc +0 -0
  118. veadk/tools/__pycache__/load_knowledgebase_tool.cpython-310.pyc +0 -0
  119. veadk/tools/builtin_tools/__pycache__/__init__.cpython-310.pyc +0 -0
  120. veadk/tools/builtin_tools/__pycache__/lark.cpython-310.pyc +0 -0
  121. veadk/tools/builtin_tools/__pycache__/vesearch.cpython-310.pyc +0 -0
  122. veadk/tools/builtin_tools/__pycache__/web_search.cpython-310.pyc +0 -0
  123. veadk/tools/sandbox/__pycache__/__init__.cpython-310.pyc +0 -0
  124. veadk/tracing/__pycache__/__init__.cpython-310.pyc +0 -0
  125. veadk/tracing/__pycache__/base_tracer.cpython-310.pyc +0 -0
  126. veadk/tracing/telemetry/__pycache__/__init__.cpython-310.pyc +0 -0
  127. veadk/tracing/telemetry/__pycache__/opentelemetry_tracer.cpython-310.pyc +0 -0
  128. veadk/tracing/telemetry/exporters/__pycache__/__init__.cpython-310.pyc +0 -0
  129. veadk/tracing/telemetry/exporters/__pycache__/apiserver_exporter.cpython-310.pyc +0 -0
  130. veadk/tracing/telemetry/exporters/__pycache__/apmplus_exporter.cpython-310.pyc +0 -0
  131. veadk/tracing/telemetry/exporters/__pycache__/base_exporter.cpython-310.pyc +0 -0
  132. veadk/tracing/telemetry/exporters/__pycache__/cozeloop_exporter.cpython-310.pyc +0 -0
  133. veadk/tracing/telemetry/exporters/__pycache__/inmemory_exporter.cpython-310.pyc +0 -0
  134. veadk/tracing/telemetry/exporters/__pycache__/tls_exporter.cpython-310.pyc +0 -0
  135. veadk/tracing/telemetry/metrics/__pycache__/__init__.cpython-310.pyc +0 -0
  136. veadk/tracing/telemetry/metrics/__pycache__/opentelemetry_metrics.cpython-310.pyc +0 -0
  137. veadk/tracing/telemetry/metrics/opentelemetry_metrics.py +0 -73
  138. veadk/utils/__pycache__/__init__.cpython-310.pyc +0 -0
  139. veadk/utils/__pycache__/logger.cpython-310.pyc +0 -0
  140. veadk/utils/__pycache__/mcp_utils.cpython-310.pyc +0 -0
  141. veadk/utils/__pycache__/misc.cpython-310.pyc +0 -0
  142. veadk/utils/__pycache__/patches.cpython-310.pyc +0 -0
  143. veadk/utils/__pycache__/volcengine_sign.cpython-310.pyc +0 -0
  144. veadk_python-0.2.2.dist-info/METADATA +0 -144
  145. veadk_python-0.2.2.dist-info/RECORD +0 -213
  146. {veadk_python-0.2.2.dist-info → veadk_python-0.2.5.dist-info}/WHEEL +0 -0
  147. {veadk_python-0.2.2.dist-info → veadk_python-0.2.5.dist-info}/entry_points.txt +0 -0
  148. {veadk_python-0.2.2.dist-info → veadk_python-0.2.5.dist-info}/licenses/LICENSE +0 -0
  149. {veadk_python-0.2.2.dist-info → veadk_python-0.2.5.dist-info}/top_level.txt +0 -0
@@ -18,96 +18,74 @@ import json
18
18
  import time
19
19
  from typing import Any
20
20
 
21
- from openinference.instrumentation.google_adk import GoogleADKInstrumentor
22
21
  from opentelemetry import trace as trace_api
23
22
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
24
23
  from opentelemetry.sdk import trace as trace_sdk
25
24
  from opentelemetry.sdk.resources import Resource
26
25
  from opentelemetry.sdk.trace import TracerProvider
27
26
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
28
- from pydantic import BaseModel, ConfigDict, Field
27
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
29
28
  from typing_extensions import override
30
29
 
31
30
  from veadk.tracing.base_tracer import BaseTracer
32
31
  from veadk.tracing.telemetry.exporters.apmplus_exporter import APMPlusExporter
33
32
  from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
34
33
  from veadk.tracing.telemetry.exporters.inmemory_exporter import InMemoryExporter
35
- from veadk.tracing.telemetry.metrics.opentelemetry_metrics import MeterUploader
34
+ from veadk.tracing.telemetry.exporters.inmemory_exporter import (
35
+ _INMEMORY_EXPORTER_INSTANCE,
36
+ )
36
37
  from veadk.utils.logger import get_logger
38
+ from veadk.utils.patches import patch_google_adk_telemetry
37
39
 
38
40
  logger = get_logger(__name__)
39
41
 
40
- DEFAULT_VEADK_TRACER_NAME = "veadk_global_tracer"
41
42
 
42
-
43
- def update_resource_attributions(provider: TracerProvider, resource_attributes: dict):
43
+ def _update_resource_attributions(
44
+ provider: TracerProvider, resource_attributes: dict
45
+ ) -> None:
44
46
  provider._resource = provider._resource.merge(Resource.create(resource_attributes))
45
47
 
46
48
 
47
49
  class OpentelemetryTracer(BaseModel, BaseTracer):
48
50
  model_config = ConfigDict(arbitrary_types_allowed=True)
49
- exporters: list[BaseExporter] = Field(
50
- default=[],
51
- description="The exporters to export spans.",
52
- )
51
+
53
52
  name: str = Field(
54
- DEFAULT_VEADK_TRACER_NAME, description="The identifier of tracer."
53
+ default="veadk_opentelemetry_tracer", description="The identifier of tracer."
55
54
  )
56
55
 
57
- app_name: str = Field(
58
- "veadk_app",
59
- description="The identifier of app.",
56
+ exporters: list[BaseExporter] = Field(
57
+ default_factory=list,
58
+ description="The exporters to export spans.",
60
59
  )
61
60
 
62
- def model_post_init(self, context: Any, /) -> None:
63
- self._processors = []
64
- self._inmemory_exporter: InMemoryExporter | None = None
61
+ @field_validator("exporters")
62
+ @classmethod
63
+ def forbid_inmemory_exporter(cls, v: list[BaseExporter]) -> list[BaseExporter]:
64
+ for e in v:
65
+ if isinstance(e, InMemoryExporter):
66
+ raise ValueError("InMemoryExporter is not allowed in exporters list")
67
+ return v
65
68
 
66
- # InMemoryExporter is a default exporter for exporting local tracing file
67
- for exporter in self.exporters:
68
- if isinstance(exporter, InMemoryExporter):
69
- self._inmemory_exporter = exporter
69
+ def model_post_init(self, context: Any) -> None:
70
+ patch_google_adk_telemetry()
71
+ self._init_global_tracer_provider()
70
72
 
71
- if self._inmemory_exporter is None:
72
- self._inmemory_exporter = InMemoryExporter()
73
- self.exporters.append(self._inmemory_exporter)
74
- # ========================================================================
75
-
76
- # Process meter-related attributes
77
- self._meter_contexts = []
78
- self._meter_uploaders = []
79
- for exporter in self.exporters:
80
- meter_context = exporter.get_meter_context()
81
- if meter_context is not None:
82
- self._meter_contexts.append(meter_context)
73
+ # GoogleADKInstrumentor().instrument()
83
74
 
84
- for meter_context in self._meter_contexts:
85
- meter_uploader = MeterUploader(meter_context)
86
- self._meter_uploaders.append(meter_uploader)
87
- # ================================
88
-
89
- # init tracer provider
90
- # VeADK operates on global OpenTelemetry provider, hence return nothing
91
- self._init_tracer_provider()
92
-
93
- # just for debug
94
- self._trace_file_path = ""
95
-
96
- GoogleADKInstrumentor().instrument()
75
+ def _init_global_tracer_provider(self) -> None:
76
+ self._processors = []
97
77
 
98
- def _init_tracer_provider(self) -> None:
99
- # set provider anyway
100
- # finally, get global provider
101
- tracer_provider = trace_sdk.TracerProvider()
102
- trace_api.set_tracer_provider(tracer_provider)
78
+ # set provider anyway, then get global provider
79
+ trace_api.set_tracer_provider(trace_sdk.TracerProvider())
103
80
  global_tracer_provider: TracerProvider = trace_api.get_tracer_provider() # type: ignore
104
81
 
105
- have_apmplus_exporter = False
106
- for processor in global_tracer_provider._active_span_processor._span_processors:
107
- if isinstance(processor, (BatchSpanProcessor, SimpleSpanProcessor)):
108
- if isinstance(processor.span_exporter, OTLPSpanExporter):
109
- if "apmplus" in processor.span_exporter._endpoint:
110
- have_apmplus_exporter = True
82
+ span_processors = global_tracer_provider._active_span_processor._span_processors
83
+ have_apmplus_exporter = any(
84
+ isinstance(p, (BatchSpanProcessor, SimpleSpanProcessor))
85
+ and isinstance(p.span_exporter, OTLPSpanExporter)
86
+ and "apmplus" in p.span_exporter._endpoint
87
+ for p in span_processors
88
+ )
111
89
 
112
90
  if have_apmplus_exporter:
113
91
  self.exporters = [
@@ -115,54 +93,83 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
115
93
  ]
116
94
 
117
95
  for exporter in self.exporters:
118
- processor, resource_attributes = exporter.get_processor()
119
- if resource_attributes is not None:
120
- update_resource_attributions(
96
+ processor = exporter.processor
97
+ resource_attributes = exporter.resource_attributes
98
+
99
+ if resource_attributes:
100
+ _update_resource_attributions(
121
101
  global_tracer_provider, resource_attributes
122
102
  )
123
- global_tracer_provider.add_span_processor(processor)
124
- logger.debug(
125
- f"Add exporter `{exporter.__class__.__name__}` to OpentelemetryTracer."
103
+
104
+ if processor:
105
+ global_tracer_provider.add_span_processor(processor)
106
+ self._processors.append(processor)
107
+
108
+ logger.debug(
109
+ f"Add span processor for exporter `{exporter.__class__.__name__}` to OpentelemetryTracer."
110
+ )
111
+ else:
112
+ logger.error(
113
+ f"Add span processor for exporter `{exporter.__class__.__name__}` to OpentelemetryTracer failed."
114
+ )
115
+
116
+ self._inmemory_exporter = _INMEMORY_EXPORTER_INSTANCE
117
+ if self._inmemory_exporter.processor:
118
+ # make sure the in memory exporter processor is added at index 0
119
+ # because we use this to record all spans
120
+ global_tracer_provider._active_span_processor._span_processors = (
121
+ self._inmemory_exporter.processor,
122
+ ) + global_tracer_provider._active_span_processor._span_processors
123
+
124
+ self._processors.append(self._inmemory_exporter.processor)
125
+ else:
126
+ logger.warning(
127
+ "InMemoryExporter processor is not initialized, cannot add to OpentelemetryTracer."
126
128
  )
127
- self._processors.append(processor)
128
- logger.debug(f"Init OpentelemetryTracer with {len(self.exporters)} exporters.")
129
+
130
+ logger.info(f"Init OpentelemetryTracer with {len(self._processors)} exporters.")
131
+
132
+ @property
133
+ def trace_file_path(self) -> str:
134
+ return self._trace_file_path
135
+
136
+ @property
137
+ def trace_id(self) -> str:
138
+ try:
139
+ trace_id = hex(int(self._inmemory_exporter._exporter.trace_id))[2:] # type: ignore
140
+ return trace_id
141
+ except Exception as e:
142
+ logger.error(f"Failed to get trace_id from InMemoryExporter: {e}")
143
+ return self._trace_id
144
+
145
+ def force_export(self) -> None:
146
+ """Force to export spans in all processors."""
147
+ for processor in self._processors:
148
+ time.sleep(0.05)
149
+ processor.force_flush()
129
150
 
130
151
  @override
131
152
  def dump(
132
153
  self,
133
- user_id: str,
134
- session_id: str,
154
+ user_id: str = "unknown_user_id",
155
+ session_id: str = "unknown_session_id",
135
156
  path: str = "/tmp",
136
157
  ) -> str:
158
+ def _build_trace_file_path(path: str, user_id: str, session_id: str) -> str:
159
+ return f"{path}/{self.name}_{user_id}_{session_id}_{self.trace_id}.json"
160
+
137
161
  if not self._inmemory_exporter:
138
162
  logger.warning(
139
163
  "InMemoryExporter is not initialized. Please check your tracer exporters."
140
164
  )
141
165
  return ""
166
+ self.force_export()
142
167
 
143
- prompt_tokens = self._inmemory_exporter._real_exporter.prompt_tokens
144
- completion_tokens = self._inmemory_exporter._real_exporter.completion_tokens
145
-
146
- # upload
147
- for meter_uploader in self._meter_uploaders:
148
- meter_uploader.record(
149
- prompt_tokens=prompt_tokens, completion_tokens=completion_tokens
150
- )
151
- # clear tokens after dump
152
- self._inmemory_exporter._real_exporter.completion_tokens = []
153
- self._inmemory_exporter._real_exporter.prompt_tokens = []
154
-
155
- for processor in self._processors:
156
- time.sleep(0.05) # give some time for the exporter to upload spans
157
- processor.force_flush()
158
-
159
- spans = self._inmemory_exporter._real_exporter.get_finished_spans(
168
+ spans = self._inmemory_exporter._exporter.get_finished_spans( # type: ignore
160
169
  session_id=session_id
161
170
  )
162
- if not spans:
163
- data = []
164
- else:
165
- data = [
171
+ data = (
172
+ [
166
173
  {
167
174
  "name": s.name,
168
175
  "span_id": s.context.span_id,
@@ -174,22 +181,18 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
174
181
  }
175
182
  for s in spans
176
183
  ]
184
+ if spans
185
+ else []
186
+ )
177
187
 
178
- trace_id = hex(int(self._inmemory_exporter._real_exporter.trace_id))[2:]
179
- self._trace_id = trace_id
180
- file_path = f"{path}/{self.name}_{user_id}_{session_id}_{trace_id}.json"
181
- with open(file_path, "w") as f:
182
- json.dump(data, f, indent=4)
183
-
184
- self._trace_file_path = file_path
188
+ self._trace_file_path = _build_trace_file_path(path, user_id, session_id)
189
+ with open(self._trace_file_path, "w") as f:
190
+ json.dump(
191
+ data, f, indent=4, ensure_ascii=False
192
+ ) # ensure_ascii=False to support Chinese characters
185
193
 
186
- for exporter in self.exporters:
187
- if not isinstance(exporter, InMemoryExporter):
188
- exporter.export()
189
194
  logger.info(
190
- f"OpenTelemetryTracer tracing done, trace id: {self._trace_id} (hex)"
195
+ f"OpenTelemetryTracer dumps {len(spans)} spans to {self._trace_file_path}. Trace id: {self.trace_id} (hex)"
191
196
  )
192
197
 
193
- self._spans = spans
194
- logger.info(f"OpenTelemetryTracer dumps {len(spans)} spans to {file_path}")
195
- return file_path
198
+ return self._trace_file_path
@@ -0,0 +1,238 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Any
16
+
17
+ from google.adk.agents.invocation_context import InvocationContext
18
+ from google.adk.events import Event
19
+ from google.adk.models.llm_request import LlmRequest
20
+ from google.adk.models.llm_response import LlmResponse
21
+ from google.adk.tools import BaseTool
22
+ from opentelemetry import trace
23
+ from opentelemetry.sdk.trace import _Span
24
+
25
+ from veadk.tracing.telemetry.attributes.attributes import ATTRIBUTES
26
+ from veadk.tracing.telemetry.attributes.extractors.types import (
27
+ ExtractorResponse,
28
+ LLMAttributesParams,
29
+ ToolAttributesParams,
30
+ )
31
+ from veadk.tracing.telemetry.exporters.inmemory_exporter import (
32
+ _INMEMORY_EXPORTER_INSTANCE,
33
+ )
34
+ from veadk.utils.logger import get_logger
35
+
36
+ logger = get_logger(__name__)
37
+
38
+
39
+ def upload_metrics(
40
+ invocation_context: InvocationContext,
41
+ llm_request: LlmRequest,
42
+ llm_response: LlmResponse,
43
+ ) -> None:
44
+ from veadk.agent import Agent
45
+
46
+ if isinstance(invocation_context.agent, Agent):
47
+ tracers = invocation_context.agent.tracers
48
+ for tracer in tracers:
49
+ for exporter in getattr(tracer, "exporters", []):
50
+ if getattr(exporter, "meter_uploader", None):
51
+ exporter.meter_uploader.record(llm_request, llm_response)
52
+
53
+
54
+ def _set_agent_input_attribute(
55
+ span: _Span, invocation_context: InvocationContext
56
+ ) -> None:
57
+ # We only save the original user input as the agent input
58
+ # hence once the `agent.input` has been set, we don't overwrite it
59
+ event_names = [event.name for event in span.events]
60
+ if "gen_ai.user.message" in event_names:
61
+ return
62
+
63
+ # input = {
64
+ # "agent_name": invocation_context.agent.name,
65
+ # "app_name": invocation_context.session.app_name,
66
+ # "user_id": invocation_context.user_id,
67
+ # "session_id": invocation_context.session.id,
68
+ # "input": invocation_context.user_content.model_dump(exclude_none=True)
69
+ # if invocation_context.user_content
70
+ # else None,
71
+ # }
72
+
73
+ user_content = invocation_context.user_content
74
+ if user_content and user_content.parts:
75
+ span.add_event(
76
+ "gen_ai.user.message",
77
+ {
78
+ "agent_name": invocation_context.agent.name,
79
+ "app_name": invocation_context.session.app_name,
80
+ "user_id": invocation_context.user_id,
81
+ "session_id": invocation_context.session.id,
82
+ },
83
+ )
84
+ for idx, part in enumerate(user_content.parts):
85
+ if part.text:
86
+ span.add_event(
87
+ "gen_ai.user.message",
88
+ {f"parts.{idx}.type": "text", f"parts.{idx}.content": part.text},
89
+ )
90
+ if part.inline_data:
91
+ span.add_event(
92
+ "gen_ai.user.message",
93
+ {
94
+ f"parts.{idx}.type": "image_url",
95
+ f"parts.{idx}.image_url.name": part.inline_data.display_name.split(
96
+ "/"
97
+ )[-1],
98
+ f"parts.{idx}.image_url.url": part.inline_data.display_name,
99
+ },
100
+ )
101
+
102
+
103
+ def _set_agent_output_attribute(span: _Span, llm_response: LlmResponse) -> None:
104
+ content = llm_response.content
105
+ if content and content.parts:
106
+ for idx, part in enumerate(content.parts):
107
+ if part.text:
108
+ span.add_event(
109
+ "gen_ai.choice",
110
+ {
111
+ f"message.parts.{idx}.type": "text",
112
+ f"message.parts.{idx}.text": part.text,
113
+ },
114
+ )
115
+
116
+
117
+ def set_common_attributes_on_model_span(
118
+ invocation_context: InvocationContext,
119
+ llm_response: LlmResponse,
120
+ current_span: _Span,
121
+ **kwargs,
122
+ ) -> None:
123
+ if current_span.context:
124
+ current_span_id = current_span.context.trace_id
125
+ else:
126
+ logger.warning(
127
+ "Current span context is missing, failed to get `trace_id` to set common attributes."
128
+ )
129
+ return
130
+
131
+ try:
132
+ spans = _INMEMORY_EXPORTER_INSTANCE.processor.spans # type: ignore
133
+
134
+ spans_in_current_trace = [
135
+ span
136
+ for span in spans
137
+ if span.context and span.context.trace_id == current_span_id
138
+ ]
139
+
140
+ common_attributes = ATTRIBUTES.get("common", {})
141
+ for span in spans_in_current_trace:
142
+ if span.is_recording():
143
+ if span.name.startswith("invocation"):
144
+ span.set_attribute("gen_ai.operation.name", "chain")
145
+ _set_agent_input_attribute(span, invocation_context)
146
+ _set_agent_output_attribute(span, llm_response)
147
+ elif span.name.startswith("agent_run"):
148
+ span.set_attribute("gen_ai.operation.name", "agent")
149
+ _set_agent_input_attribute(span, invocation_context)
150
+ _set_agent_output_attribute(span, llm_response)
151
+ for attr_name, attr_extractor in common_attributes.items():
152
+ value = attr_extractor(**kwargs)
153
+ span.set_attribute(attr_name, value)
154
+ except Exception as e:
155
+ logger.error(f"Failed to set common attributes for spans: {e}")
156
+
157
+
158
+ def set_common_attributes_on_tool_span(current_span: _Span) -> None:
159
+ # find parent span (generally a llm span)
160
+ if not current_span.context:
161
+ logger.warning(
162
+ f"Get tool span's context failed. Skip setting common attributes for span {current_span.name}"
163
+ )
164
+ return
165
+
166
+ if not current_span.parent:
167
+ logger.warning(
168
+ f"Get tool span's parent failed. Skip setting common attributes for span {current_span.name}"
169
+ )
170
+ return
171
+
172
+ parent_span_id = current_span.parent.span_id
173
+ for span in _INMEMORY_EXPORTER_INSTANCE.processor.spans: # type: ignore
174
+ if span.context.span_id == parent_span_id:
175
+ common_attributes = ATTRIBUTES.get("common", {})
176
+ for attr_name in common_attributes.keys():
177
+ current_span.set_attribute(attr_name, span.attributes[attr_name])
178
+
179
+
180
+ def trace_send_data(): ...
181
+
182
+
183
+ def trace_tool_call(
184
+ tool: BaseTool,
185
+ args: dict[str, Any],
186
+ function_response_event: Event,
187
+ ) -> None:
188
+ span = trace.get_current_span()
189
+
190
+ set_common_attributes_on_tool_span(current_span=span) # type: ignore
191
+
192
+ tool_attributes_mapping = ATTRIBUTES.get("tool", {})
193
+ params = ToolAttributesParams(tool, args, function_response_event)
194
+
195
+ for attr_name, attr_extractor in tool_attributes_mapping.items():
196
+ response: ExtractorResponse = attr_extractor(params)
197
+ ExtractorResponse.update_span(span, attr_name, response)
198
+
199
+
200
+ def trace_call_llm(
201
+ invocation_context: InvocationContext,
202
+ event_id: str,
203
+ llm_request: LlmRequest,
204
+ llm_response: LlmResponse,
205
+ ) -> None:
206
+ span = trace.get_current_span()
207
+
208
+ from veadk.agent import Agent
209
+
210
+ set_common_attributes_on_model_span(
211
+ invocation_context=invocation_context,
212
+ llm_response=llm_response,
213
+ current_span=span, # type: ignore
214
+ agent_name=invocation_context.agent.name,
215
+ user_id=invocation_context.user_id,
216
+ app_name=invocation_context.app_name,
217
+ session_id=invocation_context.session.id,
218
+ model_provider=invocation_context.agent.model_provider
219
+ if isinstance(invocation_context.agent, Agent)
220
+ else "",
221
+ model_name=invocation_context.agent.model_name
222
+ if isinstance(invocation_context.agent, Agent)
223
+ else "",
224
+ )
225
+
226
+ llm_attributes_mapping = ATTRIBUTES.get("llm", {})
227
+ params = LLMAttributesParams(
228
+ invocation_context=invocation_context,
229
+ event_id=event_id,
230
+ llm_request=llm_request,
231
+ llm_response=llm_response,
232
+ )
233
+
234
+ for attr_name, attr_extractor in llm_attributes_mapping.items():
235
+ response: ExtractorResponse = attr_extractor(params)
236
+ ExtractorResponse.update_span(span, attr_name, response)
237
+
238
+ upload_metrics(invocation_context, llm_request, llm_response)
veadk/types.py CHANGED
@@ -15,6 +15,9 @@
15
15
  from pydantic import BaseModel, Field
16
16
 
17
17
  from veadk.agent import Agent
18
+ from veadk.agents.loop_agent import LoopAgent
19
+ from veadk.agents.parallel_agent import ParallelAgent
20
+ from veadk.agents.sequential_agent import SequentialAgent
18
21
  from veadk.memory.short_term_memory import ShortTermMemory
19
22
 
20
23
 
@@ -35,7 +38,9 @@ class AgentRunConfig(BaseModel):
35
38
  default="veadk_vefaas_app", description="The name of the application"
36
39
  )
37
40
 
38
- agent: Agent = Field(..., description="The root agent instance")
41
+ agent: Agent | SequentialAgent | ParallelAgent | LoopAgent = Field(
42
+ ..., description="The root agent instance"
43
+ )
39
44
 
40
45
  short_term_memory: ShortTermMemory = Field(
41
46
  default_factory=ShortTermMemory, description="The short-term memory instance"
veadk/utils/misc.py CHANGED
@@ -13,9 +13,11 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import importlib.util
16
+ import json
16
17
  import sys
17
18
  import time
18
19
  import types
20
+ from typing import Any, Dict, List, MutableMapping, Tuple
19
21
 
20
22
  import requests
21
23
 
@@ -27,7 +29,7 @@ def read_file(file_path):
27
29
  return data
28
30
 
29
31
 
30
- def formatted_timestamp():
32
+ def formatted_timestamp() -> str:
31
33
  # YYYYMMDDHHMMSS
32
34
  return time.strftime("%Y%m%d%H%M%S", time.localtime())
33
35
 
@@ -60,3 +62,41 @@ def load_module_from_file(module_name: str, file_path: str) -> types.ModuleType:
60
62
  )
61
63
  else:
62
64
  raise ImportError(f"Could not load module {module_name} from {file_path}")
65
+
66
+
67
+ def flatten_dict(
68
+ d: MutableMapping[str, Any], parent_key: str = "", sep: str = "_"
69
+ ) -> Dict[str, Any]:
70
+ """Flatten a nested dictionary.
71
+
72
+ Input:
73
+ {"a": {"b": 1}}
74
+ Output:
75
+ {"a_b": 1}
76
+ """
77
+ items: List[Tuple[str, Any]] = []
78
+ for k, v in d.items():
79
+ new_key = f"{parent_key}{sep}{k}" if parent_key else k
80
+ if isinstance(v, MutableMapping):
81
+ items.extend(flatten_dict(v, new_key, sep=sep).items())
82
+ else:
83
+ items.append((new_key, v))
84
+ return dict(items)
85
+
86
+
87
+ def safe_json_serialize(obj) -> str:
88
+ """Convert any Python object to a JSON-serializable type or string.
89
+
90
+ Args:
91
+ obj: The object to serialize.
92
+
93
+ Returns:
94
+ The JSON-serialized object string or <non-serializable> if the object cannot be serialized.
95
+ """
96
+
97
+ try:
98
+ return json.dumps(
99
+ obj, ensure_ascii=False, default=lambda o: "<not serializable>"
100
+ )
101
+ except (TypeError, OverflowError):
102
+ return "<not serializable>"
veadk/utils/patches.py CHANGED
@@ -13,7 +13,14 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import asyncio
16
+ import sys
17
+ from typing import Callable
16
18
 
19
+ from veadk.tracing.telemetry.telemetry import (
20
+ trace_call_llm,
21
+ trace_send_data,
22
+ trace_tool_call,
23
+ )
17
24
  from veadk.utils.logger import get_logger
18
25
 
19
26
  logger = get_logger(__name__)
@@ -53,3 +60,21 @@ def patch_asyncio():
53
60
  raise
54
61
 
55
62
  CancelScope.__exit__ = patched_cancel_scope_exit
63
+
64
+
65
+ def patch_google_adk_telemetry() -> None:
66
+ trace_functions = {
67
+ "trace_tool_call": trace_tool_call,
68
+ "trace_call_llm": trace_call_llm,
69
+ "trace_send_data": trace_send_data,
70
+ }
71
+
72
+ for mod_name, mod in sys.modules.items():
73
+ if mod_name.startswith("google.adk"):
74
+ for var_name in dir(mod):
75
+ var = getattr(mod, var_name, None)
76
+ if var_name in trace_functions.keys() and isinstance(var, Callable):
77
+ setattr(mod, var_name, trace_functions[var_name])
78
+ logger.debug(
79
+ f"Patch {mod_name} {var_name} with {trace_functions[var_name]}"
80
+ )
veadk/version.py CHANGED
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- VERSION = "0.2.2"
15
+ VERSION = "0.2.5"