Library// diagnostic

The number is in a table and the assistant cannot find it

In short

This is almost never a ranking failure. The row and its header stopped being connected somewhere between the file and the chunk, so the figure survives as a bare number with no retrievable language attached to it. Print the chunk that should hold the answer and read it: that single action separates a table that was never parsed from one that was parsed and then cut in half.

Key takeaways

  • Print the chunk before theorising. Four failure modes look identical from the outside and different on the page.
  • A row without its header is a list of numbers, and numbers share no vocabulary with the question that needs them.
  • Empty or caption-only chunk text means the table was never parsed, not that it was chunked badly.
  • A units footnote two pages away turns 907 into a wrong answer that is impossible to spot in the output.
  • Repeat the header into every chunk of a long table. It is the cheapest repair available and it fixes most cases.

Retrieval scoring is rarely at fault here. The question 'what was the Q3 figure for the northern region' is a sentence about a region and a quarter; the chunk that holds the answer is often the string 'North 1,482 1,610 907 1,204'. Those two share no vocabulary at all. The similarity function is working correctly and the passage was made unfindable earlier, at parse or chunk time.

So the investigation is short and physical. Find the chunk that should contain the number, print its text exactly as stored, and read it. Four failure modes are indistinguishable from the outside and immediately distinct on the page.

  1. Locate the value in the source file and note its page and its position — which table, which row, which column heading.
  2. Query the chunk store for chunks belonging to that document around that page, and print the raw text field. Not a rendering, not a preview: the exact stored string that was embedded.
  3. Search that text for the number itself. Present or absent is the first branch, and it is decisive.
  4. If the number is present, search the same chunk for the column heading and the row label. Their absence is the diagnosis.
  5. Run the user's question and record the top-k chunk ids. If the target chunk is not among them, you now know why.

A two-page table, and what each failure leaves in the chunk

Take a common shape: a revenue table running across pages 7 and 8, 5 columns wide and 42 rows deep, with the header row repeated at the top of page 8 and a footnote reading 'all figures in thousands' at the bottom of page 8. The question is what the northern region did in Q3.

Failure modeWhat the stored chunk text looks likeWhat it retrieves for
HealthyTable 4. Revenue by region and quarter. Region, Q1, Q2, Q3, Q4. North, 1,482, 1,610, 907, 1,204Questions naming a region and a quarter — which is what users ask
Header severed from rowsNorth, 1,482, 1,610, 907, 1,204. South, 2,004, 1,878, 1,922, 2,110Almost nothing. There is no word in it that a question would contain
Merged cells flattenedRegion, Q1, Q2, Q3, Q4, North, 1,482, 1,610, 907, 1,204, SouthThe right topic, with values misaligned by one column — the worst outcome, because it answers confidently and wrongly
Never parsedTable 4. Revenue by region and quarter. (nothing else, or an empty string)The caption only. The assistant can describe that a table exists and not read it
Four failure modes, the chunk text each produces, and what that chunk can be retrieved for

The second row is the common one, and it has a specific cause: the chunker split on a size boundary partway through the table, so the header stayed with the preceding chunk. Everything after the split is numbers. Note also that page 8's repeated header is not a duplicate to be removed — it is the only thing making the second half of the table readable, and boilerplate-stripping rules routinely delete it.

Four ways a table breaks, and the check for each

CauseCheckStage to fix
Header separated from rowsThe chunk holds numbers and row labels but no column headingsChunking: split on table boundaries and repeat the header into every chunk
Merged or spanning cells flattenedCount the values in a row against the number of columns; a mismatch confirms itParsing: a layout-aware extractor, or a manual rule for that document family
Units or scale in a footnoteThe chunk has figures and no unit, and the footnote sits in a different chunkParsing: attach the caption, units and footnote to every chunk of the table
Numeric-only chunkThe chunk is more than half digits and punctuationIndexing: generate a short natural-language description per row or per table
Cause, check, and the stage that owns the repair

The units case is the one that does real damage, because it does not look like a failure. The number retrieves, the answer is fluent, and 907 is reported as 907 rather than 907,000. Nothing downstream can catch that: the passage genuinely says 907. A units footnote that lives in a separate chunk from its table is a wrong-answer generator with no visible symptom.

A row without its header is not a weak passage. It is a list of numbers with nothing in it that any question will ever contain.

Never parsed, or parsed and then cut in half

These two need completely different work, and the check that separates them takes a minute.

  • Never parsed. The chunks covering those pages contain a caption, surrounding prose, or nothing — and none of the cell values appear anywhere in the chunk store. Grep the entire chunk table for one distinctive value from the middle of the table: zero hits proves it. The table is an image, a vector drawing, or a structure the extractor skipped, and the repair is in the extractor.
  • Parsed then cut. The values are present in the chunk store, and so is the header, but they are in different chunks. Grep for the value and grep for the column heading, then compare chunk ids. Different ids proves it, and the repair is in the chunker rather than the parser.
  • Parsed and misaligned. Values present, header present, same chunk, wrong pairing. Count fields per row against the header count. This is the only one of the three where the answer looks right, which is why it should be checked even when the other two are ruled out.

One consequence worth naming: numeric chunks cluster tightly in the embedding space, because they are made of the same handful of tokens. That means the ordering among them carries very little signal, and a table-heavy corpus is a common reason for the same question returning different passages every run. If your table chunks reshuffle between identical runs, that is the mechanism, and a lexical arm or a metadata filter will do more than any reranking.

Which stage to change, in cost order

  1. Repeat the header into every chunk of the table. The cheapest change in this list, usually a few lines in the chunker, and it resolves the most common failure outright.
  2. Never split inside a table. Treat the table as an atomic unit; where it is too large for one chunk, split by row groups and carry the caption, the header and the units into each piece.
  3. Attach the footnote and the units to the table's chunks at parse time, not as separate text. A figure without its scale is not a fact.
  4. Emit a short description per row for tables that are genuinely queried by row — 'North region, Q3, revenue 907 thousand' — so there is language for a question to match. This is index-time generation, and it is worth it only for the tables users actually ask about.
  5. Replace the extractor last. It is the largest change and the one most likely to alter every other document's chunks at the same time, which makes a regression impossible to attribute.

Two boundaries. If the tables are the primary content rather than an occasional structure — a set of spreadsheets, an exported ledger, a product matrix — the fix is not repair but design, and the end-to-end approach belongs to turning a spreadsheet into something a retrieval system can answer from. And if you split a table into per-row records, each row must inherit the parent document's metadata, permissions included, or those rows vanish for filtered users in the way described in recall collapsing after permission filtering.

Confirming the repair, and one thing it will not fix

Build a probe set of 20 questions whose answers are single cells, chosen from different tables and different positions — first row, middle, last row of a table that spans a page break. Record whether the correct chunk is retrieved and whether the answer matches the cell. Re-run it on every ingest. Cell-level questions are the only probe that exercises this path, and a general question set will pass while every table in the corpus is broken.

One limit to set expectations against. Getting the table into the prompt is not the same as getting it read. A wide table placed in the middle of a long context is exactly where a specific figure gets overlooked, for the reasons in facts in the middle of a long context getting missed. Put the table near the end of the prompt, keep it narrow, and check the answer against the cell rather than trusting it.

The durable version of all of this is a table-aware ingest path with its own probe set and its own gate, which is a small, well-bounded piece of engineering rather than a platform decision — the shape of work we run as MVP and product builds. It sits with the rest of retrieval and grounding, inside the wider engineering library.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

Why can't my RAG system answer questions about numbers in tables?

Because the number usually reaches the index without the words that make it findable. A question names a row and a column; the chunk often holds only the figures, because the header stayed in the preceding chunk or the table was never parsed as a table at all. The retrieval scoring is behaving correctly — there is nothing in that chunk for the question to match.

How do I tell whether a table was parsed or just badly chunked?

Grep the chunk store for one distinctive value from the middle of the table. No hits anywhere means the table was never parsed and the repair is in the extractor. Hits in one chunk with the column headings in a different chunk means it was parsed and then cut, and the repair is in the chunker. That single check separates two problems with no overlapping work.

Should I put tables in the index as text, or convert them to something else?

For occasional tables inside prose documents, keep them as text with the caption, header and units repeated into every chunk — it is cheap and it fixes most failures. For sources that are mostly tabular, text is the wrong representation entirely: those belong in a queryable store the system can compute against, because aggregation and counting questions cannot be answered by retrieving passages.

Why does the assistant give a number that is off by a factor of a thousand?

Because the scale lives in a caption or footnote that ended up in a different chunk from the figures. The retrieved passage genuinely says 907, so nothing downstream can detect the error, and the answer reads as confident and precise. Attach units and footnotes to every chunk of the table at parse time; this is the one table failure that produces wrong answers rather than missing ones.

  • retrieval
  • tables
  • chunking
  • document parsing
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation