veadk-python 0.2.9__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 +3 -0
- veadk/agent_builder.py +2 -3
- veadk/cli/cli.py +2 -0
- veadk/cli/cli_kb.py +75 -0
- veadk/cli/cli_web.py +4 -0
- veadk/integrations/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +293 -0
- veadk/memory/__init__.py +1 -1
- veadk/memory/long_term_memory.py +25 -0
- veadk/memory/long_term_memory_backends/mem0_backend.py +17 -2
- 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 +23 -8
- veadk/tools/builtin_tools/generate_image.py +50 -16
- veadk/tools/builtin_tools/image_edit.py +9 -4
- veadk/tools/builtin_tools/image_generate.py +9 -4
- veadk/tools/builtin_tools/video_generate.py +6 -6
- veadk/tools/builtin_tools/web_search.py +10 -3
- veadk/tools/load_knowledgebase_tool.py +12 -0
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +5 -0
- 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/telemetry.py +41 -5
- veadk/version.py +1 -1
- {veadk_python-0.2.9.dist-info → veadk_python-0.2.10.dist-info}/METADATA +3 -3
- {veadk_python-0.2.9.dist-info → veadk_python-0.2.10.dist-info}/RECORD +34 -30
- {veadk_python-0.2.9.dist-info → veadk_python-0.2.10.dist-info}/WHEEL +0 -0
- {veadk_python-0.2.9.dist-info → veadk_python-0.2.10.dist-info}/entry_points.txt +0 -0
- {veadk_python-0.2.9.dist-info → veadk_python-0.2.10.dist-info}/licenses/LICENSE +0 -0
- {veadk_python-0.2.9.dist-info → veadk_python-0.2.10.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,7 @@ from google.adk.sessions import (
|
|
|
19
19
|
BaseSessionService,
|
|
20
20
|
DatabaseSessionService,
|
|
21
21
|
InMemorySessionService,
|
|
22
|
+
Session,
|
|
22
23
|
)
|
|
23
24
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
24
25
|
|
|
@@ -106,7 +107,7 @@ class ShortTermMemory(BaseModel):
|
|
|
106
107
|
app_name: str,
|
|
107
108
|
user_id: str,
|
|
108
109
|
session_id: str,
|
|
109
|
-
) -> None:
|
|
110
|
+
) -> Session | None:
|
|
110
111
|
if isinstance(self._session_service, DatabaseSessionService):
|
|
111
112
|
list_sessions_response = await self._session_service.list_sessions(
|
|
112
113
|
app_name=app_name, user_id=user_id
|
|
@@ -122,7 +123,12 @@ class ShortTermMemory(BaseModel):
|
|
|
122
123
|
)
|
|
123
124
|
is None
|
|
124
125
|
):
|
|
125
|
-
|
|
126
|
-
await self._session_service.create_session(
|
|
126
|
+
return await self._session_service.create_session(
|
|
127
127
|
app_name=app_name, user_id=user_id, session_id=session_id
|
|
128
128
|
)
|
|
129
|
+
else:
|
|
130
|
+
logger.info(
|
|
131
|
+
f"Session {session_id} already exists with app_name={app_name} user_id={user_id}."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return None
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
from functools import cached_property
|
|
16
16
|
from typing import Any
|
|
17
|
+
from venv import logger
|
|
17
18
|
|
|
18
19
|
from google.adk.sessions import (
|
|
19
20
|
BaseSessionService,
|
|
@@ -33,7 +34,8 @@ class PostgreSqlSTMBackend(BaseShortTermMemoryBackend):
|
|
|
33
34
|
postgresql_config: PostgreSqlConfig = Field(default_factory=PostgreSqlConfig)
|
|
34
35
|
|
|
35
36
|
def model_post_init(self, context: Any) -> None:
|
|
36
|
-
self._db_url = f"postgresql
|
|
37
|
+
self._db_url = f"postgresql://{self.postgresql_config.user}:{self.postgresql_config.password}@{self.postgresql_config.host}:{self.postgresql_config.port}/{self.postgresql_config.database}"
|
|
38
|
+
logger.debug(self._db_url)
|
|
37
39
|
|
|
38
40
|
@cached_property
|
|
39
41
|
@override
|
veadk/runner.py
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import os
|
|
16
15
|
import functools
|
|
16
|
+
import os
|
|
17
17
|
from types import MethodType
|
|
18
18
|
from typing import Union
|
|
19
19
|
|
|
@@ -249,15 +249,21 @@ class Runner(ADKRunner):
|
|
|
249
249
|
)
|
|
250
250
|
|
|
251
251
|
if self.short_term_memory:
|
|
252
|
-
await self.short_term_memory.create_session(
|
|
253
|
-
app_name=self.app_name, user_id=
|
|
252
|
+
session = await self.short_term_memory.create_session(
|
|
253
|
+
app_name=self.app_name, user_id=user_id, session_id=session_id
|
|
254
|
+
)
|
|
255
|
+
assert session, (
|
|
256
|
+
f"Failed to create session with app_name={self.app_name}, user_id={user_id}, session_id={session_id}, "
|
|
257
|
+
)
|
|
258
|
+
logger.debug(
|
|
259
|
+
f"Auto create session: {session.id}, user_id: {session.user_id}, app_name: {self.app_name}"
|
|
254
260
|
)
|
|
255
261
|
|
|
256
262
|
final_output = ""
|
|
257
263
|
for converted_message in converted_messages:
|
|
258
264
|
try:
|
|
259
265
|
async for event in self.run_async(
|
|
260
|
-
user_id=
|
|
266
|
+
user_id=user_id,
|
|
261
267
|
session_id=session_id,
|
|
262
268
|
new_message=converted_message,
|
|
263
269
|
run_config=run_config,
|
|
@@ -359,19 +365,28 @@ class Runner(ADKRunner):
|
|
|
359
365
|
)
|
|
360
366
|
return eval_set_path
|
|
361
367
|
|
|
362
|
-
async def save_session_to_long_term_memory(
|
|
368
|
+
async def save_session_to_long_term_memory(
|
|
369
|
+
self, session_id: str, user_id: str = "", app_name: str = ""
|
|
370
|
+
) -> None:
|
|
363
371
|
if not self.long_term_memory:
|
|
364
372
|
logger.warning("Long-term memory is not enabled. Failed to save session.")
|
|
365
373
|
return
|
|
366
374
|
|
|
375
|
+
if not user_id:
|
|
376
|
+
user_id = self.user_id
|
|
377
|
+
|
|
378
|
+
if not app_name:
|
|
379
|
+
app_name = self.app_name
|
|
380
|
+
|
|
367
381
|
session = await self.session_service.get_session(
|
|
368
|
-
app_name=
|
|
369
|
-
user_id=
|
|
382
|
+
app_name=app_name,
|
|
383
|
+
user_id=user_id,
|
|
370
384
|
session_id=session_id,
|
|
371
385
|
)
|
|
386
|
+
|
|
372
387
|
if not session:
|
|
373
388
|
logger.error(
|
|
374
|
-
f"Session {session_id} not found in session service, cannot save to long-term memory."
|
|
389
|
+
f"Session {session_id} (app_name={app_name}, user_id={user_id}) not found in session service, cannot save to long-term memory."
|
|
375
390
|
)
|
|
376
391
|
return
|
|
377
392
|
|
|
@@ -12,28 +12,30 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import base64
|
|
16
|
+
import json
|
|
17
|
+
import mimetypes
|
|
18
|
+
import traceback
|
|
15
19
|
from typing import Dict
|
|
16
20
|
|
|
17
21
|
from google.adk.tools import ToolContext
|
|
18
|
-
from
|
|
19
|
-
from veadk.consts import DEFAULT_IMAGE_GENERATE_MODEL_NAME, DEFAULT_MODEL_AGENT_API_BASE
|
|
20
|
-
|
|
21
|
-
import base64
|
|
22
|
-
from volcenginesdkarkruntime import Ark
|
|
22
|
+
from google.genai.types import Blob, Part
|
|
23
23
|
from opentelemetry import trace
|
|
24
|
-
import traceback
|
|
25
|
-
from veadk.version import VERSION
|
|
26
24
|
from opentelemetry.trace import Span
|
|
27
|
-
from
|
|
25
|
+
from volcenginesdkarkruntime import Ark
|
|
28
26
|
from volcenginesdkarkruntime.types.images.images import SequentialImageGenerationOptions
|
|
29
|
-
import json
|
|
30
27
|
|
|
28
|
+
from veadk.config import getenv
|
|
29
|
+
from veadk.consts import DEFAULT_IMAGE_GENERATE_MODEL_NAME, DEFAULT_MODEL_AGENT_API_BASE
|
|
30
|
+
from veadk.utils.logger import get_logger
|
|
31
|
+
from veadk.utils.misc import formatted_timestamp, read_png_to_bytes
|
|
32
|
+
from veadk.version import VERSION
|
|
31
33
|
|
|
32
34
|
logger = get_logger(__name__)
|
|
33
35
|
|
|
34
36
|
client = Ark(
|
|
35
37
|
api_key=getenv("MODEL_AGENT_API_KEY"),
|
|
36
|
-
base_url=DEFAULT_MODEL_AGENT_API_BASE,
|
|
38
|
+
base_url=getenv("MODEL_AGENT_API_BASE", DEFAULT_MODEL_AGENT_API_BASE),
|
|
37
39
|
)
|
|
38
40
|
|
|
39
41
|
|
|
@@ -121,7 +123,7 @@ async def image_generate(
|
|
|
121
123
|
- size 推荐使用 2048x2048 或表格里的标准比例,确保生成质量。
|
|
122
124
|
"""
|
|
123
125
|
|
|
124
|
-
success_list = []
|
|
126
|
+
success_list: list[dict] = []
|
|
125
127
|
error_list = []
|
|
126
128
|
|
|
127
129
|
for idx, item in enumerate(tasks):
|
|
@@ -184,7 +186,9 @@ async def image_generate(
|
|
|
184
186
|
and max_images
|
|
185
187
|
):
|
|
186
188
|
response = client.images.generate(
|
|
187
|
-
model=
|
|
189
|
+
model=getenv(
|
|
190
|
+
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
191
|
+
),
|
|
188
192
|
**inputs,
|
|
189
193
|
sequential_image_generation_options=SequentialImageGenerationOptions(
|
|
190
194
|
max_images=max_images
|
|
@@ -192,7 +196,10 @@ async def image_generate(
|
|
|
192
196
|
)
|
|
193
197
|
else:
|
|
194
198
|
response = client.images.generate(
|
|
195
|
-
model=
|
|
199
|
+
model=getenv(
|
|
200
|
+
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
201
|
+
),
|
|
202
|
+
**inputs,
|
|
196
203
|
)
|
|
197
204
|
if not response.error:
|
|
198
205
|
for i, image_data in enumerate(response.data):
|
|
@@ -261,8 +268,12 @@ async def image_generate(
|
|
|
261
268
|
output_part=output_part,
|
|
262
269
|
output_tokens=output_tokens,
|
|
263
270
|
total_tokens=total_tokens,
|
|
264
|
-
request_model=
|
|
265
|
-
|
|
271
|
+
request_model=getenv(
|
|
272
|
+
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
273
|
+
),
|
|
274
|
+
response_model=getenv(
|
|
275
|
+
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
276
|
+
),
|
|
266
277
|
)
|
|
267
278
|
if len(success_list) == 0:
|
|
268
279
|
return {
|
|
@@ -271,6 +282,28 @@ async def image_generate(
|
|
|
271
282
|
"error_list": error_list,
|
|
272
283
|
}
|
|
273
284
|
else:
|
|
285
|
+
app_name = tool_context._invocation_context.app_name
|
|
286
|
+
user_id = tool_context._invocation_context.user_id
|
|
287
|
+
session_id = tool_context._invocation_context.session.id
|
|
288
|
+
|
|
289
|
+
artifact_service = tool_context._invocation_context.artifact_service
|
|
290
|
+
if artifact_service:
|
|
291
|
+
for image in success_list:
|
|
292
|
+
for _, image_tos_url in image.items():
|
|
293
|
+
filename = f"artifact_{formatted_timestamp()}"
|
|
294
|
+
await artifact_service.save_artifact(
|
|
295
|
+
app_name=app_name,
|
|
296
|
+
user_id=user_id,
|
|
297
|
+
session_id=session_id,
|
|
298
|
+
filename=filename,
|
|
299
|
+
artifact=Part(
|
|
300
|
+
inline_data=Blob(
|
|
301
|
+
display_name=filename,
|
|
302
|
+
data=read_png_to_bytes(image_tos_url),
|
|
303
|
+
mime_type=mimetypes.guess_type(image_tos_url)[0],
|
|
304
|
+
)
|
|
305
|
+
),
|
|
306
|
+
)
|
|
274
307
|
return {
|
|
275
308
|
"status": "success",
|
|
276
309
|
"success_list": success_list,
|
|
@@ -332,10 +365,11 @@ def add_span_attributes(
|
|
|
332
365
|
|
|
333
366
|
def _upload_image_to_tos(image_bytes: bytes, object_key: str) -> None:
|
|
334
367
|
try:
|
|
335
|
-
from veadk.integrations.ve_tos.ve_tos import VeTOS
|
|
336
368
|
import os
|
|
337
369
|
from datetime import datetime
|
|
338
370
|
|
|
371
|
+
from veadk.integrations.ve_tos.ve_tos import VeTOS
|
|
372
|
+
|
|
339
373
|
timestamp: str = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
|
|
340
374
|
object_key = f"{timestamp}-{object_key}"
|
|
341
375
|
bucket_name = os.getenv("DATABASE_TOS_BUCKET")
|
|
@@ -29,7 +29,7 @@ logger = get_logger(__name__)
|
|
|
29
29
|
|
|
30
30
|
client = Ark(
|
|
31
31
|
api_key=getenv("MODEL_AGENT_API_KEY"),
|
|
32
|
-
base_url=DEFAULT_MODEL_AGENT_API_BASE,
|
|
32
|
+
base_url=getenv("MODEL_AGENT_API_BASE", DEFAULT_MODEL_AGENT_API_BASE),
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
|
|
@@ -123,7 +123,8 @@ async def image_edit(
|
|
|
123
123
|
"parts.1.image_url.url": origin_image,
|
|
124
124
|
}
|
|
125
125
|
response = client.images.generate(
|
|
126
|
-
model=DEFAULT_IMAGE_EDIT_MODEL_NAME,
|
|
126
|
+
model=getenv("MODEL_EDIT_NAME", DEFAULT_IMAGE_EDIT_MODEL_NAME),
|
|
127
|
+
**inputs,
|
|
127
128
|
)
|
|
128
129
|
output_part = None
|
|
129
130
|
if response.data and len(response.data) > 0:
|
|
@@ -175,8 +176,12 @@ async def image_edit(
|
|
|
175
176
|
output_part=output_part,
|
|
176
177
|
output_tokens=response.usage.output_tokens,
|
|
177
178
|
total_tokens=response.usage.total_tokens,
|
|
178
|
-
request_model=
|
|
179
|
-
|
|
179
|
+
request_model=getenv(
|
|
180
|
+
"MODEL_EDIT_NAME", DEFAULT_IMAGE_EDIT_MODEL_NAME
|
|
181
|
+
),
|
|
182
|
+
response_model=getenv(
|
|
183
|
+
"MODEL_EDIT_NAME", DEFAULT_IMAGE_EDIT_MODEL_NAME
|
|
184
|
+
),
|
|
180
185
|
)
|
|
181
186
|
|
|
182
187
|
except Exception as e:
|
|
@@ -30,7 +30,7 @@ logger = get_logger(__name__)
|
|
|
30
30
|
|
|
31
31
|
client = Ark(
|
|
32
32
|
api_key=getenv("MODEL_AGENT_API_KEY"),
|
|
33
|
-
base_url=DEFAULT_MODEL_AGENT_API_BASE,
|
|
33
|
+
base_url=getenv("MODEL_AGENT_API_BASE", DEFAULT_MODEL_AGENT_API_BASE),
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
|
|
@@ -120,7 +120,8 @@ async def image_generate(
|
|
|
120
120
|
"content": json.dumps(inputs, ensure_ascii=False),
|
|
121
121
|
}
|
|
122
122
|
response = client.images.generate(
|
|
123
|
-
model=DEFAULT_TEXT_TO_IMAGE_MODEL_NAME,
|
|
123
|
+
model=getenv("MODEL_IMAGE_NAME", DEFAULT_TEXT_TO_IMAGE_MODEL_NAME),
|
|
124
|
+
**inputs,
|
|
124
125
|
)
|
|
125
126
|
output_part = None
|
|
126
127
|
if response.data and len(response.data) > 0:
|
|
@@ -172,8 +173,12 @@ async def image_generate(
|
|
|
172
173
|
output_part=output_part,
|
|
173
174
|
output_tokens=response.usage.output_tokens,
|
|
174
175
|
total_tokens=response.usage.total_tokens,
|
|
175
|
-
request_model=
|
|
176
|
-
|
|
176
|
+
request_model=getenv(
|
|
177
|
+
"MODEL_IMAGE_NAME", DEFAULT_TEXT_TO_IMAGE_MODEL_NAME
|
|
178
|
+
),
|
|
179
|
+
response_model=getenv(
|
|
180
|
+
"MODEL_IMAGE_NAME", DEFAULT_TEXT_TO_IMAGE_MODEL_NAME
|
|
181
|
+
),
|
|
177
182
|
)
|
|
178
183
|
|
|
179
184
|
except Exception as e:
|
|
@@ -34,7 +34,7 @@ logger = get_logger(__name__)
|
|
|
34
34
|
|
|
35
35
|
client = Ark(
|
|
36
36
|
api_key=getenv("MODEL_AGENT_API_KEY"),
|
|
37
|
-
base_url=DEFAULT_MODEL_AGENT_API_BASE,
|
|
37
|
+
base_url=getenv("MODEL_AGENT_API_BASE", DEFAULT_MODEL_AGENT_API_BASE),
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
|
|
@@ -43,7 +43,7 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
|
|
|
43
43
|
if first_frame_image is None:
|
|
44
44
|
logger.debug("text generation")
|
|
45
45
|
response = client.content_generation.tasks.create(
|
|
46
|
-
model=DEFAULT_VIDEO_MODEL_NAME,
|
|
46
|
+
model=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
|
|
47
47
|
content=[
|
|
48
48
|
{"type": "text", "text": prompt},
|
|
49
49
|
],
|
|
@@ -51,7 +51,7 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
|
|
|
51
51
|
elif last_frame_image is None:
|
|
52
52
|
logger.debug("first frame generation")
|
|
53
53
|
response = client.content_generation.tasks.create(
|
|
54
|
-
model=DEFAULT_VIDEO_MODEL_NAME,
|
|
54
|
+
model=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
|
|
55
55
|
content=cast(
|
|
56
56
|
list[CreateTaskContentParam], # avoid IDE warning
|
|
57
57
|
[
|
|
@@ -66,7 +66,7 @@ async def generate(prompt, first_frame_image=None, last_frame_image=None):
|
|
|
66
66
|
else:
|
|
67
67
|
logger.debug("last frame generation")
|
|
68
68
|
response = client.content_generation.tasks.create(
|
|
69
|
-
model=DEFAULT_VIDEO_MODEL_NAME,
|
|
69
|
+
model=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
|
|
70
70
|
content=[
|
|
71
71
|
{"type": "text", "text": prompt},
|
|
72
72
|
{
|
|
@@ -263,8 +263,8 @@ async def video_generate(params: list, tool_context: ToolContext) -> Dict:
|
|
|
263
263
|
output_part=output_part,
|
|
264
264
|
output_tokens=total_tokens,
|
|
265
265
|
total_tokens=total_tokens,
|
|
266
|
-
request_model=DEFAULT_VIDEO_MODEL_NAME,
|
|
267
|
-
response_model=DEFAULT_VIDEO_MODEL_NAME,
|
|
266
|
+
request_model=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
|
|
267
|
+
response_model=getenv("MODEL_VIDEO_NAME", DEFAULT_VIDEO_MODEL_NAME),
|
|
268
268
|
)
|
|
269
269
|
|
|
270
270
|
if len(success_list) == 0:
|
|
@@ -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(
|
|
@@ -25,6 +25,9 @@ from typing_extensions import override
|
|
|
25
25
|
|
|
26
26
|
from veadk.knowledgebase import KnowledgeBase
|
|
27
27
|
from veadk.knowledgebase.entry import KnowledgebaseEntry
|
|
28
|
+
from veadk.utils.logger import get_logger
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
28
31
|
|
|
29
32
|
if TYPE_CHECKING:
|
|
30
33
|
from google.adk.models.llm_request import LlmRequest
|
|
@@ -96,6 +99,15 @@ class LoadKnowledgebaseTool(FunctionTool):
|
|
|
96
99
|
|
|
97
100
|
def __init__(self):
|
|
98
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
|
|
99
111
|
|
|
100
112
|
@override
|
|
101
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),
|
|
@@ -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)
|