Skip to content

[bug] google-adk: final response output is overwritten and cleared by subsequent state_delta events #3200

Description

@thomaslprr

Describe the bug
In openinference-instrumentation-google-adk (observed in v0.1.15), the _RunnerRunAsync and _BaseAgentRunAsync wrappers intercept events and set the span's OUTPUT_VALUE when event.is_final_response() is True.

However, the Google ADK can yield multiple events where is_final_response() is True during a single invocation (for instance, the actual model response containing content, followed by a state synchronization event like actions.state_delta which has no content).

Because the wrapper unconditionally overwrites OUTPUT_VALUE on every final response event, the last event (which lacks content) wins. This effectively erases the LLM/Agent's actual text response from the trace output in downstream visualization tools like Langfuse or Arize Phoenix.

To Reproduce
Steps to reproduce the behavior:

  1. Run a Google ADK agent that emits state updates or navigation deltas after generating its text response.
  2. Observe the invocation span in Phoenix/Langfuse.
  3. See error: The output field is empty or only contains the state delta JSON, missing the core content object.

Expected behavior
The instrumentation should preserve the actual generated content. It should only overwrite the OUTPUT_VALUE if the new final event actually contains content, or alternatively, retain the last final response that possessed a non-empty content payload.

Screenshots
N/A

Desktop:

  • OS: Linux
  • Version: openinference-instrumentation-google-adk==0.1.15

Additional context

Proposed Fix

In openinference/instrumentation/google_adk/_wrappers.py, track whether an event with content has already been captured during the stream evaluation.

Inside _RunnerRunAsync and _BaseAgentRunAsync:

has_set_content = False
async for event in self.__wrapped__:
    if event.is_final_response():
        event_has_content = getattr(event, "content", None) is not None
        if event_has_content or not has_set_content:
            if event_has_content:
                has_set_content = True
            try:
                span.set_attribute(
                    SpanAttributes.OUTPUT_VALUE,
                    event.model_dump_json(exclude_none=True),
                )
                # ... rest of the span tracking logic

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageIssues that require triage

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions