veadk-python 0.2.6__py3-none-any.whl → 0.2.8__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 (102) hide show
  1. veadk/agent.py +11 -18
  2. veadk/agent_builder.py +94 -0
  3. veadk/{database/__init__.py → auth/base_auth.py} +7 -2
  4. veadk/auth/veauth/apmplus_veauth.py +65 -0
  5. veadk/auth/veauth/ark_veauth.py +77 -0
  6. veadk/auth/veauth/base_veauth.py +50 -0
  7. veadk/auth/veauth/opensearch_veauth.py +75 -0
  8. veadk/auth/veauth/postgresql_veauth.py +75 -0
  9. veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
  10. veadk/auth/veauth/vesearch_veauth.py +62 -0
  11. veadk/cli/cli.py +4 -0
  12. veadk/cli/cli_deploy.py +3 -2
  13. veadk/cli/cli_eval.py +160 -0
  14. veadk/cli/cli_init.py +1 -1
  15. veadk/cli/cli_pipeline.py +220 -0
  16. veadk/cli/cli_prompt.py +4 -4
  17. veadk/cli/cli_web.py +3 -1
  18. veadk/config.py +45 -81
  19. veadk/configs/database_configs.py +117 -0
  20. veadk/configs/model_configs.py +74 -0
  21. veadk/configs/tool_configs.py +42 -0
  22. veadk/configs/tracing_configs.py +110 -0
  23. veadk/consts.py +13 -1
  24. veadk/evaluation/base_evaluator.py +60 -44
  25. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +18 -12
  26. veadk/evaluation/eval_set_recorder.py +2 -2
  27. veadk/integrations/ve_code_pipeline/__init__.py +13 -0
  28. veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
  29. veadk/integrations/ve_cozeloop/__init__.py +13 -0
  30. veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
  31. veadk/integrations/ve_cr/ve_cr.py +20 -5
  32. veadk/integrations/ve_faas/template/cookiecutter.json +1 -1
  33. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +2 -2
  34. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +1 -1
  35. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +1 -5
  36. veadk/integrations/ve_faas/ve_faas.py +351 -36
  37. veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +6 -3
  38. veadk/integrations/ve_tls/__init__.py +13 -0
  39. veadk/integrations/ve_tls/utils.py +117 -0
  40. veadk/integrations/ve_tls/ve_tls.py +208 -0
  41. veadk/integrations/ve_tos/ve_tos.py +71 -75
  42. veadk/knowledgebase/backends/__init__.py +13 -0
  43. veadk/knowledgebase/backends/base_backend.py +59 -0
  44. veadk/knowledgebase/backends/in_memory_backend.py +82 -0
  45. veadk/knowledgebase/backends/opensearch_backend.py +136 -0
  46. veadk/knowledgebase/backends/redis_backend.py +144 -0
  47. veadk/knowledgebase/backends/utils.py +91 -0
  48. veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +412 -0
  49. veadk/knowledgebase/knowledgebase.py +109 -55
  50. veadk/memory/__init__.py +22 -0
  51. veadk/memory/long_term_memory.py +120 -51
  52. veadk/memory/long_term_memory_backends/__init__.py +13 -0
  53. veadk/{database/base_database.py → memory/long_term_memory_backends/base_backend.py} +10 -22
  54. veadk/memory/long_term_memory_backends/in_memory_backend.py +65 -0
  55. veadk/memory/long_term_memory_backends/opensearch_backend.py +120 -0
  56. veadk/memory/long_term_memory_backends/redis_backend.py +127 -0
  57. veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +148 -0
  58. veadk/memory/short_term_memory.py +80 -72
  59. veadk/memory/short_term_memory_backends/__init__.py +13 -0
  60. veadk/memory/short_term_memory_backends/base_backend.py +31 -0
  61. veadk/memory/short_term_memory_backends/mysql_backend.py +41 -0
  62. veadk/memory/short_term_memory_backends/postgresql_backend.py +41 -0
  63. veadk/memory/short_term_memory_backends/sqlite_backend.py +48 -0
  64. veadk/memory/short_term_memory_processor.py +9 -4
  65. veadk/runner.py +204 -247
  66. veadk/tools/builtin_tools/vesearch.py +2 -2
  67. veadk/tools/builtin_tools/video_generate.py +27 -20
  68. veadk/tools/builtin_tools/web_scraper.py +1 -1
  69. veadk/tools/builtin_tools/web_search.py +7 -7
  70. veadk/tools/load_knowledgebase_tool.py +1 -1
  71. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +20 -2
  72. veadk/tracing/telemetry/exporters/apmplus_exporter.py +178 -14
  73. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +6 -9
  74. veadk/tracing/telemetry/exporters/inmemory_exporter.py +22 -8
  75. veadk/tracing/telemetry/exporters/tls_exporter.py +6 -10
  76. veadk/tracing/telemetry/opentelemetry_tracer.py +5 -8
  77. veadk/tracing/telemetry/telemetry.py +66 -60
  78. veadk/utils/logger.py +1 -1
  79. veadk/utils/misc.py +63 -0
  80. veadk/utils/volcengine_sign.py +6 -2
  81. veadk/version.py +1 -1
  82. {veadk_python-0.2.6.dist-info → veadk_python-0.2.8.dist-info}/METADATA +16 -3
  83. {veadk_python-0.2.6.dist-info → veadk_python-0.2.8.dist-info}/RECORD +93 -64
  84. veadk/database/database_adapter.py +0 -368
  85. veadk/database/database_factory.py +0 -80
  86. veadk/database/kv/redis_database.py +0 -159
  87. veadk/database/local_database.py +0 -61
  88. veadk/database/relational/mysql_database.py +0 -173
  89. veadk/database/vector/opensearch_vector_database.py +0 -263
  90. veadk/database/vector/type.py +0 -50
  91. veadk/database/viking/viking_database.py +0 -471
  92. veadk/database/viking/viking_memory_db.py +0 -525
  93. /veadk/{database/kv → auth}/__init__.py +0 -0
  94. /veadk/{database/relational → auth/veauth}/__init__.py +0 -0
  95. /veadk/{database/vector/__init__.py → auth/veauth/cozeloop_veauth.py} +0 -0
  96. /veadk/{database/viking → configs}/__init__.py +0 -0
  97. /veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{{ cookiecutter.app_name|replace('-', '_') }} → {{ cookiecutter.app_name }}}/__init__.py +0 -0
  98. /veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{{ cookiecutter.app_name|replace('-', '_') }} → {{ cookiecutter.app_name }}}/agent.py +0 -0
  99. {veadk_python-0.2.6.dist-info → veadk_python-0.2.8.dist-info}/WHEEL +0 -0
  100. {veadk_python-0.2.6.dist-info → veadk_python-0.2.8.dist-info}/entry_points.txt +0 -0
  101. {veadk_python-0.2.6.dist-info → veadk_python-0.2.8.dist-info}/licenses/LICENSE +0 -0
  102. {veadk_python-0.2.6.dist-info → veadk_python-0.2.8.dist-info}/top_level.txt +0 -0
@@ -244,8 +244,26 @@ def llm_gen_ai_messages(params: LLMAttributesParams) -> ExtractorResponse:
244
244
  )
245
245
  if part.inline_data:
246
246
  if len(content.parts) == 1:
247
- # TODO(qingliang)
248
- pass
247
+ part = content.parts[0]
248
+ user_event["gen_ai.user.message"].update(
249
+ {
250
+ "parts.0.type": "image_url",
251
+ "parts.0.image_url.name": (
252
+ part.inline_data.display_name.split(
253
+ "/"
254
+ )[-1]
255
+ if part.inline_data
256
+ and part.inline_data.display_name
257
+ else "<unknown_image_name>"
258
+ ),
259
+ "parts.0.image_url.url": (
260
+ part.inline_data.display_name
261
+ if part.inline_data
262
+ and part.inline_data.display_name
263
+ else "<unknown_image_url>"
264
+ ),
265
+ }
266
+ )
249
267
  else:
250
268
  user_event["gen_ai.user.message"].update(
251
269
  {
@@ -12,11 +12,14 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import time
16
+ from dataclasses import dataclass
15
17
  from typing import Any
16
18
 
19
+ from google.adk.agents.invocation_context import InvocationContext
17
20
  from google.adk.models.llm_request import LlmRequest
18
21
  from google.adk.models.llm_response import LlmResponse
19
- from opentelemetry import metrics
22
+ from opentelemetry import metrics, trace
20
23
  from opentelemetry import metrics as metrics_api
21
24
  from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
22
25
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
@@ -28,13 +31,100 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
28
31
  from pydantic import BaseModel, Field
29
32
  from typing_extensions import override
30
33
 
31
- from veadk.config import getenv
34
+ from veadk.config import settings
32
35
  from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
33
36
  from veadk.utils.logger import get_logger
34
37
 
35
38
  logger = get_logger(__name__)
36
39
 
37
40
 
41
+ _GEN_AI_CLIENT_OPERATION_DURATION_BUCKETS = [
42
+ 0.01,
43
+ 0.02,
44
+ 0.04,
45
+ 0.08,
46
+ 0.16,
47
+ 0.32,
48
+ 0.64,
49
+ 1.28,
50
+ 2.56,
51
+ 5.12,
52
+ 10.24,
53
+ 20.48,
54
+ 40.96,
55
+ 81.92,
56
+ ]
57
+
58
+ _GEN_AI_SERVER_TIME_PER_OUTPUT_TOKEN_BUCKETS = [
59
+ 0.01,
60
+ 0.025,
61
+ 0.05,
62
+ 0.075,
63
+ 0.1,
64
+ 0.15,
65
+ 0.2,
66
+ 0.3,
67
+ 0.4,
68
+ 0.5,
69
+ 0.75,
70
+ 1.0,
71
+ 2.5,
72
+ ]
73
+
74
+ _GEN_AI_SERVER_TIME_TO_FIRST_TOKEN_BUCKETS = [
75
+ 0.001,
76
+ 0.005,
77
+ 0.01,
78
+ 0.02,
79
+ 0.04,
80
+ 0.06,
81
+ 0.08,
82
+ 0.1,
83
+ 0.25,
84
+ 0.5,
85
+ 0.75,
86
+ 1.0,
87
+ 2.5,
88
+ 5.0,
89
+ 7.5,
90
+ 10.0,
91
+ ]
92
+
93
+ _GEN_AI_CLIENT_TOKEN_USAGE_BUCKETS = [
94
+ 1,
95
+ 4,
96
+ 16,
97
+ 64,
98
+ 256,
99
+ 1024,
100
+ 4096,
101
+ 16384,
102
+ 65536,
103
+ 262144,
104
+ 1048576,
105
+ 4194304,
106
+ 16777216,
107
+ 67108864,
108
+ ]
109
+
110
+
111
+ @dataclass
112
+ class Meters:
113
+ LLM_CHAT_COUNT = "gen_ai.chat.count"
114
+ LLM_TOKEN_USAGE = "gen_ai.client.token.usage"
115
+ LLM_OPERATION_DURATION = "gen_ai.client.operation.duration"
116
+ LLM_COMPLETIONS_EXCEPTIONS = "gen_ai.chat_completions.exceptions"
117
+ LLM_STREAMING_TIME_TO_FIRST_TOKEN = (
118
+ "gen_ai.chat_completions.streaming_time_to_first_token"
119
+ )
120
+ LLM_STREAMING_TIME_TO_GENERATE = (
121
+ "gen_ai.chat_completions.streaming_time_to_generate"
122
+ )
123
+ LLM_STREAMING_TIME_PER_OUTPUT_TOKEN = (
124
+ "gen_ai.chat_completions.streaming_time_per_output_token"
125
+ )
126
+
127
+
38
128
  class MeterUploader:
39
129
  def __init__(
40
130
  self, name: str, endpoint: str, headers: dict, resource_attributes: dict
@@ -65,17 +155,53 @@ class MeterUploader:
65
155
 
66
156
  # create meter attributes
67
157
  self.llm_invoke_counter = self.meter.create_counter(
68
- name="gen_ai.chat.count",
158
+ name=Meters.LLM_CHAT_COUNT,
69
159
  description="Number of LLM invocations",
70
160
  unit="count",
71
161
  )
72
162
  self.token_usage = self.meter.create_histogram(
73
- name="gen_ai.client.token.usage",
163
+ name=Meters.LLM_TOKEN_USAGE,
74
164
  description="Token consumption of LLM invocations",
75
165
  unit="count",
166
+ explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_TOKEN_USAGE_BUCKETS,
167
+ )
168
+ self.duration_histogram = self.meter.create_histogram(
169
+ name=Meters.LLM_OPERATION_DURATION,
170
+ unit="s",
171
+ description="GenAI operation duration",
172
+ explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_OPERATION_DURATION_BUCKETS,
173
+ )
174
+ self.chat_exception_counter = self.meter.create_counter(
175
+ name=Meters.LLM_COMPLETIONS_EXCEPTIONS,
176
+ unit="time",
177
+ description="Number of exceptions occurred during chat completions",
178
+ )
179
+ self.streaming_time_to_first_token = self.meter.create_histogram(
180
+ name=Meters.LLM_STREAMING_TIME_TO_FIRST_TOKEN,
181
+ unit="s",
182
+ description="Time to first token in streaming chat completions",
183
+ explicit_bucket_boundaries_advisory=_GEN_AI_SERVER_TIME_TO_FIRST_TOKEN_BUCKETS,
184
+ )
185
+ self.streaming_time_to_generate = self.meter.create_histogram(
186
+ name=Meters.LLM_STREAMING_TIME_TO_GENERATE,
187
+ unit="s",
188
+ description="Time between first token and completion in streaming chat completions",
189
+ explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_OPERATION_DURATION_BUCKETS,
190
+ )
191
+ self.streaming_time_per_output_token = self.meter.create_histogram(
192
+ name=Meters.LLM_STREAMING_TIME_PER_OUTPUT_TOKEN,
193
+ unit="s",
194
+ description="Time per output token in streaming chat completions",
195
+ explicit_bucket_boundaries_advisory=_GEN_AI_SERVER_TIME_PER_OUTPUT_TOKEN_BUCKETS,
76
196
  )
77
197
 
78
- def record(self, llm_request: LlmRequest, llm_response: LlmResponse) -> None:
198
+ def record(
199
+ self,
200
+ invocation_context: InvocationContext,
201
+ event_id: str,
202
+ llm_request: LlmRequest,
203
+ llm_response: LlmResponse,
204
+ ) -> None:
79
205
  attributes = {
80
206
  "gen_ai_system": "volcengine",
81
207
  "gen_ai_response_model": llm_request.model,
@@ -99,22 +225,58 @@ class MeterUploader:
99
225
  token_attributes = {**attributes, "gen_ai_token_type": "output"}
100
226
  self.token_usage.record(output_token, attributes=token_attributes)
101
227
 
228
+ # Get llm duration
229
+ span = trace.get_current_span()
230
+ if span and hasattr(span, "start_time") and self.duration_histogram:
231
+ # We use span start time as the llm request start time
232
+ tik = span.start_time # type: ignore
233
+ # We use current time as the llm request end time
234
+ tok = time.time_ns()
235
+ # Calculate duration in seconds
236
+ duration = (tok - tik) / 1e9
237
+ self.duration_histogram.record(
238
+ duration, attributes=attributes
239
+ ) # unit in seconds
240
+
241
+ # Get model request error
242
+ if llm_response.error_code and self.chat_exception_counter:
243
+ exception_attributes = {
244
+ **attributes,
245
+ "error_type": llm_response.error_message,
246
+ }
247
+ self.chat_exception_counter.add(1, exception_attributes)
248
+
249
+ # TODO: Get streaming time to first token
250
+ # time_to_frist_token = 0.1
251
+ # if self.streaming_time_to_first_token:
252
+ # self.streaming_time_to_first_token.record(
253
+ # time_to_frist_token, attributes=attributes
254
+ # )
255
+
256
+ # TODO: Get streaming time to generate
257
+ # time_to_generate = 1.0
258
+ # if self.streaming_time_to_generate:
259
+ # self.streaming_time_to_generate.record(
260
+ # time_to_generate, attributes=attributes
261
+ # )
262
+
263
+ # TODO: Get streaming time per output token
264
+ # time_per_output_token = 0.01
265
+ # if self.streaming_time_per_output_token:
266
+ # self.streaming_time_per_output_token.record(
267
+ # time_per_output_token, attributes=attributes
268
+ # )
269
+
102
270
 
103
271
  class APMPlusExporterConfig(BaseModel):
104
272
  endpoint: str = Field(
105
- default_factory=lambda: getenv(
106
- "OBSERVABILITY_OPENTELEMETRY_APMPLUS_ENDPOINT",
107
- "http://apmplus-cn-beijing.volces.com:4317",
108
- ),
273
+ default_factory=lambda: settings.apmplus_config.otel_exporter_endpoint,
109
274
  )
110
275
  app_key: str = Field(
111
- default_factory=lambda: getenv("OBSERVABILITY_OPENTELEMETRY_APMPLUS_API_KEY"),
276
+ default_factory=lambda: settings.apmplus_config.otel_exporter_api_key,
112
277
  )
113
278
  service_name: str = Field(
114
- default_factory=lambda: getenv(
115
- "OBSERVABILITY_OPENTELEMETRY_APMPLUS_SERVICE_NAME",
116
- "veadk_tracing_service",
117
- ),
279
+ default_factory=lambda: settings.apmplus_config.otel_exporter_service_name,
118
280
  description="Service name shown in APMPlus frontend.",
119
281
  )
120
282
 
@@ -123,6 +285,8 @@ class APMPlusExporter(BaseExporter):
123
285
  config: APMPlusExporterConfig = Field(default_factory=APMPlusExporterConfig)
124
286
 
125
287
  def model_post_init(self, context: Any) -> None:
288
+ logger.info(f"APMPlusExporter sevice name: {self.config.service_name}")
289
+
126
290
  headers = {
127
291
  "x-byteapm-appkey": self.config.app_key,
128
292
  }
@@ -19,7 +19,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
19
19
  from pydantic import BaseModel, Field
20
20
  from typing_extensions import override
21
21
 
22
- from veadk.config import getenv
22
+ from veadk.config import settings
23
23
  from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
24
24
  from veadk.utils.logger import get_logger
25
25
 
@@ -28,18 +28,13 @@ logger = get_logger(__name__)
28
28
 
29
29
  class CozeloopExporterConfig(BaseModel):
30
30
  endpoint: str = Field(
31
- default_factory=lambda: getenv(
32
- "OBSERVABILITY_OPENTELEMETRY_COZELOOP_ENDPOINT",
33
- "https://api.coze.cn/v1/loop/opentelemetry/v1/traces",
34
- ),
31
+ default_factory=lambda: settings.cozeloop_config.otel_exporter_endpoint,
35
32
  )
36
33
  space_id: str = Field(
37
- default_factory=lambda: getenv(
38
- "OBSERVABILITY_OPENTELEMETRY_COZELOOP_SERVICE_NAME"
39
- ),
34
+ default_factory=lambda: settings.cozeloop_config.otel_exporter_space_id,
40
35
  )
41
36
  token: str = Field(
42
- default_factory=lambda: getenv("OBSERVABILITY_OPENTELEMETRY_COZELOOP_API_KEY"),
37
+ default_factory=lambda: settings.cozeloop_config.otel_exporter_api_key,
43
38
  )
44
39
 
45
40
 
@@ -47,6 +42,8 @@ class CozeloopExporter(BaseExporter):
47
42
  config: CozeloopExporterConfig = Field(default_factory=CozeloopExporterConfig)
48
43
 
49
44
  def model_post_init(self, context: Any) -> None:
45
+ logger.info(f"CozeloopExporter space ID: {self.config.space_id}")
46
+
50
47
  headers = {
51
48
  "cozeloop-workspace-id": self.config.space_id,
52
49
  "authorization": f"Bearer {self.config.token}",
@@ -75,11 +75,22 @@ class _InMemoryExporter(export.SpanExporter):
75
75
  class _InMemorySpanProcessor(export.SimpleSpanProcessor):
76
76
  def __init__(self, exporter: _InMemoryExporter) -> None:
77
77
  super().__init__(exporter)
78
- self.spans = []
79
78
 
80
79
  def on_start(self, span, parent_context) -> None:
81
- if span.context:
82
- self.spans.append(span)
80
+ if span.name.startswith("invocation"):
81
+ span.set_attribute("gen_ai.operation.name", "chain")
82
+ span.set_attribute("gen_ai.usage.total_tokens", 0)
83
+
84
+ ctx = set_value("invocation_span_instance", span, context=parent_context)
85
+ token = attach(ctx) # mount context on `invocation` root span in Google ADK
86
+ setattr(span, "_invocation_token", token) # for later detach
87
+
88
+ if span.name.startswith("agent_run"):
89
+ span.set_attribute("gen_ai.operation.name", "agent")
90
+
91
+ ctx = set_value("agent_run_span_instance", span, context=parent_context)
92
+ token = attach(ctx)
93
+ setattr(span, "_agent_run_token", token) # for later detach
83
94
 
84
95
  def on_end(self, span: ReadableSpan) -> None:
85
96
  if span.context:
@@ -92,8 +103,14 @@ class _InMemorySpanProcessor(export.SimpleSpanProcessor):
92
103
  except Exception:
93
104
  logger.exception("Exception while exporting Span.")
94
105
  detach(token)
95
- if span in self.spans:
96
- self.spans.remove(span)
106
+
107
+ token = getattr(span, "_invocation_token", None)
108
+ if token:
109
+ detach(token)
110
+
111
+ token = getattr(span, "_agent_run_token", None)
112
+ if token:
113
+ detach(token)
97
114
 
98
115
 
99
116
  class InMemoryExporter(BaseExporter):
@@ -106,6 +123,3 @@ class InMemoryExporter(BaseExporter):
106
123
 
107
124
  self._exporter = _InMemoryExporter()
108
125
  self.processor = _InMemorySpanProcessor(self._exporter)
109
-
110
-
111
- _INMEMORY_EXPORTER_INSTANCE = InMemoryExporter()
@@ -19,7 +19,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
19
19
  from pydantic import BaseModel, Field
20
20
  from typing_extensions import override
21
21
 
22
- from veadk.config import getenv
22
+ from veadk.config import getenv, settings
23
23
  from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
24
24
  from veadk.utils.logger import get_logger
25
25
 
@@ -28,19 +28,13 @@ logger = get_logger(__name__)
28
28
 
29
29
  class TLSExporterConfig(BaseModel):
30
30
  endpoint: str = Field(
31
- default_factory=lambda: getenv(
32
- "OBSERVABILITY_OPENTELEMETRY_TLS_ENDPOINT",
33
- "https://tls-cn-beijing.volces.com:4318/v1/traces",
34
- ),
31
+ default_factory=lambda: settings.tls_config.otel_exporter_endpoint,
35
32
  )
36
33
  region: str = Field(
37
- default_factory=lambda: getenv(
38
- "OBSERVABILITY_OPENTELEMETRY_TLS_REGION",
39
- "cn-beijing",
40
- ),
34
+ default_factory=lambda: settings.tls_config.otel_exporter_region,
41
35
  )
42
36
  topic_id: str = Field(
43
- default_factory=lambda: getenv("OBSERVABILITY_OPENTELEMETRY_TLS_SERVICE_NAME"),
37
+ default_factory=lambda: settings.tls_config.otel_exporter_topic_id,
44
38
  )
45
39
  access_key: str = Field(default_factory=lambda: getenv("VOLCENGINE_ACCESS_KEY"))
46
40
  secret_key: str = Field(default_factory=lambda: getenv("VOLCENGINE_SECRET_KEY"))
@@ -50,6 +44,8 @@ class TLSExporter(BaseExporter):
50
44
  config: TLSExporterConfig = Field(default_factory=TLSExporterConfig)
51
45
 
52
46
  def model_post_init(self, context: Any) -> None:
47
+ logger.info(f"TLSExporter topic ID: {self.config.topic_id}")
48
+
53
49
  headers = {
54
50
  "x-tls-otel-tracetopic": self.config.topic_id,
55
51
  "x-tls-otel-ak": self.config.access_key,
@@ -19,7 +19,6 @@ import time
19
19
  from typing import Any
20
20
 
21
21
  from opentelemetry import trace as trace_api
22
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
23
22
  from opentelemetry.sdk import trace as trace_sdk
24
23
  from opentelemetry.sdk.resources import Resource
25
24
  from opentelemetry.sdk.trace import TracerProvider
@@ -30,12 +29,10 @@ from typing_extensions import override
30
29
  from veadk.tracing.base_tracer import BaseTracer
31
30
  from veadk.tracing.telemetry.exporters.apmplus_exporter import APMPlusExporter
32
31
  from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
33
- from veadk.tracing.telemetry.exporters.inmemory_exporter import (
34
- _INMEMORY_EXPORTER_INSTANCE,
35
- InMemoryExporter,
36
- )
32
+ from veadk.tracing.telemetry.exporters.inmemory_exporter import InMemoryExporter
37
33
  from veadk.utils.logger import get_logger
38
34
  from veadk.utils.patches import patch_google_adk_telemetry
35
+ from veadk.utils.misc import get_temp_dir
39
36
 
40
37
  logger = get_logger(__name__)
41
38
 
@@ -88,7 +85,7 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
88
85
  span_processors = global_tracer_provider._active_span_processor._span_processors
89
86
  have_apmplus_exporter = any(
90
87
  isinstance(p, (BatchSpanProcessor, SimpleSpanProcessor))
91
- and isinstance(p.span_exporter, OTLPSpanExporter)
88
+ and hasattr(p.span_exporter, "_endpoint")
92
89
  and "apmplus" in p.span_exporter._endpoint
93
90
  for p in span_processors
94
91
  )
@@ -119,7 +116,7 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
119
116
  f"Add span processor for exporter `{exporter.__class__.__name__}` to OpentelemetryTracer failed."
120
117
  )
121
118
 
122
- self._inmemory_exporter = _INMEMORY_EXPORTER_INSTANCE
119
+ self._inmemory_exporter = InMemoryExporter()
123
120
  if self._inmemory_exporter.processor:
124
121
  # make sure the in memory exporter processor is added at index 0
125
122
  # because we use this to record all spans
@@ -159,7 +156,7 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
159
156
  self,
160
157
  user_id: str = "unknown_user_id",
161
158
  session_id: str = "unknown_session_id",
162
- path: str = "/tmp",
159
+ path: str = get_temp_dir(),
163
160
  ) -> str:
164
161
  def _build_trace_file_path(path: str, user_id: str, session_id: str) -> str:
165
162
  return f"{path}/{self.name}_{user_id}_{session_id}_{self.trace_id}.json"
@@ -20,7 +20,8 @@ from google.adk.models.llm_request import LlmRequest
20
20
  from google.adk.models.llm_response import LlmResponse
21
21
  from google.adk.tools import BaseTool
22
22
  from opentelemetry import trace
23
- from opentelemetry.sdk.trace import _Span
23
+ from opentelemetry.context import get_value
24
+ from opentelemetry.sdk.trace import Span, _Span
24
25
 
25
26
  from veadk.tracing.telemetry.attributes.attributes import ATTRIBUTES
26
27
  from veadk.tracing.telemetry.attributes.extractors.types import (
@@ -28,16 +29,14 @@ from veadk.tracing.telemetry.attributes.extractors.types import (
28
29
  LLMAttributesParams,
29
30
  ToolAttributesParams,
30
31
  )
31
- from veadk.tracing.telemetry.exporters.inmemory_exporter import (
32
- _INMEMORY_EXPORTER_INSTANCE,
33
- )
34
32
  from veadk.utils.logger import get_logger
35
33
 
36
34
  logger = get_logger(__name__)
37
35
 
38
36
 
39
- def upload_metrics(
37
+ def _upload_metrics(
40
38
  invocation_context: InvocationContext,
39
+ event_id: str,
41
40
  llm_request: LlmRequest,
42
41
  llm_response: LlmResponse,
43
42
  ) -> None:
@@ -48,11 +47,13 @@ def upload_metrics(
48
47
  for tracer in tracers:
49
48
  for exporter in getattr(tracer, "exporters", []):
50
49
  if getattr(exporter, "meter_uploader", None):
51
- exporter.meter_uploader.record(llm_request, llm_response)
50
+ exporter.meter_uploader.record(
51
+ invocation_context, event_id, llm_request, llm_response
52
+ )
52
53
 
53
54
 
54
55
  def _set_agent_input_attribute(
55
- span: _Span, invocation_context: InvocationContext
56
+ span: Span, invocation_context: InvocationContext
56
57
  ) -> None:
57
58
  # We only save the original user input as the agent input
58
59
  # hence once the `agent.input` has been set, we don't overwrite it
@@ -106,7 +107,7 @@ def _set_agent_input_attribute(
106
107
  )
107
108
 
108
109
 
109
- def _set_agent_output_attribute(span: _Span, llm_response: LlmResponse) -> None:
110
+ def _set_agent_output_attribute(span: Span, llm_response: LlmResponse) -> None:
110
111
  content = llm_response.content
111
112
  if content and content.parts:
112
113
  for idx, part in enumerate(content.parts):
@@ -126,64 +127,64 @@ def set_common_attributes_on_model_span(
126
127
  current_span: _Span,
127
128
  **kwargs,
128
129
  ) -> None:
129
- if current_span.context:
130
- current_span_id = current_span.context.trace_id
131
- else:
132
- logger.warning(
133
- "Current span context is missing, failed to get `trace_id` to set common attributes."
134
- )
135
- return
136
-
130
+ common_attributes = ATTRIBUTES.get("common", {})
137
131
  try:
138
- spans = _INMEMORY_EXPORTER_INSTANCE.processor.spans # type: ignore
139
-
140
- spans_in_current_trace = [
141
- span
142
- for span in spans
143
- if span.context and span.context.trace_id == current_span_id
144
- ]
145
-
146
- common_attributes = ATTRIBUTES.get("common", {})
147
- for span in spans_in_current_trace:
148
- if span.is_recording():
149
- if span.name.startswith("invocation"):
150
- span.set_attribute("gen_ai.operation.name", "chain")
151
- _set_agent_input_attribute(span, invocation_context)
152
- _set_agent_output_attribute(span, llm_response)
153
- elif span.name.startswith("agent_run"):
154
- span.set_attribute("gen_ai.operation.name", "agent")
155
- _set_agent_input_attribute(span, invocation_context)
156
- _set_agent_output_attribute(span, llm_response)
157
- for attr_name, attr_extractor in common_attributes.items():
158
- value = attr_extractor(**kwargs)
159
- span.set_attribute(attr_name, value)
132
+ invocation_span: Span = get_value("invocation_span_instance") # type: ignore
133
+ agent_run_span: Span = get_value("agent_run_span_instance") # type: ignore
134
+
135
+ if invocation_span and invocation_span.name.startswith("invocation"):
136
+ _set_agent_input_attribute(invocation_span, invocation_context)
137
+ _set_agent_output_attribute(invocation_span, llm_response)
138
+ for attr_name, attr_extractor in common_attributes.items():
139
+ value = attr_extractor(**kwargs)
140
+ invocation_span.set_attribute(attr_name, value)
141
+
142
+ # Calculate the token usage for the whole invocation span
143
+ current_step_token_usage = (
144
+ llm_response.usage_metadata.total_token_count
145
+ if llm_response.usage_metadata
146
+ and llm_response.usage_metadata.total_token_count
147
+ else 0
148
+ )
149
+ prev_total_token_usage = (
150
+ invocation_span.attributes["gen_ai.usage.total_tokens"]
151
+ if invocation_span.attributes
152
+ else 0
153
+ )
154
+ accumulated_total_token_usage = (
155
+ current_step_token_usage + int(prev_total_token_usage) # type: ignore
156
+ ) # we can ignore this warning, cause we manually set the attribute to int before
157
+ invocation_span.set_attribute(
158
+ "gen_ai.usage.total_tokens", accumulated_total_token_usage
159
+ )
160
+
161
+ if agent_run_span and agent_run_span.name.startswith("agent_run"):
162
+ _set_agent_input_attribute(agent_run_span, invocation_context)
163
+ _set_agent_output_attribute(agent_run_span, llm_response)
164
+ for attr_name, attr_extractor in common_attributes.items():
165
+ value = attr_extractor(**kwargs)
166
+ agent_run_span.set_attribute(attr_name, value)
167
+
168
+ for attr_name, attr_extractor in common_attributes.items():
169
+ value = attr_extractor(**kwargs)
170
+ current_span.set_attribute(attr_name, value)
160
171
  except Exception as e:
161
172
  logger.error(f"Failed to set common attributes for spans: {e}")
162
173
 
163
174
 
164
175
  def set_common_attributes_on_tool_span(current_span: _Span) -> None:
165
- # find parent span (generally a llm span)
166
- if not current_span.context:
167
- logger.warning(
168
- f"Get tool span's context failed. Skip setting common attributes for span {current_span.name}"
169
- )
170
- return
171
-
172
- if not current_span.parent:
173
- logger.warning(
174
- f"Get tool span's parent failed. Skip setting common attributes for span {current_span.name}"
175
- )
176
- return
177
-
178
- parent_span_id = current_span.parent.span_id
179
- for span in _INMEMORY_EXPORTER_INSTANCE.processor.spans: # type: ignore
180
- if span.context.span_id == parent_span_id:
181
- common_attributes = ATTRIBUTES.get("common", {})
182
- for attr_name in common_attributes.keys():
183
- current_span.set_attribute(attr_name, span.attributes[attr_name])
176
+ common_attributes = ATTRIBUTES.get("common", {})
184
177
 
178
+ invocation_span: Span = get_value("invocation_span_instance") # type: ignore
185
179
 
186
- def trace_send_data(): ...
180
+ for attr_name in common_attributes.keys():
181
+ if (
182
+ invocation_span
183
+ and invocation_span.name.startswith("invocation")
184
+ and invocation_span.attributes
185
+ and attr_name in invocation_span.attributes
186
+ ):
187
+ current_span.set_attribute(attr_name, invocation_span.attributes[attr_name])
187
188
 
188
189
 
189
190
  def trace_tool_call(
@@ -209,7 +210,7 @@ def trace_call_llm(
209
210
  llm_request: LlmRequest,
210
211
  llm_response: LlmResponse,
211
212
  ) -> None:
212
- span = trace.get_current_span()
213
+ span: Span = trace.get_current_span() # type: ignore
213
214
 
214
215
  from veadk.agent import Agent
215
216
 
@@ -231,6 +232,7 @@ def trace_call_llm(
231
232
  span.context.trace_state.get("call_type", "")
232
233
  if (
233
234
  hasattr(span, "context")
235
+ and span.context
234
236
  and hasattr(span.context, "trace_state")
235
237
  and hasattr(span.context.trace_state, "get")
236
238
  )
@@ -250,4 +252,8 @@ def trace_call_llm(
250
252
  response: ExtractorResponse = attr_extractor(params)
251
253
  ExtractorResponse.update_span(span, attr_name, response)
252
254
 
253
- upload_metrics(invocation_context, llm_request, llm_response)
255
+ _upload_metrics(invocation_context, event_id, llm_request, llm_response)
256
+
257
+
258
+ # Do not modify this function
259
+ def trace_send_data(): ...
veadk/utils/logger.py CHANGED
@@ -16,7 +16,7 @@ import sys
16
16
 
17
17
  from loguru import logger
18
18
 
19
- from veadk.config import getenv
19
+ from veadk.utils.misc import getenv
20
20
 
21
21
 
22
22
  def filter_log():