<?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[Paul Oremland - 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[Paul Oremland - 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/paul-oremland</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/paul-oremland</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/paul-oremland.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 24 Oct 2026 20:52:16 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Using Elasticsearch as a vector database for Azure OpenAI On Your Data]]></title>
    <description><![CDATA[Learn how to set up and ingest data into Elasticsearch for use as a vector database with Azure OpenAI On Your Data, allowing you to chat with your private data.]]></description>
    <content:encoded><![CDATA[<h3>Background and introduction to Azure OpenAI On Your Data</h3><p>Recently <a href="https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/azure-openai-service-expands-quot-on-your-data-quot-with/ba-p/4097023">Microsoft integrated Elasticsearch directly into Azure via Azure OpenAI Service "On Your Data"</a>. "On Your Data" empowers organizations to construct state-of-the-art conversational experiences by leveraging powerful AI models such as GPT-4 and the RAG model. This gives you the ability to ground LLMs with your private data ensuring that conversations are informed and accurate.</p><p>In this article I wanted to demonstrate how easy it is to spin up an Elasticsearch cluster, ingest data using an Elasticsearch Connector, and then chat with that data using the new Azure OpenAI Service "On Your Data" integration. One of the things we'll use in this article is the recently added <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/connector-apis.html">Connectors API</a> and CLI, which enables us to interact with Elasticsearch fully programatically with no need to switch back and forth between your dev environment and Kibana.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb24c0b3b68ccbb8f/6a17db14033c8d46f66bb048/bce62596b7393338d58bee70c641b6774c6d7b5e-1440x771.png" alt="integration-diagram" /><h3>Pre-requisites</h3><p>For this example I’m going to use the OpenLibrary dataset from <a href="https://openlibrary.org">https://openlibrary.org</a>. In order to use this dataset you’ll need to do some pre-work to get the data ready to ingest into Elasticsearch. Specifically, you’ll need to:</p><ol><li><p>Download a recent dump of the data from <a href="https://openlibrary.org/developers/dumps">https://openlibrary.org/developers/dumps</a></p></li><li><p>Import the data dump into PostgreSQL. I used the open source tools and steps listed in <a href="https://github.com/LibrariesHacked/openlibrary-search">https://github.com/LibrariesHacked/openlibrary-search</a></p></li></ol><p>While step 2 isn’t strictly necessary because you could write a custom ingestion script, I like staging the data in PostgreSQL for a few reasons. First, Elasticsearch has support out of the box for ingesting data from PostgreSQL via a connector. The connector is already optimized for throughput and has some nice resilience built-in and I hate re-inventing the wheel. Second, I can write a custom SQL query for use with the connector. This means I have a lot of flexibility to extract only the data I want from the dataset and can represent the highly normalized data for a “book” as a single document without having to write custom code to do it from the raw referential data dumps. So instead of spending a few days writing and tweaking a custom script to create a single document representing the edition, author, and work that makes up a book, I can spend just a few minutes importing the data into PostgreSQL and setting up the connector to ingest the data into Elasticsearch.</p><h3>Spinning up the Elasticsearch Cluster</h3><p>Now that I have all my data ready to ingest I’m going to create an Elasticsearch deployment. I’m using the Elastic Cloud because I don’t want to worry about or have to manage any infrastructure.</p><p>Note, I’m going to be doing most of this via the command line and curl. I’ll be using a few shell variables that you’ll want to export these on the command line as you get them, specifically:</p><ul><li><p><strong>ES_URL</strong>: the url to your Elasticsearch deployment</p></li><li><p><strong>API_KEY</strong>: the key that we’ll create a few steps down</p></li><li><p><strong>CONNECTOR_ID</strong>: the ID of the connector that gets created a few steps down</p></li></ul><p>The first step is to create a deployment at <a href="https://cloud.elastic.co/deployments/create?utm_source=elastic-search-labs&amp;utm_medium=referral&amp;utm_campaign=search-labs&amp;utm_content=article">https://cloud.elastic.co/deployments/create</a> or <a href="https://www.elastic.co/guide/en/cloud/current/Deployment_-_CRUD.html#create-deployment?utm_source=elastic-search-labs&amp;utm_medium=referral&amp;utm_campaign=search-labs&amp;utm_content=article">via the API</a>). Because I know I’m going to be generating embeddings on millions of documents and performing text expansion on my queries I’m going to bump the minimum size of the Machine Learning instances to 4GB.</p><p>Expand the "Advanced settings" section</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b473d8075db1c24/6a17db167f6f151fd8c099d8/ed24477c9073e930f27d5656abb846e6bae11f2a-819x602.png" alt="Advanced settings" /><p>Scroll down to the Machine Learning Instance and update the "Minimum size per zone" to 4GB</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e09b25984adf2e3/6a17db173e03d7780c4f2b31/0dc515d0ae67355ff2e8e076c07c1edf8ee7ecc0-823x815.png" alt="Set ML min size" /><p>Now you can click the "Create deployment" button. In a few moments your deployment will be complete.</p><p>Next, let’s create an API key. Remember to save this somewhere secure as we’re going to use it a lot as we go. Note, in order to keep this example simple I'm using overly permissive access. In practice (and in production) you will likely want to <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html">set more granular permission using roles and/or restrictions</a>.</p>curl --user elastic -X PUT "${ES_URL}/_security/api_key?pretty" \
-H "Content-Type: application/json" \
-d'
{
    "name": "books-api-key",
    "role_descriptors": {
        "role-name": {
            "cluster": ["all"],
            "index": [ { "names": ["*"], "privileges": ["all"] } ]
        }
    }
}
'
<p>Next, you’ll want to create the index and set up the dense_vector mappings for the title and description fields. This step assumes that you know the document schema which creates a sort of “chicken and the egg” problem since you don’t know the schema until after you’ve ingested the data. For simplicity's sake, I set up a temporary index using the same steps listed below, created a connector, and ingested a single document (setting a limit to 1 on the SQL query). That enabled me to know what the document schema would look like so I could generate the mappings I wanted at the start. This is a known sharp edge that our team is working to resolve in future versions.</p>curl -X PUT "${ES_URL}/books-index?pretty" \
-H "Authorization: ApiKey "${API_KEY}"" \
-H "Content-Type: application/json" \
-d'
{
    "mappings": {
        "properties": {
            "public_author_works_authors_edition_isbns_editions_works_description": {
                "type": "text",
                "copy_to": ["description"]
            },
            "public_author_works_authors_edition_isbns_editions_works_title": {
                "type": "text",
                "copy_to": ["title"]
            },
            "description": { "type": "text" },
            "title": { "type": "text" },
            "title_embedding": { "type": "dense_vector" },
            "description_embedding": { "type": "dense_vector" }
        }
    }
}
'
<p>Now that we’ve got our index created and mappings set up, let’s download the E5 model and set the input field names to the dense vector mappings we just created.</p>curl -X PUT "${ES_URL}/_ml/trained_models/.multilingual-e5-small_linux-x86_64?pretty" \
-H "Authorization: ApiKey "${API_KEY}"" \
-H "Content-Type: application/json" \
-d'
{
    "input": {
        "field_names": ["title_embedding", "description_embedding"]
    }
}
'
<p>Next we want to deploy the model. If you get a 408, you’ll want to wait a minute after the previous command to allow the download to complete first.</p>curl -X POST "${ES_URL}/_ml/trained_models/.multilingual-e5-small_linux-x86_64/deployment/_start?wait_for=started&amp;deployment_id=for_search&amp;pretty" -H "Authorization: ApiKey "${API_KEY}""
<p>And finally, we’ll want to create an inference pipeline that will create embeddings on the title and description fields as we ingest data from the connector.</p>curl -X PUT "${ES_URL}/_ingest/pipeline/e5-small-books?pretty" \
-H "Authorization: ApiKey "${API_KEY}"" \
-H "Content-Type: application/json" \
-d'
{
    "processors": [{
        "inference": {
            "model_id": "for_search",
            "input_output": [
                {
                    "input_field": "public_author_works_authors_edition_isbns_editions_works_title",
                    "output_field": "title_embedding"
                },
                {
                    "input_field": "public_author_works_authors_edition_isbns_editions_works_description",
                    "output_field": "description_embedding"
                }
            ]
        }
    }]
}
'
<h3>Ingesting the Data</h3><p>Now that we have our cluster created, configured, and ready to use, let’s start ingesting the data. To do that we’re going to use the <a href="https://www.elastic.co/docs/reference/search-connectors/es-postgresql-connector-client-tutorial">Elasticsearch PostgreSQL connector</a>. I’m going to do this from source, because why not, but if you want something more out of the box you can you can <a href="https://www.elastic.co/docs/reference/search-connectors/es-connectors-run-from-docker">install from Docker</a>.</p><p>First, let’s clone the GitHub repo and install all the dependencies.</p>git clone git@github.com:elastic/connectors.git
cd connectors &amp;&amp; make clean install
<p>Next, I’m going to use the magical connector CLI to authenticate with my Elasticsearch instance and let it handle the basic connector configuration. The first step is to login to Elasticsearch via the CLI and enter your Elasticsearch instance URL and API key when prompted.</p>./bin/connectors login --method apikey
<p>Next, create the connector and enter your PostgreSQL connection information when prompted.</p>./bin/connectors connector create \
--from-index \
--index-name books-index \
--service-type postgresql \
--index-language en \
--update-config

? Connector name: books-postgresql-connector
? Host []: localhost
? Port []: 5432
? Username []: \&lt;USERNAME&gt;
? Password []: \&lt;PASSWORD&gt;
? Database []: openlibrary
? Schema []: public
? Comma-separated list of tables []: *
? Rows fetched per request []:
? Retries per request []:
? Enable SSL verification [False]:
<p>Because our data source has A LOT of data that is normalized we’re going to want to create a custom SQL statement which we can configure the connector to use with its <a href="https://www.elastic.co/guide/en/enterprise-search/current/sync-rules.html#sync-rules-advanced">advanced sync rules</a> capability. We'll use the following SQL statement which helps us create a single document from the highly relational data. Note, since this SQL will be sent via a curl command, we need to escape <code>'</code> using <code>'\''</code>.</p>SELECT DISTINCT ON (author_works.work_key, editions.data-&gt;&gt;'\''publish_date'\'') author_works.author_key, 
  editions.key, 
  author_works.author_key, 
  author_works.work_key, 
  edition_isbns.edition_key, 
  edition_isbns.isbn, 
  authors.key, 
  works.key, 
  works.data-&gt;&gt;'\''title'\'' AS title, 
  authors.data-&gt;&gt;'\''name'\'' AS author_name, 
  COALESCE(works.data-&gt;'\''description'\''-&gt;&gt;'\''value'\'', '\'''\'') AS description, 
  edition_isbns.isbn AS isbn, 
  editions.data-&gt;&gt;'\''publish_date'\'' as publish_date 
FROM editions 
  JOIN edition_isbns on edition_isbns.edition_key = editions.key 
  JOIN works on works.key = editions.work_key 
  JOIN author_works on author_works.work_key = works.key 
  JOIN authors on author_works.author_key = authors.key 
WHERE 
  EXISTS (SELECT 1 FROM jsonb_array_elements(editions.data-&gt;'\''languages'\'') AS elem WHERE elem-&gt;&gt;'\''key'\'' = '\''/languages/eng'\'') 
ORDER BY 
  editions.data-&gt;&gt;'\''publish_date'\'' desc
<p>Draft the advanced sync rule. Once the connector is running, the draft sync rule will be activated upon passing validation.</p><p>Note: This API call is currently available in Elasticsearch Serverless and will be included in the Elasticsearch <code>8.14</code> release.</p>curl -X PUT "${ES_URL}/_connector/${CONNECTOR_ID}/_filtering?pretty" \
-H "Authorization: ApiKey "${API_KEY}"" \
-H "Content-Type: application/json" \
-d'
{
    "advanced_snippet": {
        "value": [{
            "tables": [ "editions", "works", "author_works", "authors", "edition_isbns" ],
            "query": "\&lt;SQL FROM ABOVE&gt;"
        }]
    }
}
'
<p>We also want to wire up the connector to the inference pipeline we created earlier so that embeddings will be automatically generated as documents are ingested.</p>curl -X PUT "${ES_URL}/_connector/${CONNECTOR_ID}/_pipeline?pretty" \
-H "Authorization: ApiKey "${API_KEY}"" \
-H "Content-Type: application/json" \
-d'
{
    "pipeline": {
        "extract_binary_content": true,
        "name": "e5-small-books",
        "reduce_whitespace": true,
        "run_ml_inference": true
    }
}
'
<p>Next, we'll use the API to kick off a sync to ingest the data into Elasticsearch.</p>curl -X POST "${ES_URL}/_connector/_sync_job?pretty" \
-H "Authorization: ApiKey "${API_KEY}"" \
-H "Content-Type: application/json" \
-d'
{
    "id": $CONNECTOR_ID,
    "job_type": "full"
}
'
<p>Finally, it’s time to run the connector</p>make run
<p>You can check how many documents were ingested using:</p>curl -X POST "${ES_URL}/books-index/_count&amp;pretty" -H "Authorization: ApiKey "${API_KEY}""
<h3>Connecting Azure OpenAI On Your Data to Elasticsearch</h3><p>Now that we've got Elasticsearch deployed and all our data ingested, let's use Azure OpenAI Service’s "On Your Data" to chat with the index. For this I’m going to use the Azure OpenAI console.</p><p>The first thing we need to do is open <a href="https://oai.azure.com/portal/">Azure AI Studio's web interface</a> and:</p><ol><li><p>Open the "Chat" playground</p></li><li><p>Select the "Add your data" tab</p></li><li><p>Click the "Add your data source" button</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37d291747e0900fa/6a17db19ec0f8939965a64cc/83da0fa713ea7a796bffae4cada53bbc27b29566-650x620.png" alt="Open the Chat playground" /><p>Next, select Elasticsearch as your data source and:</p><ol><li><p>Enter your Elasticsearch endpoint URL ($ES_URL from above)</p></li><li><p>Enter your API key ($API_KEY from above)</p></li><li><p>Click the "Verify connection" button</p></li><li><p>Select your books-index</p></li><li><p>Check the "Use custom field mapping" button</p></li><li><p>Clicking the "Next" button</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ad04d21c2117057/6a17db1aaf47b64135cdde37/4081d1dd28a9dc48b87ae8ce7cb7776792d12eca-981x299.png" alt="Select Elasticsearch as your data source" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb1487b41dfa84714/6a17db1b7b54f93eed8b379d/1d18d6f86f6efe88da35ca19e741462d40b871a1-998x828.png" alt="Configure your Elasticsearch data source" /><p>Next, we'll set the search type and embedding model by:</p><ol><li><p>Selecting "Vector" from the Search type dropdown</p></li><li><p>Selecting "Elasticsearch - .multilingual-e5-small_linux-x86_64" as the embedding model</p></li><li><p>Clicking the "Next" button</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f6ee832b2aa098f/6a17db1d577262de591bcae7/c70f43a846e84a69c626b83fdb3a574f482f2dde-998x826.png" alt="Select Elasticsearch as your data source" /><p>Next, we'll configure the data fields by:</p><ol><li><p>Selecting all the fields (such as "author_name" and "description") that contain relevant content from the "Content data" dropdown</p></li><li><p>Leave defaults for the "title" and "Vector Fields"</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt428b8149bc45a28e/6a17db1e445de92c584cffc2/10509afe91d2ddf742f55b951b443297e1a7c467-1066x866.png" alt="Configure your Elasticsearch data source" /><p>On the next screen, review your data source settings and click the "Save and close" button and you're ready to chat away.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ca196e85b9bcb65/6a17db20a2929967f2d02b4d/60df5dd56c2be82a0cd14ec66fa9a96cd31f55c4-998x828.png" alt="Review and save your configuration" /><p>Now you're ready to chat with your data. This is where semantic search really shines. I'm able to ask an initial question and then, using the semantic meaning and context of the chat history, able to ask follow-up questions of my data in natural language.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt741b74546536434f/6a17db217b54f9a2c58b37a1/3501060c4e683f4ef98faedf1a6c9f74bd721cb4-1292x870.png" alt="Chat Session" /><p>The great thing is that I can see the citations inline with the context provided in the flyout.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f510035113ca827/6a17db236df7314a790a0db3/02ff379b8c5af0b70aea6733d629bec7e55dc5e8-1294x874.png" alt="Chat Citations" /><h3>Where to go from here?</h3><p>Chatting with your index is just one of the many powerful things you can do. Outside of the Azure OpenAI On Your Data integration, once you’ve got your data in Elasticsearch there are many other things to try such as using one of the many other 3rd party embedding models <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html">we support</a> by <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-text-emb-vector-search-example.html">deploying a text embedding model</a>.</p><ol><li><p>Play around with <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search-elser.html">ELSER</a>, a sparse vector retrieval model trained by Elastic.</p></li><li><p>Setup <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search-inference.html">semantic search with Cohere, HuggingFace, or OpenAI</a> embedding models.</p></li><li><p>In general, any trained model that <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html">has a supported architecture</a> is deployable in Elasticsearch by using eland.</p></li></ol><p>Elasticsearch also gives you the ability to:</p><ol><li><p>Use <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html">search templates</a>, a stored search you can run with different variables, to run searches without exposing Elasticsearch’s query syntax to your users. Search templates let you change your searches without modifying your app’s code.</p></li><li><p>Use <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">hybrid search</a>, an approach to combine the power of lexical (BM25) search with approximate nearest neighbor (kNN) search.</p></li><li><p>Use mid-stage re-ranking using <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/learning-to-rank.html">Learn To Rank</a> or <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/filter-search-results.html#rescore">query rescore</a> as well as <a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank?ref=txt.cohere.com">using Cohere's rerank API</a>.</p></li></ol><p>Not all search uses cases are semantic search use cases. If lexical search (BM25) is a better fit for your search use cases, there are several aspects of Elasticsearch to help you get the most relevant search results.</p><ol><li><p>Synonyms can help you find documents with similar words, correct common misspellings, or make domain specific language more user friendly. You can use the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/synonyms-apis.html">synonyms API</a> to define and manage synonyms in an internal system index or group related synonyms into a "synonyms set".</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-using-query-rules.html">Query Rules</a> allow customization of search results for queries that match specified criteria metadata. This allows for more control over results, for example ensuring that promoted documents that match defined criteria are returned at the top of the result list.</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-as-a-vector-db-for-azure-openai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-as-a-vector-db-for-azure-openai</guid>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Paul Oremland]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb24c0b3b68ccbb8f/6a17db14033c8d46f66bb048/bce62596b7393338d58bee70c641b6774c6d7b5e-1440x771.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 May 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>