DEV Community

Cover image for PDF citations for RAG: keep the page and block behind each chunk
Derek
Derek

Posted on Fully Autonomous

PDF citations for RAG: keep the page and block behind each chunk

A RAG answer can quote the right sentence and still link to the wrong page. If the pipeline drops the source version or page reference, adding a citation button later will not recover that information.

The teaching input, outputs and example code are available for inspection. The archive includes no API key or SDK license.

Start by keeping the parser output and a small source map. Test the map before changing the retrieval model.

Parse a document you can inspect

Choose a short teaching PDF with a known heading, a paragraph and a table. Record which page contains each item. Keep the original file and its digest so a later upload cannot silently replace the cited version.

ComPDF provides page-level structured parsing. This request uses the documented overseas endpoint; set COMPDF_API_KEY on the server and provide sample.pdf before running it.

curl --fail-with-body --request POST \
  'https://api-server.compdf.com/server/v2/process/idp/documentParsing' \
  --header "x-api-key: $COMPDF_API_KEY" \
  --form 'file=@sample.pdf' \
  --form 'pageRanges=1-3' \
  --output response.json
Enter fullscreen mode Exit fullscreen mode

Check the request status and returned result before continuing. The parsing guide covers the request and response; the command alone does not establish extraction quality.

Download and inspect the result first

In this teaching run, the successful request returned a download URL. The downloaded ZIP contained result_json.json and a Markdown file. The page data used below came from that JSON file inside the ZIP, not directly from the HTTP envelope.

The three-page teaching input included a raster-only page. Its downloaded result contained three page records and 21 text-bearing blocks. Each mapped block had an eight-number polygon; table blocks contained HTML. Keep the table type and source reference when normalizing it rather than treating the markup as plain prose.

Map the actual fields

In the documented response, result.pages contains page output. page_id is one-based, and structured contains blocks with fields such as id, type, text and pos. Confirm these against your response rather than replacing missing values with invented coordinates.

The following code reads the downloaded archive and builds source records. Use the same input bytes that were submitted for parsing.

import hashlib, json, zipfile
from pathlib import Path

document_id = 'teaching-mixed'
document_version = hashlib.sha256(Path('sample.pdf').read_bytes()).hexdigest()
with zipfile.ZipFile('result.zip') as archive:
    response_data = json.loads(archive.read('result_json.json'))

records = []
review = []
for page in response_data['result']['pages']:
    blocks = page.get('structured')
    if not isinstance(blocks, list) or not blocks:
        review.append({'page_number': page['page_id'],
                       'reason': 'No structured blocks'})
        continue
    for block in blocks:
        text = block.get('text')
        if not isinstance(text, str) or not text.strip():
            continue
        records.append({
            'document_id': document_id,
            'document_version': document_version,
            'page_number': page['page_id'],
            'block_id': block['id'],
            'kind': block['type'],
            'text': text,
            'source_polygon': block.get('pos'),
            'page_width': page['width'],
            'page_height': page['height'],
        })
Enter fullscreen mode Exit fullscreen mode

The complete mapping script ran against this archive and produced 21 text-bearing records. Missing polygons also need a review entry before a citation renderer uses them. This checks source mapping; it does not prove that every extracted block is correct.

These record names belong to the application. Keep the original response alongside them. Missing or failed pages need their own review record; this text-only mapping does not count empty blocks as proof that a page contained nothing.

Preserve references through chunking

Each chunk should retain an ordered list of source records, including all pages it spans. Scope block IDs by document version and page; an integer block ID alone is not a global identifier.

The page structure reference defines pos as a four-point polygon. A citation renderer must account for the source coordinate system, page dimensions, rotation and displayed size. Do not treat its first two numbers as a ready-made browser rectangle.

ComPDF supplies extraction and location information. The application still owns chunking, retrieval, permissions and navigation to the original document.

Teaching source checks

Figure: teaching source and the mapped fields from the actual downloaded parser result.

Check a citation before trusting it

Ask a question whose answer is known. Follow the returned citation and inspect the original page. Check the sentence, nearby heading and table relationship, then repeat with a paragraph that crosses pages and a newer version of the same filename.

Also check a failed page. Keep it visible in the review list rather than silently indexing an empty result. Useful evaluation records show the input, returned blocks, citation target and remaining extraction errors; they do not need a claimed accuracy score.

For a repeated ingestion workflow, use ComPDF developer access to evaluate authorized samples with the same checks. Continue to integration only when the output provides the structure and traceability your application needs. This mapping alone does not prove retrieval accuracy or a reduction in hallucinations.

Top comments (0)