feat(telemetry): Expand load_skill telemetry

Extend the `execute_tool load_skill` span by adding new skill-related attributes. Adds new experimental attributes, and new namespace for them.

PiperOrigin-RevId: 964033870
This commit is contained in:
Google Team Member
2026-08-13 05:55:17 -07:00
committed by Copybara-Service
parent 2e878ed412
commit bddbb3dd8c
12 changed files with 1839 additions and 2 deletions
@@ -0,0 +1,35 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""ADK-owned span attribute names.
These attributes are defined by ADK itself; they are not part of any
OpenTelemetry semantic convention (neither the stable one in
``_stable_semconv`` nor the experimental one in ``_experimental_semconv``).
Everything named ``adk.experimental.*`` is emitted only when experimental
telemetry is enabled and carries no compatibility guarantee: an attribute may be
renamed, restructured, or removed in any release.
"""
from __future__ import annotations
ADK_EXPERIMENTAL_SKILL_NAME = 'adk.experimental.skill.name'
ADK_EXPERIMENTAL_SKILL_SOURCE_TYPE = 'adk.experimental.skill.source.type'
ADK_EXPERIMENTAL_SKILL_CACHE_HIT = 'adk.experimental.skill.cache_hit'
ADK_EXPERIMENTAL_SKILL_DESCRIPTION = 'adk.experimental.skill.description'
ADK_EXPERIMENTAL_SKILL_ADDITIONAL_TOOLS = (
'adk.experimental.skill.additional_tools'
)
ADK_EXPERIMENTAL_SKILL_SOURCE_URI = 'adk.experimental.skill.source.uri'
@@ -16,6 +16,7 @@ from __future__ import annotations
import contextlib
import dataclasses
import enum
import logging
import sys
import time
@@ -26,6 +27,7 @@ from typing import TYPE_CHECKING
from opentelemetry import trace
import opentelemetry.context as context_api
from . import _adk_attributes
from . import _metrics
from . import tracing
from ._schema_version import resolve_schema_version
@@ -38,12 +40,16 @@ if TYPE_CHECKING:
from ..events import event as event_lib
from ..models.llm_request import LlmRequest
from ..models.llm_response import LlmResponse
from ..skills.models import Skill
from ..tools.base_tool import BaseTool
from ..workflow._base_node import BaseNode
logger = logging.getLogger("google_adk." + __name__)
_INVOKE_AGENT_TELEMETRY_KEY = context_api.create_key("invoke_agent_telemetry")
_TOOL_EXECUTION_TELEMETRY_KEY = context_api.create_key(
"tool_execution_telemetry"
)
@contextlib.contextmanager
@@ -85,6 +91,37 @@ def record_invocation(
yield
class SkillTelemetrySpanType(enum.Enum):
"""Skill telemetry type."""
SKILL_LOAD = enum.auto()
@dataclasses.dataclass
class SkillTelemetry:
"""Skill related telemetry.
Added to the enclosing tool execution via :func:`record_skill_telemetry`,
which is what turns it into attributes on the skill related spans.
Attributes:
span_type: The type of skill telemetry being recorded.
skill_name: The name of the skill.
skill: The loaded skill, or None if the load did not produce one (unknown
skill name, registry failure). Nothing is recorded in that case; the
failure itself is already reported as the span's ``error.type``.
cache_hit: Whether the skill came from the per-invocation fetch cache
instead of the registry. Only meaningful for registry-sourced skills.
additional_tools: The list of additional tools reported by the skill.
"""
span_type: SkillTelemetrySpanType
skill_name: str | None = None
skill: Skill | None = None
cache_hit: bool = False
additional_tools: list[str] = dataclasses.field(default_factory=list)
@dataclasses.dataclass
class TelemetryContext:
"""Stores all telemetry related state."""
@@ -93,6 +130,7 @@ class TelemetryContext:
function_response_event: event_lib.Event | None = None
error_type: str | None = None
span: tracing.GenerateContentSpan | trace.Span | None = None
skill_telemetry: SkillTelemetry | None = None
_llm_responses: list[LlmResponse] = dataclasses.field(default_factory=list)
_inference_call_count: int = 0
_tool_call_count: int = 0
@@ -167,6 +205,109 @@ def _accumulate_invoke_agent_inference_call() -> None:
span_tel_ctx.increment_inference_calls()
def _active_tool_execution_tel_ctx() -> TelemetryContext | None:
"""Returns the TelemetryContext of the active execute_tool span."""
value = context_api.get_value(_TOOL_EXECUTION_TELEMETRY_KEY)
return value if isinstance(value, TelemetryContext) else None
def record_skill_telemetry(
telemetry_type: SkillTelemetrySpanType,
) -> SkillTelemetry:
"""Attaches skill telemetry to the enclosing tool execution.
The attributes are written by :func:`record_tool_execution`, which owns the
``execute_tool`` span, once the tool call completes. Callers therefore never
depend on a span being open: outside a tool execution this is a no-op rather
than an attribute silently landing on whatever span happens to be current.
A tool execution references a single skill, so a second call within the same
tool execution replaces the first.
Args:
telemetry_type: The type of skill telemetry being recorded.
Returns:
skill_telemetry: Skill telemetry reference to record against the active tool
call.
"""
tel_ctx = _active_tool_execution_tel_ctx()
if tel_ctx is None:
logger.debug(
"No tool execution is being recorded, skill telemetry will not be"
" attached to current span."
)
return SkillTelemetry(span_type=telemetry_type)
if tel_ctx.skill_telemetry is not None:
logger.warning(
"Tool execution already has attached skill telemetry, overwriting."
)
tel_ctx.skill_telemetry = SkillTelemetry(span_type=telemetry_type)
return tel_ctx.skill_telemetry
def record_skill_cache_hit() -> None:
"""Records a skill cache hit against the enclosing tool execution."""
tel_ctx = _active_tool_execution_tel_ctx()
if tel_ctx is None:
logger.debug(
"Skipping skill cache hit: no tool execution is being recorded."
)
return
if tel_ctx.skill_telemetry is None:
logger.warning(
"Tool execution has no attached skill telemetry, skipping cache hit."
)
return
tel_ctx.skill_telemetry.cache_hit = True
def _trace_skill_load(
span: trace.Span,
skill_telemetry: SkillTelemetry | None,
invocation_context: InvocationContext,
) -> None:
"""Stamps the skill load attributes onto the ``execute_tool`` span."""
if skill_telemetry is None:
return
telemetry_config = tracing._telemetry_config_from_invocation_context(
invocation_context
)
if not telemetry_config.should_emit_experimental_telemetry:
return
if skill_telemetry.skill_name is not None:
span.set_attribute(
_adk_attributes.ADK_EXPERIMENTAL_SKILL_NAME, skill_telemetry.skill_name
)
skill = skill_telemetry.skill
if skill is None:
return
span.set_attribute(
_adk_attributes.ADK_EXPERIMENTAL_SKILL_DESCRIPTION, skill.description
)
if skill._uri is not None:
span.set_attribute(
_adk_attributes.ADK_EXPERIMENTAL_SKILL_SOURCE_URI, skill._uri
)
if skill._uri.startswith(("http", "https")):
# Only meaningful if skill is from registry.
span.set_attribute(
_adk_attributes.ADK_EXPERIMENTAL_SKILL_CACHE_HIT,
skill_telemetry.cache_hit,
)
span.set_attribute(
_adk_attributes.ADK_EXPERIMENTAL_SKILL_ADDITIONAL_TOOLS,
skill_telemetry.additional_tools,
)
@contextlib.asynccontextmanager
async def record_agent_invocation(
ctx: InvocationContext, agent: BaseAgent
@@ -216,12 +357,19 @@ async def record_tool_execution(
with tracing.tracer.start_as_current_span(span_name) as s:
span = s
tel_ctx = TelemetryContext(otel_context=context_api.get_current())
# Published so the running tool can report telemetry back to this span
# (see `record_skill_telemetry`) without reaching for the ambient span
# itself.
token = context_api.attach(
context_api.set_value(_TOOL_EXECUTION_TELEMETRY_KEY, tel_ctx)
)
try:
yield tel_ctx
except Exception as e:
caught_error = e
raise
finally:
context_api.detach(token)
detected_error_type = tel_ctx.error_type
response_event = (
tel_ctx.function_response_event if caught_error is None else None
@@ -234,6 +382,12 @@ async def record_tool_execution(
invocation_context=invocation_context,
error_type=tel_ctx.error_type,
)
if (
tel_ctx.skill_telemetry is not None
and tel_ctx.skill_telemetry.span_type
== SkillTelemetrySpanType.SKILL_LOAD
):
_trace_skill_load(span, tel_ctx.skill_telemetry, invocation_context)
finally:
_accumulate_invoke_agent_tool_call()
try:
+14 -1
View File
@@ -40,6 +40,7 @@ from ..code_executors.code_execution_utils import CodeExecutionInput
from ..skills import models
from ..skills import prompt
from ..skills import SkillRegistry
from ..telemetry import _instrumentation
from ..utils import instructions_utils
from .base_tool import BaseTool
from .base_toolset import BaseToolset
@@ -249,13 +250,18 @@ class LoadSkillTool(BaseTool):
async def run_async(
self, *, args: dict[str, Any], tool_context: ToolContext
) -> Any:
skill_name = args.get("skill_name")
skill_name: str | None = args.get("skill_name")
if not skill_name:
return {
"error": "Argument 'skill_name' is required.",
"error_code": "INVALID_ARGUMENTS",
}
skill_telemetry = _instrumentation.record_skill_telemetry(
_instrumentation.SkillTelemetrySpanType.SKILL_LOAD
)
skill_telemetry.skill_name = skill_name
try:
skill = await self._toolset._get_or_fetch_skill(
skill_name, tool_context.invocation_id
@@ -272,6 +278,11 @@ class LoadSkillTool(BaseTool):
"error_code": "SKILL_NOT_FOUND",
}
skill_telemetry.skill = skill
skill_telemetry.additional_tools = skill.frontmatter.metadata.get(
"adk_additional_tools", []
)
# Record skill activation in agent state for tool resolution.
agent_name = tool_context.agent_name
state_key = f"_adk_activated_skill_{agent_name}"
@@ -1349,6 +1360,8 @@ class SkillToolset(BaseToolset):
turn_cache = self._fetched_skill_cache[invocation_id]
if skill_name in turn_cache:
cached = turn_cache[skill_name]
_instrumentation.record_skill_cache_hit()
if isinstance(cached, asyncio.Future):
return await cached
return cached
@@ -21,6 +21,7 @@ the configuration to drive it under.
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import field
from typing import Literal
from typing import TYPE_CHECKING
@@ -31,8 +32,10 @@ import pytest
from typing_extensions import assert_never
from ._digests import TelemetryDigest
from ._scenarios import ADK_EXPERIMENTAL_TELEMETRY
from ._scenarios import ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN
from ._scenarios import build_mcp_test_runner
from ._scenarios import build_skill_test_runner
from ._scenarios import build_test_runner
from ._scenarios import CAPTURE_CONTENT
from ._scenarios import FakeMcpSession
@@ -41,6 +44,7 @@ from ._scenarios import OTEL_OPT_IN
from ._scenarios import run_agent_scenario
from ._scenarios import run_node_scenario
from ._scenarios import Scenario
from ._scenarios import SkillType
if TYPE_CHECKING:
from google.adk.events.event import Event
@@ -67,6 +71,8 @@ class FunctionalTestCase:
# When true, the tool raises instead of returning, and the scenario is
# expected to propagate it (tool-failure telemetry path).
tool_fails: bool = False
experimental_telemetry: bool = False
loaded_skills: list[SkillType] = field(default_factory=list)
@property
def expects_failure(self) -> bool:
@@ -99,6 +105,9 @@ class FunctionalTestCase:
ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN, str(self.schema_version)
)
monkeypatch.setenv("ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS", "false")
monkeypatch.setenv(
ADK_EXPERIMENTAL_TELEMETRY, str(self.experimental_telemetry).lower()
)
# ---------------------------------------------------------------------------
@@ -163,5 +172,8 @@ async def _run_scenario(
build_mcp_test_runner(monkeypatch, FakeMcpSession())
)
return []
elif case.scenario == "skill":
await run_agent_scenario(build_skill_test_runner(skills=case.loaded_skills))
return []
else:
assert_never(case.scenario)
@@ -24,17 +24,22 @@ from __future__ import annotations
from contextlib import aclosing
from typing import Literal
from typing import NamedTuple
from typing import Sequence
from typing import TYPE_CHECKING
from google.adk.agents.llm_agent import Agent
from google.adk.models.llm_response import LlmResponse
from google.adk.runners import InMemoryRunner
from google.adk.skills.models import Frontmatter
from google.adk.skills.models import Skill
from google.adk.skills.skill_registry import SkillRegistry
from google.adk.telemetry import _metrics
from google.adk.telemetry import node_tracing
from google.adk.telemetry import tracing
from google.adk.tools.function_tool import FunctionTool
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
from google.adk.tools.skill_toolset import SkillToolset
from google.adk.workflow._base_node import START
from google.adk.workflow._workflow import Workflow
from google.genai.types import Content
@@ -71,6 +76,7 @@ OTEL_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
CAPTURE_CONTENT = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
EXPERIMENTAL_OPT_IN = "gen_ai_latest_experimental"
ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN = "ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN"
ADK_EXPERIMENTAL_TELEMETRY = "ADK_EXPERIMENTAL_TELEMETRY"
# Stable semconv event names.
GEN_AI_SYSTEM_MESSAGE_EVENT = "gen_ai.system.message"
@@ -81,7 +87,10 @@ GEN_AI_CHOICE_EVENT = "gen_ai.choice"
GEN_AI_COMPLETION_DETAILS_EVENT = "gen_ai.client.inference.operation.details"
# Which end-to-end scenario a test case drives.
Scenario = Literal["agent", "node", "mcp"]
Scenario = Literal["agent", "node", "mcp", "skill"]
# The type of skill being used in a test case.
SkillType = Literal["local", "registry", "nonexistent"]
# ---------------------------------------------------------------------------
# Telemetry plumbing.
@@ -494,3 +503,95 @@ def build_mcp_test_runner(
tools=[toolset],
)
)
# ---------------------------------------------------------------------------
# Skill telemetry scenario.
# ---------------------------------------------------------------------------
REGISTRY_SKILL_NAME = "registry-skill"
LOCAL_SKILL_NAME = "local-skill"
NONEXISTENT_SKILL_NAME = "nonexistent-skill"
SKILL_DESCRIPTION = "A sample skill."
def _make_skill(
*,
name: str = LOCAL_SKILL_NAME,
source: str = "static",
additional_tools: Sequence[str] | None = None,
) -> Skill:
additional_tools = additional_tools or []
skill = Skill(
frontmatter=Frontmatter(
name=name,
description=SKILL_DESCRIPTION,
metadata={"adk_additional_tools": additional_tools},
),
instructions="skill instructions",
)
if source == "registry":
skill._uri = f"https://fake-registry.com/skill/{name}"
else:
skill._uri = f"file://{name}"
return skill
class _FakeSkillRegistry(SkillRegistry):
"""Registry serving one in-memory skill, with no network of its own."""
def __init__(self, skill: Skill) -> None:
self._skill = skill
@override
async def get_skill(self, *, name: str) -> Skill:
# A fresh copy per fetch: the toolset stamps `source` on what it gets back.
if name == self._skill.frontmatter.name:
return self._skill.model_copy(deep=True)
else:
raise KeyError(f"Skill {name} not found")
@override
async def search_skills(self, *, query: str) -> list[Frontmatter]:
return []
def build_skill_test_runner(
*, skills: Sequence[SkillType] | None = None
) -> TestInMemoryRunner:
"""Builds a runner whose model calls ``load_skill`` then answers."""
skills = skills or []
part_map: dict[SkillType, Part] = {
"local": Part.from_function_call(
name="load_skill", args={"skill_name": LOCAL_SKILL_NAME}
),
"registry": Part.from_function_call(
name="load_skill", args={"skill_name": REGISTRY_SKILL_NAME}
),
"nonexistent": Part.from_function_call(
name="load_skill", args={"skill_name": NONEXISTENT_SKILL_NAME}
),
}
mock_model = MockModel.create(
responses=[
*(part_map[skill] for skill in skills),
Part.from_text(text=FINAL_TEXT),
]
)
registry = _FakeSkillRegistry(
_make_skill(name=REGISTRY_SKILL_NAME, source="registry"),
)
toolset = SkillToolset(
[_make_skill(additional_tools=["foo", "bar"])], registry=registry
)
test_agent = Agent(
name=AGENT_NAME,
description=AGENT_DESCRIPTION,
instruction=BASE_INSTRUCTION,
model=mock_model,
tools=[toolset],
)
return TestInMemoryRunner(node=test_agent)
@@ -0,0 +1,245 @@
{
"root_span": {
"name": "invoke_workflow some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_workflow",
"gen_ai.conversation.id": "PRESENT",
"gen_ai.workflow.name": "some_root_agent"
},
"status": "UNSET",
"children": [
{
"name": "invoke_agent some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.description": "A sample root agent.",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"error.type": "REGISTRY_ERROR",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}",
"adk.experimental.skill.name": "nonexistent-skill"
},
"status": "ERROR",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
}
],
"logs": []
}
],
"logs": []
},
"metric_points": {
"gen_ai.client.operation.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.operation.name": "generate_content",
"gen_ai.provider.name": "gemini",
"gen_ai.request.model": "mock",
"gen_ai.response.model": "mock"
},
"value": "PRESENT"
}
],
"gen_ai.execute_tool.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"error.type": "REGISTRY_ERROR"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.inference_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 2
}
],
"gen_ai.invoke_agent.tool_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 1
}
],
"gen_ai.invoke_workflow.duration": [
{
"attributes": {
"gen_ai.operation.name": "invoke_workflow",
"gen_ai.workflow.name": "some_root_agent"
},
"value": "PRESENT"
}
]
}
}
@@ -0,0 +1,368 @@
{
"root_span": {
"name": "invoke_workflow some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_workflow",
"gen_ai.conversation.id": "PRESENT",
"gen_ai.workflow.name": "some_root_agent"
},
"status": "UNSET",
"children": [
{
"name": "invoke_agent some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.description": "A sample root agent.",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}",
"adk.experimental.skill.name": "registry-skill",
"adk.experimental.skill.description": "A sample skill.",
"adk.experimental.skill.source.uri": "https://fake-registry.com/skill/registry-skill",
"adk.experimental.skill.cache_hit": false,
"adk.experimental.skill.additional_tools": []
},
"status": "UNSET",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}",
"adk.experimental.skill.name": "registry-skill",
"adk.experimental.skill.description": "A sample skill.",
"adk.experimental.skill.source.uri": "https://fake-registry.com/skill/registry-skill",
"adk.experimental.skill.cache_hit": true,
"adk.experimental.skill.additional_tools": []
},
"status": "UNSET",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
}
],
"logs": []
}
],
"logs": []
},
"metric_points": {
"gen_ai.client.operation.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.operation.name": "generate_content",
"gen_ai.provider.name": "gemini",
"gen_ai.request.model": "mock",
"gen_ai.response.model": "mock"
},
"value": "PRESENT"
}
],
"gen_ai.execute_tool.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.inference_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 3
}
],
"gen_ai.invoke_agent.tool_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 2
}
],
"gen_ai.invoke_workflow.duration": [
{
"attributes": {
"gen_ai.operation.name": "invoke_workflow",
"gen_ai.workflow.name": "some_root_agent"
},
"value": "PRESENT"
}
]
}
}
@@ -0,0 +1,345 @@
{
"root_span": {
"name": "invocation",
"attributes": {},
"status": "UNSET",
"children": [
{
"name": "invoke_agent some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.description": "A sample root agent.",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}"
},
"status": "UNSET",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}"
},
"status": "UNSET",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
}
],
"logs": []
}
],
"logs": []
},
"metric_points": {
"gen_ai.client.operation.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.operation.name": "generate_content",
"gen_ai.provider.name": "gemini",
"gen_ai.request.model": "mock",
"gen_ai.response.model": "mock"
},
"value": "PRESENT"
}
],
"gen_ai.execute_tool.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.inference_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 3
}
],
"gen_ai.invoke_agent.tool_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 2
}
]
}
}
@@ -0,0 +1,236 @@
{
"root_span": {
"name": "invocation",
"attributes": {},
"status": "UNSET",
"children": [
{
"name": "invoke_agent some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.description": "A sample root agent.",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}",
"adk.experimental.skill.name": "local-skill",
"adk.experimental.skill.description": "A sample skill.",
"adk.experimental.skill.source.uri": "file://local-skill",
"adk.experimental.skill.additional_tools": [
"foo",
"bar"
]
},
"status": "UNSET",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
}
],
"logs": []
}
],
"logs": []
},
"metric_points": {
"gen_ai.client.operation.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.operation.name": "generate_content",
"gen_ai.provider.name": "gemini",
"gen_ai.request.model": "mock",
"gen_ai.response.model": "mock"
},
"value": "PRESENT"
}
],
"gen_ai.execute_tool.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.inference_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 2
}
],
"gen_ai.invoke_agent.tool_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 1
}
]
}
}
@@ -0,0 +1,249 @@
{
"root_span": {
"name": "invoke_workflow some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_workflow",
"gen_ai.conversation.id": "PRESENT",
"gen_ai.workflow.name": "some_root_agent"
},
"status": "UNSET",
"children": [
{
"name": "invoke_agent some_root_agent",
"attributes": {
"gen_ai.operation.name": "invoke_agent",
"gen_ai.agent.description": "A sample root agent.",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [
{
"name": "execute_tool load_skill",
"attributes": {
"gen_ai.operation.name": "execute_tool",
"gen_ai.tool.description": "Loads the SKILL.md instructions for a given skill.",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool",
"gen_ai.agent.name": "some_root_agent",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}",
"gcp.vertex.agent.tool_call_args": "{}",
"gen_ai.tool.call.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.tool_response": "{}",
"adk.experimental.skill.name": "local-skill",
"adk.experimental.skill.description": "A sample skill.",
"adk.experimental.skill.source.uri": "file://local-skill",
"adk.experimental.skill.additional_tools": [
"foo",
"bar"
]
},
"status": "UNSET",
"children": [],
"logs": []
}
],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
},
{
"name": "call_llm",
"attributes": {
"gen_ai.system": "gcp.vertex.agent",
"gen_ai.request.model": "mock",
"gcp.vertex.agent.invocation_id": "PRESENT",
"gcp.vertex.agent.session_id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.llm_request": "{}",
"gcp.vertex.agent.llm_response": "{}"
},
"status": "UNSET",
"children": [
{
"name": "generate_content mock",
"attributes": {
"gen_ai.system": "gemini",
"gen_ai.operation.name": "generate_content",
"gen_ai.request.model": "mock",
"gen_ai.agent.name": "some_root_agent",
"gen_ai.conversation.id": "PRESENT",
"gcp.vertex.agent.event_id": "PRESENT",
"gcp.vertex.agent.invocation_id": "PRESENT"
},
"status": "UNSET",
"children": [],
"logs": [
{
"event_name": "gen_ai.choice",
"body": {
"content": "<elided>",
"index": 0
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.system.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
},
{
"event_name": "gen_ai.user.message",
"body": {
"content": "<elided>"
},
"attributes": {
"gen_ai.system": "gemini"
}
}
]
}
],
"logs": []
}
],
"logs": []
}
],
"logs": []
},
"metric_points": {
"gen_ai.client.operation.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.operation.name": "generate_content",
"gen_ai.provider.name": "gemini",
"gen_ai.request.model": "mock",
"gen_ai.response.model": "mock"
},
"value": "PRESENT"
}
],
"gen_ai.execute_tool.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent",
"gen_ai.tool.name": "load_skill",
"gen_ai.tool.type": "LoadSkillTool"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.duration": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": "PRESENT"
}
],
"gen_ai.invoke_agent.inference_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 2
}
],
"gen_ai.invoke_agent.tool_calls": [
{
"attributes": {
"gen_ai.agent.name": "some_root_agent"
},
"value": 1
}
],
"gen_ai.invoke_workflow.duration": [
{
"attributes": {
"gen_ai.operation.name": "invoke_workflow",
"gen_ai.workflow.name": "some_root_agent"
},
"value": "PRESENT"
}
]
}
}
@@ -128,6 +128,52 @@ ALL_CASES: list[FunctionalTestCase] = semconv_matrix("agent") + [
schema_version=2,
tool_fails=True,
),
# Skill telemetry scenarios.
FunctionalTestCase(
test_id="skill-telemetry-schema-v1",
scenario="skill",
semconv_opt_in=None,
experimental_telemetry=True,
capture_content="false",
schema_version=1,
loaded_skills=["local"],
),
FunctionalTestCase(
test_id="skill-telemetry-schema-v2",
scenario="skill",
semconv_opt_in=None,
experimental_telemetry=True,
capture_content="false",
schema_version=2,
loaded_skills=["local"],
),
FunctionalTestCase(
test_id="skill-registry-cache-hit-schema-v2",
scenario="skill",
semconv_opt_in=None,
experimental_telemetry=True,
capture_content="false",
schema_version=2,
loaded_skills=["registry", "registry"],
),
FunctionalTestCase(
test_id="invalid-skill-schema-v2",
scenario="skill",
semconv_opt_in=None,
experimental_telemetry=True,
capture_content="false",
schema_version=2,
loaded_skills=["nonexistent"],
),
FunctionalTestCase(
test_id="skill-telemetry-disabled-schema-v1",
scenario="skill",
semconv_opt_in=None,
experimental_telemetry=False,
capture_content="false",
schema_version=1,
loaded_skills=["local", "registry"],
),
]
# The MCP integration case: an agent whose only tool source is a (fake) MCP
@@ -126,6 +126,39 @@ async def test_record_tool_execution_forwards_detected_error_type():
assert mock_record.call_args.kwargs["error_type"] == "MCP_TOOL_ERROR"
@pytest.mark.asyncio
async def test_record_skill_load_reaches_the_enclosing_tool_execution():
"""A skill load is reported to the tool execution that wraps it."""
tool = mock.MagicMock()
tool.name = "load_skill"
agent = mock.MagicMock()
agent.name = "sample_agent"
async with _instrumentation.record_tool_execution(
tool=tool,
agent=agent,
function_args={},
invocation_context=mock.MagicMock(),
) as tel_ctx:
skill_telemetry = _instrumentation.record_skill_telemetry(
_instrumentation.SkillTelemetrySpanType.SKILL_LOAD
)
skill_telemetry.skill_name = "sample_skill"
skill_telemetry.skill = mock.MagicMock()
skill_telemetry.cache_hit = True
assert tel_ctx.skill_telemetry is skill_telemetry
def test_record_skill_load_outside_tool_execution_is_a_noop():
"""Callers never depend on a tool execution (and thus a span) being open."""
_instrumentation.record_skill_telemetry(
_instrumentation.SkillTelemetrySpanType.SKILL_LOAD,
)
assert _instrumentation._active_tool_execution_tel_ctx() is None
# ---------------------------------------------------------------------------
# The consolidated span + metric context managers.
#