Skip to content

OpenTelemetry integration creates span ID mismatch between LLM_REQUEST and LLM_RESPONSE/LLM_ERROR (BigQuery Analytics Plugin) #4851

Closed

Description

@evekhm

When OpenTelemetry tracing is enabled in the ADK, the span_id no longer matches between LLM_REQUEST and its corresponding LLM_RESPONSE (and LLM_ERROR) events.
This happens because before_model_callback runs before the framework's internal call_llm span begins. As a result, the BigQueryAgentAnalyticsPlugin grabs the parent span ID (e.g. from the execute_tool span) to log the LLM_REQUEST. Then, the model executes inside the child call_llm span. The on_model_error_callback and after_model_callback then execute within that child span.
Because of this, LLM_REQUEST.span_id == LLM_RESPONSE.parent_span_id.

This can break any downstream data warehouses or analysis tools that rightfully expect an atomic request/response LLM pair to share the identical span_id. Notably, without OpenTelemetry, span_id matches perfectly between all three events

When querying raw event tables with OTel enabled:
TOOL_STARTING to TOOL_COMPLETED still match perfectly on span_id.
AGENT_STARTING to AGENT_COMPLETED still match perfectly on span_id.
The issue is completely isolated to the LLM flow callbacks.

Activity

  1. added
    tracing[Component] This issue is related to OpenTelemetry tracing
    on Mar 16, 2026
  2. added theissue type on Mar 16, 2026
  3. surajksharma07 commented on Mar 17, 2026

    @surajksharma07
    Collaborator

    @evekhm the mismatch comes down to timing. before_model_callback fires just before the call_llm span opens in _call_llm_async, which means LLM_REQUEST ends up picking up the parent span context while LLM_RESPONSE captures the child span.
    That's what's causing the pairing to break.

    One thing worth trying: move _handle_before_model_callback inside the with tracer.start_as_current_span('call_llm') block so all three callbacks are working within the same span context.
    Could you test that on your end and see if it fixes the pairing for you?

    If it holds up after some thorough testing, it'd be great to carry that forward into #4854!

  4. brucearctor commented on Mar 18, 2026

    @brucearctor
    Contributor

    @surajksharma07 Spot on with the diagnosis! PR #4854 implements exactly that — but found one additional subtlety worth noting:

    Just moving the before_model_callback inside the call_llm span isn't enough on its own. The after_model_callback runs inside an async for loop that consumes yields from the error handler, which creates a child generate_content span via use_inference_span. Because async generators preserve their span context across yields, the after model callback ends up seeing the child span instead of call_llm.

    The fix in #4854 addresses both sides:

    1. Moves the before model callback inside the call_llm span
    2. Wraps the after model callback with trace.use_span to re-activate the call_llm span
  5. surajksharma07 commented on Mar 26, 2026

    @surajksharma07
    Collaborator

    Good catch on the trace.use_span detail, @brucearctor — the async generator span context persisting across yields is easy to miss, and wrapping after_model_callback to re-activate the call_llm span makes the fix properly complete on both sides.

    @evekhm given your v0.0.4 commit it looks like you may have already verified this on your end — could you confirm whether the two-part approach in PR #4854 fully resolves the LLM_REQUEST/LLM_RESPONSE pairing for you?

    • Moving the before callback inside the span
    • trace.use_span for the after callback

    Once we have that confirmation, we'll get it through review with @jawoszek and move toward merge.

    Team is looking more into it.

  6. surajksharma07 commented on Mar 26, 2026

    @surajksharma07
    Collaborator

    @jawoszek Please have a look into it once.

  7. removed their assignment
    on Mar 26, 2026
  8. added a commit that references this issue on Mar 28, 2026
    9a47b6f
  9. added a commit that references this issue on Mar 28, 2026
    b219c9b
  10. haiyuan-eng-google commented on Mar 30, 2026

    @haiyuan-eng-google
    Collaborator

    Fixed in #5041

    Summary

    • Moves before_model_callback inside the call_llm span so plugins see a valid span (previously ran before the span started)
    • Wraps after_model_callback with trace.use_span(span, end_on_exit=False) to rebind to the call_llm span (previously could see a child inference span)
    • Threads call_llm_span into _run_and_handle_error and wraps on_model_error_callback the same way
    • Adds 5 regression tests proving all three callbacks observe the same call_llm span ID

    Changes

    src/google/adk/flows/llm_flows/base_llm_flow.py:

    • _call_llm_async(): Moved before_model_callback, label setup, and __get_llm() inside the with tracer.start_as_current_span('call_llm') as span: block
    • Both CFC and non-CFC after_model_callback call sites wrapped with trace.use_span(span, end_on_exit=False)
    • _run_and_handle_error(): New optional call_llm_span parameter; error callbacks run under use_span(call_llm_span) when provided
    • Class method wrapper updated to pass through call_llm_span

    tests/unittests/flows/llm_flows/test_llm_callback_span_consistency.py (new):

    1. test_before_and_after_callbacks_share_same_span — success path
    2. test_callbacks_same_trace_id — same trace
    3. test_before_and_error_callbacks_share_same_span — error path
    4. test_short_circuit_before_callback_sees_valid_span — short-circuit path
    5. test_all_three_callbacks_share_span_on_error — error recovery path (all 3 callbacks)

    Design decisions

    • end_on_exit=False: The call_llm span is already managed by the with tracer.start_as_current_span(...) context manager. use_span is only used to re-activate the span context, not to end it.
    • Backward-compatible: call_llm_span defaults to None; when absent, behavior is unchanged.
    • Plugin workaround preserved: This PR does NOT remove the ambient-span workaround in BigQueryAgentAnalyticsPlugin. That should be a separate follow-up PR after this fix is proven in production.

    Test plan

    • All 5 new span consistency tests pass
    • All 363 existing llm_flows tests pass (0 regressions)
    • Autoformat clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

needs review[Status] The PR/issue is awaiting review from the maintainertracing[Component] This issue is related to OpenTelemetry tracing

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions