Skip to content

feat(plugins): project LlmResponse finish_reason to BigQuery analytics#5704

Open
roanny wants to merge 1 commit into
google:mainfrom
roanny:feat/llm-response-finish-reason
Open

feat(plugins): project LlmResponse finish_reason to BigQuery analytics#5704
roanny wants to merge 1 commit into
google:mainfrom
roanny:feat/llm-response-finish-reason

Conversation

@roanny
Copy link
Copy Markdown

@roanny roanny commented May 14, 2026

Summary

Projects finish_reason from LlmResponse to the attributes JSON column of LLM_RESPONSE events written to BigQuery by BigQueryAgentAnalyticsPlugin, and exposes it in the generated per-event-type SQL view.

Without this projection, classifying model failure modes (MAX_TOKENS, SAFETY, MALFORMED_FUNCTION_CALL, RECITATION, etc.) via SQL against the analytics table is impossible — the signal lives only in unstructured Cloud Logging output that is not joinable to the structured event stream.

Fixes #5644.

Changes

  • EventData gets a finish_reason: Optional[str] field.
  • after_model_callback reads llm_response.finish_reason.name and passes it through to EventData.
  • _enrich_attributes writes attrs["finish_reason"] when present (omitted entirely when None, so the JSON shape stays backwards-compatible for existing consumers).
  • _EVENT_VIEW_DEFS["LLM_RESPONSE"] projects JSON_VALUE(attributes, '$.finish_reason') AS finish_reason so it shows up as a typed column on the per-event-type view.
  • Schema-field description for the attributes column mentions the new key with example values.

Backwards compatibility

The new key is only added to the JSON when LlmResponse.finish_reason is not None, so events emitted before this change and events for streaming partials (where finish_reason is unset) remain unchanged. SQL consumers reading the existing documented fields are unaffected. New consumers can opt in via JSON_VALUE(attributes, '$.finish_reason') or via the finish_reason column exposed by the generated view.

Test plan

New unit tests (all passing locally; 229 passed, 5 skipped for the full plugin suite):

  • test_after_model_callback_projects_finish_reason — asserts MAX_TOKENS round-trips end-to-end into the BQ row's attributes.
  • test_after_model_callback_omits_finish_reason_when_absent — asserts the key is omitted when LlmResponse.finish_reason is None.
  • TestEnrichAttributes::test_finish_reason_included_when_set — direct _enrich_attributes coverage.
  • TestEnrichAttributes::test_finish_reason_omitted_when_none — direct _enrich_attributes coverage of the None branch.
  • Existing 229 tests in the plugin suite still pass (no regressions).

To run locally:

pytest tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py -k finish_reason -v

cc @sanketpatil06 @darshil3011

@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 14, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Projects `finish_reason` from `LlmResponse` to the `attributes` JSON
column of `LLM_RESPONSE` events written to BigQuery by
`BigQueryAgentAnalyticsPlugin`, and exposes it as a typed column in the
generated per-event SQL view. Without this, classifying model failure
modes (MAX_TOKENS, SAFETY, MALFORMED_FUNCTION_CALL, RECITATION, etc.)
via SQL against the analytics table is impossible — the signal lives
only in unstructured Cloud Logging output that is not joinable to the
structured event stream.

Changes:
- `EventData` gets a `finish_reason: Optional[str]` field.
- `after_model_callback` reads `llm_response.finish_reason.name` via a
  defensive getattr (same back-compat pattern as `cache_metadata`,
  covering legacy response shapes).
- `_enrich_attributes` writes `attrs["finish_reason"]` only when set,
  keeping existing consumers and partial streaming events unaffected.
- `_EVENT_VIEW_DEFS["LLM_RESPONSE"]` projects
  `JSON_VALUE(attributes, '$.finish_reason')` as `finish_reason`.
- Schema-field description for `attributes` mentions the new key with
  example values.

Tests:
- `test_after_model_callback_projects_finish_reason` — MAX_TOKENS
  round-trips end-to-end into the BQ row's attributes.
- `test_after_model_callback_omits_finish_reason_when_absent` — key is
  omitted when `LlmResponse.finish_reason` is None.
- `TestEnrichAttributes::test_finish_reason_included_when_set` and
  `..._omitted_when_none` — direct `_enrich_attributes` coverage.
- Existing 229 plugin tests still pass; 5 skipped (pre-existing).

Fixes google#5644
@roanny roanny force-pushed the feat/llm-response-finish-reason branch from 5063422 to 2198289 Compare May 14, 2026 18:21
@roanny roanny changed the title feat(plugins): project LlmResponse finish_reason in BigQueryAgentAnalyticsPlugin LLM_RESPONSE events feat(plugins): project LlmResponse finish_reason to BigQuery analytics May 14, 2026
@roanny
Copy link
Copy Markdown
Author

roanny commented May 14, 2026

@googlebot I signed it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: project finish_reason from LlmResponse to attributes in BigQueryAgentAnalyticsPlugin LLM_RESPONSE events

1 participant