A retrieval result can contain the correct sentence and still send a reader to the wrong page. One common cause is a document pipeline that concatenates extracted text, splits it into chunks, and calculates citations afterward. Once the page boundaries are gone, matching a chunk back to the PDF becomes guesswork, especially when headers and repeated phrases appear on several pages.
Keep page references attached to extracted blocks before chunking. Then carry those references forward when blocks are merged. This also gives an evaluator somewhere concrete to look when a retrieved answer is wrong.
The page-break trap
Imagine a report whose final paragraph on page 4 continues on page 5:
Page 4: The device operates at a maximum pressure of
Page 5: 12 bar when the ambient temperature is below 40 C.
A useful chunk may contain both lines. Labeling it only page=4 points to the beginning but hides the page containing the actual limit. Labeling it page=5 loses the start. Store the ordered set [4, 5], and preserve the original block references for more precise inspection. The example uses PDF physical page numbers, counted from one; printed page labels may differ, particularly in documents with front matter.
There is a second trap: a single PDF page may have several text regions. A passage assembled from a caption and a paragraph needs the source regions for both. Bounding boxes help locate those regions, but coordinate conventions vary between extraction tools. Record the coordinate system and page rotation with the boxes; do not assume a box from one tool can be drawn unchanged by another.
Carry references through chunking
The following Python example accepts already ordered blocks. It groups them by a character budget and carries page and block IDs into each chunk. The budget is illustrative; model token counts differ from character counts.
from dataclasses import dataclass
@dataclass(frozen=True)
class Block:
page: int
block_id: str
text: str
def make_chunks(blocks: list[Block], max_chars: int) -> list[dict]:
if max_chars < 1:
raise ValueError("max_chars must be positive")
chunks = []
current = []
size = 0
def flush():
nonlocal current, size
if not current:
return
chunks.append({
"text": "\n".join(block.text for block in current),
"pages": list(dict.fromkeys(block.page for block in current)),
"block_ids": [block.block_id for block in current],
})
current = []
size = 0
for block in blocks:
if not block.text:
continue
added = len(block.text) + (1 if current else 0)
if current and size + added > max_chars:
flush()
size += len(block.text) + (1 if current else 0)
current.append(block)
flush()
return chunks
blocks = [
Block(4, "p4-b7", "The device operates at a maximum pressure of"),
Block(5, "p5-b1", "12 bar when the ambient temperature is below 40 C."),
]
result = make_chunks(blocks, max_chars=120)
assert result[0]["pages"] == [4, 5]
assert result[0]["block_ids"] == ["p4-b7", "p5-b1"]
A block larger than the budget is emitted intact in this example. In production, split oversized blocks separately, preferably at sentence boundaries, while retaining their original block ID and offsets. The function also assumes that the extraction stage has already determined a sensible reading order. Provenance records where the text came from; it does not repair scrambled columns.
Which citation should the answer show?
Store the full reference list with the indexed chunk, then select the relevant source when presenting an answer. If the answer uses both halves of the pressure limit above, cite both pages. For a large chunk spanning several pages, citing all of them can be technically traceable but inconvenient. Smaller blocks or sentence-level offsets let the interface narrow the citation to the supporting passage.
Keep the original PDF and an extraction version identifier alongside the chunk metadata. Page 5 in a revised edition can contain different text, so a document title alone is insufficient for reproducible citations. A content hash can identify the exact file; access controls still need to follow the source document through the index and citation viewer.
Test the boundary, not just the answer
Build a small fixture set with a paragraph crossing a page break, repeated running headers, and a printed page label that differs from the physical page number. For each expected answer, record the supporting block IDs and physical pages. Run retrieval and check both whether the supporting text was returned and whether the citation points to those blocks. A correct answer with a wrong reference should fail the citation check.
Also inspect the opposite failure: the citation opens the right page but the extracted sentence combines unrelated columns. That needs a reading-order fix upstream, not a citation formatter. For a related example of column-order pitfalls, see this walkthrough of multi-column extraction.
Top comments (0)