veadk-python 0.2.7__py3-none-any.whl → 0.2.9__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 (75) hide show
  1. veadk/agent.py +3 -2
  2. veadk/auth/veauth/opensearch_veauth.py +75 -0
  3. veadk/auth/veauth/postgresql_veauth.py +75 -0
  4. veadk/cli/cli.py +3 -1
  5. veadk/cli/cli_eval.py +160 -0
  6. veadk/cli/cli_prompt.py +9 -2
  7. veadk/cli/cli_web.py +6 -1
  8. veadk/configs/database_configs.py +43 -0
  9. veadk/configs/model_configs.py +32 -0
  10. veadk/consts.py +11 -4
  11. veadk/evaluation/adk_evaluator/adk_evaluator.py +5 -2
  12. veadk/evaluation/base_evaluator.py +95 -68
  13. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +23 -15
  14. veadk/evaluation/eval_set_recorder.py +2 -2
  15. veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +9 -3
  16. veadk/integrations/ve_tls/utils.py +1 -2
  17. veadk/integrations/ve_tls/ve_tls.py +9 -5
  18. veadk/integrations/ve_tos/ve_tos.py +542 -68
  19. veadk/knowledgebase/backends/base_backend.py +59 -0
  20. veadk/knowledgebase/backends/in_memory_backend.py +82 -0
  21. veadk/knowledgebase/backends/opensearch_backend.py +136 -0
  22. veadk/knowledgebase/backends/redis_backend.py +144 -0
  23. veadk/knowledgebase/backends/utils.py +91 -0
  24. veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +524 -0
  25. veadk/{database/__init__.py → knowledgebase/entry.py} +10 -2
  26. veadk/knowledgebase/knowledgebase.py +120 -139
  27. veadk/memory/__init__.py +22 -0
  28. veadk/memory/long_term_memory.py +124 -41
  29. veadk/{database/base_database.py → memory/long_term_memory_backends/base_backend.py} +10 -22
  30. veadk/memory/long_term_memory_backends/in_memory_backend.py +65 -0
  31. veadk/memory/long_term_memory_backends/mem0_backend.py +129 -0
  32. veadk/memory/long_term_memory_backends/opensearch_backend.py +120 -0
  33. veadk/memory/long_term_memory_backends/redis_backend.py +127 -0
  34. veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +148 -0
  35. veadk/memory/short_term_memory.py +80 -72
  36. veadk/memory/short_term_memory_backends/base_backend.py +31 -0
  37. veadk/memory/short_term_memory_backends/mysql_backend.py +41 -0
  38. veadk/memory/short_term_memory_backends/postgresql_backend.py +41 -0
  39. veadk/memory/short_term_memory_backends/sqlite_backend.py +48 -0
  40. veadk/runner.py +12 -19
  41. veadk/tools/builtin_tools/generate_image.py +355 -0
  42. veadk/tools/builtin_tools/image_edit.py +56 -16
  43. veadk/tools/builtin_tools/image_generate.py +51 -15
  44. veadk/tools/builtin_tools/video_generate.py +41 -41
  45. veadk/tools/builtin_tools/web_scraper.py +1 -1
  46. veadk/tools/builtin_tools/web_search.py +7 -7
  47. veadk/tools/load_knowledgebase_tool.py +2 -8
  48. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +21 -3
  49. veadk/tracing/telemetry/exporters/apmplus_exporter.py +24 -6
  50. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +2 -0
  51. veadk/tracing/telemetry/exporters/inmemory_exporter.py +22 -8
  52. veadk/tracing/telemetry/exporters/tls_exporter.py +2 -0
  53. veadk/tracing/telemetry/opentelemetry_tracer.py +13 -10
  54. veadk/tracing/telemetry/telemetry.py +66 -63
  55. veadk/utils/misc.py +15 -0
  56. veadk/version.py +1 -1
  57. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/METADATA +28 -5
  58. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/RECORD +65 -56
  59. veadk/database/database_adapter.py +0 -533
  60. veadk/database/database_factory.py +0 -80
  61. veadk/database/kv/redis_database.py +0 -159
  62. veadk/database/local_database.py +0 -62
  63. veadk/database/relational/mysql_database.py +0 -173
  64. veadk/database/vector/opensearch_vector_database.py +0 -263
  65. veadk/database/vector/type.py +0 -50
  66. veadk/database/viking/__init__.py +0 -13
  67. veadk/database/viking/viking_database.py +0 -638
  68. veadk/database/viking/viking_memory_db.py +0 -525
  69. /veadk/{database/kv → knowledgebase/backends}/__init__.py +0 -0
  70. /veadk/{database/relational → memory/long_term_memory_backends}/__init__.py +0 -0
  71. /veadk/{database/vector → memory/short_term_memory_backends}/__init__.py +0 -0
  72. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/WHEEL +0 -0
  73. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/entry_points.txt +0 -0
  74. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/licenses/LICENSE +0 -0
  75. {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/top_level.txt +0 -0
@@ -26,14 +26,15 @@ from volcenginesdkarkruntime.types.content_generation.create_task_content_param
26
26
  )
27
27
 
28
28
  from veadk.config import getenv
29
+ from veadk.consts import DEFAULT_MODEL_AGENT_API_BASE, DEFAULT_VIDEO_MODEL_NAME
29
30
  from veadk.utils.logger import get_logger
30
31
  from veadk.version import VERSION
31
32
 
32
33
  logger = get_logger(__name__)
33
34
 
34
35
  client = Ark(
35
- api_key=getenv("MODEL_VIDEO_API_KEY"),
36
- base_url=getenv("MODEL_VIDEO_API_BASE"),
36
+ api_key=getenv("MODEL_AGENT_API_KEY"),
37
+ base_url=DEFAULT_MODEL_AGENT_API_BASE,
37
38
  )
38
39
 
39
40
 
@@ -42,7 +43,7 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
42
43
  if first_frame_image is None:
43
44
  logger.debug("text generation")
44
45
  response = client.content_generation.tasks.create(
45
- model=getenv("MODEL_VIDEO_NAME"),
46
+ model=DEFAULT_VIDEO_MODEL_NAME,
46
47
  content=[
47
48
  {"type": "text", "text": prompt},
48
49
  ],
@@ -50,7 +51,7 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
50
51
  elif last_frame_image is None:
51
52
  logger.debug("first frame generation")
52
53
  response = client.content_generation.tasks.create(
53
- model=getenv("MODEL_VIDEO_NAME"),
54
+ model=DEFAULT_VIDEO_MODEL_NAME,
54
55
  content=cast(
55
56
  list[CreateTaskContentParam], # avoid IDE warning
56
57
  [
@@ -65,7 +66,7 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
65
66
  else:
66
67
  logger.debug("last frame generation")
67
68
  response = client.content_generation.tasks.create(
68
- model=getenv("MODEL_VIDEO_NAME"),
69
+ model=DEFAULT_VIDEO_MODEL_NAME,
69
70
  content=[
70
71
  {"type": "text", "text": prompt},
71
72
  {
@@ -194,19 +195,19 @@ async def video_generate(params: list, tool_context: ToolContext) -> Dict:
194
195
  batch_size = 10
195
196
  success_list = []
196
197
  error_list = []
197
- tracer = trace.get_tracer("gcp.vertex.agent")
198
- with tracer.start_as_current_span("call_llm") as span:
199
- input_part = {"role": "user"}
200
- output_part = {"message.role": "model"}
201
-
202
- for idx, item in enumerate(params):
203
- input_part[f"parts.{idx}.type"] = "text"
204
- input_part[f"parts.{idx}.text"] = json.dumps(item, ensure_ascii=False)
205
-
206
- for start_idx in range(0, len(params), batch_size):
207
- batch = params[start_idx : start_idx + batch_size]
208
- task_dict = {}
198
+
199
+ for start_idx in range(0, len(params), batch_size):
200
+ batch = params[start_idx : start_idx + batch_size]
201
+ task_dict = {}
202
+ tracer = trace.get_tracer("gcp.vertex.agent")
203
+ with tracer.start_as_current_span("call_llm") as span:
204
+ input_part = {"role": "user"}
205
+ output_part = {"message.role": "model"}
206
+ total_tokens = 0
209
207
  for idx, item in enumerate(batch):
208
+ input_part[f"parts.{idx}.type"] = "text"
209
+ input_part[f"parts.{idx}.text"] = json.dumps(item, ensure_ascii=False)
210
+
210
211
  video_name = item["video_name"]
211
212
  prompt = item["prompt"]
212
213
  first_frame = item.get("first_frame", None)
@@ -223,7 +224,6 @@ async def video_generate(params: list, tool_context: ToolContext) -> Dict:
223
224
  logger.error(f"Error: {e}")
224
225
  error_list.append(video_name)
225
226
 
226
- total_tokens = 0
227
227
  while True:
228
228
  task_list = list(task_dict.keys())
229
229
  if len(task_list) == 0:
@@ -256,29 +256,29 @@ async def video_generate(params: list, tool_context: ToolContext) -> Dict:
256
256
  )
257
257
  time.sleep(10)
258
258
 
259
- add_span_attributes(
260
- span,
261
- tool_context,
262
- input_part=input_part,
263
- output_part=output_part,
264
- output_tokens=total_tokens,
265
- total_tokens=total_tokens,
266
- request_model=getenv("MODEL_VIDEO_NAME"),
267
- response_model=getenv("MODEL_VIDEO_NAME"),
268
- )
269
-
270
- if len(success_list) == 0:
271
- return {
272
- "status": "error",
273
- "success_list": success_list,
274
- "error_list": error_list,
275
- }
276
- else:
277
- return {
278
- "status": "success",
279
- "success_list": success_list,
280
- "error_list": error_list,
281
- }
259
+ add_span_attributes(
260
+ span,
261
+ tool_context,
262
+ input_part=input_part,
263
+ output_part=output_part,
264
+ output_tokens=total_tokens,
265
+ total_tokens=total_tokens,
266
+ request_model=DEFAULT_VIDEO_MODEL_NAME,
267
+ response_model=DEFAULT_VIDEO_MODEL_NAME,
268
+ )
269
+
270
+ if len(success_list) == 0:
271
+ return {
272
+ "status": "error",
273
+ "success_list": success_list,
274
+ "error_list": error_list,
275
+ }
276
+ else:
277
+ return {
278
+ "status": "success",
279
+ "success_list": success_list,
280
+ "error_list": error_list,
281
+ }
282
282
 
283
283
 
284
284
  def add_span_attributes(
@@ -72,5 +72,5 @@ def web_scraper(query: str) -> dict[str, Any]:
72
72
  return results_str
73
73
 
74
74
  except requests.exceptions.RequestException as e:
75
- error_message = f"Error: {str(e)}"
75
+ error_message = f"Error: {str(e)}, response: {response}"
76
76
  raise ValueError(error_message)
@@ -180,13 +180,13 @@ def web_search(query: str) -> list[str]:
180
180
  "WebSearch",
181
181
  json.dumps(req),
182
182
  )
183
+
183
184
  try:
184
185
  results: list = response_body["Result"]["WebResults"]
186
+ final_results = []
187
+ for result in results:
188
+ final_results.append(result["Summary"].strip())
189
+ return final_results
185
190
  except Exception as e:
186
- logger.error(f"Web search failed: {e}")
187
- return []
188
-
189
- final_results = []
190
- for result in results:
191
- final_results.append(result["Summary"].strip())
192
- return final_results
191
+ logger.error(f"Web search failed {e}, response body: {response_body}")
192
+ return [response_body]
@@ -24,6 +24,7 @@ from pydantic import BaseModel, Field
24
24
  from typing_extensions import override
25
25
 
26
26
  from veadk.knowledgebase import KnowledgeBase
27
+ from veadk.knowledgebase.entry import KnowledgebaseEntry
27
28
 
28
29
  if TYPE_CHECKING:
29
30
  from google.adk.models.llm_request import LlmRequest
@@ -32,10 +33,6 @@ if TYPE_CHECKING:
32
33
  knowledgebase: KnowledgeBase | None = None
33
34
 
34
35
 
35
- class KnowledgebaseEntry(BaseModel):
36
- content: str
37
-
38
-
39
36
  class LoadKnowledgebaseResponse(BaseModel):
40
37
  knowledges: list[KnowledgebaseEntry] = Field(default_factory=list)
41
38
 
@@ -55,10 +52,7 @@ async def search_knowledgebase(
55
52
  ) -> SearchKnowledgebaseResponse:
56
53
  """Searches the knowledgebase of the current user."""
57
54
  if isinstance(knowledgebase, KnowledgeBase):
58
- res = knowledgebase.search(query, app_name=app_name)
59
- entry_list = []
60
- for r in res:
61
- entry_list.append(KnowledgebaseEntry(content=r))
55
+ entry_list = knowledgebase.search(query)
62
56
  return SearchKnowledgebaseResponse(knowledges=entry_list)
63
57
  else:
64
58
  return SearchKnowledgebaseResponse(knowledges=[])
@@ -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
  {
@@ -510,7 +528,7 @@ def llm_gen_ai_request_functions(params: LLMAttributesParams) -> ExtractorRespon
510
528
  f"gen_ai.request.functions.{idx}.name": tool_instance.name,
511
529
  f"gen_ai.request.functions.{idx}.description": tool_instance.description,
512
530
  f"gen_ai.request.functions.{idx}.parameters": str(
513
- tool_instance._get_declaration().parameters.model_dump( # type: ignore
531
+ tool_instance._get_declaration().parameters.model_dump_json( # type: ignore
514
532
  exclude_none=True
515
533
  )
516
534
  if tool_instance._get_declaration()
@@ -12,12 +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
15
16
  from dataclasses import dataclass
16
17
  from typing import Any
17
18
 
19
+ from google.adk.agents.invocation_context import InvocationContext
18
20
  from google.adk.models.llm_request import LlmRequest
19
21
  from google.adk.models.llm_response import LlmResponse
20
- from opentelemetry import metrics
22
+ from opentelemetry import metrics, trace
21
23
  from opentelemetry import metrics as metrics_api
22
24
  from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
23
25
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
@@ -193,7 +195,13 @@ class MeterUploader:
193
195
  explicit_bucket_boundaries_advisory=_GEN_AI_SERVER_TIME_PER_OUTPUT_TOKEN_BUCKETS,
194
196
  )
195
197
 
196
- 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:
197
205
  attributes = {
198
206
  "gen_ai_system": "volcengine",
199
207
  "gen_ai_response_model": llm_request.model,
@@ -217,10 +225,18 @@ class MeterUploader:
217
225
  token_attributes = {**attributes, "gen_ai_token_type": "output"}
218
226
  self.token_usage.record(output_token, attributes=token_attributes)
219
227
 
220
- # TODO: Get llm duration
221
- # duration = 5.0
222
- # if self.duration_histogram:
223
- # self.duration_histogram.record(duration, attributes=attributes)
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
224
240
 
225
241
  # Get model request error
226
242
  if llm_response.error_code and self.chat_exception_counter:
@@ -269,6 +285,8 @@ class APMPlusExporter(BaseExporter):
269
285
  config: APMPlusExporterConfig = Field(default_factory=APMPlusExporterConfig)
270
286
 
271
287
  def model_post_init(self, context: Any) -> None:
288
+ logger.info(f"APMPlusExporter sevice name: {self.config.service_name}")
289
+
272
290
  headers = {
273
291
  "x-byteapm-appkey": self.config.app_key,
274
292
  }
@@ -42,6 +42,8 @@ class CozeloopExporter(BaseExporter):
42
42
  config: CozeloopExporterConfig = Field(default_factory=CozeloopExporterConfig)
43
43
 
44
44
  def model_post_init(self, context: Any) -> None:
45
+ logger.info(f"CozeloopExporter space ID: {self.config.space_id}")
46
+
45
47
  headers = {
46
48
  "cozeloop-workspace-id": self.config.space_id,
47
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()
@@ -44,6 +44,8 @@ class TLSExporter(BaseExporter):
44
44
  config: TLSExporterConfig = Field(default_factory=TLSExporterConfig)
45
45
 
46
46
  def model_post_init(self, context: Any) -> None:
47
+ logger.info(f"TLSExporter topic ID: {self.config.topic_id}")
48
+
47
49
  headers = {
48
50
  "x-tls-otel-tracetopic": self.config.topic_id,
49
51
  "x-tls-otel-ak": self.config.access_key,
@@ -19,10 +19,9 @@ 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
- from opentelemetry.sdk.trace import TracerProvider
24
+ from opentelemetry.sdk.trace import TracerProvider, SpanLimits
26
25
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
27
26
  from pydantic import BaseModel, ConfigDict, Field, field_validator
28
27
  from typing_extensions import override
@@ -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
 
@@ -82,13 +79,19 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
82
79
 
83
80
  def _init_global_tracer_provider(self) -> None:
84
81
  # set provider anyway, then get global provider
85
- trace_api.set_tracer_provider(trace_sdk.TracerProvider())
82
+ trace_api.set_tracer_provider(
83
+ trace_sdk.TracerProvider(
84
+ span_limits=SpanLimits(
85
+ max_attributes=4096,
86
+ )
87
+ )
88
+ )
86
89
  global_tracer_provider: TracerProvider = trace_api.get_tracer_provider() # type: ignore
87
90
 
88
91
  span_processors = global_tracer_provider._active_span_processor._span_processors
89
92
  have_apmplus_exporter = any(
90
93
  isinstance(p, (BatchSpanProcessor, SimpleSpanProcessor))
91
- and isinstance(p.span_exporter, OTLPSpanExporter)
94
+ and hasattr(p.span_exporter, "_endpoint")
92
95
  and "apmplus" in p.span_exporter._endpoint
93
96
  for p in span_processors
94
97
  )
@@ -119,7 +122,7 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
119
122
  f"Add span processor for exporter `{exporter.__class__.__name__}` to OpentelemetryTracer failed."
120
123
  )
121
124
 
122
- self._inmemory_exporter = _INMEMORY_EXPORTER_INSTANCE
125
+ self._inmemory_exporter = InMemoryExporter()
123
126
  if self._inmemory_exporter.processor:
124
127
  # make sure the in memory exporter processor is added at index 0
125
128
  # because we use this to record all spans
@@ -159,7 +162,7 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
159
162
  self,
160
163
  user_id: str = "unknown_user_id",
161
164
  session_id: str = "unknown_session_id",
162
- path: str = "/tmp",
165
+ path: str = get_temp_dir(),
163
166
  ) -> str:
164
167
  def _build_trace_file_path(path: str, user_id: str, session_id: str) -> str:
165
168
  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,67 +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
- if hasattr(span.attributes, attr_name):
184
- current_span.set_attribute(attr_name, span.attributes[attr_name])
185
- else:
186
- logger.error(f"Parent span does not have attribute {attr_name}")
176
+ common_attributes = ATTRIBUTES.get("common", {})
187
177
 
178
+ invocation_span: Span = get_value("invocation_span_instance") # type: ignore
188
179
 
189
- 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])
190
188
 
191
189
 
192
190
  def trace_tool_call(
@@ -212,7 +210,7 @@ def trace_call_llm(
212
210
  llm_request: LlmRequest,
213
211
  llm_response: LlmResponse,
214
212
  ) -> None:
215
- span = trace.get_current_span()
213
+ span: Span = trace.get_current_span() # type: ignore
216
214
 
217
215
  from veadk.agent import Agent
218
216
 
@@ -234,6 +232,7 @@ def trace_call_llm(
234
232
  span.context.trace_state.get("call_type", "")
235
233
  if (
236
234
  hasattr(span, "context")
235
+ and span.context
237
236
  and hasattr(span.context, "trace_state")
238
237
  and hasattr(span.context.trace_state, "get")
239
238
  )
@@ -253,4 +252,8 @@ def trace_call_llm(
253
252
  response: ExtractorResponse = attr_extractor(params)
254
253
  ExtractorResponse.update_span(span, attr_name, response)
255
254
 
256
- 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/misc.py CHANGED
@@ -148,3 +148,18 @@ def set_envs(config_yaml_path: str) -> tuple[dict, dict]:
148
148
  os.environ[k] = str(v)
149
149
 
150
150
  return config_dict, veadk_environments
151
+
152
+
153
+ def get_temp_dir():
154
+ """
155
+ Return the corresponding temporary directory based on the operating system
156
+ - For Windows systems, return the system's default temporary directory
157
+ - For other systems (macOS, Linux, etc.), return the /tmp directory
158
+ """
159
+ # First determine if it is a Windows system
160
+ if sys.platform.startswith("win"):
161
+ # Windows systems use the temporary directory from environment variables
162
+ return os.environ.get("TEMP", os.environ.get("TMP", r"C:\WINDOWS\TEMP"))
163
+ else:
164
+ # Non-Windows systems (macOS, Linux, etc.) uniformly return /tmp
165
+ return "/tmp"
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.7"
15
+ VERSION = "0.2.9"