| model | base | +focused | +full |
|---|
| model | variant | correct | empty_result | join_error | other_exec_error | syntax_error | unknown_field_or_index | wrong_result |
|---|---|---|---|---|---|---|---|---|
| claude-opus-4-8 | no_skills | 163 | 19 | 0 | 0 | 18 | 143 | 157 |
| claude-opus-4-8 | skills_focused | 174 | 20 | 0 | 0 | 88 | 4 | 214 |
| claude-opus-4-8 | skills_full | 170 | 26 | 1 | 0 | 84 | 11 | 208 |
| claude-sonnet-4-6 | no_skills | 81 | 26 | 0 | 0 | 181 | 21 | 191 |
| claude-sonnet-4-6 | skills_focused | 106 | 29 | 0 | 1 | 86 | 9 | 269 |
| claude-sonnet-4-6 | skills_full | 107 | 29 | 0 | 1 | 78 | 11 | 274 |
| gpt-5.4-mini | no_skills | 65 | 19 | 3 | 0 | 236 | 70 | 107 |
| gpt-5.4-mini | skills_focused | 93 | 24 | 2 | 0 | 150 | 35 | 196 |
| gpt-5.4-mini | skills_full | 95 | 22 | 2 | 0 | 137 | 42 | 202 |
| gpt-5.5 | no_skills | 228 | 25 | 0 | 2 | 7 | 0 | 238 |
| gpt-5.5 | skills_focused | 166 | 27 | 0 | 1 | 79 | 0 | 227 |
| gpt-5.5 | skills_full | 164 | 23 | 0 | 2 | 69 | 0 | 242 |
Single-shot, zero-shot text-to-ES|QL on BIRD Mini-Dev (500 questions), run against Elasticsearch 9.5. The headline: GPT-5.5 writes correct ES|QL 59% of the time under lenient scoring with nothing but a schema, comfortably above what GPT-4 scored writing SQL on the same benchmark (~48%). For the best model the syntax barrier is effectively gone; what is left is semantic correctness.
| Model | no skill | focused skill (~33k) | full skill (~65k) |
|---|---|---|---|
| gpt-5.5 | 59.0% | 48.8% | 50.4% |
| claude-opus-4-8 | 40.4% | 50.0% | 49.0% |
| claude-sonnet-4-6 | 30.2% | 39.2% | 39.6% |
| gpt-5.4-mini | 19.8% | 30.4% | 31.6% |
Lenient EX. Strict BIRD EX for the same cells: gpt-5.5 45.6 / 33.2 / 32.8, opus 32.6 / 34.8 / 34.0, sonnet 16.2 / 21.2 / 21.4, mini 13.0 / 18.6 / 19.0. Flip the switch above.
Calibration reference: single-call zero-shot text-to-SQL (GPT-4) is ~48% on the same questions. BIRD's public leaderboard tops out higher, but those entries are engineered pipelines with schema linking and self-correction, not one call with one prompt.
first_name, last_name returned as one full_name).
That last rule recovers 88 results that were correct and scored zero on column split alone.
Every runs-but-wrong candidate was re-executed in full against the live cluster, so there is no
offline sample gap in these numbers.e0d6b02 with one local change: because this run targets 9.5, we rewrote
the join-key guidance to promote the 9.2 equality predicate (ON source == lookup) over
RENAME. Re-running GPT-5.5 with the focused skill exactly as published, same cluster
and same 500 questions, scores 59.0%, level with its own base. So the unmodified skill costs
GPT-5.5 nothing and the entire 10.2-point drop comes from that one edit: it pushed RENAME
usage down from 47% of GPT-5.5's joins to 11%, and Found ambiguous reference errors up from
120 to 706 across the run while the join bucket fell from 939 to 354. Almost exactly a wash. A language
feature only pays once the guidance says when not to reach for it.The models are not bad at the logic of these questions. They trip on a handful of ES|QL-specific mechanics and on the strictness of execution-accuracy scoring. The single biggest cause is one join rule.
races.id = results.race_id). ES|QL's bare LOOKUP JOIN form requires the join key
to have the same name in both indices (... | LOOKUP JOIN races ON race_id). The model
writes a SQL-shaped join and ES|QL rejects it: Unknown column [race_id] in right side of join
(or mismatched input '=' when it also writes ON a = b). This is ~45% of Opus's
337 base failures. 9.2's equality predicate lifts the restriction, but only when the key name is
unambiguous; when it exists on both sides, RENAME before the join is still the fix.
| Root cause | ~count | What happens |
|---|---|---|
| LOOKUP JOIN same-name-key | 134 | SQL join on different key names; query won't run |
| Join runs but mis-collapses rows | ~73 | wrong base table / one-to-many fan-out corrupts aggregates |
| Missing DISTINCT | 24 | gold uses SELECT DISTINCT; prediction returns duplicates |
| Ratio / integer division / CAST | 24 | int/int truncates; model invents DIVIDE() (not an ES|QL function) |
| Value / case / literal mismatch | ~15 | exact stored value not replicated (e.g. 'east Bohemia', 'VYBER') |
| Invented funcs / subqueries / prose leak | ~27 | DIVIDE,YEAR, SQL subqueries, chain-of-thought emitted as query text |
Field-grounding (won't execute) vs logic-but-valid is almost exactly 50/50. The grounding half throws precise errors, so an execution-feedback loop would fix most of it. The other half runs silently wrong, so a loop alone gives no signal.
Adding Elastic's ES|QL reference to the prompt slashes syntax errors, but those queries then fail one stage later on schema grounding or semantics. The failure mix shifts from "doesn't run" to "runs but wrong."
| category | opus no-skill | opus skill | sonnet no-skill | sonnet skill |
|---|---|---|---|---|
| syntax_error | 18 | 88 | 181 | 86 |
| unknown_field_or_index | 143 | 4 | 21 | 9 |
| wrong_result | 157 | 214 | 191 | 269 |
| empty_result | 19 | 20 | 26 | 29 |
"Runs-but-wrong" share of all failures climbs from 52%→72% (Opus) and
52%→76% (Sonnet). The skill all but eliminates field-grounding errors (Opus 143→4,
Sonnet 21→9), and for Sonnet it halves the syntax errors its SQL habits produce (single
= instead of ==, CASE WHEN, single quotes,
COUNT(DISTINCT)). Opus already had the syntax mostly right, so for it the trade shows up
as a rise in syntax errors instead: those are the ambiguous-reference failures the join edit introduced.
The hardest bucket: the query executes cleanly but returns the wrong rows. Main reasons:
KEEPs the wrong columns (or adds the sort
key) scores zero. Lenient scoring rescues 830 of the 2,814 clean-running strict misses, 88 of them on
column split alone. That is why this cause contributes 0% to the bucket below: the lenient metric
has already forgiven every one of them.LOOKUP JOIN emits a row
per match, inflating counts vs gold's DISTINCT. A third of this bucket joins and then
aggregates, but that only bounds the exposure: 6.5% joins and aggregates where the gold uses
DISTINCT, and 2.8% returns a single number larger than gold's.COUNT() returns 1, because LOOKUP JOIN enriches the single left row rather than expanding it.rank vs position, the
exact stored code, which of two near-identical tables holds the value, knowledge the model can't get
from the schema alone. 9.9% of the bucket returns nothing while filtering on a string literal, the
signature of a guessed value; 14.6% returns nothing for any reason.TO_DATETIME/DATE_PARSE
misparse, or "now" arithmetic drifts. 24.9% of this bucket is questions whose gold query does date
arithmetic, yet only 5.3% of the failing queries call an ES|QL date function at all.Scope: the headline table, the per-category counts, the skill findings and the GPT-5.5 control are all re-derived from this run (Elasticsearch 9.5, 6,000 scored queries, plus a 500-query control for GPT-5.5 on the unmodified skill), as is every wrong_result share marked "measured", computed over the runs-but-wrong bucket (executed cleanly, wrong data under lenient scoring, n=1,984). Where a share bounds the exposure rather than establishing the cause, both figures are given. The failure taxonomy table above is the one section still carried over from a manual read of the earlier 9.1 run; its underlying buckets barely moved between runs (wrong_result 2,414 to 2,525, empty_result 283 to 289), so the structure holds, but treat its counts as indicative.