<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Ben-Amin York Jr. - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Ben-Amin York Jr. - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/author/ben-amin-york-jr</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/ben-amin-york-jr</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/ben-amin-york-jr.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Fri, 23 Oct 2026 11:35:52 GMT</lastBuildDate>
  <item>
    <title><![CDATA[56 scanned pages to a cited answer in 5 minutes: RAG pipeline for the documents search cannot read]]></title>
    <description><![CDATA[Ask a scanned, table-heavy PDF a question in plain language and get an answer stamped with the page it came from, because jina-ocr-v1 turns every page into structured Markdown that Elasticsearch embeds at ingest.]]></description>
    <content:encoded><![CDATA[<p>Upload a scanned 56-page plant guide, and ask which trees tolerate shallow limestone soil. In less than five minutes, you’ll have an answer, along with the page that it came from and the applicable plants that you can buy. The retrieval augmented generation (RAG) pipeline behind it has two managed pieces. <a href="https://www.elastic.co/search-labs/blog/ocr-model-jina-ocr-v1-document-parsing">jina-ocr-v1</a> reads each scanned page and returns structured Markdown with its tables intact, and Elasticsearch’s <code>semantic_text</code> field chunks and embeds that Markdown at ingest. As a result, you don’t need chunking code or an embedding job. Hybrid retrieval fuses keyword and semantic rankings before Claude Haiku 4.5 writes the grounded answer, and all of it runs on Elastic Inference Service (EIS) inside one project.</p><p>This matters because most of the knowledge that organizations rely on never makes it into a database. It lives in PDFs or scanned forms, or in photographed documents that traditional search cannot read. Classic optical character recognition (OCR) pipelines got at it by chaining separate models for layout detection, text recognition, table parsing, and reading order, each with its own cost and failure modes. The new generation of <a href="https://ir.elastic.co/News--Events/news/news-details/2026/Elastic-Introduces-jina-ocr-v1-End-to-End-Document-Processing-in-a-Single-Frontier-Grade-Model/default.aspx">vision-language document parsers</a> does the same job in a single model pass. When paired with a <a href="https://www.elastic.co/elasticsearch/vector-database">vector-capable search engine</a>, a PDF, or a photo of one, it becomes a queryable, citable knowledge source in minutes.</p><p>Combining document parsing with hybrid search and RAG lets teams:</p><ul><li><p>Make tables, handwriting, and complex layouts searchable, where keyword search and legacy OCR miss them.</p></li><li><p>Ask natural language questions against documents and get grounded answers with page-level citations.</p></li><li><p>Combine <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/keyword">exact term matching</a> (such as SKUs or botanical names) with <a href="https://www.elastic.co/docs/solutions/search/semantic-search">semantic matching</a> (meaning and intent) in a single query.</p></li><li><p>Keep the entire pipeline, including parsing and embeddings, along with generation, inside one platform with one bill and a single security boundary.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt107dead3f1ce9177/6ad9e5e6d26d92781e9f786e/image3.gif" alt="Uploading a scanned plant guide PDF and asking the RAG chatbot which invasive plants to avoid" /><p>In this post, we walk through adding a document-parsing feature to an ecommerce site for Texas native plants. (I love plants!) You upload the guide, watch <a href="https://www.elastic.co/search-labs/blog/ocr-model-jina-ocr-v1-document-parsing">jina-ocr-v1</a> parse every page, index the output into an <a href="https://www.elastic.co/docs/manage-data/data-store/index-basics">Elasticsearch index</a> named after the document, and query it through a RAG chatbot that cites sources by page number and recommends products from the catalog. The stack is as follows:</p><ul><li><p><strong>Jina OCR (jina-ocr-v1):</strong> A document-parsing model that reads a page image and returns clean, structured Markdown in a single multimodal pass.</p></li><li><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis"><strong>EIS</strong></a><strong>:</strong> GPU-accelerated inference included with Elastic Cloud Serverless. EIS hosts jina-ocr-v1 behind a preconfigured <code>chat_completion</code> endpoint, alongside embedding models and conversational large language models (LLMs) (in this build, Claude Haiku 4.5), so there’s no model deployment or GPU provisioning, and there’s no separate vendor account. EIS usage is metered per token and billed as a Serverless add-on. Current rates by model are on the <a href="https://cloud.elastic.co/cloud-pricing-table?productType=serverless&amp;group=addons&amp;dimension=elastic-inference-service&amp;solution=elasticsearch&amp;provider=aws&amp;region=us-east-1">Elastic Cloud pricing table</a>.</p></li><li><p><strong>Elasticsearch:</strong> Stores the parsed documents in the <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_text</code></a> field type, which chunks and embeds text at ingest through an inference endpoint and serves hybrid retrieval that fuses lexical and semantic rankings with <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">reciprocal rank fusion</a> (RRF).</p></li><li><p><strong>React, TypeScript, and Vite with an Express back end:</strong> The storefront, extended with a document upload page that streams parsing progress over server-sent events, and a floating chat widget. The Express layer keeps all credentials server side.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb0e593167d001a83/6ad9e603fcdfd1543d05f873/image1.png" alt="Architecture diagram of a RAG pipeline for scanned PDFs using jina-ocr-v1 and Elasticsearch" /><h2><strong>How is jina-ocr-v1 different from classic OCR pipelines?</strong></h2><p>jina-ocr-v1 is a single model; that is, layout, text recognition, table reconstruction, and reading order happen in one pass. Traditional OCR pipelines did the same work by chaining separate models for layout detection, text recognition, table parsing, and reading order reconstruction. Its output is built for retrieval:</p><ul><li><p>Structured Markdown with reading order preserved and section headings kept as Markdown headings.</p></li><li><p>Tables emitted as HTML with their values intact, so a 50-state license schedule or a plant care grid remains a table.</p></li><li><p>Handwriting transcribed inline.</p></li><li><p>Repeated page furniture (including running headers and footers, along with printed page numbers) dropped, so it never pollutes retrieval; page numbers come from the ingestion loop instead.</p></li></ul><p>That clean structure is what makes chunking, embedding, and citation trustworthy downstream. And it reads one page image per pass (legibility holds up to about 2048 x 2048 pixels), so someone has to run the page loop.</p><h2><strong>Where the page loop runs: Jina Reader API, chat completions, or EIS</strong></h2><p>Who runs that loop is the architecture decision.</p><p><strong>Route</strong></p><p><strong>Who runs the page loop</strong></p><p><strong>Input granularity</strong></p><p><strong>Where it runs</strong></p><p><a href="https://jina.ai/reader/">Jina Reader API</a> (<code>r.jina.ai</code>)</p><p>Jina, server side</p><p>One PDF per call</p><p>Outside your Elastic project; separate keys and billing</p><p>Raw chat completions API</p><p>Your application</p><p>One page per call</p><p>Outside your Elastic project</p><p>EIS (<code>.jina-ocr-v1-chat_completion</code>)</p><p>Your application</p><p>One page image per call</p><p>Inside your Elastic project; one key, one bill</p><p>This post uses the EIS route; that is, one platform boundary, and the page loop is a few dozen lines in <code>server/documents.js</code>.</p><h2><strong>Why a scanned plant guide is a hard test for document parsing</strong></h2><p>A demo corpus is only convincing if it’s challenging. Our test document is <a href="https://williamson.agrilife.org/files/2014/08/Native-and-Adaptive-Plants.pdf"><em>Native and Adapted Landscape Plants: An Earthwise Guide for Central Texas</em></a>, an educational publication codeveloped by the City of Austin and the Texas AgriLife Extension Service to help the public with water conservation and eco-friendly gardening. It’s the plant list behind the city's <a href="https://www.austintexas.gov/watershed-protection/grow-green">Grow Green program</a>. Its 56 scanned pages are exactly the type of content that basic PDF text extraction gets wrong. Nearly every page is a dense multicolumn table packed with abbreviations (such as, ″VL″ for very low water, and ″B/E″ for Blackland Prairie and Edwards Plateau), check marks for wildlife value and deer resistance, botanical names in italics, and photo captions interleaved with data. A parser that reconstructs those tables with their values intact can handle your invoices.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d9cd9974a0f7677/6ad9e6314f43aa179dc108da/image2.png" alt="Invasive plants table from a scanned guide, reconstructed by jina-ocr-v1 with every row intact" /><p>The plant theme also gives document intelligence a home. The host application is a Texas native plant storefront with a seeded Elasticsearch catalog: 48 plants with <code>semantic_text</code> descriptions and a full-text versus hybrid search toggle. Add document intelligence, and a shopper can upload a regional planting guide and then ask W<em>hich trees tolerate shallow limestone soil?</em> They’ll get an answer grounded in the guide, with pages cited and a list of products that fit.</p><h2><strong>Prerequisites: Elastic Cloud, an API key, and a scanned document</strong></h2><ul><li><p>An Elastic Cloud account (a <a href="https://www.elastic.co/cloud/cloud-trial-overview">free trial</a> works). Create an Elastic Cloud Serverless project. The <a href="https://www.elastic.co/docs/solutions/search/vector">vector database</a> project type fits this embedding-heavy workload.</p></li><li><p>In your project, confirm that the EIS model list includes Jina OCR v1 (<code>.jina-ocr-v1-chat_completion</code>) and Claude Haiku 4.5 (<code>.anthropic-claude-4.5-haiku-chat_completion</code>). Both are preconfigured, so there’s nothing to deploy.</p></li><li><p>An Elasticsearch API key with index read/write and inference privileges, and your project endpoint URL.</p></li><li><p>Node.js 20 or later, npm, and git.</p></li><li><p>A test document, such as the <em>Native and Adapted Landscape Plants</em> guide (linked above), or any scanned, table-heavy document of your own.</p></li></ul><h2><strong>Set up the RAG pipeline for scanned PDFs</strong></h2><p><strong>Step 1:</strong> Clone the repository, and install dependencies.</p>git clone https://github.com/elastic/elastic-labs.git
cd elastic-labs/supporting-blog-content/jina-ocr-v1-hybrid-rag-elasticsearch
npm install<p><strong>Step 2: </strong>Configure credentials. Copy the template, and fill in your project values. The .env file is gitignored, so secrets never reach the repository. The OCR endpoint is preconfigured and the embedding model is the project default, so the chat model is the only model choice in the build, and it’s already made for you.</p>cp .env.example .envELASTICSEARCH_URL=https://&lt;your-project&gt;.es.&lt;region&gt;.elastic.cloud:443
ELASTICSEARCH_API_KEY=&lt;your-api-key&gt;
LLM_INFERENCE_ID=.anthropic-claude-4.5-haiku-chat_completion<p><strong>Step 3: </strong>Seed the product catalog. The seed script creates the catalog index (<code>long_description</code> is a text field mirrored into <code>semantic_text</code>, so hybrid product search works immediately) and bulk loads 48 plants. The first run takes about a minute because Elasticsearch embeds every description at ingest.</p>npm run seed<p><strong>Step 4:</strong> Start the back end and the front end in separate terminals, with the back end first.</p><p>In the first terminal, start the Express API. It listens on port 3001 (set by <code>PORT</code> in <code>.env</code>) and runs the OCR pipeline and chat.</p>npm run server <p>In the second terminal, start the Vite dev server for the React app. It proxies <code>/api</code> calls to the back end.</p>npm run dev   <p>Vite prints the URL that it’s serving, normally<a href="http://localhost:5173"> http://localhost:5173</a>. (It picks the next free port, if 5173 is taken.) Open that URL; don’t browse to port 3001 directly, because it only exposes the API.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6a92fc4a40dea9d/6ad9e6cb9e74355a47745e3d/image4.png" alt="Texas native plants storefront with a 48-plant catalog and full-text, hybrid and compare toggle" /><h2><strong>How the OCR pipeline turns scanned pages into Markdown</strong></h2><p>The <strong>Documents</strong> page is where the parsing story unfolds on screen, rather than in a terminal. A user drops in a PDF or photo, and the Express server runs a four-stage pipeline while streaming every step back to the browser over server-sent events.</p><h3><strong>Stage 1: Render PDF pages to images</strong></h3><p>PDFs are rendered to PNG page images in memory at roughly 1224 x 1584 pixels, under the model's 2048-pixel legibility ceiling. A photographed document skips straight through as a single page.</p><h3><strong>Stage 2: Transcribe each page, and rebuild its tables</strong></h3><p>Each page image is sent to the EIS endpoint as a base64 data URL, exactly like a vision call to any chat model. Four pages run in parallel, and order is preserved on reassembly:</p>const resp = await fetch(
  `${ES_URL}/_inference/chat_completion/.jina-ocr-v1-chat_completion/_stream`,
  {
    method: 'POST',
    headers: { Authorization: `ApiKey ${ES_API_KEY}` },
    body: JSON.stringify({
      messages: [{
        role: 'user',
        content: [
          { type: 'image_url',
            image_url: { url: `data:image/png;base64,${png}` } },
          { type: 'text',
            text: 'Transcribe this page into clean Markdown, preserving reading order. Represent tables as HTML tables.' }
        ]
      }]
    })
  }
);<p>As each page completes, the browser logs it; for example, "page 12: 2,572 characters of Markdown". The moment that any page yields its first HTML table, the server emits it and the UI renders it fully styled. Watching a dense, scanned plant table reappear as a structured HTML table with every water code and height range intact is the most convincing moment of the demo.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38a32859fa1db314/6ad9e6fafad734481dd092b5/image5.png" alt="Parsing 56 scanned PDF pages with jina-ocr-v1, showing a table extracted from page 6" /><h3><strong>Stage 3: Index and embed the document with semantic_text</strong></h3><p>Rather than piling everything into a generic documents index, each upload gets its own index named after the file: <code>Native-and-Adaptive-Plants.pdf</code> becomes <code>native-and-adaptive-plants</code>. (The index follows the file name, so yours will match whatever you upload.) The <code>content</code> field is indexed as lexical text and mirrored by <code>copy_to</code> into a <code>semantic_text</code> field, where Elasticsearch chunks the Markdown and generates embeddings at ingest through the project's default inference endpoint. As a result, you don’t need to write chunking code or build an embedding pipeline, and there’s no need for vector bookkeeping.</p>PUT native-and-adaptive-plants
{
  "mappings": {
    "properties": {
      "content":          { "type": "text", "copy_to": "content_semantic" },
      "content_semantic": { "type": "semantic_text" },
      "title":            { "type": "keyword" },
      "page_count":       { "type": "integer" }
    }
  }
}<h3><strong>Stage 4: Check the new index in Elastic</strong></h3><p>The document is indexed, and the UI tells you that parsing is complete and to check your Elastic indices. Open your project, and the index is sitting there, named after the document, chunked and embedded. A small registry index (<code>doc-registry</code>) tracks every parsed document so the chat can list them.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta838d74df3438118/6ad9e72874bdb809e6905c5f/image6.png" alt="Parsing complete banner and the new Elasticsearch index shown in Kibana Index Management" /><h2><strong>Asking questions: Hybrid retrieval plus grounded generation</strong></h2><p>The leaf-shaped chat button in the corner of every page opens the Plant advisor. By default, it answers from “everything” (the uploaded guide and the product catalog together). A user can narrow the scope to a single document or to the catalog alone, but the default is where the retail story lives. The server answers in three moves:</p><h3><strong>1. Hybrid retrieval: How lexical and semantic search combine </strong></h3><p>Two hybrid searches run in parallel, one across the document indices and one across the catalog. Each uses an RRF retriever that fuses two rankings over the same text: a lexical match (which catches exact terms like "Quercus macrocarpa" or "VL" or a price), and a semantic query (which catches "trees for rocky alkaline soil", even when no sentence contains those words). On this corpus, hybrid consistently surfaced plants that keyword matching missed; the <strong>Compare</strong> view in the storefront shows exactly which ones for any query.</p>"retriever": {
  "rrf": {
    "retrievers": [
      { "standard": { "query": { "match":    { "content": question } } } },
      { "standard": { "query": { "semantic": { "field": "content_semantic",
                                               "query": question } } } }
    ]
  }
}<h3><strong>2. Source extraction and page citations</strong></h3><p>Highlighters do the work. The semantic highlighter returns the best matching embedded chunks, and the plain highlighter returns lexical fragments. The page numbers are recovered from the page markers that the ingestion loop embedded. Each source is labeled with the guide's name and page.</p><h3><strong>3. Generation: Grounded answers with Claude Haiku 4.5</strong></h3><p>The sources, the matching catalog products, and the question go to Claude Haiku 4.5 on EIS with strict grounding instructions: <em>answer only from the sources, cite them by name and page, recommend only products that appear in the catalog, and say so if the answer is not present</em>. The model also returns a hidden line listing the SKUs that it recommended, which the server strips from the stream; only those products are shown, so a question that the store cannot satisfy produces an honest answer with no product cards. The answer streams into the chat with an expandable sources panel and a collapsed "See recommendations" section underneath.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f3594761c740a7c/6ad9e76b619b01f0433cfcd5/image7.gif" alt="Plant advisor chat answering a low-water plants question using hybrid retrieval over the catalog" /><h3><strong>Queries to try against your indexed document</strong></h3><ul><li><p><em>Which trees tolerate shallow limestone soil?</em> (Semantic retrieval does the lifting.)</p></li><li><p><em>What does the guide say about Quercus macrocarpa?</em> (Lexical retrieval identifies the exact botanical name.)</p></li><li><p><em>Which invasive plants should I avoid, and what are the recommended alternatives?</em> (Answers live in a table that jina-ocr-v1 reconstructed.)</p></li><li><p><em>What do the water codes VL and M mean?</em> (This tests the legend table from the guide's key page.)</p></li></ul><h2><strong>From scanned PDF to cited answer</strong></h2><p>A year ago, this pipeline would have required a layout model, an OCR engine, a table parser, a chunking service, an embedding job, and a vector database, each separately deployed. Now two managed pieces carry the whole path from scanned page to cited answer: jina-ocr-v1 turns pixels into structured Markdown in one pass, and Elasticsearch's <code>semantic_text</code> field turns that Markdown into hybrid searchable knowledge at ingest.</p><p>The plant guide is a stand-in for whatever your hardest documents are. Swap in claim forms, lab reports, equipment manuals, or supplier catalogs, and then upload, parse, index per document, and ask. Start a free <a href="https://www.elastic.co/cloud/cloud-trial-overview?utm_campaign=G-TXT-AMER-US-Core-EN-Lead_Gen-CloudTrials-BR-Top&amp;utm_content=Elastic-Free-Trial&amp;utm_source=google&amp;utm_medium=cpc&amp;device=c&amp;utm_term=elastic%20cloud%20free%20trial&amp;utm_id=701610000005lJVAAY&amp;gad_source=1&amp;gad_campaignid=24193228008&amp;gbraid=0AAAAADrDgoJIR4gE5jmkXO0rrgJNjHPpF&amp;gclid=CjwKCAjwirLWBhBBEiwAlItbEqMYI1wYi-BG2sQ3_b_q3810GZqoGyLX1BMMrQORrkcvyKqMs4vW8hoCNhwQAvD_BwE">Elastic Cloud trial</a>, and point the pipeline at the PDF that your team complains about most.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/rag-pipeline-scanned-pdf-jina-ocr</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/rag-pipeline-scanned-pdf-jina-ocr</guid>
    <category><![CDATA[Jina AI]]></category>
    <category><![CDATA[Hybrid Search]]></category>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Ben-Amin York Jr.]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9297ed11a10209d8/6ad9e58b7f526391da83e0e2/image8.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>