veadk-python 0.1.0__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 (110) hide show
  1. veadk/__init__.py +31 -0
  2. veadk/a2a/__init__.py +13 -0
  3. veadk/a2a/agent_card.py +45 -0
  4. veadk/a2a/remote_ve_agent.py +19 -0
  5. veadk/a2a/ve_a2a_server.py +77 -0
  6. veadk/a2a/ve_agent_executor.py +78 -0
  7. veadk/a2a/ve_task_store.py +37 -0
  8. veadk/agent.py +253 -0
  9. veadk/cli/__init__.py +13 -0
  10. veadk/cli/main.py +278 -0
  11. veadk/cli/services/agentpilot/__init__.py +17 -0
  12. veadk/cli/services/agentpilot/agentpilot.py +77 -0
  13. veadk/cli/services/veapig/__init__.py +17 -0
  14. veadk/cli/services/veapig/apig.py +224 -0
  15. veadk/cli/services/veapig/apig_utils.py +332 -0
  16. veadk/cli/services/vefaas/__init__.py +17 -0
  17. veadk/cli/services/vefaas/template/deploy.py +44 -0
  18. veadk/cli/services/vefaas/template/src/app.py +30 -0
  19. veadk/cli/services/vefaas/template/src/config.py +58 -0
  20. veadk/cli/services/vefaas/vefaas.py +346 -0
  21. veadk/cli/services/vefaas/vefaas_utils.py +408 -0
  22. veadk/cli/services/vetls/__init__.py +17 -0
  23. veadk/cli/services/vetls/vetls.py +87 -0
  24. veadk/cli/studio/__init__.py +13 -0
  25. veadk/cli/studio/agent_processor.py +247 -0
  26. veadk/cli/studio/fast_api.py +232 -0
  27. veadk/cli/studio/model.py +116 -0
  28. veadk/cloud/__init__.py +13 -0
  29. veadk/cloud/cloud_agent_engine.py +144 -0
  30. veadk/cloud/cloud_app.py +123 -0
  31. veadk/cloud/template/app.py +30 -0
  32. veadk/cloud/template/config.py +55 -0
  33. veadk/config.py +131 -0
  34. veadk/consts.py +17 -0
  35. veadk/database/__init__.py +17 -0
  36. veadk/database/base_database.py +45 -0
  37. veadk/database/database_factory.py +80 -0
  38. veadk/database/kv/__init__.py +13 -0
  39. veadk/database/kv/redis_database.py +109 -0
  40. veadk/database/local_database.py +43 -0
  41. veadk/database/relational/__init__.py +13 -0
  42. veadk/database/relational/mysql_database.py +114 -0
  43. veadk/database/vector/__init__.py +13 -0
  44. veadk/database/vector/opensearch_vector_database.py +205 -0
  45. veadk/database/vector/type.py +50 -0
  46. veadk/database/viking/__init__.py +13 -0
  47. veadk/database/viking/viking_database.py +378 -0
  48. veadk/database/viking/viking_memory_db.py +521 -0
  49. veadk/evaluation/__init__.py +17 -0
  50. veadk/evaluation/adk_evaluator/__init__.py +13 -0
  51. veadk/evaluation/adk_evaluator/adk_evaluator.py +291 -0
  52. veadk/evaluation/base_evaluator.py +242 -0
  53. veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
  54. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +223 -0
  55. veadk/evaluation/eval_set_file_loader.py +28 -0
  56. veadk/evaluation/eval_set_recorder.py +91 -0
  57. veadk/evaluation/utils/prometheus.py +142 -0
  58. veadk/knowledgebase/__init__.py +17 -0
  59. veadk/knowledgebase/knowledgebase.py +83 -0
  60. veadk/knowledgebase/knowledgebase_database_adapter.py +259 -0
  61. veadk/memory/__init__.py +13 -0
  62. veadk/memory/long_term_memory.py +119 -0
  63. veadk/memory/memory_database_adapter.py +235 -0
  64. veadk/memory/short_term_memory.py +124 -0
  65. veadk/memory/short_term_memory_processor.py +90 -0
  66. veadk/prompts/__init__.py +13 -0
  67. veadk/prompts/agent_default_prompt.py +30 -0
  68. veadk/prompts/prompt_evaluator.py +20 -0
  69. veadk/prompts/prompt_memory_processor.py +55 -0
  70. veadk/prompts/prompt_optimization.py +158 -0
  71. veadk/runner.py +252 -0
  72. veadk/tools/__init__.py +13 -0
  73. veadk/tools/builtin_tools/__init__.py +13 -0
  74. veadk/tools/builtin_tools/lark.py +67 -0
  75. veadk/tools/builtin_tools/las.py +23 -0
  76. veadk/tools/builtin_tools/vesearch.py +49 -0
  77. veadk/tools/builtin_tools/web_scraper.py +76 -0
  78. veadk/tools/builtin_tools/web_search.py +192 -0
  79. veadk/tools/demo_tools.py +58 -0
  80. veadk/tools/load_knowledgebase_tool.py +144 -0
  81. veadk/tools/sandbox/__init__.py +13 -0
  82. veadk/tools/sandbox/browser_sandbox.py +27 -0
  83. veadk/tools/sandbox/code_sandbox.py +30 -0
  84. veadk/tools/sandbox/computer_sandbox.py +27 -0
  85. veadk/tracing/__init__.py +13 -0
  86. veadk/tracing/base_tracer.py +172 -0
  87. veadk/tracing/telemetry/__init__.py +13 -0
  88. veadk/tracing/telemetry/exporters/__init__.py +13 -0
  89. veadk/tracing/telemetry/exporters/apiserver_exporter.py +60 -0
  90. veadk/tracing/telemetry/exporters/apmplus_exporter.py +101 -0
  91. veadk/tracing/telemetry/exporters/base_exporter.py +28 -0
  92. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +69 -0
  93. veadk/tracing/telemetry/exporters/inmemory_exporter.py +88 -0
  94. veadk/tracing/telemetry/exporters/tls_exporter.py +78 -0
  95. veadk/tracing/telemetry/metrics/__init__.py +13 -0
  96. veadk/tracing/telemetry/metrics/opentelemetry_metrics.py +73 -0
  97. veadk/tracing/telemetry/opentelemetry_tracer.py +167 -0
  98. veadk/types.py +23 -0
  99. veadk/utils/__init__.py +13 -0
  100. veadk/utils/logger.py +59 -0
  101. veadk/utils/misc.py +33 -0
  102. veadk/utils/patches.py +85 -0
  103. veadk/utils/volcengine_sign.py +199 -0
  104. veadk/version.py +15 -0
  105. veadk_python-0.1.0.dist-info/METADATA +124 -0
  106. veadk_python-0.1.0.dist-info/RECORD +110 -0
  107. veadk_python-0.1.0.dist-info/WHEEL +5 -0
  108. veadk_python-0.1.0.dist-info/entry_points.txt +2 -0
  109. veadk_python-0.1.0.dist-info/licenses/LICENSE +201 -0
  110. veadk_python-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ def get_city_weather(city: str) -> dict[str, str]:
