feat(evalbench): add BigQuery run reader and trace mapping - #444
Conversation
Code reviewVerdict: approve after two fixes. This is a well-built reader: the parameterized
Also noted approvingly: the module wires in via direct submodule import instead of touching |
Full review - Ready with fixesI reviewed the complete 1,193-line diff, linked issue #97, all importer tests, BQAA trace/evaluator consumers, and the current EvalBench producer and BigQuery reporter shapes at head
Direct reproductions confirmed the failed-tool event mismatch and duplicate trace IDs. I also compared latency handling to the current upstream EvalBench normalizers, not only the PR fixtures. |
caohy1988
left a comment
There was a problem hiding this comment.
Review: Approve with nits
Verified locally: checked out the PR head, ran the new tests (12/12 pass), pyink/isort/git diff --check clean; the 2 full-suite failures also fail on main (pre-existing). I also verified the assumed EvalBench source schema against the actual evalbench repo and the emitted 16-column row contract against _GET_TRACE_QUERY (client.py:138-159), TraceFilter.to_sql_conditions (trace.py:758-768), SESSION_SUMMARY_QUERY (evaluators.py:923), and the judge-text query (evaluators.py:1079-1102) — all match. Well-scoped, defensive, deterministic, injection-safe. Findings are all minor polish for a read-only first phase.
Findings
MINOR — missing source table aborts the whole load with a raw NotFound (src/bigquery_agent_analytics/evalbench.py:116-133)
from_bigquery unconditionally queries configs, results, and scores. EvalBench's bqstore.py only writes all three when reporters are configured and results exist; a dataset with results but no scores (scoring disabled) or a legacy dataset without configs raises google.api_core.exceptions.NotFound with no context. Suggest catching NotFound for scores/configs and defaulting to () (results rightly mandatory), or wrapping errors with table/job context.
MINOR — failed tools emit TOOL_COMPLETED with status="ERROR" instead of TOOL_ERROR (evalbench.py:221-261)
TOOL_ERROR is a first-class event type in this SDK (trace.py:97), is what SESSION_SUMMARY_QUERY counts as tool_errors (evaluators.py:928), and what trace_evaluator.py:211 matches. Imported runs will always report tool_errors = 0 even when tools failed (has_error still trips via status, so not invisible — just undercounted in the dedicated metric). Consider emitting TOOL_ERROR when tool_error is set.
MINOR — total_tokens under-reports for imported runs (evalbench.py:685-698 + evaluators.py:958-969)
The mapper puts usage_metadata.prompt_token_count on the terminal row; SESSION_SUMMARY_QUERY's total_tokens is COALESCE(prompt_token_count, content.usage.total, input+output) — so a run with input=120/output=30 reports total_tokens = 120, not the 150 the mapper computed. Partly a pre-existing query quirk, but the mapper could avoid shadowing or document it in docs/evalbench.md.
MINOR — duplicate scenario IDs silently merge (evalbench.py:150-158)
Two result rows with the same id/eval_id produce identical session_id/invocation_id/span_ids; after materialization they'd merge into one corrupted trace. Dedupe with a warning, suffix, or raise. Untested.
MINOR — one malformed scenario aborts the entire run (evalbench.py:159-163)
A single row missing nl_prompt/prompt raises ValueError, killing conversion of all other valid scenarios. Documented as intentional, but per-scenario error collection (e.g. run.mapping_errors) would be more useful for a batch importer. Worth revisiting before the CLI phase.
MINOR — no signal for a nonexistent job_id
A typo'd job id returns an empty run and to_agent_event_rows() returns [] silently — looks like a successful empty import. Consider a warning or strict flag. Untested.
NIT — evalbench_error_fields populated on successful runs (evalbench.py:577-591)
returncode: 0 passes _usable_text, so every successful agentic row carries evalbench_error_fields = {"returncode": 0} — benign data in an "error" field. Filter zero returncodes as _source_error_message already does (:609-619).
NIT — _first_int drops float-string telemetry (evalbench.py:706-717)
int("850.5") raises, so token/latency values serialized as float strings (which EvalBench's float-to-string workaround can produce) are silently lost. int(float(value)) would be more forgiving.
NIT — _MISSING_TEXT rejects legitimate content (evalbench.py:43)
A prompt literally equal to "null"/"none" is treated as missing. Extremely unlikely; noting for completeness.
Test coverage gaps — the 12 tests are good, but for an 840-line module: empty results, duplicate scenario IDs, malformed/non-JSON stdout, per-row run_time precedence, and the missing-table error path are all untested.
Positives
- SQL injection properly handled:
project_id/evalbench_datasetvalidated against^[A-Za-z0-9_-]+$before interpolation,job_idalways a query parameter — both explicitly tested, including aDROP TABLEpayload. - Cost behavior as advertised: all three queries filter
WHERE job_id = @job_idin SQL (asserted in tests). - Epoch-fallback for missing
run_timewith an explicit flag instead of inventingnow()— the right call. - docs/evalbench.md is unusually accurate; every cited line range spot-checked correct against both this repo and the evalbench source.
- Deterministic sha256-based invocation/span IDs and stable scenario sorting make re-imports reproducible — good groundwork for the idempotency phase.
Unverified claims
- "4082 passed, 73 skipped" full suite — I got 4172 passed / 54 skipped / 2 pre-existing failures (environment drift); consistent with a clean run but exact numbers not reproduced.
- "normalizes both current agentic and NL2SQL result shapes" — verified via tests/code paths, but no live test against real EvalBench BigQuery output (acknowledged in the PR as deferred to a later #97 phase).
Review by @caohy1988's assistant (Kimi Code CLI).
|
Addressed the confirmed mapping defects in
Regression coverage now includes the successful-return-code path, failed-tool event semantics, duplicate scenario IDs, and a two-model latency/token fixture. I kept the documented hard failure for a missing prompt, as required by #97. I also did not broaden The reported Validation:
|
|
CI note: the current Upstream PR #447 pins |
caohy1988
left a comment
There was a problem hiding this comment.
LGTM - approved at exact head b4b0a06.
I rechecked the full importer diff and the latest fixes. Successful return codes no longer emit an error attribute, duplicate scenario IDs are rejected, tool failures emit TOOL_ERROR, scenario latency uses the maximum relevant duration, and token totals are summed. The focused exact-head importer suite passes (14 tests), with regression coverage for the corrected behavior.
The remaining red Format check is the repository-wide pyink 26 workflow regression fixed by merged PR #447. Please refresh from main and rerun required CI before merge; this approval is not a CI waiver.
5694e8c to
e281f3b
Compare
e6e763c
into
GoogleCloudPlatform:main
…configurator (#450) * chore(release): 0.5.1 — evalbench reader, canonical rubrics, single-input configurator Version bump 0.5.0 -> 0.5.1 and the changelog cut for everything merged since v0.5.0 (2026-08-11). In the wheel: the EvalBench BigQuery run reader (#444), canonical evaluation rubrics in core (#431), and the CLI judge-feedback escaping fix (#438). Repo/live-template side: the single fully-qualified table-ID configurator entrance (#449), Console table-link paste (#424), the attested external-access contract with its staleness workflow (#446), the end-user manual (#425), Grafana metric and scan-bound fixes (#433) with the one-command local run (#422), and the documentation and CI follow-ups. * chore(release): fold #432 golden-matching into the 0.5.1 cut The release branch was cut one commit before #432 landed on main; the synthetic merge CI builds would already ship golden_matching.py in the 0.5.1 wheel, so the module and its three top-level exports must be in the release section. Merges main and documents the module in Release highlights and Added.
Summary
bigquery_agent_analytics.evalbenchmodule withEvalBenchRunconfigs,results, andscoresjob_idIssue status
Tracking issue: #97.
This is the first implementation phase only. Issue #97 should remain open after
this PR merges.
Completed here:
EvalBenchRun.from_bigquery(...)job_idfilteringStill required under #97:
WRITE_APPENDevalbench_scores_importedmaterializationevalbench-importandevalbench-scoreCLI commandsClient.get_session_trace(...)andLLMAsJudgeintegration testsWhy
EvalBench owns benchmark execution and its existing BigQuery output, while BQAA
owns trace reconstruction and semantic evaluation. This reader establishes the
pull-based normalization boundary without coupling EvalBench to the ADK
plugin's production
agent_eventstable.Keeping the reader and pure row mapping separate from materialization also
allows the mirror-table ownership, idempotency, and write-disposition behavior
to be reviewed independently.
Source compatibility
The mapper supports both major EvalBench result shapes:
ideval_idnl_promptpromptorscenario.starting_promptgenerated_sqlstdout.responsestdout.tool_callsoraccumulated_toolsNested values may arrive as JSON or Python-literal strings because of
EvalBench's DataFrame reporting path. Both structured encodings are normalized
without interpreting ordinary text as structured data.
Event mapping
Each scenario maps to:
All emitted rows:
session_id = trace_id = evalbench:{job_id}:{scenario_id}attributes.experiment_id = job_idattributes.evalbench_scenario_id = scenario_idcontent.text_summaryfor the LLM judge trace-text queryMissing tool data emits no tool rows. Missing final output omits
AGENT_COMPLETED. Missingnl_prompt/promptis a hard error.Scope boundaries
This PR performs no BigQuery writes and never targets the ADK plugin's
production
agent_eventstable.run.scoresretains source score rows in memory, but writingevalbench_scores_importedbelongs to the materialization phase.When historical data has no run timestamp, the mapper uses the Unix epoch and
marks
attributes.evalbench_run_time_missing = trueinstead of inventing thecurrent time.
Validation
pytest -q tests/test_evalbench_importer.pypyink --checkisort --check-onlygit diff --checkThe tests use a stubbed BigQuery client. Live BigQuery materialization and
evaluation validation remain part of the later #97 phases.