@@ -8088,6 +8088,203 @@ def __init__(self):
80888088 attributes = json.loads(log_entry["attributes"])
80898089 assert "cache_metadata" not in attributes
80908090
8091+ async def _run_after_model(
8092+ self,
8093+ bq_plugin_inst,
8094+ mock_write_client,
8095+ callback_context,
8096+ dummy_arrow_schema,
8097+ llm_response,
8098+ ):
8099+ """Drives after_model_callback and returns the LLM_RESPONSE attributes."""
8100+ bigquery_agent_analytics_plugin.TraceManager.push_span(callback_context)
8101+ await bq_plugin_inst.after_model_callback(
8102+ callback_context=callback_context,
8103+ llm_response=llm_response,
8104+ )
8105+ await asyncio.sleep(0.05)
8106+ rows = await _get_captured_rows_async(mock_write_client, dummy_arrow_schema)
8107+ log_entry = next(r for r in rows if r["event_type"] == "LLM_RESPONSE")
8108+ return json.loads(log_entry["attributes"])
8109+
8110+ @pytest.mark.asyncio
8111+ async def test_cache_type_explicit(
8112+ self,
8113+ bq_plugin_inst,
8114+ mock_write_client,
8115+ callback_context,
8116+ dummy_arrow_schema,
8117+ ):
8118+ """cache_name set + cached tokens -> explicit (ADK-managed cache)."""
8119+ llm_response = llm_response_lib.LlmResponse(
8120+ content=types.Content(parts=[types.Part(text="hi")]),
8121+ usage_metadata=types.GenerateContentResponseUsageMetadata(
8122+ prompt_token_count=100,
8123+ candidates_token_count=20,
8124+ total_token_count=120,
8125+ cached_content_token_count=80,
8126+ ),
8127+ cache_metadata={
8128+ "cache_name": "projects/p/locations/us-central1/cachedContents/c",
8129+ "expire_time": 9999999999.0,
8130+ "fingerprint": "fp-1",
8131+ "invocations_used": 1,
8132+ "contents_count": 2,
8133+ "created_at": 1.0,
8134+ },
8135+ )
8136+ attributes = await self._run_after_model(
8137+ bq_plugin_inst,
8138+ mock_write_client,
8139+ callback_context,
8140+ dummy_arrow_schema,
8141+ llm_response,
8142+ )
8143+ assert attributes["cache_type"] == "explicit"
8144+
8145+ @pytest.mark.asyncio
8146+ async def test_cache_type_implicit(
8147+ self,
8148+ bq_plugin_inst,
8149+ mock_write_client,
8150+ callback_context,
8151+ dummy_arrow_schema,
8152+ ):
8153+ """Cached tokens with no cache_metadata -> implicit (provider prefix)."""
8154+ llm_response = llm_response_lib.LlmResponse(
8155+ content=types.Content(parts=[types.Part(text="hi")]),
8156+ usage_metadata=types.GenerateContentResponseUsageMetadata(
8157+ prompt_token_count=100,
8158+ candidates_token_count=20,
8159+ total_token_count=120,
8160+ cached_content_token_count=80,
8161+ ),
8162+ )
8163+ attributes = await self._run_after_model(
8164+ bq_plugin_inst,
8165+ mock_write_client,
8166+ callback_context,
8167+ dummy_arrow_schema,
8168+ llm_response,
8169+ )
8170+ assert attributes["cache_type"] == "implicit"
8171+
8172+ @pytest.mark.asyncio
8173+ async def test_cache_type_explicit_fingerprint_only(
8174+ self,
8175+ bq_plugin_inst,
8176+ mock_write_client,
8177+ callback_context,
8178+ dummy_arrow_schema,
8179+ ):
8180+ """Fingerprint-only cache_metadata (cache_name=None) is still explicit."""
8181+ llm_response = llm_response_lib.LlmResponse(
8182+ content=types.Content(parts=[types.Part(text="hi")]),
8183+ usage_metadata=types.GenerateContentResponseUsageMetadata(
8184+ prompt_token_count=100,
8185+ candidates_token_count=20,
8186+ total_token_count=120,
8187+ cached_content_token_count=80,
8188+ ),
8189+ cache_metadata={"fingerprint": "fp-1", "contents_count": 2},
8190+ )
8191+ attributes = await self._run_after_model(
8192+ bq_plugin_inst,
8193+ mock_write_client,
8194+ callback_context,
8195+ dummy_arrow_schema,
8196+ llm_response,
8197+ )
8198+ assert attributes["cache_type"] == "explicit"
8199+
8200+ @pytest.mark.asyncio
8201+ async def test_cache_type_none_with_active_cache(
8202+ self,
8203+ bq_plugin_inst,
8204+ mock_write_client,
8205+ callback_context,
8206+ dummy_arrow_schema,
8207+ ):
8208+ """Active cache but no cached tokens (creation turn / miss) -> none."""
8209+ llm_response = llm_response_lib.LlmResponse(
8210+ content=types.Content(parts=[types.Part(text="hi")]),
8211+ usage_metadata=types.GenerateContentResponseUsageMetadata(
8212+ prompt_token_count=100,
8213+ candidates_token_count=20,
8214+ total_token_count=120,
8215+ ),
8216+ cache_metadata={
8217+ "cache_name": "projects/p/locations/us-central1/cachedContents/c",
8218+ "expire_time": 9999999999.0,
8219+ "fingerprint": "fp-1",
8220+ "invocations_used": 1,
8221+ "contents_count": 2,
8222+ "created_at": 1.0,
8223+ },
8224+ )
8225+ attributes = await self._run_after_model(
8226+ bq_plugin_inst,
8227+ mock_write_client,
8228+ callback_context,
8229+ dummy_arrow_schema,
8230+ llm_response,
8231+ )
8232+ assert attributes["cache_type"] == "none"
8233+
8234+ @pytest.mark.asyncio
8235+ async def test_cache_type_none(
8236+ self,
8237+ bq_plugin_inst,
8238+ mock_write_client,
8239+ callback_context,
8240+ dummy_arrow_schema,
8241+ ):
8242+ """No cached tokens -> none."""
8243+ llm_response = llm_response_lib.LlmResponse(
8244+ content=types.Content(parts=[types.Part(text="hi")]),
8245+ usage_metadata=types.GenerateContentResponseUsageMetadata(
8246+ prompt_token_count=100,
8247+ candidates_token_count=20,
8248+ total_token_count=120,
8249+ ),
8250+ )
8251+ attributes = await self._run_after_model(
8252+ bq_plugin_inst,
8253+ mock_write_client,
8254+ callback_context,
8255+ dummy_arrow_schema,
8256+ llm_response,
8257+ )
8258+ assert attributes["cache_type"] == "none"
8259+
8260+ @pytest.mark.asyncio
8261+ async def test_cache_type_absent_on_partial_response(
8262+ self,
8263+ bq_plugin_inst,
8264+ mock_write_client,
8265+ callback_context,
8266+ dummy_arrow_schema,
8267+ ):
8268+ """Partial streaming rows carry no cache_type, even with cached tokens."""
8269+ llm_response = llm_response_lib.LlmResponse(
8270+ content=types.Content(parts=[types.Part(text="hi")]),
8271+ partial=True,
8272+ usage_metadata=types.GenerateContentResponseUsageMetadata(
8273+ prompt_token_count=100,
8274+ candidates_token_count=20,
8275+ total_token_count=120,
8276+ cached_content_token_count=80,
8277+ ),
8278+ )
8279+ attributes = await self._run_after_model(
8280+ bq_plugin_inst,
8281+ mock_write_client,
8282+ callback_context,
8283+ dummy_arrow_schema,
8284+ llm_response,
8285+ )
8286+ assert "cache_type" not in attributes
8287+
80918288
80928289# ==============================================================
80938290# TEST CLASS: A2A_INTERACTION event logging via on_event_callback
0 commit comments