17
+ """Retrieves the weather information of a given city. the args must in English"""
18
+ fixed_weather = {
19
+ "beijing": {"condition": "Sunny", "temperature": 25},
20
+ "shanghai": {"condition": "Cloudy", "temperature": 22},
21
+ "guangzhou": {"condition": "Rainy", "temperature": 28},
22
+ "shenzhen": {"condition": "Partly cloudy", "temperature": 29},
23
+ "chengdu": {"condition": "Windy", "temperature": 20},
24
+ "hangzhou": {"condition": "Snowy", "temperature": -2},
25
+ "wuhan": {"condition": "Humid", "temperature": 26},
26
+ "chongqing": {"condition": "Hazy", "temperature": 30},
27
+ "xi'an": {"condition": "Cool", "temperature": 18},
28
+ "nanjing": {"condition": "Hot", "temperature": 32},
29
+ }
30
+
31
+ city = city.lower().strip()
32
+ if city in fixed_weather:
33
+ info = fixed_weather[city]
34
+ return {"result": f"{info['condition']}, {info['temperature']}°C"}
35
+ else:
36
+ return {"result": f"Weather information not found for {city}"}
37
+
38
+
39
+ def get_location_weather(city: str) -> dict[str, str]:
40
+ """Retrieves the weather information of a given city. the args must in English"""
41
+ import random
42
+
43
+ condition = random.choice(
44
+ [
45
+ "Sunny",
46
+ "Cloudy",
47
+ "Rainy",
48
+ "Partly cloudy",
49
+ "Windy",
50
+ "Snowy",
51
+ "Humid",
52
+ "Hazy",
53
+ "Cool",
54
+ "Hot",
55
+ ]
56
+ )
57
+ temperature = random.randint(-10, 40)
58
+ return {"result": f"{condition}, {temperature}°C"}
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING
18
+
19
+ import wrapt
20
+ from google.adk.tools.function_tool import FunctionTool
21
+ from google.adk.tools.tool_context import ToolContext
22
+ from google.genai import types
23
+ from pydantic import BaseModel, Field
24
+ from typing_extensions import override
25
+
26
+ from veadk.knowledgebase import KnowledgeBase
27
+
28
+ if TYPE_CHECKING:
29
+ from google.adk.models import LlmRequest
30
+
31
+
32
+ knowledgebase: KnowledgeBase | None = None
33
+
34
+
35
+ class KnowledgebaseEntry(BaseModel):
36
+ content: str
37
+
38
+
39
+ class LoadKnowledgebaseResponse(BaseModel):
40
+ knowledges: list[KnowledgebaseEntry] = Field(default_factory=list)
41
+
42
+
43
+ class SearchKnowledgebaseResponse(BaseModel):
44
+ """Represents the response from a knowledgebase search.
45
+
46
+ Attributes:
47
+ knowledges: A list of knowledgebase entries that relate to the search query.
48
+ """
49
+
50
+ knowledges: list[KnowledgebaseEntry] = Field(default_factory=list)
51
+
52
+
53
+ async def search_knowledgebase(
54
+ self, query: str, app_name: str, user_id: str
55
+ ) -> SearchKnowledgebaseResponse:
56
+ """Searches the knowledgebase of the current user."""
57
+ if isinstance(knowledgebase, KnowledgeBase):
58
+ res = knowledgebase.search(query, app_name=app_name, user_id=user_id)
59
+ entry_list = []
60
+ for r in res:
61
+ entry_list.append(KnowledgebaseEntry(content=r))
62
+ return SearchKnowledgebaseResponse(knowledges=entry_list)
63
+ else:
64
+ return SearchKnowledgebaseResponse(knowledges=[])
65
+
66
+
67
+ @wrapt.when_imported("google.adk.tools.tool_context")
68
+ def on_tool_context_imported(module):
69
+ class_ = getattr(module, "ToolContext", None)
70
+ if not class_:
71
+ raise ImportError("Could not find ToolContext in module")
72
+
73
+ if not hasattr(class_, "search_knowledgebase"):
74
+ class_.search_knowledgebase = search_knowledgebase
75
+
76
+
77
+ async def load_knowledgebase(
78
+ query: str, tool_context: ToolContext
79
+ ) -> LoadKnowledgebaseResponse:
80
+ """Loads the knowledgebase for the user.
81
+
82
+ Args:
83
+ query: The query to load the knowledgebase for.
84
+
85
+ Returns:
86
+ A list of knowledgebase results.
87
+ """
88
+ search_knowledgebase_response = await tool_context.search_knowledgebase(
89
+ query,
90
+ tool_context._invocation_context.app_name,
91
+ tool_context._invocation_context.user_id,
92
+ )
93
+ return LoadKnowledgebaseResponse(
94
+ knowledges=search_knowledgebase_response.knowledges
95
+ )
96
+
97
+
98
+ class LoadKnowledgebaseTool(FunctionTool):
99
+ """A tool that loads the common knowledgebase.
100
+
101
+ In the future, we will support multiple knowledgebase based on different user.
102
+ """
103
+
104
+ def __init__(self):
105
+ super().__init__(load_knowledgebase)
106
+
107
+ @override
108
+ def _get_declaration(self) -> types.FunctionDeclaration | None:
109
+ return types.FunctionDeclaration(
110
+ name=self.name,
111
+ description=self.description,
112
+ parameters=types.Schema(
113
+ type=types.Type.OBJECT,
114
+ properties={
115
+ "query": types.Schema(
116
+ type=types.Type.STRING,
117
+ )
118
+ },
119
+ required=["query"],
120
+ ),
121
+ )
122
+
123
+ @override
124
+ async def process_llm_request(
125
+ self,
126
+ *,
127
+ tool_context: ToolContext,
128
+ llm_request: LlmRequest,
129
+ ) -> None:
130
+ await super().process_llm_request(
131
+ tool_context=tool_context, llm_request=llm_request
132
+ )
133
+ # Tell the model about the knowledgebase.
134
+ llm_request.append_instructions(
135
+ [
136
+ """
137
+ You have a knowledgebase. You can use it to answer questions. If any questions need
138
+ you to look up the knowledgebase, you should call load_knowledgebase function with a query.
139
+ """
140
+ ]
141
+ )
142
+
143
+
144
+ load_knowledgebase_tool = LoadKnowledgebaseTool()
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ browser_sandbox = ...
16
+
17
+
18
+ def browser_use(prompt: str) -> str:
19
+ """Using the remote browser sandbox to according to the prompt.
20
+
21
+ Args:
22
+ prompt (str): The prompt to be used.
23
+
24
+ Returns:
25
+ str: The response from the sandbox.
26
+ """
27
+ ...
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ code_sandbox = ...
16
+
17
+
18
+ def code_execution(code: str, language: str) -> str:
19
+ """Execute code in sandbox.
20
+
21
+ Args:
22
+ code (str): The code to be executed.
23
+ language (str): The language of the code.
24
+
25
+ Returns:
26
+ str: The response from the sandbox.
27
+ """
28
+
29
+ res = code_sandbox(code, language)
30
+ return res
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ computer_sandbox = ...
16
+
17
+
18
+ def computer_use(prompt: str) -> str:
19
+ """Using the remote computer sandbox to according to the prompt.
20
+
21
+ Args:
22
+ prompt (str): The prompt to be used.
23
+
24
+ Returns:
25
+ str: The response from the sandbox.
26
+ """
27
+ ...
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,172 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ from abc import ABC, abstractmethod
17
+ from typing import Optional
18
+
19
+ from google.adk.agents.callback_context import CallbackContext
20
+ from google.adk.models import LlmRequest, LlmResponse
21
+ from opentelemetry import trace
22
+
23
+ from veadk.utils.logger import get_logger
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ class BaseTracer(ABC):
29
+ def __init__(self, name: str):
30
+ pass
31
+
32
+ @abstractmethod
33
+ def dump(self) -> str: ...
34
+
35
+ def llm_metrics_hook(
36
+ self, callback_context: CallbackContext, llm_request: LlmRequest
37
+ ) -> Optional[LlmResponse]:
38
+ trace.get_tracer("gcp.vertex.agent")
39
+ span = trace.get_current_span()
40
+ # logger.debug(f"llm_request: {llm_request}")
41
+
42
+ req = llm_request.model_dump()
43
+
44
+ model_name = req.get("model", "unknown")
45
+ max_tokens = (
46
+ None
47
+ if not req.get("live_connect_config")
48
+ else req["live_connect_config"].get("max_output_tokens", None)
49
+ )
50
+ temperature = (
51
+ None
52
+ if not req.get("live_connect_config")
53
+ else req["live_connect_config"].get("temperature", None)
54
+ )
55
+ top_p = (
56
+ None
57
+ if not req.get("live_connect_config")
58
+ else req["live_connect_config"].get("top_p", None)
59
+ )
60
+
61
+ attributes = {}
62
+ attributes["gen_ai.system"] = "veadk"
63
+ if model_name:
64
+ attributes["gen_ai.request.model"] = model_name
65
+ attributes["gen_ai.response.model"] = (
66
+ model_name # The req model and the resp model should be consistent.
67
+ )
68
+ attributes["gen_ai.request.type"] = "completion"
69
+ if max_tokens:
70
+ attributes["gen_ai.request.max_tokens"] = max_tokens
71
+ if temperature:
72
+ attributes["gen_ai.request.temperature"] = temperature
73
+ if top_p:
74
+ attributes["gen_ai.request.top_p"] = top_p
75
+
76
+ # Print attributes for debugging
77
+ # print("Tracing attributes:", attributes)
78
+
79
+ # Set all attributes at once if possible, else fallback to individual
80
+ if hasattr(span, "set_attributes"):
81
+ span.set_attributes(attributes)
82
+ else:
83
+ # Fallback for OpenTelemetry versions without set_attributes
84
+ for k, v in attributes.items():
85
+ span.set_attribute(k, v)
86
+
87
+ def token_metrics_hook(
88
+ self, callback_context: CallbackContext, llm_response: LlmResponse
89
+ ) -> Optional[LlmResponse]:
90
+ trace.get_tracer("gcp.vertex.agent")
91
+ span = trace.get_current_span()
92
+ # logger.debug(f"llm_response: {llm_response}")
93
+ # logger.debug(f"callback_context: {callback_context}")
94
+
95
+ # Refined: collect all attributes, use set_attributes, print for debugging
96
+ attributes = {}
97
+
98
+ # prompt
99
+ user_content = callback_context.user_content
100
+ if getattr(user_content, "role", None):
101
+ role = getattr(user_content, "role", None)
102
+ else:
103
+ role = None
104
+ if getattr(user_content, "parts", None):
105
+ content = callback_context.user_content.model_dump(exclude_none=True).get(
106
+ "parts", None
107
+ )
108
+ if content:
109
+ content = json.dumps(content)
110
+ else:
111
+ content = None
112
+ else:
113
+ content = None
114
+ if role and content:
115
+ attributes["gen_ai.prompt.0.role"] = role
116
+ attributes["gen_ai.prompt.0.content"] = content
117
+
118
+ # completion
119
+ completion_content = getattr(llm_response, "content").model_dump(
120
+ exclude_none=True
121
+ )
122
+ if completion_content:
123
+ content = json.dumps(
124
+ getattr(llm_response, "content").model_dump(exclude_none=True)["parts"]
125
+ )
126
+ role = getattr(llm_response, "content").model_dump(exclude_none=True)[
127
+ "role"
128
+ ]
129
+ if role and content:
130
+ attributes["gen_ai.completion.0.role"] = role
131
+ attributes["gen_ai.completion.0.content"] = content
132
+
133
+ if not llm_response.usage_metadata:
134
+ return
135
+
136
+ # tokens
137
+ metadata = llm_response.usage_metadata.model_dump()
138
+ if metadata:
139
+ prompt_tokens = metadata.get("prompt_token_count", None)
140
+ completion_tokens = metadata.get("candidates_token_count", None)
141
+ total_tokens = metadata.get("total_token_count", None)
142
+ cache_read_input_tokens = (
143
+ metadata.get("cache_read_input_tokens") or 0
144
+ ) # Might change, once openai introduces their equivalent.
145
+ cache_create_input_tokens = (
146
+ metadata.get("cache_create_input_tokens") or 0
147
+ ) # Might change, once openai introduces their equivalent.
148
+ if prompt_tokens:
149
+ attributes["gen_ai.usage.prompt_tokens"] = prompt_tokens
150
+ if completion_tokens:
151
+ attributes["gen_ai.usage.completion_tokens"] = completion_tokens
152
+ if total_tokens:
153
+ attributes["gen_ai.usage.total_tokens"] = total_tokens
154
+ if cache_read_input_tokens is not None:
155
+ attributes["gen_ai.usage.cache_read_input_tokens"] = (
156
+ cache_read_input_tokens
157
+ )
158
+ if cache_create_input_tokens is not None:
159
+ attributes["gen_ai.usage.cache_create_input_tokens"] = (
160
+ cache_create_input_tokens
161
+ )
162
+
163
+ # Print attributes for debugging
164
+ # print("Tracing attributes:", attributes)
165
+
166
+ # Set all attributes at once if possible, else fallback to individual
167
+ if hasattr(span, "set_attributes"):
168
+ span.set_attributes(attributes)
169
+ else:
170
+ # Fallback for OpenTelemetry versions without set_attributes
171
+ for k, v in attributes.items():
172
+ span.set_attribute(k, v)
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import typing
16
+
17
+ from opentelemetry.sdk.trace import ReadableSpan, export
18
+ from pydantic import BaseModel
19
+ from typing_extensions import override
20
+
21
+ from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
22
+
23
+
24
+ # ======== Adapted from Google ADK ========
25
+ class _ApiServerSpanExporter(export.SpanExporter):
26
+ def __init__(self, trace_dict):
27
+ self.trace_dict = trace_dict
28
+ self.trace_id = ""
29
+
30
+ def export(self, spans: typing.Sequence[ReadableSpan]) -> export.SpanExportResult:
31
+ for span in spans:
32
+ if (
33
+ span.name == "call_llm"
34
+ or span.name == "send_data"
35
+ or span.name.startswith("execute_tool")
36
+ ):
37
+ attributes = dict(span.attributes)
38
+ attributes["trace_id"] = span.get_span_context().trace_id
39
+ attributes["span_id"] = span.get_span_context().span_id
40
+ if attributes.get("gcp.vertex.agent.event_id", None):
41
+ self.trace_dict[attributes["gcp.vertex.agent.event_id"]] = (
42
+ attributes
43
+ )
44
+ return export.SpanExportResult.SUCCESS
45
+
46
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
47
+ return True
48
+
49
+
50
+ class ApiServerExporter(BaseModel, BaseExporter):
51
+ name: str = "apiserver_exporter"
52
+ trace_dict: dict = {}
53
+
54
+ def model_post_init(self, context) -> None:
55
+ self._exporter = _ApiServerSpanExporter(self.trace_dict)
56
+
57
+ @override
58
+ def get_processor(self):
59
+ processor = export.SimpleSpanProcessor(self._exporter)
60
+ return processor, None
@@ -0,0 +1,101 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from opentelemetry import metrics
16
+ from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
17
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
18
+ from opentelemetry.sdk.metrics import MeterProvider
19
+ from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
20
+ from opentelemetry.sdk.resources import Resource
21
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
22
+ from pydantic import BaseModel, Field
23
+ from typing_extensions import override
24
+
25
+ from veadk.config import getenv
26
+ from veadk.tracing.telemetry.exporters.base_exporter import BaseExporter
27
+ from veadk.tracing.telemetry.metrics.opentelemetry_metrics import MeterContext
28
+ from veadk.utils.logger import get_logger
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ class APMPlusExporterConfig(BaseModel):
34
+ endpoint: str = Field(
35
+ ...,
36
+ default_factory=lambda: getenv(
37
+ "OBSERVABILITY_OPENTELEMETRY_APMPLUS_ENDPOINT",
38
+ "http://apmplus-cn-beijing.volces.com:4317",
39
+ ),
40
+ )
41
+ app_key: str = Field(
42
+ ...,
43
+ default_factory=lambda: getenv("OBSERVABILITY_OPENTELEMETRY_APMPLUS_API_KEY"),
44
+ )
45
+ service_name: str = Field(
46
+ ...,
47
+ default_factory=lambda: getenv(
48
+ "OBSERVABILITY_OPENTELEMETRY_APMPLUS_SERVICE_NAME",
49
+ "veadk_tracing_service",
50
+ ),
51
+ description="Service name shown in APMPlus frontend.",
52
+ )
53
+
54
+
55
+ class APMPlusExporter(BaseModel, BaseExporter):
56
+ config: APMPlusExporterConfig = Field(default_factory=APMPlusExporterConfig)
57
+
58
+ @override
59
+ def get_processor(self):
60
+ resource_attributes = {
61
+ "service.name": self.config.service_name,
62
+ }
63
+
64
+ headers = {
65
+ "x-byteapm-appkey": self.config.app_key,
66
+ }
67
+ exporter = OTLPSpanExporter(
68
+ endpoint=self.config.endpoint, insecure=True, headers=headers
69
+ )
70
+ self._real_exporter = exporter
71
+ processor = BatchSpanProcessor(exporter)
72
+ return processor, resource_attributes
73
+
74
+ def export(self):
75
+ self._real_exporter.force_flush()
76
+ logger.info(
77
+ f"APMPlusExporter exports data to {self.config.endpoint}, service name: {self.config.service_name}"
78
+ )
79
+
80
+ @override
81
+ def get_meter_context(self) -> MeterContext:
82
+ resource_attributes = {
83
+ "service.name": self.config.service_name,
84
+ }
85
+ endpoint = self.config.endpoint
86
+ headers = {
87
+ "x-byteapm-appkey": self.config.app_key,
88
+ }
89
+
90
+ resource = Resource.create(resource_attributes)
91
+ exporter = OTLPMetricExporter(endpoint=endpoint, headers=headers)
92
+ metric_reader = PeriodicExportingMetricReader(exporter)
93
+ provider = MeterProvider(metric_readers=[metric_reader], resource=resource)
94
+ metrics.set_meter_provider(provider)
95
+ meter = metrics.get_meter("my.meter.name")
96
+ meter_context = MeterContext(
97
+ meter=meter,
98
+ provider=provider,
99
+ reader=metric_reader,
100
+ )
101
+ return meter_context