GoogleCloudPlatform / GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK
adk-2.0 producer: emit TOOL_PAUSED / TOOL_COMPLETED with pause_kind + function_call_id (long-running event path)
- Dominant language
- Python
- Stars
- 47
- Forks
- 21
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 33
Description
Parent tracker: #190 (v15 contract).
Wave: 2 — Producer event rows.
Blocked by: #195 (identity stamping for the registry key), #194 (telemetry context — needs source event `content` to match long-running IDs back to function-call parts).
Design dependency: #206 (pause registry read-after-write strategy) determines the registry source-of-truth implementation.
## Contract
### `TOOL_PAUSED`
When `event.long_running_tool_ids` is non-empty, emit one `TOOL_PAUSED` per id with:
- `attributes.adk.function_call_id = `.
- `attributes.adk.pause_kind ∈ {tool, hitl_credential, hitl_confirmation, hitl_input}`.
**`pause_kind` derivation — explicit ID→name mapping required:**
`event.long_running_tool_ids` is a set of function-call IDs. `_HITL_EVENT_MAP` (line 87) is keyed by function-call **name** (`adk_request_credential` / `adk_request_confirmation` / `adk_request_input`), not by ID. Derive `pause_kind` by:
1. For each `long_running_tool_id`, locate the matching `part.function_call` in the same source event's `content.parts` where `part.function_call.id == long_running_tool_id`.
2. Look up `part.function_call.name` in `_HITL_EVENT_MAP`. Match → `pause_kind = hitl_credential` / `hitl_confirmation` / `hitl_input` accordingly. No match → `pause_kind = 'tool'`.
3. If no matching `part.function_call` is found in the event (malformed/unexpected event), emit `pause_kind = 'tool'` and **log a warning** so visibility into the anomaly is preserved. Do not silently coerce.
**Do not** look up `_HITL_EVENT_MAP` against the id value itself — that would classify every HITL pause as `'tool'` and defeat the discriminator.
### `TOOL_COMPLETED` (long-running event/user-message path) — non-HITL only
Detect **non-HITL** `function_response.id` completions in `on_event_callback` / `on_user_message_callback` (not only `after_tool_callback` — user-supplied long-running responses arrive as `function_response` parts on user messages or events). Reuse the iteration already present at `bigquery_agent_analytics_plugin.py:3022`.
**HITL completions are NOT emitted as `TOOL_COMPLETED` rows** — they continue to surface through the existing `HITL_CREDENTIAL_REQUEST_COMPLETED` / `HITL_CONFIRMATION_REQUEST_COMPLETED` / `HITL_INPUT_REQUEST_COMPLETED` event flow (via `_HITL_EVENT_MAP` at `:3023`). Double-emission would inflate HITL analytics; this issue does not change the HITL flow.
`TOOL_COMPLETED` rows emitted from this path carry:
- `attributes.adk.function_call_id = `.
- `attributes.adk.pause_kind = 'tool'` (always — HITL completions don't reach this path).
- `attributes.adk.pause_orphan = true` when no matching `TOOL_PAUSED` exists per the chosen visibility strategy (#206).
The `pause_kind` field on `TOOL_COMPLETED` is retained even though it's always `'tool'` here, so the consumer view's filter is uniform across paired and orphan rows and so future widening (e.g. structured non-HITL pause classes) doesn't require schema changes.
### Pairing contract
Composite key: `(app_name, user_id, session_id, function_call_id)`. `function_call_id` alone is not safe; the full ADK session identity is needed because the BigQuery `agent_events` table can aggregate multiple apps and users.
`pause_orphan = true` is emitted only **after** the chosen visibility strategy (#206) fails to find a matching `TOOL_PAUSED`. Never on a bare immediate-BQ-miss.
## Acceptance
- [ ] Every `TOOL_PAUSED` carries `attributes.adk.{function_call_id, pause_kind}`.
- [ ] `pause_kind` is derived via the **id→name** path: locate `part.function_call.id == long_running_tool_id`, look up `_HITL_EVENT_MAP` by `part.function_call.name`.
- [ ] If no matching `part.function_call` is found, emit `pause_kind = 'tool'` with a warning log.
- [ ] Every `TOOL_COMPLETED` from the long-running event/user-message path carries `attributes.adk.{function_call_id, pause_kind = 'tool', pause_orphan}`.
- [ ] **HITL function responses do not produce `TOOL_COMPLETED` rows** — they continue through the existing `HITL_*_COMPLETED` flow. Fixture asserts that an `adk_request_confirmation` function response emits `HITL_CONFIRMATION_REQUEST_COMPLETED` and **not** `TOOL_COMPLETED`.
- [ ] Non-HITL `function_response.id` completions are detected in event/user-message paths, not only `after_tool_callback`.
- [ ] `pause_orphan = true` only emitted after the chosen visibility strategy (#206) fails.
Note: the HITL-orphan **defensive** test (a synthetic `TOOL_COMPLETED` with `pause_kind = 'hitl_confirmation'` asserted absent from `long_running_tool_durations`) lives in #215 and #218, not here — it defends against incorrectly-tagged rows that the producer should never emit in the first place.
## References
- #190 (v15 producer §6).
- `src/google/adk/plugins/bigquery_agent_analytics_plugin.py:87` (`_HITL_EVENT_MAP`), `:3022` (existing `function_response` iteration), `:3092` (long_running_tool_ids guard).
Contributor guide
Assessment
This issue has not been assessed yet.