Emails, spreadsheets, SQL: can AI agents connect the dots?

The answer hides across emails, Excel workbooks, screenshots and databases. The flagship model is not always the best one.

Content

Can an AI agent open an Outlook email, follow it into an Excel workbook, query a database it has never seen and come back with the right number?

In my last post, How to Build your Own LLM Benchmark, I shared tips for building your own. I kept two things back: the model names and the benchmark itself. Today you get both.

Meet finrec-bm, my benchmark for financial reconciliation, now on GitHub. It hands AI agents a number to verify and a pile of evidence: emails, spreadsheets, screenshots and a database. The answer has to be right to the cent, with the paper trail to prove it.

25
model configurations
22
hand-written cases
550
agent runs
$13.65
test costs

The job in 60 seconds

Every company keeps a record of the money it receives and spends: the ledger. Everyone it deals with keeps their own version: the bank, suppliers, customers, the colleague with the spreadsheet. The job is to line those versions up, match every item and explain every difference.

Sounds like adding numbers. It isn’t. One payment settles three invoices. The bank keeps a fee. A euro invoice gets paid in dollars. A June sale lands in July. Somebody types 8,420 instead of 8,240.

Table titled ‘Five receipts, two versions of the truth’, comparing a colleague’s spreadsheet with the ledger. Receipts RC-25-0398 and RC-25-0404 (Fabrikam Industrial) and RC-25-0400 and RC-25-0402 (Contoso Logistics) match. RC-25-0401 (Fabrikam Industrial) shows 8,420.00 in the spreadsheet and 8,240.00 in the ledger. Totals: 60,230.00 against 60,050.00, a difference of 180. Below the table, chips list what usually hides in the gap: split payments, bank fees, FX rates, cut-off dates, duplicates, typos and conflicting emails.
Your job: find the 180.00 and explain it. Here, two digits swapped places. Remember this table: it comes back later.

Adding up numbers is the easy part. Deciding which numbers count is the job.

Accountants do this every month, under deadline, across emails, CSV exports, Excel files and screenshots. That makes it a great test for AI agents: lots of reading, a little arithmetic and zero tolerance for “close enough”.

What finrec-bm tests

Each case reads like a message from a colleague: “Internal audit wants us to confirm this net cash flow. The email and the spreadsheets are in the folder.” Nobody tells the model where to look. It gets tools, functions it can call, and decides for itself:

  • Emails: read_msg_file unpacks Outlook .msg files, a binary format, into sender, recipients, dates, subject, body and attachments. Ten of the 22 cases include at least one.
  • Spreadsheets: file readers open Excel .xls workbooks, CSVs and plain text, including cell formulas, fill colours and embedded pictures.
  • Databases: query_ledger runs SQL against a ledger database the model has never seen. It explores the tables and writes its own queries, up to ten per call: 4,723 across all runs.
  • Screenshots: read_image_file lets the model view images.
  • Arithmetic: calculate does exact decimal maths, because floating point and money don’t mix.

The model gets 30 turns to return a structured answer: a verdict, the value the evidence supports, and every record, cell and row behind it. Four test families raise the bar one file format at a time:

  1. Plain text · txt-matching · 3 cases · 89% of runs passed (67 of 75)

    Find the transactions behind a reported total, converting currencies at the right monthly rate.

    Example: “Coho Vineyard reported September spending of 4513368.4. Adventure Works reported 4.9M.” Each log line shows two unlabelled amounts. Only the first amounts of two Coho lines add up, and 4.9M is a single line rounded to millions.

  2. Database + CSV · csv-matching · 10 cases · 68% of runs passed (169 of 250)

    Combine four or more sources: database tables, CSVs, meeting minutes and .msg emails. Typical traps: period cut-offs, accruals, VAT and cost allocations.

    Example: “The Q3 board pack reported marketing spend of USD 1,580 thousand.” The minutes set the rules: marketing campaigns only (the database says which), monthly average FX rates, and round only the total. Round each line first and you get 1,579.

  3. Excel · xls-matching · 6 cases · 59% of runs passed (88 of 150)

    Check highlighted cells against .msg emails and the ledger. Watch the formulas: the figure itself might be wrong.

    Example: The request is one of five .msg emails: confirm EUR 41,618.00 for two highlighted rows. Each row’s comment names whose email holds its final amount, and both people wrote theirs in hexadecimal. A third, unhighlighted row has a name too. Leave it out.

  4. Screenshots · img-matching · 3 cases · 59% of runs passed (44 of 75)

    Same as Excel, except the numbers live inside images: in .msg email bodies, attachments and workbooks.

    Example: Accounts payable emails a screenshot of the payment run (a batch of supplier payments) and asks whether its highlighted total agrees with the ledger. The image shows 63,950.40 and the rows add up, but one payment appears twice. The ledger supports 59,137.80.

No LLM judges the answers. A rule-based scorer compares the cited evidence with the expected list, so every score is reproducible. A case passes only on an exact match: every expected item, nothing extra, the right verdict and the right value to the cent. Tough? Sure. So is your auditor.

Diagram: npm run benchmark picks models, tests and cases. Each model runs its test families and cases in its own parallel lane, scores them by exact match and F1, and the results aggregate into summary.json, which feeds dashboard.html.
One command picks the models, tests and cases. Each case runs with a fresh agent and database, so no run can affect another. Results land in one summary and one dashboard.

Your turn: spot the trap

Here’s one of the hardest cases, from the Excel family. Tailspin Toys, a group of three companies, re-bills (“recharges”) its customer Northwind Traders for the freight it paid on their orders. Three .msg emails land in your inbox, in this order:

Priya Natarajan · Logistics manager · Mon 29 Sep

“For the August recharge to Northwind Traders: it covers every freight charge on a Northwind Traders order dated in August 2025, whichever Tailspin Toys entity booked it (BV, GmbH or Inc), plus our handling fee for August. The fee is highlighted in green in freight-recharge-2025-08.xls.”

Tom Whitaker · Account manager · Tue 30 Sep

“Small correction to Priya’s note. The Northwind recharge only covers the freight booked by Tailspin Toys BV […] And the handling fee to add is the one highlighted in yellow in the workbook, not the green one.”

Ingrid Halvorsen · Group controller · Wed 1 Oct

“[…] Priya’s email is the correct one. […] The yellow figures in the workbook are the July fees, which have already been invoiced. Before we invoice it, please confirm the August 2025 recharge to Northwind Traders: EUR 10,760.80, per the draft.”

Here’s the workbook the emails refer to:

Excel sheet ‘handling fees’: handling fees by customer and month, 2025. Adventure Works August 410.00 in tan, note ‘August fee under review’. Northwind Traders July 598.20 in yellow and August 642.75 in green. Contoso Logistics July 530.10 in yellow.
freight-recharge-2025-08.xls, as Excel shows it. Four highlighted cells, three colours.

The freight charges live in the ledger. The customer sits in sales_orders and the booking entity in entities, so the model’s query needs two joins:

SQL query selecting charge_id, entity_id, charge_date and amount from freight_charges, joined to sales_orders and entities, where the customer is Northwind Traders. Twelve result rows. ENT-NL: FC-7001 28/07/2025 1240.00, FC-7002 05/08/2025 2315.40, FC-7003 19/08/2025 1876.25, FC-7005 08/09/2025 1450.00. ENT-DE: FC-7010 11/08/2025 3120.80, FC-7011 26/08/2025 845.60, FC-7012 31/07/2025 610.00. ENT-US: FC-7020 08/04/2025 2780.00, FC-7021 08/21/2025 1105.35, FC-7022 07/08/2025 1960.00, FC-7023 08/12/2025 2435.10, FC-7025 07/29/2025 720.00.
Twelve Northwind freight charges, each date stored as text, exactly as its entity's system writes it.

Does EUR 10,760.80 hold up? Which cells and charges make up the right number? Take a minute before you peek.

Show the answer

Not confirmed. The evidence supports EUR 15,121.25.

  1. Follow Priya. Tom wrote after her, but the group controller, who outranks both, wrote last and backs Priya.
  2. Keep D5, the only green cell: 642.75. Drop C5 and C7 (yellow: July fees, already invoiced) and D4 (tan: another customer’s fee, under review).
  3. Keep every entity’s freight, not just the BV’s.
  4. Read each date in its entity’s format. ENT-NL (the Dutch BV) and ENT-DE (the German GmbH) write the day first; ENT-US writes the month first. So 08/04/2025 means 4 August and 07/08/2025 means 8 July.
  5. Keep the seven August charges: FC-7002, 7003, 7010, 7011, 7020, 7021 and 7023, for 14,478.50. Add 642.75 and you get 15,121.25.

And the draft’s 10,760.80? It reads every date day first, so the US entity’s 8 July charge sneaks into August and three real August charges drop out.

Only 5 of 25 configurations passed, all of them DeepSeek. Most others found 15,121.25 but cited an email as a source record, which counts as wrong evidence (more on that below). Three GPT-6 Luna settings confirmed the draft’s 10,760.80 outright.

Boss level: every format in one case

The quiz kept each clue in its own file. The boss level nests them, so each format holds one piece of the puzzle: an Outlook .msg email, an Excel workbook, a screenshot and ledger postings the model finds with its own SQL.

Five-step chain for img-matching case-03, ‘Confirm the approved net cash flow’, difficulty hard. Step 1, email (.msg): audit asks you to confirm the approved net cash flow in the review workbook; the email gives no figure and carries no attachment. Step 2, Excel: the sign-off sheet lists two drafts, Draft 1 superseded and Draft 2 approved, each naming the screenshot that holds its figure. Step 3, image: a picture inside the workbook holds the approved figure, 1,680.70; no cell holds it. Step 4, Excel: the highlighted totals come from formulas that skip rows; receipts =SUM(F4:F8) = 47,245.90, payments =SUM(F4:F6,F9:F11) = 45,385.20. Step 5, ledger (SQL): eleven postings match the formula rows one for one; 47,245.90 − 45,385.20 = 1,860.70. Result: not confirmed; approved 1,680.70, evidence supports 1,860.70.
Sound familiar? Two digits swapped places again, this time inside a picture. The superseded draft's 7,888.10 adds up whole columns and ignores the formulas.

Eighteen of the 25 configurations found 1,860.70, but only six passed. The other twelve cited the sign-off sheet as evidence: it points to the right picture but proves nothing about the total.

DeepSeek V4 Pro never got that far. It failed every screenshot case at every setting, 0 of 12, while its smaller sibling DeepSeek Flash passed 8 of 12. In one run, V4 Pro even tried to read a PNG’s raw bytes through SQL. Points for creativity.

Here’s how GPT-6.1 Sol at low reasoning cracked it, tool call by tool call. Nobody gave it the schema: it discovered the tables and wrote every query itself.

Tool calls of GPT-6.1 Sol at low reasoning on img-matching case-03, in order: read_msg_file net-cash-signoff-2025-09-24.msg; read_xls_file cash-review-2025-09.xls; query_ledger SHOW ALL TABLES; query_ledger SELECT * FROM duckdb_constraints(); read_image_file ncf-2025-09-15.png; read_image_file ncf-2025-09-19.png; query_ledger SUMMARIZE transactions; three more queries to profile and sample both tables; query_ledger summing five receipts, TR-40147, TR-40150, TR-40141, TR-40153 and TR-40138; query_ledger summing six supplier payments, PY-2508-01, 02, 03, 06, 07 and 08; calculate 47245.90 - 45385.20.
Email, workbook, database, then both screenshots. The IDs it summed are exactly the rows inside each formula's range, none of the ones the formulas skip.

The results

First, let’s unmask the letters from my last post:

LetterModelProvider
AGPT-6.1 SolOpenAI
BClaude Sonnet 5.5Anthropic
CDeepSeek FlashDeepSeek
DGPT-6 LunaOpenAI
FDeepSeek V4 ProDeepSeek

A model’s reasoning level sets how much it thinks before answering: more thinking means more tokens and a bigger bill. Add Claude Haiku 5.5, run each model at every level its API offers, and you get 25 configurations. Here’s how many of the 22 cases each one passed:

Heat-map grid of cases passed out of 22, with the cost per case under each score, for six models across five reasoning levels: off or none, low, medium, high, and max or xhigh. GPT-6.1 Sol: low 19 (3.7¢), medium 17 (4.0¢), high 18 (4.8¢), xhigh 19 (5.7¢). Claude Sonnet 5.5: low 14 (3.1¢), medium 14 (3.2¢), high 14 (3.9¢), max 18 (16.7¢). DeepSeek Flash: off 12 (0.33¢), low 15 (0.44¢), high 17 (0.67¢), max 17 (1.9¢). GPT-6 Luna: none 10 (0.30¢), low 12 (0.21¢), medium 12 (0.31¢), high 15 (0.32¢), xhigh 14 (0.38¢). Claude Haiku 5.5: low 10 (0.24¢), medium 15 (0.34¢), high 14 (0.39¢), max 14 (1.5¢). DeepSeek V4 Pro: off 15 (1.7¢), low 15 (2.4¢), high 14 (2.2¢), max 14 (3.1¢). Levels a model’s API doesn’t offer are left blank.
Darker means more cases passed. Read across a row to see what extra reasoning buys you. Mostly: a bigger bill.

GPT-6.1 Sol at low reasoning takes the top spot: 19 of 22 for 3.7 cents a case. Raising it to xhigh buys the same 19 for 54% more. To weigh score and price together, plot them against each other. The line is the Pareto frontier: the configurations no other configuration beats on both. Everything off the line pays more for the same score, or scores less for the same price.

Scatter plot of cases passed against cost per case, on a log scale, for 25 configurations coloured by provider: OpenAI, Anthropic and DeepSeek. A staircase line joins the Pareto frontier: GPT-6 Luna low (12 passed, 0.21 cents), GPT-6 Luna high (15, 0.32 cents), DeepSeek Flash high (17, 0.67 cents) and GPT-6.1 Sol low (19, 3.7 cents). Claude Sonnet 5.5 max sits far right at 18 passed for 16.7 cents.
Claude Sonnet 5.5 at max sits alone on the right: 18 passes for 16.7 cents a case, 4.5× the price of Sol low.
All 25 configurations as a table

Ranked by cases passed, then by cost. Bold rows sit on the Pareto frontier.

ModelReasoningCases passed (of 22)Cost per case
GPT-6.1 Sollow193.7¢
GPT-6.1 Solxhigh195.7¢
GPT-6.1 Solhigh184.8¢
Claude Sonnet 5.5max1816.7¢
DeepSeek Flashhigh170.67¢
DeepSeek Flashmax171.9¢
GPT-6.1 Solmedium174.0¢
GPT-6 Lunahigh150.32¢
Claude Haiku 5.5medium150.34¢
DeepSeek Flashlow150.44¢
DeepSeek V4 Prooff151.7¢
DeepSeek V4 Prolow152.4¢
GPT-6 Lunaxhigh140.38¢
Claude Haiku 5.5high140.39¢
Claude Haiku 5.5max141.5¢
DeepSeek V4 Prohigh142.2¢
Claude Sonnet 5.5low143.1¢
DeepSeek V4 Promax143.1¢
Claude Sonnet 5.5medium143.2¢
Claude Sonnet 5.5high143.9¢
GPT-6 Lunalow120.21¢
GPT-6 Lunamedium120.31¢
DeepSeek Flashoff120.33¢
Claude Haiku 5.5low100.24¢
GPT-6 Lunanone100.30¢

Five things that surprised me

1. More thinking rarely pays

The top reasoning level beat every cheaper setting for exactly one model: Claude Sonnet, which jumped from 14 to 18 passes at 4.3× the cost. For the other five, it tied or lost. DeepSeek V4 Pro did its best work with thinking off or low. DeepSeek Flash at max matched Flash at high for 3× the money and 3× the wait. Claude Haiku at max ran out of tokens before answering on four cases: it overthought itself out of an answer.

2. The bargain bin holds a gem

