lexsi-ds-agent / docs /dab_failure_analysis.md
bp-lexsi's picture
Add new tools and dab runs
4131399
|
Raw
History Blame Contribute Delete
9.89 kB
# DataAgentBench failure analysis β€” runs of 2026-06-05
Trajectory analysis of full `--all` sweeps, scored with DAB's own
per-task `validate.py`. Captured with `--log-dir` (full per-turn prompts,
raw LLM responses, tool args, and untruncated results); diagnoses below
cite those traces.
## Run config
```
python -m benchmark.dab.runner --all --llm lexsi --log-dir artifacts/dab_runs/new_tool
```
- 5 of 17 datasets runnable (sqlite/duckdb); 12 skipped (postgres/mongo).
- **Score: 10/17 tasks passed** β€” `consolidate_tables` (fix B) recovered
stockmarket/query4 + query5 over the pre-tool baseline of 8/17 (both
confirmed using the tool; see Β§B).
> **Measurement note.** Single-run pass@1 carries per-task sampling
> noise β€” any system-prompt change re-rolls multi-step trajectories, so a
> few tasks can flip run-to-run. Use `--iterations 3` (majority vote) to
> pin a stable number before trusting a future delta.
## Results
βœ… = pass, ❌ = fail, 🎯 = recovered by `consolidate_tables`.
| Dataset | Query | Result | Validator reason / note |
|---|---|---|---|
| DEPS_DEV_V1 | query1 | ❌ | Missing name `@dmrvos/infrajs>0.0.6>typescript` (§C/§D) |
| DEPS_DEV_V1 | query2 | ❌ | Missing project `mui-org/material-ui` (§D) |
| GITHUB_REPOS | query1 | ❌ | No value rounds to 0.33 β€” wrong join (Β§A note) |
| GITHUB_REPOS | query2 | βœ… | Fuzzy matched `swiftandroid/swift` |
| GITHUB_REPOS | query3 | βœ… | Found 1077 |
| GITHUB_REPOS | query4 | βœ… | All repo names matched |
| music_brainz_20k | query1 | βœ… | Ground truth found |
| music_brainz_20k | query2 | βœ… | Ground truth found |
| music_brainz_20k | query3 | ❌ | Wrong song (`Zo gaat het leven…`) (Β§D) |
| stockindex | query1 | βœ… | `399001.SZ` primary |
| stockindex | query2 | ❌ | `IXIC` not in first 200 chars (§C) |
| stockindex | query3 | βœ… | All name-country pairs matched |
| stockmarket | query1 | βœ… | 18.44 β‰ˆ 18.44 |
| stockmarket | query2 | ❌ | Missing number 31 β€” consolidated, but count SQL still wrong (Β§B) |
| stockmarket | query3 | ❌ | `Apex Global Brands Inc` β€” consolidated, but avg-volume SQL still wrong (Β§B) |
| stockmarket | query4 | πŸŽ―βœ… | `MFA Financial, Inc` found β€” **recovered via `consolidate_tables`** |
| stockmarket | query5 | πŸŽ―βœ… | `Synthesis Energy Systems, Inc` found β€” **recovered via `consolidate_tables`** |
**Total: 10/17** β€” 8 baseline passers + 2 🎯 recoveries via `consolidate_tables`.
---
## Failure taxonomy
### A. Harness bug β€” history truncates tool-call args, agent thinks its SQL is broken (1 task, systemic)
**GITHUB_REPOS/query1.** The agent wrote a complete 960-char query and
`run_sql` **executed it fine** (returned `1 row Γ— 3 cols`). But the loop
echoes each tool call into the observation history truncated to 400
chars ([loop.py:236](../lexsi_ds/agent/loop.py#L236)):
```python
f"TOOL CALL: {tool_name}({json.dumps(args_obj, default=str)[:400]})\n"
```
The 400-char cut lands mid-`JOIN`, and the appended `)` makes the echo
read as a query that ends `…JOIN artifacts_database_contents c ON c)` β€”
i.e. **truncated mid-join**. The agent re-read this, concluded the
tooling was broken, re-ran the same query 10 times, and gave up:
> "every `run_sql` execution shown is truncated mid-join
> (`JOIN artifacts_database_contents c ON c)`), which causes the query
> to effectively fail" β€” *agent's final answer*
So a runnable task was lost to a display artifact, not a real error.
(The SQL logic was also likely wrong β€” proportion β‰  0.33 β€” but the agent
never got far enough to find out.)
**Fix (done).** [loop.py](../lexsi_ds/agent/loop.py) now echoes args via
`_echo_args()`: full up to an 8000-char cap, and when it must truncate
it appends an explicit `… [+N chars truncated for display only; the full
args above were executed]` marker β€” so a display cut can never read as a
broken statement. The 960-char query that triggered this is now echoed
in full.
### B. Scale β€” per-shard table explosion vs the 12-step budget (4 tasks) β€” FIXED via `consolidate_tables`
**The problem.** stockmarket materializes **2753 `stocktrade_database_<ticker>`
tables** (one per symbol, identical schema `Date,Open,High,Low,Close,
Adj Close,Volume`, ~410 rows each) plus one `stockinfo` table. The shard
key (ticker) lives in the **table name, not a column**. A question like
"across all NYSE stocks…" is a `GROUP BY ticker` that can only be
expressed as a 2753-way `UNION ALL` β€” unwriteable in a 12-step budget.
All four tasks hit step 13 and surrendered:
> "the query requires scanning/UNIONing thousands of
> `stocktrade_database_<ticker>` tables, and I ran out of allowed tool
> calls" β€” *query3, baseline*
**The fix (done): a generic `consolidate_tables` tool**
([consolidate_tables.py](../lexsi_ds/agent/tools/consolidate_tables.py)).
It folds a sharded family into one table in a single call β€” the giant
`UNION ALL BY NAME` is generated in our code, never against the agent's
step budget:
```json
{"tool": "consolidate_tables",
"args": {"pattern": "stocktrade_database_*", "into": "stocktrade", "key_column": "ticker"}}
```
It auto-derives the key from the pattern's literal prefix (β†’ bare
tickers), schema-guards (unions only same-schema shards, reports the
rest), and defaults to a cheap VIEW. Generic by design: per-ticker,
per-day, per-region β€” any BYO datalake with sharded tables. Planner
rule 14 tells the agent to reach for it when `inspect_data` shows many
same-prefix tables.
**Measured:** 2753 tables β†’ one 6.47M-row view in **1.2s**; the
previously-timed-out cross-ticker query runs in **~1s**. On the
`new_tool/` sweep the agent called it in all 4 stockmarket tasks and
**recovered query4 + query5** (the name-lookup tasks). query2 (count off
by the "31") and query3 (avg-volume per company) still fail: the tool
removes the *structural* blocker, but the agent must still write correct
analytical SQL on top β€” which it nailed for q4/q5 and missed for q2/q3
(the latter compounded by Β§E coded values).
### C. Output format / "primary answer" expectations (2 tasks)
- **stockindex/query2** β€” the answer was **correct** (`IXIC`: 44 up / 31
down) but the validator requires the target index in the first 200
chars as the *primary* answer; the agent buried it after a prose
preamble and a markdown table, and added a second index (`GSPTSE`).
Penalized on presentation, not correctness.
- **DEPS_DEV_V1/query1** β€” the agent's query *did* surface the
composite-key rows (`@dmrvos/infrajs>0.0.5>typescript`) but (a) chose
version **0.0.5**, not the ground-truth **0.0.6** ("latest release"
logic wrong β€” see Β§D), and (b) hedged/refused because the values
"look like dependency paths rather than just the package name".
**Fix:** a benchmark-mode answer directive β€” lead with the bare
answer/identifier in the exact requested format, then optional detail.
### D. Wrong query semantics β†’ confidently wrong values (2 tasks)
- **DEPS_DEV_V1/query2** β€” returned `react-component/picker`, `tailwindcss/typography`, …;
ground truth is `mui-org/material-ui`. The "marked as release" filter
+ fork-count join produced a different top-5.
- **music_brainz_20k/query3** β€” answered `"Groovey" by Rich Matteson,
$4,128.59` in a single `run_sql`; ground truth is
`"Zo gaat het leven aan je voor"`. Revenue was almost certainly
aggregated by the wrong entity (track title vs. recording, or not
summed across stores). The agent was overconfident β€” 4 steps, one
query, no cross-check.
**Fix:** these need the DAB `db_description` field-semantics to be
weighted more (e.g. how "latest release" / "revenue" are defined), and a
self-check step before finalizing on aggregation questions.
### E. Coded-value / data-semantics gaps (compounds B)
**stockmarket/query5, query3** β€” the agent filtered on
`Listing Exchange='Q' AND Market Category='QCM'`, got 0 rows, and
concluded the data didn't match. `Listing Exchange`/`Market Category`/
`Financial Status` are **coded** columns whose codes differ from the
agent's assumptions. It spent steps probing codes instead of answering,
which (with Β§B) guaranteed a timeout.
---
## Priorities
| Fix | Status | Impact |
|---|---|---|
| **A.** `_echo_args()` no longer cuts SQL mid-string | βœ… done | Removes false "truncated SQL" failures; helps any long-SQL task |
| **B.** `consolidate_tables` folds sharded families into one table | βœ… done | **+2** (stockmarket q4, q5); generic for any sharded datalake |
| **D/E.** Lean on `db_description` semantics + a pre-final self-check | open | DEPS/query2, music_brainz/query3, stockmarket q2/q3 SQL logic |
| **C.** Benchmark answer-format directive (lead with bare answer) | open | stockindex/query2 (+ partial DEPS/query1) |
**Net effect of A + B: 10/17.** A is a correctness floor (no more false
failures); B is the +2 (stockmarket q4, q5). The remaining levers are
**D/E** (analytical-SQL / coded-value semantics β€” the dominant failure
mode now that the structural blockers are gone) and **C** (output
format).
**Measure with `--iterations`.** Single-run pass@1 is noisy β€” a prompt
change re-rolls every multi-step task. Run `--iterations 3` (majority
vote) before trusting a future delta.
## Reproduce / inspect
```bash
# Noise-adjusted measurement run (majority vote per task):
python -m benchmark.dab.runner --all --llm lexsi --iterations 3 \
--log-dir artifacts/dab_runs/measure_v1
# Inspect one trajectory end-to-end (prompts, raw responses, SQL, payloads):
python -c "import json; d=json.load(open('artifacts/dab_runs/new_tool/stockmarket_query4_iter0.json')); \
print(d['reason']); [print(s['n'], s['tool'], (s['args'] or {}).get('sql','')[:80]) for s in d['run']['steps']]"
# Confirm the new tool fired on the stockmarket tasks:
grep -l consolidate_tables artifacts/dab_runs/new_tool/stockmarket_*.json
```