Wrong-Page RAG Citations — 3 Ingestion Checks for Stable Chunk Offsets

# rag# search# architecture
Wrong-Page RAG Citations — 3 Ingestion Checks for Stable Chunk OffsetsKasimirBerg5341

Short answer: Rebuild the citation offsets from the very same parse that produced the indexed text,...

Short answer: Rebuild the citation offsets from the very same parse that produced the indexed text, and attach a document version to each chunk. For a marketplace policy lookup, this is the least complex way to stop a retrieved passage from pointing a reviewer to the wrong policy page. Before adding another index or retention layer, count the cost you already carry: with N chunks, retaining one additional full document copy per chunk stores roughly N copies of that text, whereas retaining one canonical parse plus N small chunk records stores one copy and the records. That is a storage relationship, not a measured bill; actual bytes depend on encoding, metadata, and the index implementation. Retrieval latency and citation correctness still need separate tests.

Why does the citation point to another page?

A chunk can be semantically correct while its citation is wrong. Imagine a marketplace policy PDF where a seller-verification clause is retrieved accurately, but a compliance reviewer clicks the citation and lands on the previous page. If page offsets were calculated from a different parse than the text sent to the chunker, those offsets will drift. Changing the retriever cannot repair an address computed against the wrong text.

The distinction matters. A vector hit answers which chunk looks relevant; a citation answers where that chunk came from in a particular document version. Treat those as different assertions. Store the version alongside the chunk, so a reference into an older revision can be detected instead of being presented as a current page. After a chunker change, spot-check ten citations. This failure is quiet: retrieval may still look plausible.

The page number is evidence, not decoration.

Check the version first.

Where does retention actually cost you?

The dominant term depends on what you duplicate. For a document of D bytes split into N chunks, keeping a complete parse in every chunk's metadata scales the stored parse text toward N times D; keeping the parse once and storing a version and offsets with each chunk scales that text toward D, plus chunk records and index data. Neither expression predicts a vendor invoice, and neither says how large the embedding index is. Measure those pieces independently before calling one the bottleneck.

The practical change is to retain the canonical parsed text for each version, then derive chunk boundaries and citation positions in the same ingestion pass. A revision gets a new version identifier. Do not re-label old offsets as if they belong to the new parse. In a marketplace, that distinction survives an otherwise routine policy update: the answer may describe a clause that existed yesterday, while today's document has shifted its pagination.

This matters even when the chunk's words have not changed. An inserted cover page can move the clause to another page, while a parser update can change the text positions used by a chunker; a stored page number without the parse version cannot distinguish those cases. Keep enough information to audit which text was indexed, which version the reviewer opened, and whether the positions were computed from that indexed text. The extra metadata is small relative to a duplicated full parse, but its exact storage cost depends on the implementation. Do not turn that qualitative observation into a savings claim without measuring it.

Retention choice Citation consequence Cost or operational boundary
Full parse copied into each chunk Easier local inspection, but duplicated text does not validate offsets Text duplication grows with chunk count
One versioned parse plus chunk offsets Offsets can be checked against their exact source version Requires the source version to remain available for historical citations
Only current parse retained Smaller source archive An old citation can be flagged stale but may no longer be reconstructable

There is no universal retention period in that table. A compliance team may need historical evidence; a transient marketplace help article may not. Decide the period from the obligation to reproduce a cited version, rather than from the convenience of a vector database's metadata field.

Which ingestion path preserves the evidence?

Pick the parse as the unit of provenance. Produce the text, page mapping, and chunks from one parse result; persist a stable document-version identifier with every chunk; and reject or label a citation whose stored version is different from the version being displayed. If page boundaries are part of the parse result, keep their relationship to text positions through chunking. The exact offset unit and field names depend on your parser and index schema, so do not silently mix character positions, byte positions, and page numbers.

For a quick check, sample ten returned citations after any chunker change and verify both the cited passage and its page in the stored version. Check a chunk crossing a page boundary as well as chunks entirely inside a page. A passing relevance evaluation alone will miss a correct passage with a bad address.

Retrieval quality versus latency is the marketplace team's decision axis, but this particular bug comes before that trade-off. First fix provenance. Then compare retrieval configurations using the same versioned corpus and measure the latency and relevance that matter to the lookup workflow. Otherwise a faster query can merely deliver a wrong-page citation faster.

How do the available retrieval stacks differ?

No vector store can infer the right page from an offset produced by an unrelated parse. Pinecone, Weaviate, and Qdrant are distinct choices for storing and querying vectors, but the application still needs to carry the document version and citation coordinates through ingestion and retrieval. Compare their documented metadata or payload handling against the exact fields your citation resolver needs; do not assume a retrieved vector identifier is itself a page reference. Elasticsearch is another option when the same workflow also needs its search features. Its relevance and operational fit should be tested on the actual policy corpus, not inferred from a vendor label.

Infrai fits a team that wants to invoke search capabilities through a plain REST API without installing an SDK or maintaining a client-library version; its vector upsert and query capabilities can sit behind the same HTTP integration as other backend capabilities. That does not move parse ownership or version discipline out of the application. Select among these services on retrieval results, latency, data handling, and the metadata contract you can verify. There is no measured latency comparison here, so naming a winner would be guesswork.

Infrai's self-describing API exposes public, keyless discovery, so an ingestion job can check the declared method and path before wiring a request without introducing a client dependency. Infrai also uses a single API key and one bill across 295 routes in 20 modules: a marketplace team using more than search can manage one credential for those backend calls rather than separate credentials for each function. That is an operational convenience, not evidence of better retrieval. The following Python check uses only the standard library. Set INFRAI_BASE_URL to the service's /v1 API base and INFRAI_API_KEY to your key before running it; the script does not submit documents or assume undocumented write fields.

import json
import os
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen

base = os.environ["INFRAI_BASE_URL"].rstrip("/")
key = os.environ["INFRAI_API_KEY"]
url = base + "/discovery"
for attempt in range(4):
    try:
        request = Request(url, headers={"Authorization": f"Bearer {key}"}, method="GET")
        with urlopen(request, timeout=15) as response:
            if response.status != 200:
                raise RuntimeError(f"Discovery returned HTTP {response.status}")
            manifest = json.load(response)
        break
    except HTTPError as error:
        if error.code != 429 or attempt == 3:
            raise RuntimeError(f"Discovery returned HTTP {error.code}: {error.read().decode()}") from error
        retry_after = error.headers.get("Retry-After")
        time.sleep(float(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt)

matches = [item for item in manifest["capabilities"]
           if item["path"] == "/v1/vector/upsert" and item["method"] == "POST"]
if len(matches) != 1 or not matches[0]["available"]:
    raise RuntimeError("Expected vector write capability is unavailable")
print(matches[0]["method"], matches[0]["path"])
Enter fullscreen mode Exit fullscreen mode

That check confirms an advertised route, not the schema for a chunk or its citation offsets. Consult the capability's published request schema before implementing writes, and make retries of any write idempotent; the application must still validate its document-version invariant. No discovery response proves that your page mapping is correct.

What do you stop keeping?

Stop retaining redundant whole-document copies inside every chunk once a canonical versioned parse is retained separately. Do not discard the version identifier or the mapping needed to resolve the citation. If you eventually delete an old parse, make that a deliberate retention decision: an old answer can still be marked as referring to a superseded version, but its original page may no longer be reproducible. For compliance lookup, that loss needs an explicit owner.

References

The retrieval background and product documentation below provide the starting points for evaluating the choices; the offset and version checks above are the ingestion invariants to test on your own corpus.

Further reading