DeepSeek Flash at high reasoning passed 17 of 22 for 0.67 cents a case: 15 cents for the whole benchmark. GPT-6 Luna at high passed 15 for a third of a cent. If you match thousands of transactions a month, that changes the maths.

3. Models “fix” the spreadsheet you asked them to check

Remember the 180.00 from the intro? It comes from another hard Excel case. Audit asks you to confirm a net cash flow: the highlighted receipts total minus the highlighted payments total. Two errors hide inside. One receipt has transposed digits, and the payments formula includes the July subtotal, so it counts July twice.

Most models caught both. Then 16 of 25 configurations made the same third mistake and landed on the same wrong number, 47,080.35. The receipts formula, =SUM(F4:F8), stops two rows short of the column. The models decided the formula was wrong and added those rows back in.

❌ Failed · Claude Sonnet 5.5, max reasoning

“Highlighted receipts total 60,230.00 is =SUM(F4:F8): it omits rows 9-10 (16,395.75) and carries 8,420 for row 5 where the ledger has 8,240.”

✅ Passed · GPT-6.1 Sol, low reasoning

“Highlighted receipts total is EUR 60,230.00, summing F4:F8. The matching ledger entries support EUR 60,050.00: row 5 overstates its receipt by EUR 180.00.”

Audit asked whether the total holds up, not what it should include. An auditor who quietly changes the population (the set of items under test) doesn’t solve a problem; they create a new one. Only five configurations resisted the urge.

4. Right number, wrong paper trail

On the Excel and screenshot cases, 42 of 86 failed answers had the right verdict and the right value to the cent. They failed on evidence. On the freight recharge from the quiz, 19 configurations cited an instruction email as a source record. On the boss level, twelve cited the sign-off sheet. Both documents tell you where to look; neither proves an amount. Harsh? In finance, the number is half the deliverable. The evidence is the other half, and every extra item in it is one more thing a reviewer has to check.

5. Hard cases stay hard

I rate each case’s difficulty when I write it, from its sources, formats and reasoning steps. Never from model scores, which would make the check circular. The ratings held up:

Three tiles showing the pass rate by case difficulty across all 25 configurations. Easy, 2 cases: 94%, 47 of 50 runs passed. Medium, 13 cases: 76%, 247 of 325 runs passed. Hard, 7 cases: 42%, 74 of 175 runs passed.
Four hard cases stopped all but five or six configurations. No configuration passed all four, and Claude Haiku passed none of them at any setting.

The fine print

  • 22 cases is small. Each case is worth 4.5 points of pass rate, and with 95% confidence intervals, 23 of the 25 configurations statistically tie for first. Read the ranking as a shortlist, not a verdict.
  • One run per configuration and case, so luck isn’t averaged out. Costs use list prices as of 12 October 2026.
  • My cases, my job. Your mileage will vary, which was the whole point of the last post.

So, who would I hire?

It depends on the job. Here’s who won each part of it:

  • Images: GPT-6.1 Sol. It passed 11 of 12 screenshot runs across its four settings, the best of any model. On a budget, GPT-6 Luna at low reasoning passed all three screenshot cases for 0.21¢ a case.
  • SQL: GPT-6.1 Sol. Nineteen of the 22 cases need the ledger. Sol passed 82% of those runs; no other model passed more than 66%. At low reasoning, it passed 17 of the 19 with 145 queries, and not one query failed.
  • High reasoning: DeepSeek Flash. Thinking lifts it from 12 passes (off) to 17 (high), one case behind GPT-6.1 Sol at high for a seventh of the price. Going past high buys nothing.
  • Cost: DeepSeek Flash, high. 17 of 22 for 0.67¢ a case. GPT-6 Luna at high passes 15 for even less.

My picks: for high-volume matching, DeepSeek Flash at high reasoning, with a human checking its misses. For anything an auditor will read, especially with screenshots or ledger queries, GPT-6.1 Sol at low reasoning. For both: skip the max setting and insist on the evidence trail.

A correct number without evidence isn’t verified. It’s a lucky guess.

Try it yourself: the cases, the harness and the results are at github.com/rubenafo/finrec-bm.