Skip to content

Commit 236b790

Browse files
committed
fix(bqaa): put C7 pair keys under attributes.adk (not top-level)
Caught in review of #6: the C7 pair keys (pause_kind, function_call_id) were being passed via EventData.extra_attributes, which _enrich_attributes() copies at the top of attrs *before* attrs["adk"] = _build_adk_envelope(...). That landed them at attributes.pause_kind / attributes.function_call_id, not attributes.adk.pause_kind / attributes.adk.function_call_id. The customer SQL pinned in google#293 v5 acceptance #3 is: JSON_VALUE(attributes, '$.adk.function_call_id') = JSON_VALUE(...) so the pair join would have returned null on every row. This commit makes the contract match the SQL. Changes: * EventData gains adk_extras: dict[str, Any], a sibling of extra_attributes that lives INSIDE attributes.adk. * _enrich_attributes merges adk_extras into the envelope after _build_adk_envelope (envelope wins on conflict — producer-derived identity fields like source_event_id are the source of truth). * The two emit sites (TOOL_PAUSED in on_event_callback, TOOL_COMPLETED in on_user_message_callback) pass the pair keys via adk_extras= instead of extra_attributes=. * The three C7 tests are updated to assert json.loads(row["attributes"])["adk"]["pause_kind"] etc., locking in the right shape this time. Full plugin suite: 252 passed.
1 parent 119b8ea commit 236b790

2 files changed

Lines changed: 27 additions & 13 deletions

File tree

src/google/adk/plugins/bigquery_agent_analytics_plugin.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,12 @@ class EventData:
20202020
# an Event — the envelope helper writes those attributes as null
20212021
# rather than synthesizing fake identity (per #293 v5 A3 contract).
20222022
source_event: Optional["Event"] = None
2023+
# Producer-supplied extras that belong INSIDE ``attributes.adk`` (not
2024+
# at the top level of ``attributes``). C7's pair keys
2025+
# (``pause_kind`` / ``function_call_id``) ride here so consumer SQL
2026+
# like ``JSON_VALUE(attributes, '$.adk.function_call_id')`` lands at
2027+
# the right JSON path per #293 v5.
2028+
adk_extras: dict[str, Any] = field(default_factory=dict)
20232029

20242030

20252031
class BigQueryAgentAnalyticsPlugin(BasePlugin):
@@ -2923,9 +2929,17 @@ def _enrich_attributes(
29232929
A new dict ready for JSON serialization into the attributes column.
29242930
"""
29252931
attrs: dict[str, Any] = dict(event_data.extra_attributes)
2926-
attrs["adk"] = self._build_adk_envelope(
2932+
adk_envelope = self._build_adk_envelope(
29272933
callback_context, event_data.source_event
29282934
)
2935+
# Merge producer-supplied adk_extras (#293 v5 C7 pair keys etc.)
2936+
# INTO the adk envelope so consumer SQL on
2937+
# ``$.adk.pause_kind`` / ``$.adk.function_call_id`` resolves.
2938+
# adk_envelope wins on key conflict — producer-derived envelope
2939+
# is the source of truth for identity fields like source_event_id.
2940+
for k, v in event_data.adk_extras.items():
2941+
adk_envelope.setdefault(k, v)
2942+
attrs["adk"] = adk_envelope
29292943

29302944
attrs["root_agent_name"] = TraceManager.get_root_agent_name()
29312945
if event_data.model:
@@ -3136,7 +3150,7 @@ async def on_user_message_callback(
31363150
raw_content=content_dict,
31373151
is_truncated=is_truncated,
31383152
event_data=EventData(
3139-
extra_attributes={
3153+
adk_extras={
31403154
"pause_kind": "tool",
31413155
"function_call_id": part.function_response.id,
31423156
},
@@ -3304,7 +3318,7 @@ async def on_event_callback(
33043318
is_truncated=is_truncated,
33053319
event_data=EventData(
33063320
source_event=event,
3307-
extra_attributes={
3321+
adk_extras={
33083322
"pause_kind": pause_kind,
33093323
"function_call_id": part.function_call.id,
33103324
},

tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8007,7 +8007,6 @@ async def test_skips_executable_code_only_events(
80078007
assert mock_write_client.append_rows.call_count == 0
80088008

80098009

8010-
80118010
# -----------------------------------------------------------------------------
80128011
# ADK 2.0 minimum producer cut (#293 v5)
80138012
#
@@ -8346,10 +8345,11 @@ async def test_tool_paused_non_hitl_pause_kind_tool(
83468345
rows = await _get_captured_rows_async(mock_write_client, dummy_arrow_schema)
83478346
pauses = [r for r in rows if r["event_type"] == "TOOL_PAUSED"]
83488347
assert len(pauses) == 1
8348+
# C7 pair keys live UNDER ``attributes.adk`` so the consumer SQL on
8349+
# ``JSON_VALUE(attributes, '$.adk.function_call_id')`` resolves.
83498350
adk = json.loads(pauses[0]["attributes"])["adk"]
8350-
extras = json.loads(pauses[0]["attributes"])
8351-
assert extras["pause_kind"] == "tool"
8352-
assert extras["function_call_id"] == "call-1"
8351+
assert adk["pause_kind"] == "tool"
8352+
assert adk["function_call_id"] == "call-1"
83538353

83548354
@pytest.mark.asyncio
83558355
async def test_tool_paused_hitl_pause_kind(
@@ -8379,9 +8379,9 @@ async def test_tool_paused_hitl_pause_kind(
83798379
rows = await _get_captured_rows_async(mock_write_client, dummy_arrow_schema)
83808380
pauses = [r for r in rows if r["event_type"] == "TOOL_PAUSED"]
83818381
assert len(pauses) == 1
8382-
extras = json.loads(pauses[0]["attributes"])
8383-
assert extras["pause_kind"] == "hitl_confirmation"
8384-
assert extras["function_call_id"] == "call-hitl-1"
8382+
adk = json.loads(pauses[0]["attributes"])["adk"]
8383+
assert adk["pause_kind"] == "hitl_confirmation"
8384+
assert adk["function_call_id"] == "call-hitl-1"
83858385

83868386
@pytest.mark.asyncio
83878387
async def test_user_message_function_response_emits_tool_completed(
@@ -8407,9 +8407,9 @@ async def test_user_message_function_response_emits_tool_completed(
84078407
rows = await _get_captured_rows_async(mock_write_client, dummy_arrow_schema)
84088408
completed = [r for r in rows if r["event_type"] == "TOOL_COMPLETED"]
84098409
assert len(completed) == 1
8410-
extras = json.loads(completed[0]["attributes"])
8411-
assert extras["pause_kind"] == "tool"
8412-
assert extras["function_call_id"] == "call-1"
8410+
adk = json.loads(completed[0]["attributes"])["adk"]
8411+
assert adk["pause_kind"] == "tool"
8412+
assert adk["function_call_id"] == "call-1"
84138413

84148414
@pytest.mark.asyncio
84158415
async def test_hitl_user_message_does_not_emit_tool_completed(

0 commit comments

Comments
 (0)