Repository navigation
Built-in google_search calls are not recorded as OpenTelemetry span attributes (grounding_metadata is ignored by telemetry) #7345
Description
🔴 Required Information
Is your feature request related to a specific problem?
google_search is a built-in, server-side tool: the search runs inside the Gemini call, so there is no local function call and no execute_tool span. That part is expected.
However, the only record of the search is LlmResponse.grounding_metadata, and nothing in google/adk/telemetry/ reads it:
- Content capture on (
ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, default on): the search appears only inside the serializedgcp.vertex.agent.llm_responseJSON on thecall_llmspan. There's no attribute to query or filter on. - Content capture off (
ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS=false, whichadk deploy agent_engine --otel_to_cloudsets by default, seecli_deploy.py): nothing in any span shows a search happened.
The data is kept on the session event (event.grounding_metadata), so it is not lost. The gap is only in traces, which is where people debug production agents. Today you can't answer "did this answer come from a search, and what was searched?" from traces alone.
Repro, live on Vertex AI (InMemoryRunner + LlmAgent(tools=[google_search]); spans captured with InMemorySpanExporter; GOOGLE_GENAI_USE_VERTEXAI=TRUE, location global):
| Model | Content capture | grounding_metadata on event |
Search visible in span attributes? |
|---|---|---|---|
gemini-2.5-flash |
on | 2 web_search_queries, 2 chunks |
only inside the gcp.vertex.agent.llm_response JSON |
gemini-2.5-flash |
off | 2 web_search_queries, 2 chunks |
no |
gemini-3.5-flash |
on | 9 web_search_queries, 3 chunks |
only inside the gcp.vertex.agent.llm_response JSON |
gemini-3.5-flash |
off | 8 web_search_queries, 3 chunks |
no |
Spans emitted in every case: invocation, invoke_agent root, call_llm, generate_content <model>. The response contained only text parts; the search is not returned as a tool_call/tool_response part.
Repro script
import asyncio, os, sys
os.environ["ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS"] = sys.argv[2] # "true" / "false"
from google.genai import types
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
trace.set_tracer_provider(provider)
from google.adk.agents.llm_agent import LlmAgent
from google.adk.runners import InMemoryRunner
from google.adk.tools.google_search_tool import google_search
agent = LlmAgent(name="root", model=sys.argv[1],
instruction="Always use Google Search before answering.",
tools=[google_search])
async def main():
runner = InMemoryRunner(agent=agent, app_name="live")
s = await runner.session_service.create_session(app_name="live", user_id="u")
events = [e async for e in runner.run_async(
user_id="u", session_id=s.id,
new_message=types.Content(role="user", parts=[types.Part(
text="What is the latest google-adk release on PyPI?")]))]
print("grounded:", any(e.grounding_metadata for e in events))
for span in exporter.get_finished_spans():
for k, v in (span.attributes or {}).items():
if "grounding" in k or "grounding" in str(v):
print(span.name, k)
asyncio.run(main())Run as python repro.py gemini-2.5-flash false. It prints grounded: True and no span lines.
Describe the Solution You'd Like
When a model response carries grounding_metadata, record it as attributes on the existing model-call span, only when it is present, so ungrounded spans are unchanged.
- Always recorded (no user content): whether the call was grounded, the number of search queries, and the number of grounding chunks.
- Only when message content capture is enabled (queries are derived from user input): the search query text and the source URIs.
Impact on your work
Without this, you can't use traces to debug or monitor search-grounded agents in deployments that turn content capture off, including the Agent Engine --otel_to_cloud default. For example, you can't tell whether a wrong answer happened because no search ran, the queries were poor, or the sources were poor.
Willingness to contribute
Yes. I can send a PR with unit tests once the naming and gating are agreed.
🟡 Recommended Information
Describe Alternatives You've Considered
- Emit a synthetic
execute_tool google_searchspan. Rejected: the search runs inside the model call, so ADK has no real start or end time. A made-up span would mislead latency analysis. - Rely on
gcp.vertex.agent.llm_response. Only available with content capture on, can't be queried as an attribute, and is off by default foradk deploy agent_engine --otel_to_cloud. - Enable
opentelemetry-instrumentation-google-genai. It has an open TODO to reportgrounding_metadata, so it doesn't record this today either. - Set
include_server_side_tool_invocations. Larger behaviour change; belongs with Feature Request: Native AFC & Multi-Tool Combination Support (Built-in Grounding + Custom Python Callables) on Vertex AI #5772, not here.
Proposed API / Implementation
Two naming options. I'd like maintainer input on which fits:
A. Reuse incubating OTel GenAI attributes where they exist
gen_ai.retrieval.query.text: the spec marks it as possibly sensitive, so content-gated.gen_ai.retrieval.documents: JSON documents withid/score, content-gated.- Mismatches:
query.textis a single string, but Gemini issues several queries per call (8–9 in thegemini-3.5-flashruns above).- Grounding chunks have a
uri/titlebut no ID and no per-document score. - These attributes appear intended for a separate
retrievaloperation span, not agenerate_contentspan.
B. ADK-specific experimental attributes, following _set_context_cache_attributes
adk.experimental.grounding.{grounded, query_count, chunk_count}, always recorded.adk.experimental.grounding.{web_search_queries, source_uris}, content-gated.- Behind a new
@experimental_telemetry(gate="grounding").
My suggestion is B for the counts (there's no semconv equivalent), and a maintainer decision on whether the content-gated fields should use A or B.
# telemetry/tracing.py, called from trace_call_llm and trace_inference_result
@experimental_telemetry(gate="grounding")
def _set_grounding_attributes(span, grounding_metadata, capture_content: bool):
if grounding_metadata is None:
return
...Open questions:
- Should this use A, B, or the mix above?
- Should the content-gated fields follow
should_add_content_to_legacy_spansor the OTel GenAI content-capture setting?
Additional Context
Environment: google-adk 2.10.0 (main @ def458b6), google-genai 2.24.0, opentelemetry-sdk 1.42.1, Python 3.11.15, Windows 11, Vertex AI backend.
Scope: the other built-in grounding tools (VertexAiSearchTool, EnterpriseWebSearchTool, GoogleMapsGroundingTool, url_context) reach the model through the same path (process_llm_request appends to config.tools). A mock-model repro shows the same result for VertexAiSearchTool. I have only verified google_search live.
Prior art checked:
trace_agent_invocation(telemetry/tracing.py) notes thatgen_ai.data_source.idis not set becauseGroundingMetadatahas no ID. This proposal does not try to set that attribute.DebugLoggingPluginrecordshas_grounding_metadata, but only in its own YAML debug file, not in OTel spans.- Not a duplicate of Google Search Tool does not return grounding metadata in ADK Runner events #1693, which was about
grounding_metadatamissing from events. Events now carry it (see the table above); this issue is only about traces. - Related but separate: Feature Request: Native AFC & Multi-Tool Combination Support (Built-in Grounding + Custom Python Callables) on Vertex AI #5772 (combining built-in tools with function tools).
Activity
sanketpatil06 commentedon Sep 30, 2026
Hi @spandankeche, thanks for the detailed analysis.
Option B (
adk.experimental.grounding.*) looks like the right approach. It follows the_set_context_cache_attributespattern and avoids thegen_ai.retrieval.*mismatches. For the content-gated fields, usetelemetry_config.should_add_content_to_legacy_spans.Feel free to open a PR with unit tests, our team would be happy to review it.
spandankeche commentedon Sep 30, 2026
Thanks @sanketpatil06! Going with Option B and
should_add_content_to_legacy_spansfor the content-gated fields. I'll open a PR with unit tests shortly.feat: record google_search grounding on LLM spans