veadk-python 0.2.8__py3-none-any.whl → 0.2.10__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 (49) hide show
  1. veadk/a2a/remote_ve_agent.py +63 -6
  2. veadk/agent.py +6 -0
  3. veadk/agent_builder.py +2 -3
  4. veadk/cli/cli.py +2 -0
  5. veadk/cli/cli_kb.py +75 -0
  6. veadk/cli/cli_prompt.py +9 -2
  7. veadk/cli/cli_web.py +7 -0
  8. veadk/configs/database_configs.py +9 -0
  9. veadk/consts.py +7 -0
  10. veadk/evaluation/adk_evaluator/adk_evaluator.py +5 -2
  11. veadk/evaluation/base_evaluator.py +36 -25
  12. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +5 -3
  13. veadk/integrations/__init__.py +13 -0
  14. veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +9 -3
  15. veadk/integrations/ve_tls/utils.py +1 -2
  16. veadk/integrations/ve_tls/ve_tls.py +9 -5
  17. veadk/integrations/ve_tos/ve_tos.py +538 -67
  18. veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
  19. veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +293 -0
  20. veadk/knowledgebase/backends/base_backend.py +4 -4
  21. veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +162 -50
  22. veadk/knowledgebase/entry.py +25 -0
  23. veadk/knowledgebase/knowledgebase.py +19 -4
  24. veadk/memory/__init__.py +1 -1
  25. veadk/memory/long_term_memory.py +45 -7
  26. veadk/memory/long_term_memory_backends/mem0_backend.py +144 -0
  27. veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +4 -8
  28. veadk/memory/short_term_memory.py +9 -3
  29. veadk/memory/short_term_memory_backends/postgresql_backend.py +3 -1
  30. veadk/runner.py +34 -26
  31. veadk/tools/builtin_tools/generate_image.py +389 -0
  32. veadk/tools/builtin_tools/image_edit.py +61 -16
  33. veadk/tools/builtin_tools/image_generate.py +56 -15
  34. veadk/tools/builtin_tools/video_generate.py +41 -41
  35. veadk/tools/builtin_tools/web_search.py +10 -3
  36. veadk/tools/load_knowledgebase_tool.py +14 -8
  37. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +6 -1
  38. veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +7 -0
  39. veadk/tracing/telemetry/exporters/apmplus_exporter.py +82 -2
  40. veadk/tracing/telemetry/exporters/inmemory_exporter.py +8 -2
  41. veadk/tracing/telemetry/opentelemetry_tracer.py +8 -2
  42. veadk/tracing/telemetry/telemetry.py +41 -5
  43. veadk/version.py +1 -1
  44. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/METADATA +15 -4
  45. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/RECORD +49 -42
  46. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/WHEEL +0 -0
  47. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/entry_points.txt +0 -0
  48. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/licenses/LICENSE +0 -0
  49. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.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=getenv("MODEL_AGENT_API_BASE", 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=getenv("MODEL_VIDEO_NAME", 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=getenv("MODEL_VIDEO_NAME", 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=getenv("MODEL_VIDEO_NAME", 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=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
267
+ response_model=getenv("MODEL_VIDEO_NAME", 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(
@@ -23,6 +23,7 @@ import json
23
23
  from urllib.parse import quote
24
24
 
25
25
  import requests
26
+ from google.adk.tools import ToolContext
26
27
 
27
28
  from veadk.config import getenv
28
29
  from veadk.utils.logger import get_logger
@@ -151,7 +152,7 @@ def request(method, date, query, header, ak, sk, action, body):
151
152
  return r.json()
152
153
 
153
154
 
154
- def web_search(query: str) -> list[str]:
155
+ def web_search(query: str, tool_context: ToolContext) -> list[str]:
155
156
  """Search a query in websites.
156
157
 
157
158
  Args:
@@ -166,8 +167,14 @@ def web_search(query: str) -> list[str]:
166
167
  "Count": 5,
167
168
  "NeedSummary": True,
168
169
  }
169
- ak = getenv("VOLCENGINE_ACCESS_KEY")
170
- sk = getenv("VOLCENGINE_SECRET_KEY")
170
+
171
+ ak = tool_context.state.get("VOLCENGINE_ACCESS_KEY")
172
+ if not ak:
173
+ ak = getenv("VOLCENGINE_ACCESS_KEY")
174
+
175
+ sk = tool_context.state.get("VOLCENGINE_SECRET_KEY")
176
+ if not sk:
177
+ sk = getenv("VOLCENGINE_SECRET_KEY")
171
178
 
172
179
  now = datetime.datetime.utcnow()
173
180
  response_body = request(
@@ -24,6 +24,10 @@ 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
28
+ from veadk.utils.logger import get_logger
29
+
30
+ logger = get_logger(__name__)
27
31
 
28
32
  if TYPE_CHECKING:
29
33
  from google.adk.models.llm_request import LlmRequest
@@ -32,10 +36,6 @@ if TYPE_CHECKING:
32
36
  knowledgebase: KnowledgeBase | None = None
33
37
 
34
38
 
35
- class KnowledgebaseEntry(BaseModel):
36
- content: str
37
-
38
-
39
39
  class LoadKnowledgebaseResponse(BaseModel):
40
40
  knowledges: list[KnowledgebaseEntry] = Field(default_factory=list)
41
41
 
@@ -55,10 +55,7 @@ async def search_knowledgebase(
55
55
  ) -> SearchKnowledgebaseResponse:
56
56
  """Searches the knowledgebase of the current user."""
57
57
  if isinstance(knowledgebase, KnowledgeBase):
58
- res = knowledgebase.search(query)
59
- entry_list = []
60
- for r in res:
61
- entry_list.append(KnowledgebaseEntry(content=r))
58
+ entry_list = knowledgebase.search(query)
62
59
  return SearchKnowledgebaseResponse(knowledges=entry_list)
63
60
  else:
64
61
  return SearchKnowledgebaseResponse(knowledges=[])
@@ -102,6 +99,15 @@ class LoadKnowledgebaseTool(FunctionTool):
102
99
 
103
100
  def __init__(self):
104
101
  super().__init__(load_knowledgebase)
102
+ global knowledgebase
103
+ if knowledgebase is None:
104
+ logger.info(
105
+ "Get global knowledgebase instance failed, failed to set knowledgebase tool backend."
106
+ )
107
+ else:
108
+ if not self.custom_metadata:
109
+ self.custom_metadata = {}
110
+ self.custom_metadata["backend"] = knowledgebase.backend
105
111
 
106
112
  @override
107
113
  def _get_declaration(self) -> types.FunctionDeclaration | None:
@@ -325,6 +325,10 @@ def llm_gen_ai_operation_name(params: LLMAttributesParams) -> ExtractorResponse:
325
325
  return ExtractorResponse(content="chat")
326
326
 
327
327
 
328
+ def llm_gen_ai_span_kind(params: LLMAttributesParams) -> ExtractorResponse:
329
+ return ExtractorResponse(content="llm")
330
+
331
+
328
332
  # def llm_gen_ai_system_message(params: LLMAttributesParams) -> ExtractorResponse:
329
333
  # event_attributes = {
330
334
  # "content": str(params.llm_request.config.system_instruction),
@@ -528,7 +532,7 @@ def llm_gen_ai_request_functions(params: LLMAttributesParams) -> ExtractorRespon
528
532
  f"gen_ai.request.functions.{idx}.name": tool_instance.name,
529
533
  f"gen_ai.request.functions.{idx}.description": tool_instance.description,
530
534
  f"gen_ai.request.functions.{idx}.parameters": str(
531
- tool_instance._get_declaration().parameters.model_dump( # type: ignore
535
+ tool_instance._get_declaration().parameters.model_dump_json( # type: ignore
532
536
  exclude_none=True
533
537
  )
534
538
  if tool_instance._get_declaration()
@@ -559,6 +563,7 @@ LLM_ATTRIBUTES = {
559
563
  "gen_ai.is_streaming": llm_gen_ai_is_streaming,
560
564
  # -> 1.4. span kind
561
565
  "gen_ai.operation.name": llm_gen_ai_operation_name,
566
+ "gen_ai.span.kind": llm_gen_ai_span_kind, # apmplus required
562
567
  # -> 1.5. inputs
563
568
  "gen_ai.prompt": llm_gen_ai_prompt,
564
569
  # -> 1.6. outputs
@@ -23,6 +23,10 @@ def tool_gen_ai_operation_name(params: ToolAttributesParams) -> ExtractorRespons
23
23
  return ExtractorResponse(content="execute_tool")
24
24
 
25
25
 
26
+ def tool_gen_ai_span_kind(params: ToolAttributesParams) -> ExtractorResponse:
27
+ return ExtractorResponse(content="tool")
28
+
29
+
26
30
  def tool_gen_ai_tool_message(params: ToolAttributesParams) -> ExtractorResponse:
27
31
  tool_input = {
28
32
  "role": "tool",
@@ -73,4 +77,7 @@ TOOL_ATTRIBUTES = {
73
77
  "gen_ai.tool.output": tool_gen_ai_tool_output, # TLS required
74
78
  "cozeloop.input": tool_gen_ai_tool_input, # CozeLoop required
75
79
  "cozeloop.output": tool_gen_ai_tool_output, # CozeLoop required
80
+ "gen_ai.span.kind": tool_gen_ai_span_kind, # apmplus required
81
+ "gen_ai.input": tool_gen_ai_tool_input, # apmplus required
82
+ "gen_ai.output": tool_gen_ai_tool_output, # apmplus required
76
83
  }
@@ -17,8 +17,10 @@ from dataclasses import dataclass
17
17
  from typing import Any
18
18
 
19
19
  from google.adk.agents.invocation_context import InvocationContext
20
+ from google.adk.events import Event
20
21
  from google.adk.models.llm_request import LlmRequest
21
22
  from google.adk.models.llm_response import LlmResponse
23
+ from google.adk.tools import BaseTool
22
24
  from opentelemetry import metrics, trace
23
25
  from opentelemetry import metrics as metrics_api
24
26
  from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
@@ -124,6 +126,12 @@ class Meters:
124
126
  "gen_ai.chat_completions.streaming_time_per_output_token"
125
127
  )
126
128
 
129
+ # apmplus metrics
130
+ # span duration
131
+ APMPLUS_SPAN_LATENCY = "apmplus_span_latency"
132
+ # tool token usage
133
+ APMPLUS_TOOL_TOKEN_USAGE = "apmplus_tool_token_usage"
134
+
127
135
 
128
136
  class MeterUploader:
129
137
  def __init__(
@@ -195,7 +203,21 @@ class MeterUploader:
195
203
  explicit_bucket_boundaries_advisory=_GEN_AI_SERVER_TIME_PER_OUTPUT_TOKEN_BUCKETS,
196
204
  )
197
205
 
198
- def record(
206
+ # apmplus metrics for veadk dashboard
207
+ self.apmplus_span_latency = self.meter.create_histogram(
208
+ name=Meters.APMPLUS_SPAN_LATENCY,
209
+ description="Latency of span",
210
+ unit="s",
211
+ explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_OPERATION_DURATION_BUCKETS,
212
+ )
213
+ self.apmplus_tool_token_usage = self.meter.create_histogram(
214
+ name=Meters.APMPLUS_TOOL_TOKEN_USAGE,
215
+ description="Token consumption of APMPlus tool token",
216
+ unit="count",
217
+ explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_TOKEN_USAGE_BUCKETS,
218
+ )
219
+
220
+ def record_call_llm(
199
221
  self,
200
222
  invocation_context: InvocationContext,
201
223
  event_id: str,
@@ -205,7 +227,8 @@ class MeterUploader:
205
227
  attributes = {
206
228
  "gen_ai_system": "volcengine",
207
229
  "gen_ai_response_model": llm_request.model,
208
- "gen_ai_operation_name": "chat_completions",
230
+ "gen_ai_operation_name": "chat",
231
+ "gen_ai_operation_type": "llm",
209
232
  "stream": "false",
210
233
  "server_address": "api.volcengine.com",
211
234
  } # required by Volcengine APMPlus
@@ -267,6 +290,63 @@ class MeterUploader:
267
290
  # time_per_output_token, attributes=attributes
268
291
  # )
269
292
 
293
+ # add span name attribute
294
+ span = trace.get_current_span()
295
+ if not span:
296
+ return
297
+
298
+ # record span latency
299
+ if hasattr(span, "start_time") and self.apmplus_span_latency:
300
+ # span 耗时
301
+ duration = (time.time_ns() - span.start_time) / 1e9 # type: ignore
302
+ self.apmplus_span_latency.record(duration, attributes=attributes)
303
+
304
+ def record_tool_call(
305
+ self,
306
+ tool: BaseTool,
307
+ args: dict[str, Any],
308
+ function_response_event: Event,
309
+ ):
310
+ logger.debug(f"Record tool call work in progress. Tool: {tool.name}")
311
+ span = trace.get_current_span()
312
+ if not span:
313
+ return
314
+ operation_type = "tool"
315
+ operation_name = tool.name
316
+ operation_backend = ""
317
+ if tool.custom_metadata:
318
+ operation_backend = tool.custom_metadata.get("backend", "")
319
+
320
+ attributes = {
321
+ "gen_ai_operation_name": operation_name,
322
+ "gen_ai_operation_type": operation_type,
323
+ "gen_ai_operation_backend": operation_backend,
324
+ }
325
+
326
+ if hasattr(span, "start_time") and self.apmplus_span_latency:
327
+ # span 耗时
328
+ duration = (time.time_ns() - span.start_time) / 1e9 # type: ignore
329
+ self.apmplus_span_latency.record(duration, attributes=attributes)
330
+
331
+ if self.apmplus_tool_token_usage and hasattr(span, "attributes"):
332
+ tool_input = span.attributes["gen_ai.tool.input"]
333
+ tool_token_usage_input = (
334
+ len(tool_input) / 4
335
+ ) # tool token 数量,使用文本长度/4
336
+ input_tool_token_attributes = {**attributes, "token_type": "input"}
337
+ self.apmplus_tool_token_usage.record(
338
+ tool_token_usage_input, attributes=input_tool_token_attributes
339
+ )
340
+
341
+ tool_output = span.attributes["gen_ai.tool.output"]
342
+ tool_token_usage_output = (
343
+ len(tool_output) / 4
344
+ ) # tool token 数量,使用文本长度/4
345
+ output_tool_token_attributes = {**attributes, "token_type": "output"}
346
+ self.apmplus_tool_token_usage.record(
347
+ tool_token_usage_output, attributes=output_tool_token_attributes
348
+ )
349
+
270
350
 
271
351
  class APMPlusExporterConfig(BaseModel):
272
352
  endpoint: str = Field(
@@ -79,14 +79,20 @@ class _InMemorySpanProcessor(export.SimpleSpanProcessor):
79
79
  def on_start(self, span, parent_context) -> None:
80
80
  if span.name.startswith("invocation"):
81
81
  span.set_attribute("gen_ai.operation.name", "chain")
82
+ span.set_attribute("gen_ai.span.kind", "workflow")
82
83
  span.set_attribute("gen_ai.usage.total_tokens", 0)
83
-
84
84
  ctx = set_value("invocation_span_instance", span, context=parent_context)
85
+ # suppress instrumentation for llm to avoid auto instrument from apmplus, such as openai
86
+ ctx = set_value(
87
+ "suppress_language_model_instrumentation", True, context=ctx
88
+ )
89
+
85
90
  token = attach(ctx) # mount context on `invocation` root span in Google ADK
86
91
  setattr(span, "_invocation_token", token) # for later detach
87
92
 
88
- if span.name.startswith("agent_run"):
93
+ if span.name.startswith("agent_run") or span.name.startswith("invoke_agent"):
89
94
  span.set_attribute("gen_ai.operation.name", "agent")
95
+ span.set_attribute("gen_ai.span.kind", "agent")
90
96
 
91
97
  ctx = set_value("agent_run_span_instance", span, context=parent_context)
92
98
  token = attach(ctx)
@@ -21,7 +21,7 @@ from typing import Any
21
21
  from opentelemetry import trace as trace_api
22
22
  from opentelemetry.sdk import trace as trace_sdk
23
23
  from opentelemetry.sdk.resources import Resource
24
- from opentelemetry.sdk.trace import TracerProvider
24
+ from opentelemetry.sdk.trace import TracerProvider, SpanLimits
25
25
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
26
26
  from pydantic import BaseModel, ConfigDict, Field, field_validator
27
27
  from typing_extensions import override
@@ -79,7 +79,13 @@ class OpentelemetryTracer(BaseModel, BaseTracer):
79
79
 
80
80
  def _init_global_tracer_provider(self) -> None:
81
81
  # set provider anyway, then get global provider
82
- 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
+ )
83
89
  global_tracer_provider: TracerProvider = trace_api.get_tracer_provider() # type: ignore
84
90
 
85
91
  span_processors = global_tracer_provider._active_span_processor._span_processors
@@ -30,11 +30,14 @@ from veadk.tracing.telemetry.attributes.extractors.types import (
30
30
  ToolAttributesParams,
31
31
  )
32
32
  from veadk.utils.logger import get_logger
33
+ from veadk.utils.misc import safe_json_serialize
33
34
 
34
35
  logger = get_logger(__name__)
35
36
 
37
+ meter_uploader = None
36
38
 
37
- def _upload_metrics(
39
+
40
+ def _upload_call_llm_metrics(
38
41
  invocation_context: InvocationContext,
39
42
  event_id: str,
40
43
  llm_request: LlmRequest,
@@ -47,11 +50,27 @@ def _upload_metrics(
47
50
  for tracer in tracers:
48
51
  for exporter in getattr(tracer, "exporters", []):
49
52
  if getattr(exporter, "meter_uploader", None):
50
- exporter.meter_uploader.record(
53
+ global meter_uploader
54
+ meter_uploader = exporter.meter_uploader
55
+ exporter.meter_uploader.record_call_llm(
51
56
  invocation_context, event_id, llm_request, llm_response
52
57
  )
53
58
 
54
59
 
60
+ def _upload_tool_call_metrics(
61
+ tool: BaseTool,
62
+ args: dict[str, Any],
63
+ function_response_event: Event,
64
+ ):
65
+ global meter_uploader
66
+ if meter_uploader:
67
+ meter_uploader.record_tool_call(tool, args, function_response_event)
68
+ else:
69
+ logger.warning(
70
+ "Meter uploader is not initialized yet. Skip recording tool call metrics."
71
+ )
72
+
73
+
55
74
  def _set_agent_input_attribute(
56
75
  span: Span, invocation_context: InvocationContext
57
76
  ) -> None:
@@ -73,6 +92,11 @@ def _set_agent_input_attribute(
73
92
 
74
93
  user_content = invocation_context.user_content
75
94
  if user_content and user_content.parts:
95
+ # set gen_ai.input attribute required by APMPlus
96
+ span.set_attribute(
97
+ "gen_ai.input",
98
+ safe_json_serialize(user_content.model_dump(exclude_none=True)),
99
+ )
76
100
  span.add_event(
77
101
  "gen_ai.user.message",
78
102
  {
@@ -110,6 +134,11 @@ def _set_agent_input_attribute(
110
134
  def _set_agent_output_attribute(span: Span, llm_response: LlmResponse) -> None:
111
135
  content = llm_response.content
112
136
  if content and content.parts:
137
+ # set gen_ai.output attribute required by APMPlus
138
+ span.set_attribute(
139
+ "gen_ai.output", safe_json_serialize(content.model_dump(exclude_none=True))
140
+ )
141
+
113
142
  for idx, part in enumerate(content.parts):
114
143
  if part.text:
115
144
  span.add_event(
@@ -155,10 +184,15 @@ def set_common_attributes_on_model_span(
155
184
  current_step_token_usage + int(prev_total_token_usage) # type: ignore
156
185
  ) # we can ignore this warning, cause we manually set the attribute to int before
157
186
  invocation_span.set_attribute(
158
- "gen_ai.usage.total_tokens", accumulated_total_token_usage
187
+ # record input/output token usage?
188
+ "gen_ai.usage.total_tokens",
189
+ accumulated_total_token_usage,
159
190
  )
160
191
 
161
- if agent_run_span and agent_run_span.name.startswith("agent_run"):
192
+ if agent_run_span and (
193
+ agent_run_span.name.startswith("agent_run")
194
+ or agent_run_span.name.startswith("invoke_agent")
195
+ ):
162
196
  _set_agent_input_attribute(agent_run_span, invocation_context)
163
197
  _set_agent_output_attribute(agent_run_span, llm_response)
164
198
  for attr_name, attr_extractor in common_attributes.items():
@@ -203,6 +237,8 @@ def trace_tool_call(
203
237
  response: ExtractorResponse = attr_extractor(params)
204
238
  ExtractorResponse.update_span(span, attr_name, response)
205
239
 
240
+ _upload_tool_call_metrics(tool, args, function_response_event)
241
+
206
242
 
207
243
  def trace_call_llm(
208
244
  invocation_context: InvocationContext,
@@ -252,7 +288,7 @@ def trace_call_llm(
252
288
  response: ExtractorResponse = attr_extractor(params)
253
289
  ExtractorResponse.update_span(span, attr_name, response)
254
290
 
255
- _upload_metrics(invocation_context, event_id, llm_request, llm_response)
291
+ _upload_call_llm_metrics(invocation_context, event_id, llm_request, llm_response)
256
292
 
257
293
 
258
294
  # Do not modify this function
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.8"
15
+ VERSION = "0.2.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veadk-python
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: Volcengine agent development kit, integrations with Volcengine cloud services.
5
5
  Author-email: Yaozheng Fang <fangyozheng@gmail.com>, Guodong Li <cu.eric.lee@gmail.com>, Zhi Han <sliverydayday@gmail.com>, Meng Wang <mengwangwm@gmail.com>
6
6
  License: Apache License
@@ -218,9 +218,9 @@ Requires-Dist: opentelemetry-exporter-otlp>=1.35.0
218
218
  Requires-Dist: opentelemetry-instrumentation-logging>=0.56b0
219
219
  Requires-Dist: wrapt>=1.17.2
220
220
  Requires-Dist: openai<1.100
221
- Requires-Dist: volcengine-python-sdk>=4.0.3
221
+ Requires-Dist: volcengine-python-sdk>=4.0.18
222
222
  Requires-Dist: volcengine>=1.0.193
223
- Requires-Dist: agent-pilot-sdk>=0.0.9
223
+ Requires-Dist: agent-pilot-sdk>=0.1.2
224
224
  Requires-Dist: fastmcp>=2.11.3
225
225
  Requires-Dist: cookiecutter>=2.6.0
226
226
  Requires-Dist: omegaconf>=2.3.0
@@ -235,12 +235,12 @@ Provides-Extra: extensions
235
235
  Requires-Dist: redis>=5.0; extra == "extensions"
236
236
  Requires-Dist: tos>=2.8.4; extra == "extensions"
237
237
  Requires-Dist: llama-index-vector-stores-redis>=0.6.1; extra == "extensions"
238
- Requires-Dist: mcp-server-vikingdb-memory; extra == "extensions"
239
238
  Provides-Extra: database
240
239
  Requires-Dist: redis>=5.0; extra == "database"
241
240
  Requires-Dist: pymysql>=1.1.1; extra == "database"
242
241
  Requires-Dist: volcengine>=1.0.193; extra == "database"
243
242
  Requires-Dist: tos>=2.8.4; extra == "database"
243
+ Requires-Dist: mem0ai==0.1.118; extra == "database"
244
244
  Provides-Extra: eval
245
245
  Requires-Dist: prometheus-client>=0.22.1; extra == "eval"
246
246
  Requires-Dist: deepeval>=3.2.6; extra == "eval"
@@ -270,6 +270,17 @@ A [tutorial](https://github.com/volcengine/veadk-python/blob/main/veadk_tutorial
270
270
 
271
271
  ## Installation
272
272
 
273
+ ### From PyPI
274
+
275
+ ```python
276
+ pip install veadk-python
277
+
278
+ # install extensions
279
+ pip install veadk-python[extensions]
280
+ ```
281
+
282
+ ### Build from source
283
+
273
284
  We use `uv` to build this project ([how-to-install-uv](https://docs.astral.sh/uv/getting-started/installation/)).
274
285
 
275
286
  ```bash