How to Integrate Haystack for insurance with Elasticsearch for RAG

By Cyprian AaronsUpdated 2026-04-21
haystack-for-insuranceelasticsearchrag

Combining Haystack for insurance with Elasticsearch gives you a practical RAG stack for policy, claims, and underwriting workflows. Haystack handles the retrieval pipeline and answer generation, while Elasticsearch gives you fast full-text search, metadata filtering, and scalable document storage.

For insurance teams, that means an agent can answer questions from policy wordings, claims manuals, endorsements, and regulatory documents with traceable sources. You get better recall than pure vector search and better control than stuffing everything into a prompt.

Prerequisites

  • Python 3.10+
  • An Elasticsearch cluster running locally or in Elastic Cloud
  • A Haystack for insurance project installed in your environment
  • pip access to install Python packages
  • API credentials if your Elasticsearch cluster is secured
  • A document set ready for ingestion:
    • policy PDFs
    • claims guidelines
    • underwriting playbooks
    • product brochures

Install the core packages:

pip install haystack-ai elasticsearch sentence-transformers

If your Haystack for insurance distribution uses a separate package name or internal wrapper, keep the same integration pattern below and swap the import path accordingly.

Integration Steps

1) Connect to Elasticsearch

Start by creating an Elasticsearch client. Use this client both for indexing documents and verifying cluster health.

from elasticsearch import Elasticsearch

es = Elasticsearch(
    "http://localhost:9200",
    basic_auth=("elastic", "changeme"),
)

print(es.info())

For production, use HTTPS and a service account or API key. If you are on Elastic Cloud, pass the cloud endpoint instead of a local URL.

2) Create an index for insurance documents

Use a dedicated index for your RAG corpus. Keep the schema simple: text content plus metadata like document type, product line, jurisdiction, and effective date.

index_name = "insurance-rag"

if not es.indices.exists(index=index_name):
    es.indices.create(
        index=index_name,
        mappings={
            "properties": {
                "content": {"type": "text"},
                "title": {"type": "text"},
                "doc_type": {"type": "keyword"},
                "line_of_business": {"type": "keyword"},
                "jurisdiction": {"type": "keyword"},
                "source": {"type": "keyword"}
            }
        }
    )

This mapping matters because insurance retrieval usually needs filters. A claims agent should not retrieve life policy clauses when answering a motor claim question.

3) Ingest documents with Haystack-style document objects

Haystack works well when you normalize content into Document objects before indexing. In a Haystack for insurance setup, this is where you would plug in your PDF loaders or OCR pipeline.

from haystack import Document

docs = [
    Document(
        content="Pre-existing conditions are excluded during the first 12 months of cover.",
        meta={
            "title": "Health Policy Exclusions",
            "doc_type": "policy",
            "line_of_business": "health",
            "jurisdiction": "ZA",
            "source": "policy_2024.pdf"
        }
    ),
    Document(
        content="Claims must be reported within 30 days of incident occurrence.",
        meta={
            "title": "Claims Notification Rules",
            "doc_type": "claims_manual",
            "line_of_business": "motor",
            "jurisdiction": "ZA",
            "source": "claims_playbook.md"
        }
    )
]

Now push those documents into Elasticsearch. If your Haystack for insurance package exposes an Elasticsearch document store wrapper, use that directly; otherwise use the native client as shown here.

for i, doc in enumerate(docs):
    es.index(
        index=index_name,
        id=str(i),
        document={
            "content": doc.content,
            **doc.meta
        }
    )

es.indices.refresh(index=index_name)

4) Retrieve relevant context from Elasticsearch

For RAG, retrieval quality depends on getting the right chunks back before generation. Use a hybrid approach if needed: keyword search plus metadata filters.

query = {
    "bool": {
        "must": [
            {
                "match": {
                    "content": {
                        "query": "What is the deadline to report a motor claim?"
                    }
                }
            }
        ],
        "filter": [
            {"term": {"doc_type": "claims_manual"}},
            {"term": {"line_of_business": "motor"}},
            {"term": {"jurisdiction": "ZA"}}
        ]
    }
}

results = es.search(index=index_name, query=query["bool"], size=3)
for hit in results["hits"]["hits"]:
    print(hit["_score"], hit["_source"]["content"])

In a full Haystack pipeline, this search step would typically sit inside an ElasticsearchDocumentStore + retriever setup. The important part is that Elasticsearch returns only the passages your agent should see.

5) Feed retrieved passages into your RAG generator

Once you have context from Elasticsearch, pass it to your Haystack generator or agent prompt. The exact generator class depends on your Haystack for insurance build, but the pattern stays consistent: retrieve first, answer second.

top_passages = [hit["_source"]["content"] for hit in results["hits"]["hits"]]

prompt = f"""
Answer the question using only the context below.

Context:
{chr(10).join(top_passages)}

Question:
What is the deadline to report a motor claim?
"""

print(prompt)

If you are using a Haystack pipeline with an LLM component, this prompt becomes the input to your generator node. Keep source grounding strict so the assistant does not invent policy language.

Testing the Integration

Run one end-to-end check: index a known clause, retrieve it with Elasticsearch, and confirm the expected text comes back.

test_query = {
    "bool": {
        "must": [{"match_phrase_prefix": {"content": {"query": "report a motor claim"}}}],
        "filter": [{"term": {"line_of_business": "motor"}}]
    }
}

response = es.search(index=index_name, query=test_query["bool"], size=1)

assert response["hits"]["total"]["value"] >= 1
print(response["hits"]["hits"][0]["_source"]["content"])

Expected output:

Claims must be reported within 30 days of incident occurrence.

If that prints correctly, your ingestion path and retrieval path are connected. At that point you can wire the retrieved text into your Haystack agent or generator node.

Real-World Use Cases

  • Claims assistant

    • Answer “How long do I have to notify claims?” from current claims manuals and policy wordings.
    • Filter by product line and jurisdiction so users get region-specific guidance.
  • Underwriting copilot

    • Retrieve underwriting rules, referral thresholds, and appetite statements.
    • Help underwriters check whether a risk fits within appetite before escalating.
  • Policy servicing bot

    • Pull cancellation rules, renewal terms, waiting periods, and exclusions from source documents.
    • Give customer support agents grounded answers without searching shared drives manually.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides