Skip to content

feat(bqaa): ADK 2.0 minimum producer cut (#293 v5)#6015

Closed
caohy1988 wants to merge 1 commit into
google:mainfrom
caohy1988:feat/bqaa-adk2-minimum-producer
Closed

feat(bqaa): ADK 2.0 minimum producer cut (#293 v5)#6015
caohy1988 wants to merge 1 commit into
google:mainfrom
caohy1988:feat/bqaa-adk2-minimum-producer

Conversation

@caohy1988
Copy link
Copy Markdown

Draft — for review/iteration before mid-June customer cutover.

This PR implements the customer-driven minimum producer subset of the ADK 2.0 observability work tracked in GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#293 (parent: #190). The customer needs these specific fields visible in BigQuery before their ADK 2.0 production cutover; the full v15 contract lands incrementally.

Producer-only — no consumer-side SDK / typed-view work is needed for the customer to query the new data; they read base-table JSON directly during the mid-June window.

What lands

Group Item Sub-issue
A1/A2 every row carries attributes.adk.{schema_version, app_name} (this PR)
A3 on_event_callback rows carry attributes.adk.source_event_id — never fabricated on callback-only rows #194
C1 attributes.adk.node = {path, run_id, parent_path} — default-empty path preserved verbatim, no synthesis #196
C2 attributes.adk.branch (absent stays JSON null) #197
C3 attributes.adk.scope = null | {id, kind} per #198 / #293 v5 derivation #198
C4 emit AGENT_TRANSFER (from_agent = event.author, to_agent = actions.transfer_to_agent) #200
C5 emit EVENT_COMPACTION — fractional float-epoch seconds preserved #201
C6 emit AGENT_STATE_CHECKPOINT (both {agent_state, end_of_agent} shapes), inline payload only #202
C7 emit TOOL_PAUSED with pause_kind (HITL-aware via _HITL_PAUSE_KIND_MAP) + function_call_id; user-message TOOL_COMPLETED with pause_kind='tool' for non-HITL; HITL function_responses stay on HITL_*_COMPLETED, never emit TOOL_COMPLETED #199 (pair-key subset)
C8 flat-with-prefix attributes.adk.{route, render_ui_widgets, rewind_before_invocation_id} mirror #203
D1 delete deprecated on_state_change_callback stub (this PR)

Explicitly deferred (post-mid-June, per #293 v5)

Deferred Reason Tracked at
WORKFLOW_NODE_STARTING/COMPLETED event types Design-blocked (OTel-span vs event-observation). Workflow boundaries are partially observable via attributes.adk.node today. #207
Pause registry pause_orphan semantics / read-after-write visibility Blocks the orphan-flag contract, not the row-pair join keys. Customer can compute long-running tool durations from direct TOOL_PAUSED ↔ TOOL_COMPLETED SQL joins. #206
OTel attributes.adk.otel_span_id Best-effort; consumer can join via A3 source_event_id ↔ ADK's span-side associated_event_ids. #205
Oversized-state GCS offload for AGENT_STATE_CHECKPOINT Inline covers all but very large state. #190
B0 (#194) plumbing for non-on_event_callback paths Added EventData.source_event: Optional[Event] = None as a minimal step; full per-callback coverage matrix lands with #194. #194

Compatibility

  • AGENT_RESPONSE retains the legacy flat extras (source_event_id, source_event_author, source_event_branch) for backward compat alongside the new canonical attributes.adk.* envelope. Existing consumers continue to work.
  • EventData signature gains one optional field (source_event). No breaking change.
  • Mock-based test fixtures (the HITL suite) continue to pass — the envelope helper is defensive against missing attributes on Mock objects.

Tests

257 plugin tests pass (238 existing + 19 new):

  • Envelope shape on Event-originating and non-Event-originating rows
  • node.parent_path derivation with both empty and nested paths
  • _derive_scope for None, bare node, path/node, FC IDs, empty string
  • C4 / C5 / C6 emit paths
  • C5 fractional float-epoch precision round-trip
  • C6 both-shape coverage + id-stabilization regression guard (Event.model_post_init auto-assigns id even when the constructor omits it — _create_agent_state_event is covered)
  • C7 TOOL_PAUSED pause_kind derivation for non-HITL and HITL
  • C7 HITL non-routing: HITL function_responseHITL_*_COMPLETED only, never TOOL_COMPLETED
  • C7 user-message TOOL_COMPLETED with pause_kind='tool'
  • C8 flat-with-prefix route / rewind_before_invocation_id
  • D1: on_state_change_callback removed from the public surface
$ python3 -m pytest tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py -q
257 passed, 9 warnings in 21.25s

References

Test plan

  • pytest tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py — 257/257 pass
  • pyink --config pyproject.toml src/ tests/ + isort src/ tests/ — clean
  • Manual smoke against a real BigQuery dataset with an ADK 2.0 invocation that includes transfer + compaction + checkpoint + long-running pause
  • Customer pre-cutover dry run

🤖 Generated with Claude Code

Implements the customer-driven mid-June producer-only subset of the
ADK 2.0 observability work tracked in google#293 (parent google#190). The customer
needs these specific fields visible in BigQuery before their ADK 2.0
production cutover; full v15 contract (google#190) lands incrementally.

This change is producer-only. No consumer-side SDK / typed-view work
is included — the customer reads base-table JSON directly during the
mid-June window.

What lands
----------

A1/A2 — every ADK-enriched row carries the attributes.adk envelope:
  attributes.adk.schema_version (_ADK_ENVELOPE_SCHEMA_VERSION = "1")
  attributes.adk.app_name       (from InvocationContext.session.app_name)

A3 — rows from on_event_callback additionally carry:
  attributes.adk.source_event_id  (Event.id, reliable join key)
                                  Note: never fabricated — callback rows
                                  without an originating Event leave it
                                  JSON-null.

C1 — attributes.adk.node = {path, run_id, parent_path}. parent_path is
     derived from path; default-empty path (NodeInfo.path = "") is
     preserved verbatim with parent_path = null, no synthesis.

C2 — attributes.adk.branch (absent stays JSON null).

C3 — attributes.adk.scope = null | {id, kind} per google#198 / google#293 v5
     derivation order: (1) None → null, (2) name@run_id / path/name@run_id
     → node_run, (3) any other non-empty string → function_call
     (model-provided FC IDs match here), (4) empty/non-string → unknown
     with warning.

C4 — emit AGENT_TRANSFER from event.actions.transfer_to_agent. Payload
     pinned: from_agent = event.author, to_agent = the target. Verified
     against EventActions.transfer_to_agent which stores the target only.

C5 — emit EVENT_COMPACTION from event.actions.compaction. Float
     start_timestamp / end_timestamp preserved with fractional precision
     (consumer view conversion deferred).

C6 — emit AGENT_STATE_CHECKPOINT when actions.agent_state is not None
     OR actions.end_of_agent is True. Allows {agent_state: null,
     end_of_agent: true} payloads. Inline payload only; GCS offload
     for oversized state deferred.

C7 — emit TOOL_PAUSED for each event.long_running_tool_ids id, with
     attributes.pause_kind (derived from function_call NAME via
     _HITL_PAUSE_KIND_MAP — hitl_* for adk_request_*, tool otherwise)
     and attributes.function_call_id. HITL routing is unchanged:
     HITL function_responses stay on HITL_*_COMPLETED, NEVER emit
     TOOL_COMPLETED. Non-HITL function_responses arriving via
     on_user_message_callback emit TOOL_COMPLETED with pause_kind='tool'
     so the customer can pair (TOOL_PAUSED ↔ TOOL_COMPLETED) on
     (app_name, user_id, session_id, function_call_id) directly in SQL.
     Pause registry / pause_orphan semantics deferred to google#206.

C8 — attributes.adk.{route, render_ui_widgets, rewind_before_invocation_id}
     mirror EventActions, flat-with-prefix per google#203 (matches the rest
     of the attributes.adk.* envelope convention).

D1 — delete the deprecated on_state_change_callback stub (never called
     by ADK 2.0; verified no callers).

Compatibility
-------------

* AGENT_RESPONSE retains the legacy flat extras (source_event_id,
  source_event_author, source_event_branch) for backward compat. The
  canonical keys are now under attributes.adk.*.
* The HITL test fixtures use Mock events without long_running_tool_ids
  or .id; the envelope helper is defensive against missing attrs.
* No EventData / _log_event signature change. Added one optional field
  EventData.source_event: Optional[Event] = None — a minimal B0 (google#194)
  step. Callbacks that have access to the source Event pass it through;
  others leave it None (and the envelope correctly leaves A3/C1/C2/C3
  null on those rows).

Tests
-----

257 plugin tests pass (238 existing + 19 new):
* envelope shape on event-originating and non-event-originating rows
* node parent_path derivation with both empty and nested paths
* _derive_scope for None, bare node, path/node, FC IDs, empty string
* C4/C5/C6 emit paths
* C5 fractional float-epoch precision round-trip
* C6 both-shape coverage + id-stabilization regression guard
  (Event.model_post_init auto-assigns id even when constructor omits it)
* C7 TOOL_PAUSED pause_kind derivation for non-HITL and HITL
* C7 HITL non-routing: HITL function_response → HITL_*_COMPLETED only,
  NEVER TOOL_COMPLETED
* C7 user-message TOOL_COMPLETED with pause_kind='tool'
* C8 flat-with-prefix route / rewind_before_invocation_id
* D1: on_state_change_callback removed from the public surface

Refs: google#293 (v5), google#190, google#194, google#196, google#197, google#198, google#199, google#200, google#201, google#202,
google#203, google#206 (deferred orphan semantics), #207 (deferred workflow nodes).
@adk-bot adk-bot added the tracing [Component] This issue is related to OpenTelemetry tracing label Jun 8, 2026
@caohy1988 caohy1988 closed this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tracing [Component] This issue is related to OpenTelemetry tracing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants