Skip to content

feat: pydantic evals - #51

Merged
code-alexander merged 10 commits into
mainfrom
feat/pydantic-evals
Jul 19, 2026
Merged

feat: pydantic evals#51
code-alexander merged 10 commits into
mainfrom
feat/pydantic-evals

Conversation

@code-alexander

Copy link
Copy Markdown
Collaborator

No description provided.

code-alexander and others added 10 commits July 16, 2026 08:12
The upstream constant-minus-variable fold bug (0 - a = 1) is fixed in
sqlglot 30.12.0 (tobymao/sqlglot#7791). Remove _PatchedSimplifier and its
canary test, raise the floor to sqlglot>=30.12.0, and call the stock
Simplifier directly. The two behavioural tests remain as regression cover.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `evaldata.pydantic_evals.SqlEquivalence`, a drop-in Pydantic Evals
Evaluator that scores generated SQL by executing it against a real
warehouse and checking row/semantic equivalence via observed_equivalence().

- Optional `pydantic-evals` extra (added to `all`).
- Reads ctx.output (generated SQL) and ctx.expected_output (gold SQL str or
  an evaldata GoldQuery/UntypedResultSet/TypedResultSet); returns an
  EvaluationReason(value, reason) with a composed diff-aware reason.
- Scores under a module lock: Pydantic Evals runs cases concurrently but a
  resolved adapter's connection is not thread-safe.
- Unit tests on DuckDB (incl. an end-to-end Dataset.evaluate_sync run),
  100% line+branch coverage; guide + reference docs and nav.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make query execution safe to run concurrently across eval cases so an outer
scheduler (Pydantic Evals max_concurrency, run_benchmark) genuinely parallelizes
execution and scoring — removing the bridge's serialization caveat.

Design: a per-platform-name ConnectionPool. Each case acquires a session member
for its whole execute+score pipeline and releases it; members are built lazily,
so serial callers reuse one. Keyed by case, not thread, because execute_within_budget
runs each query on an ephemeral watchdog thread (thread-local keying would thrash).

- platforms/pool.py: bounded pool (acquire/release/utility/close), Condition-based
  blocking, closed-flag wakes waiters, dedupes shared member/utility on close.
- registry.py: pools replace the single-adapter cache; resolve() returns a dedicated
  utility adapter (never a checkout member); acquired() context manager; module lock
  around pool creation. Per-engine sizes (DuckDB 8, cloud 4, SQLite 1 serial).
- DuckDB: shared parent connection, each member a .cursor() of it (shares the
  in-process DB; concurrent cursor queries run in parallel and .interrupt() is
  isolated per cursor — both verified empirically, guarded by a canary test).
- Dedicated-connection engines (Postgres/Snowflake/BigQuery/Databricks): each member
  its own connection; the shared _cursor/_job slot is safe under exclusive checkout.
- runner.py: _score_output acquires a session (try/finally release); run_benchmark
  parallelizes the whole per-case pipeline, preserving case order.
- pydantic_evals: SqlEquivalence offloads to a worker thread and drops the global
  lock; the pool provides safety. Docs: caveat replaced with a Concurrency section.
- Tests: pool units, DuckDB concurrency + interrupt-isolation canary, bridge async
  offload + concurrent dataset (free); bounded Postgres e2e and Snowflake smoke
  behind their markers. 100% line+branch coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- run_benchmark: revert to parallel-solver + serial, case-ordered scoring.
  Running the whole pipeline in workers concurrently re-entered shared scorer
  instances, whose protocol has no thread-safety contract. Concurrent execution
  lives in the Pydantic Evals bridge (which only uses the stateless built-in
  scorer). Concurrency correctness is now exercised via concurrent evaluate_case.
- sqlite: give the pool distinct connections over a per-name shared-cache
  in-memory URI, so the utility adapter is no longer also a checkout member
  (upholding the pool invariant) while members still share seeded data.
- pool: utility() rejects use after close (no resurrected adapter); release()
  after close drops the member instead of requeuing or double-closing it.
- pydantic_evals: failure reason now includes column, column-order, type, and
  per-column value mismatches, not just row counts.
- duckdb interrupt-isolation canary: use two genuinely slow overlapping queries
  so the sibling is provably still running when the other cursor is interrupted.
- docs: correct the session-state note (pooled sessions are reused and not
  reset, so session-local state can leak across cases) and note the
  uncancellable-query and stale-connection edge cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@code-alexander
code-alexander merged commit 241941f into main Jul 19, 2026
9 checks passed
@code-alexander
code-alexander deleted the feat/pydantic-evals branch July 19, 2026 23:20
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.

1 participant