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.
- veadk/agent.py +3 -2
- veadk/auth/veauth/opensearch_veauth.py +75 -0
- veadk/auth/veauth/postgresql_veauth.py +75 -0
- veadk/cli/cli.py +3 -1
- veadk/cli/cli_eval.py +160 -0
- veadk/cli/cli_prompt.py +9 -2
- veadk/cli/cli_web.py +6 -1
- veadk/configs/database_configs.py +43 -0
- veadk/configs/model_configs.py +32 -0
- veadk/consts.py +11 -4
- veadk/evaluation/adk_evaluator/adk_evaluator.py +5 -2
- veadk/evaluation/base_evaluator.py +95 -68
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +23 -15
- veadk/evaluation/eval_set_recorder.py +2 -2
- 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 +542 -68
- veadk/knowledgebase/backends/base_backend.py +59 -0
- veadk/knowledgebase/backends/in_memory_backend.py +82 -0
- veadk/knowledgebase/backends/opensearch_backend.py +136 -0
- veadk/knowledgebase/backends/redis_backend.py +144 -0
- veadk/knowledgebase/backends/utils.py +91 -0
- veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +524 -0
- veadk/{database/__init__.py → knowledgebase/entry.py} +10 -2
- veadk/knowledgebase/knowledgebase.py +120 -139
- veadk/memory/__init__.py +22 -0
- veadk/memory/long_term_memory.py +124 -41
- veadk/{database/base_database.py → memory/long_term_memory_backends/base_backend.py} +10 -22
- veadk/memory/long_term_memory_backends/in_memory_backend.py +65 -0
- veadk/memory/long_term_memory_backends/mem0_backend.py +129 -0
- veadk/memory/long_term_memory_backends/opensearch_backend.py +120 -0
- veadk/memory/long_term_memory_backends/redis_backend.py +127 -0
- veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +148 -0
- veadk/memory/short_term_memory.py +80 -72
- veadk/memory/short_term_memory_backends/base_backend.py +31 -0
- veadk/memory/short_term_memory_backends/mysql_backend.py +41 -0
- veadk/memory/short_term_memory_backends/postgresql_backend.py +41 -0
- veadk/memory/short_term_memory_backends/sqlite_backend.py +48 -0
- veadk/runner.py +12 -19
- veadk/tools/builtin_tools/generate_image.py +355 -0
- veadk/tools/builtin_tools/image_edit.py +56 -16
- veadk/tools/builtin_tools/image_generate.py +51 -15
- veadk/tools/builtin_tools/video_generate.py +41 -41
- veadk/tools/builtin_tools/web_scraper.py +1 -1
- veadk/tools/builtin_tools/web_search.py +7 -7
- veadk/tools/load_knowledgebase_tool.py +2 -8
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +21 -3
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +24 -6
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +2 -0
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +22 -8
- veadk/tracing/telemetry/exporters/tls_exporter.py +2 -0
- veadk/tracing/telemetry/opentelemetry_tracer.py +13 -10
- veadk/tracing/telemetry/telemetry.py +66 -63
- veadk/utils/misc.py +15 -0
- veadk/version.py +1 -1
- {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/METADATA +28 -5
- {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/RECORD +65 -56
- veadk/database/database_adapter.py +0 -533
- veadk/database/database_factory.py +0 -80
- veadk/database/kv/redis_database.py +0 -159
- veadk/database/local_database.py +0 -62
- veadk/database/relational/mysql_database.py +0 -173
- veadk/database/vector/opensearch_vector_database.py +0 -263
- veadk/database/vector/type.py +0 -50
- veadk/database/viking/__init__.py +0 -13
- veadk/database/viking/viking_database.py +0 -638
- veadk/database/viking/viking_memory_db.py +0 -525
- /veadk/{database/kv → knowledgebase/backends}/__init__.py +0 -0
- /veadk/{database/relational → memory/long_term_memory_backends}/__init__.py +0 -0
- /veadk/{database/vector → memory/short_term_memory_backends}/__init__.py +0 -0
- {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/WHEEL +0 -0
- {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/entry_points.txt +0 -0
- {veadk_python-0.2.7.dist-info → veadk_python-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {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("
|
|
36
|
-
base_url=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
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=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(
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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.
|
|
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(
|
|
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
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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.
|
|
82
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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