cn|
WorkWritingAboutConnect

cn

building things that matter

  • Work
  • Writing
  • About
  • Connect

© 2026 Cristian Najera

  1. Home
  2. /Work
  3. /Enterprise RAG Pipeline with Azure AI Search Citations

Enterprise RAG

Page-precise citations at enterprise scale citations

Legal and compliance teams need to cite the exact page of a source document — not just the document title. We built a citation extraction layer on top of Azure AI Search that surfaces page numbers at retrieval time, eliminating the need for a costly re-index cycle.

Client

Enterprise (Financial Services)

Role

ML Engineer

Year

2024

Duration

3 months

Project previewComing soon
Citation Accuracy

94%

Documents Indexed

2.4M

Re-indexing Required

Zero

Avg. Retrieval Latency

340ms

Enterprise document search is a solved problem — until lawyers ask "which page?"

Azure AI Search indexes document chunks, not pages. Out of the box, retrieved chunks carry no page provenance. Re-indexing with page metadata embedded would have taken 8–12 hours per corpus update and required changes to every upstream document pipeline.

The citation extraction approach

Instead of re-indexing, we attached page-number extraction to the retrieval step. Each chunk in the Azure AI Search index already carried the byte-offset range of its source document. We mapped those offsets back to the PDF page boundaries at query time — no index changes, no pipeline disruption.

from dataclasses import dataclass
from azure.search.documents.models import VectorizedQuery
 
 
@dataclass
class Citation:
    document_id: str
    title: str
    page_number: int
    excerpt: str
    score: float
 
 
def extract_citations(
    search_results: list[dict],
    page_boundaries: dict[str, list[int]],
) -> list[Citation]:
    """Map Azure AI Search chunk results to page-number citations.
 
    Args:
        search_results: Raw results from the Azure AI Search client.
        page_boundaries: Pre-loaded byte-offset boundaries per document ID.
 
    Returns:
        Citations with resolved page numbers, sorted by relevance score.
    """
    citations = []
    for result in search_results:
        doc_id = result["document_id"]
        byte_offset = result.get("chunk_offset", 0)
        boundaries = page_boundaries.get(doc_id, [])
        page_number = _offset_to_page(byte_offset, boundaries)
        citations.append(
            Citation(
                document_id=doc_id,
                title=result.get("title", ""),
                page_number=page_number,
                excerpt=result["content"][:300],
                score=result["@search.score"],
            )
        )
    return sorted(citations, key=lambda c: c.score, reverse=True)
 
 
def _offset_to_page(offset: int, boundaries: list[int]) -> int:
    """Binary search to find the 1-indexed page for a byte offset."""
    lo, hi = 0, len(boundaries) - 1
    while lo < hi:
        mid = (lo + hi) // 2
        if boundaries[mid] <= offset:
            lo = mid + 1
        else:
            hi = mid
    return lo + 1

Page boundaries were computed once per document at ingest time and stored in a lightweight DynamoDB table keyed by document ID — a read that added under 5 ms to each query.

Accuracy at scale

We benchmarked against a manually audited set of 500 randomly sampled citations across 15 document types (contracts, regulatory filings, technical specs). The system reached 94% exact-page accuracy, with most misses concentrated on scanned PDFs where OCR introduced byte-offset drift.

For scanned documents, a GPT-4o fallback re-ranked candidate pages using visual layout cues from the chunk's surrounding context — recovering another 4% of cases at the cost of an additional 200–300 ms.

Lessons learned

Byte offsets are stable; chunk text is not. Storing byte ranges at ingest meant the citation layer was immune to prompt engineering changes or re-chunking experiments.

Measure against the actual user task. Initial accuracy metrics tracked semantic overlap, which looked great (~97%). Compliance reviewers cared about the page number only — that metric was 58% before the offset mapping was added.

Keep fast paths fast. The DynamoDB page-boundary lookup added 4 ms on average. The GPT-4o fallback added 250 ms but only fired for ~12% of queries. Tiered approaches keep the common case cheap.

Citation Accuracy
~58%→94%
Measured against manual audit of 500 randomly sampled citations
Time to Accurate Citation
Re-index cycle (8–12 hrs)→Real-time extraction
No pipeline changes required when document corpus updates
Compliance Review Time
4 hrs/document→45 min/document
Reviewers could jump directly to cited page
Azure AI SearchPythonGPT-4oFastAPI
  1. Sep 2024

    Requirements & document corpus analysis

  2. Oct 2024

    Prototype — citation extraction at retrieval

  3. Oct 2024

    Accuracy benchmarking & threshold tuning

  4. Nov 2024

    Production deployment & monitoring

All projectsGet in touch →