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.
- veadk/a2a/remote_ve_agent.py +63 -6
- veadk/agent.py +6 -0
- veadk/agent_builder.py +2 -3
- veadk/cli/cli.py +2 -0
- veadk/cli/cli_kb.py +75 -0
- veadk/cli/cli_prompt.py +9 -2
- veadk/cli/cli_web.py +7 -0
- veadk/configs/database_configs.py +9 -0
- veadk/consts.py +7 -0
- veadk/evaluation/adk_evaluator/adk_evaluator.py +5 -2
- veadk/evaluation/base_evaluator.py +36 -25
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +5 -3
- veadk/integrations/__init__.py +13 -0
- veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +9 -3
- veadk/integrations/ve_tls/utils.py +1 -2
- veadk/integrations/ve_tls/ve_tls.py +9 -5
- veadk/integrations/ve_tos/ve_tos.py +538 -67
- veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +293 -0
- veadk/knowledgebase/backends/base_backend.py +4 -4
- veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +162 -50
- veadk/knowledgebase/entry.py +25 -0
- veadk/knowledgebase/knowledgebase.py +19 -4
- veadk/memory/__init__.py +1 -1
- veadk/memory/long_term_memory.py +45 -7
- veadk/memory/long_term_memory_backends/mem0_backend.py +144 -0
- veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +4 -8
- veadk/memory/short_term_memory.py +9 -3
- veadk/memory/short_term_memory_backends/postgresql_backend.py +3 -1
- veadk/runner.py +34 -26
- veadk/tools/builtin_tools/generate_image.py +389 -0
- veadk/tools/builtin_tools/image_edit.py +61 -16
- veadk/tools/builtin_tools/image_generate.py +56 -15
- veadk/tools/builtin_tools/video_generate.py +41 -41
- veadk/tools/builtin_tools/web_search.py +10 -3
- veadk/tools/load_knowledgebase_tool.py +14 -8
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +6 -1
- veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +7 -0
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +82 -2
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +8 -2
- veadk/tracing/telemetry/opentelemetry_tracer.py +8 -2
- veadk/tracing/telemetry/telemetry.py +41 -5
- veadk/version.py +1 -1
- {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/METADATA +15 -4
- {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/RECORD +49 -42
- {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/WHEEL +0 -0
- {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/entry_points.txt +0 -0
- {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/licenses/LICENSE +0 -0
- {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("
|
|
36
|
-
base_url=getenv("
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
input_part
|
|
204
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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": "
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veadk-python
|
|
3
|
-
Version: 0.2.
|
|
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.
|
|
221
|
+
Requires-Dist: volcengine-python-sdk>=4.0.18
|
|
222
222
|
Requires-Dist: volcengine>=1.0.193
|
|
223
|
-
Requires-Dist: agent-pilot-sdk>=0.
|
|
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
|