<?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[AI - 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[AI - 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/blog/category/ai</link>
    </image>
    <link>https://www.elastic.co/search-labs/blog/category/ai</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/category/ai.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sun, 27 Sep 2026 09:45:57 GMT</lastBuildDate>
  <item>
    <title><![CDATA[AI video search with Elasticsearch and Jina: Find the exact seconds of footage you need]]></title>
    <description><![CDATA[Cut each clip at its shot boundaries and embed every scene as a vector, and a plain text query gives back the file plus the exact seconds to drop on a timeline.]]></description>
    <content:encoded><![CDATA[<p>Type <em>warm sunset light over a mountain</em>, and get back the exact seconds of footage that match. <a href="https://github.com/jdarmada/omnishot">Omnishot</a> is an AI video search app that watches a folder and cuts each clip at its scene boundaries. It embeds every scene as a 1,024-dimension vector with jina-embeddings-v5-omni-small. One model handles both text and video, so a typed query and a piece of footage land in the same vector space, and a nearest neighbor search in Elasticsearch finds the clips that look like the description. An hour of footage becomes about 1,200 searchable vectors, and a folder that used to take hours to ingest goes through in minutes.</p><p>The idea came from a video editor here at Elastic. She was losing whole afternoons scrubbing through B-roll folders to find one specific visual.</p><h2><strong>Searching a footage library by description</strong></h2><p>Here's what the app does:</p><ol><li><p>Select a folder of footage.</p></li><li><p>Search for clips with a visual query (<em>drone shot over a coastline</em>).</p></li><li><p>Search for similar clips using stored vectors.</p></li><li><p>Find similar chunks within the same clip.</p></li><li><p>Reveal the clip in your file browser, ready to drop on a timeline.</p></li></ol><h2><strong>How the AI video search pipeline works</strong></h2><p>The pipeline is as follows:</p><ol><li><p>A watcher polls the linked library folder (<code>clips/</code> by default) every four seconds for new footage. </p></li><li><p><a href="https://www.scenedetect.com/">PySceneDetect</a> detects the scene boundaries in each clip. </p></li><li><p><a href="https://www.ffmpeg.org/">FFmpeg</a> cuts the clip at those boundaries with a lossless stream copy and then transcodes each scene chunk into a small proxy. </p></li><li><p>The proxy goes to <a href="https://jina.ai/models/jina-embeddings-v5-omni-small/">jina-embeddings-v5-omni-small</a> through the Jina API, which samples 32 frames and returns a 1,024-dimension vector. </p></li><li><p>That vector is ingested into Elasticsearch in a <code>dense_vector</code> field with a hierarchical navigable small world (HNSW) index (an approximate nearest neighbor graph). At query time, the editor's text runs through the same model and we do a k-nearest neighbor (kNN) search against the stored chunk vectors.</p></li></ol><p>The same model embeds both text and video, which is what makes Jina’s omni model so versatile. Video chunks and text queries are embedded into the same vector space, so a nearest-neighbor search effectively means <em>find the footage that looks like what I described in text</em>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3536d7a860c52afc/6aab9a959936f5121d8b9e53/unnamed.png" alt="AI video search pipeline: watch folder, PySceneDetect chunking, ffmpeg proxy, Jina embeddings, Elasticsearch kNN" /><h2><strong>What you need to build AI video search</strong></h2><ul><li><p>Elasticsearch (Serverless or 8.x+ with dense vector support)</p></li><li><p>A Jina API key</p></li><li><p>PySceneDetect</p></li><li><p>FFmpeg</p></li><li><p>Python 3.9+</p></li></ul><h2><strong>Downloading sample footage to search</strong></h2><p>If you don’t have any footage handy to search through, the repo includes two download scripts: one for short stock clips from the <a href="https://www.pexels.com/api/">Pexels API</a>, across categories like nature, urban, animals, and more. The other uses <a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a> to grab longer documentary-style videos directly from YouTube.</p>python scripts/download_pexels.py --out ./clips --total 50
python scripts/download_youtube.py --out ./clips --total 20<p>We’re aiming to have a good mix between short and long videos to demonstrate how important the chunking strategy becomes. We’ll discuss that in the next part.</p><h2><strong>How to chunk video for embeddings</strong></h2><p>jina-embeddings-v5-omni-small samples 32 evenly spaced frames from whatever video you send it. For a 10-second clip, 32 frames is dense coverage; almost every moment gets captured. For a 10-minute documentary, those same 32 frames are spread so thin that entire shots can be missed. Here’s a diagram illustrating how the Jina model samples each video clip and the limitations with longer clips:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbca5d1daeccd997b/6aab9ad019b22627c65a87c2/unnamed.jpg" alt="Why chunk video for embeddings: 32 sampled frames cover a 10-second clip densely but miss shots in a 10-minute video" /><p>So before we embed anything, we chunk. The question is where to cut. Here's a diagram comparing three strategies:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa4f9a56331285bc/6aab9aebe96fa6793b7b67da/unnamed_(1).jpg" alt="Three video chunking strategies compared: fixed-length, transcript-based and scene-based splitting for video search" /><ol><li><strong>Fixed-length chunking</strong> splits every N seconds. It’s simple and predictable, but the cuts can land mid-shot. You could end up with a chunk that’s half drone shot and half talking head, which can make your search results unusable. This isn’t the right fit for this use case.</li><li><strong>Transcript-based chunking</strong> first runs speech to text over the clip to generate a transcript and then applies a text chunking technique to it, splitting at topic boundaries and mapping those back to timestamps in the video. This strategy is great for podcasts, talks, and educational content but not for B-roll since it usually has no dialogue.</li><li><strong>Scene-based chunking</strong> splits at visual changes, like shot changes, transitions, and cuts. Each chunk is one specific visual, which is exactly what a video editor would search for. This is the best one for our use case.</li></ol><p>The three strategies side by side:</p><p><strong>Strategy</strong></p><p><strong>How it cuts</strong></p><p><strong>Best for</strong></p><p><strong>Weakness</strong></p><p>Fixed-length</p><p>Every N seconds</p><p>Uniform content, predictable cost</p><p>Cuts land mid-shot, producing mixed chunks</p><p>Transcript-based</p><p>At topic boundaries in speech-to-text output</p><p>Podcasts, talks, educational video</p><p>Fails on B-roll with no dialogue</p><p>Scene-based</p><p>At visual cuts and transitions</p><p>B-roll and footage libraries</p><p>Depends on reliable shot detection</p><h3>Detecting scene boundaries with PySceneDetect</h3><p>To implement it, we use PySceneDetect to find the cut points:</p>from scenedetect import AdaptiveDetector, detect

scenes = detect(
str(video_path),
    AdaptiveDetector(
        adaptive_threshold=3.0,
        min_scene_len=int(min_scene_len_sec * 24),
    ),
)<p><code>AdaptiveDetector</code> compares each frame change against a rolling average, keeping camera pans and handheld motion from registering as cuts. The minimum scene length defaults to 1.5 seconds, which, at an assumed 24 frames per second (FPS), works out to 36 frames, so rapid cuts can't produce sub-second slivers. If no boundaries are detected at all, the whole clip becomes a single chunk. Each detected scene is then cut into its own chunk file with a lossless FFmpeg stream copy (<code>-c copy</code>), so nothing is re-encoded before the proxy step.</p><p>Now those 32 sampled frames cover a few seconds and a specific visual scene instead of skimming across clips.</p><h2><strong>Why send a 640px proxy instead of the original file?</strong></h2><p>We don't send the original, full-sized files to the embedding API. A 4K ProRes chunk can run hundreds of megabytes, and the model can't use that resolution anyway. Its vision encoder produces roughly one token per 28x28-pixel block, and the<a href="https://huggingface.co/jinaai/jina-embeddings-v5-omni-small"> model's default config</a> caps each frame at 1,280 vision tokens, which is about 1 megapixel. Anything bigger gets downscaled to fit the budget before encoding, so a 4K frame arrives at the model with roughly an eighth of its pixels. Uploading full-resolution footage just wastes bandwidth and time on resolution that’s never used. For a deeper breakdown of how the model turns frames into patch tokens, see<a href="https://www.elastic.co/search-labs/blog/multimodal-embeddings-gelato-jina-v5-omni"> our architecture deep dive on jina-embeddings-v5-omni</a>.</p><p>Instead, FFmpeg transcodes each scene chunk into a lightweight proxy: 640 px wide, audio stripped, aggressive compression.</p>import base64
import subprocess
import tempfile
from pathlib import Path

def make_video_input(
    chunk_path: Path,
    max_width: int = 640,
    crf: int = 28,
    max_seconds: float = 3.0,
) -&gt; dict:
    """Return a Jina video input dict with a short 640px proxy as base64."""
    with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
        proxy_path = tmp.name
    try:
        subprocess.run(
            [
                "ffmpeg", "-y", "-loglevel", "error",
                "-i", str(chunk_path),
                "-t", str(max_seconds),
                "-vf",
                f"scale='if(gt(iw,ih),{max_width},-2)':'if(gt(iw,ih),-2,{max_width})'",
                "-c:v", "libx264", "-crf", str(crf), "-preset", "veryfast",
                "-an",  # drop audio, the model never hears it
                "-movflags", "+faststart",
                proxy_path,
            ],
            check=True,
        )
        data = base64.b64encode(Path(proxy_path).read_bytes()).decode("ascii")
    finally:
        Path(proxy_path).unlink(missing_ok=True)
    return {"video": data}<p>Let’s go over two parts of the function that are easy to miss. The scale filter checks orientation <code>gt(iw,ih)</code>, so landscape clips are capped at 640 px wide and portrait clips at 640 px tall instead of getting squashed. And <code>max_seconds</code> puts a limit on each proxy at three seconds. This works because each chunk is one visual, so the first few seconds are already a good representative and 32 frames sampled across three seconds is already dense coverage. The function returns <code>{"video": &lt;base64&gt;}</code>, which is the input shape that the Jina API expects.</p><p>The quality loss doesn’t matter much, up to a point. Embeddings care about what’s in the frame, not whether the footage is high enough quality for final delivery. But if you compress too aggressively and visual details start disappearing, the model may no longer be able to reliably tell what’s in each frame. The goal is to shrink the file without losing the visual information that matters. Proxies come out at a few hundred kilobytes instead of hundreds of megabytes, so ingesting a folder of footage takes minutes instead of hours.</p><p>Each proxy then goes to the Jina API as a base64 string, and the API returns the 1,024-dimension vector for that chunk:</p>resp = requests.post(
    "https://api.jina.ai/v1/embeddings",
    headers={"Authorization": f"Bearer {JINA_API_KEY}"},
    json={
        "model": "jina-embeddings-v5-omni-small",
        "task": "retrieval.passage",
        "dimensions": 1024,
        "embedding_type": "float",
        "normalized": True,
        "input": [make_video_input(chunk_path)],  # {"video": "&lt;base64&gt;"}
    },
)
embedding = resp.json()["data"][0]["embedding"]  # 1024 floats<p>The <code>task</code> parameter is important here. Chunks are indexed with <code>task="retrieval.passage"</code>, and at search time the query text is embedded with <code>task="retrieval.query"</code>. The model produces asymmetric embeddings tuned for retrieval, one side for documents and one for queries. </p><p>We also pin <code>dimensions</code> to 1024 and set <code>normalized</code> to true. Normalization isn't required for cosine similarity, since cosine measures the angle between vectors and length never affects the score. What it does is make every vector unit-length, and for unit vectors the dot product equals the cosine, so the engine can skip the magnitude math and compare vectors with a plain dot product.</p><p>With Elasticsearch, you can take advantage of normalized vectors by mapping the field with <code>similarity: "dot_product"</code> instead of cosine and skip the normalization overhead at query time. The repo keeps cosine for safety, since it works even if a non-normalized vector ever slips in. </p><p>In the repo, this request lives in a small client class (backend/lib/embed_jina.py) that also retries with exponential backoff on rate limits and transient server errors. After getting the vectors back, we can ingest these into Elasticsearch.</p><h2><strong>Ingesting video embeddings into Elasticsearch</strong></h2><p>First, we define an explicit mapping. <a href="https://www.elastic.co/docs/manage-data/data-store/mapping/dynamic-mapping">Elasticsearch can dynamically infer field types</a> at index time, meaning the first document that lands can determine how a field is mapped. Here, we want to be deliberate: IDs should be keywords; in-video timestamps, like <code>start_sec</code> and <code>end_sec</code>, should be floats; and, most importantly, the embedding field needs to be configured as a 1,024-dimensional <code>dense_vector </code>with cosine similarity and an HNSW index.</p><p>Here’s the mapping for the index:</p>mappings = {
    "properties": {
        "chunk_id": {"type": "keyword"},
        "clip_id": {"type": "keyword"},
        "path": {"type": "keyword", "index": False},
        "start_sec": {"type": "float"},
        "end_sec": {"type": "float"},
        "duration": {"type": "float"},
        "strategy": {"type": "keyword"},
        "uploaded_at": {"type": "date"},
        "uploader": {"type": "keyword"},
        "tags": {"type": "keyword"},
        "transcript": {"type": "text", "analyzer": "english"},
        "embedding": {
            "type": "dense_vector",
            "dims": 1024,
            "index": True,
            "similarity": "cosine",
            "index_options": {"type": "hnsw"},
        },
    }
}<p>A few things worth noting:</p><ul><li><p><code>dims</code> is 1024 to match the model output.</p></li><li><p><code>similarity</code> is cosine because the Jina embeddings are trained for cosine distance and, as we mentioned earlier, <code>dot_product</code> would score identically on normalized vectors and slightly faster, but we keep cosine for safety.</p></li><li><p><code>index: True</code> with HNSW builds an approximate nearest neighbor graph at index time, so queries don't brute-force every vector.</p></li><li><p><code>start_sec/end_sec</code> are what let us jump the editor straight to the right moment in the clip instead of just the right file.</p></li><li><p>The remaining fields are plain metadata: <code>path</code> is stored but not searchable (<code>"index": False</code>) so the app can play the chunk file back, <code>duration</code> and <code>strategy</code> describe how the chunk was made, and <code>tags</code>/<code>transcript</code> leave room for keyword and transcript search later.</p></li></ul><p>That means one document per scene chunk, not per clip. A 10-minute documentary might become 80 documents. The point is that each one is independently findable. Note that none of the video itself goes into Elasticsearch. A document is just the vector plus a few fields of metadata, including a <code>path</code> pointing at the chunk file on disk. The footage stays where it is, and Elasticsearch acts purely as the index that tells us which file, and which seconds of it, match the query. </p><p>This app runs against a local folder, so <code>path</code> is a filesystem path. If you were building this as a hosted service, the clips would live in object storage, like Amazon S3 or Google Cloud Storage instead, and that field would hold a pointer to the object in storage, but the architecture stays the same.</p><h2><strong>Running a vector search over video chunks</strong></h2><p>At query time, the editor's text goes through the same model. <em>Warm sunset light</em> becomes a 1,024-dimension vector in the same space as the video chunks, and we ask Elasticsearch for its nearest neighbors:</p>res = es.search(
    index="broll",
    knn={
        "field": "embedding",
        "query_vector": query_vector,
        "k": 50,
        "num_candidates": 100,
    },
    size=50,
    source_excludes=["embedding"],
)
hits = [{**h["_source"], "_score": h["_score"]} for h in res["hits"]["hits"]]<p><code>k</code> is how many neighbors come back; <code>num_candidates</code> is how many each shard considers before ranking. Higher candidates means better recall at slightly higher latency. We also exclude the <code>embedding</code> field from the response because 1,000 floats per hit is a lot of payload for a value that the UI never reads. We fetch more than we display (50 for a nine-card grid) because of what happens next.</p><p><em>Find similar clips</em> works the same way, except the query vector is a stored chunk embedding instead of embedded text. No second model call is needed; the vector's already in the index.</p><h2><strong>Deduplicating results from the same clip</strong></h2><p>One problem I ran into was that there were too many chunks from the same clip filling the entire results grid. Search for <em>mountains</em> and a single 10-minute nature documentary can match with a dozen chunks, pushing every other clip out of the top results. This is technically correct but practically useless to an editor who wants options.</p><p>The fix is to keep the best-scoring chunk from each clip as the representative for each result card, and let users expand a card to see the other matching chunks from the same clip:</p>def _hits_payload(hits, exclude_id: str | None = None, k: int = 9):
    """One card per clip (best chunk first), counting matched sibling scenes."""
    out = []
    cards_by_clip = {}
    for h in hits:  # hits arrive sorted by score
        if h["chunk_id"] == exclude_id:
            continue
        clip_id = h["clip_id"]
        card = cards_by_clip.get(clip_id)
        if card is None:
            card = {**_chunk_payload(h), "more_matches": 0}
            cards_by_clip[clip_id] = card
            out.append(card)
        else:
            # A lower-ranked scene from a clip we already show.
            card["more_matches"] += 1
    return out[:k]<p>This is why we over-fetch at query time. Pull 50 hits, collapse to one card per clip, and then show nine. <code>exclude_id</code> covers the <em>find similar</em> case, where the seed chunk would otherwise come back as its own top hit, and <code>_chunk_payload</code> simply trims each hit down to the fields that the UI needs. The <code>more_matches</code> count becomes a "+4 more from this clip" badge in the UI. Expanding that badge doesn't call the embedding API again; the app caches recent query vectors and reruns the kNN search filtered to that clip with a <code>term</code> filter on <code>clip_id</code>.</p><h2><strong>Conclusion: What scene chunking costs you at scale</strong></h2><p>That's the full setup; it starts with watching a folder, cutting at scene boundaries, embedding proxies, indexing vectors, and finally, searching with plain language. The editor types what visuals they need, and the footage comes to them with timestamps and a way to quickly get ahold of the actual clip.</p><p>Every vector in this index is 1,024 float32 values, about 4 KB each. It’s not much, even for a demo. Our 70-clip folder produces a few hundred chunks, a couple of megabytes of vectors. But scene chunking multiplies fast. At roughly three seconds per scene, one hour of footage is already ~1,200 vectors, so a modest 1,000-hour archive is over a million vectors and several gigabytes of floats, and a serious footage library in the hundreds of millions of vectors is a terabyte-level index. Since HNSW wants those vectors in memory to search fast, it can get expensive quickly. In Part 2 of this series, we'll look at quantization versus dimensionality reduction, two very different ways to shrink the index, and what each one costs you in recall.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-video-search-jina-embeddings</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-video-search-jina-embeddings</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Jina AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3af9cdd3ea72507e/6aab69abafd9a85814122074/unnamed.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Building context in Elasticsearch: how AI Indices power smarter agents using fewer tokens]]></title>
    <description><![CDATA[Store AI agent context in an AI Index and power smarter agents using fewer tokens. Step-by-step walkthrough with ES|QL and Kibana Workflows included.]]></description>
    <content:encoded><![CDATA[<p>Agents burn tokens exploring your data before they answer anything, inspecting mappings, sampling documents, probing which index to use. Elasticsearch AI Indices let you precompute that work once and store it as a Knowledge Indicator (KI): a structured, searchable record agents retrieve directly instead of rediscovering from scratch. This walkthrough shows you how to build the full pipeline: create an AI Index, generate routing KIs with a Kibana Workflow, and wire them to any agent harness via a portable ES|QL skill. We've also provided a <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/building-context-technical-walkthrough-part-1">notebook</a> if you'd like to run it yourself end to end as you go through the examples in this blog. This is Part 1 in a blog series providing a technical walkthrough to managing your context through KIs and AI indices. </p><p>While AI indices will be included in future Stack releases, today we recommend using Serverless.</p><h2>How it works: AI Index, Kibana Workflows, and the query-ki skill</h2><p>Building context in this walkthrough has three moving parts:</p><ol><li><p>An <strong>AI Index</strong>, where KIs live. It's a regular Elasticsearch index or data stream with a specific naming convention triggering component templates to configure the right mappings automatically.</p></li><li><p><strong>Kibana Workflows</strong>, which read from your data sources, run an LLM to structure content into KIs, and write those KIs into the AI Index.</p></li><li><p>A <strong><code>query-ki</code></strong><strong> skill</strong>, a skill that queries KIs directly from the AI Index using ES|QL, and that a chat agent can call as a tool.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb84f57dc630f26b/6a7b37268bcd80ea30262453/image4.png" alt="Architecture: Kibana Workflows write KIs to an AI Index, agents read AI agent context via a query-ki skill" /><p></p><h3>Prerequisites</h3><p>This tutorial assumes you have:</p><ol><li><p>An Elasticsearch Serverless project. You can <a href="https://cloud.elastic.co/registration?onboarding_token=search&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">sign up for a trial</a> if you don't have one.</p></li><li><p>An API key to access your Elasticsearch project. </p></li></ol><h2>Create sample indices for agent routing</h2><p>First, we’ll need some sources. Sources can be data that already exists in your Elasticsearch indices, or external data accessed via connectors or ES|QL data sources. </p><p>For this blog, we’ll create some indices with example data. We’ll start with an example using three datasets: <a href="https://huggingface.co/datasets/BeIR/fiqa">BEIR/fiqa</a> (financial), <a href="https://huggingface.co/datasets/BeIR/nfcorpus">beir-nfcorpus</a> (biomedical/nutrition), and <a href="https://huggingface.co/datasets/BeIR/scifact">beir-scifact</a> (scientific fact-checking). Each index is populated with its own <code>_meta.description</code>. </p><p>Here are the mappings we define for these indices: </p>{
  "beir-fiqa": {
    "mappings": {
      "_meta": {
        "description": "FiQA: financial question answering corpus from StackExchange Finance community posts and web crawls. Covers investments, banking, taxes, and market analysis. BM25-only index."
      },
      "properties": {
        "text": {
          "type": "text",
          "meta": {
            "description": "Full document body text."
          }
        },
        "title": {
          "type": "text",
          "meta": {
            "description": "Document or article title."
          }
        }
      }
    }
  }
}


{
  "beir-nfcorpus": {
    "mappings": {
      "_meta": {
        "description": "NFCorpus: biomedical information retrieval corpus from NutritionFacts.org. Contains nutrition science and medical research documents on diet, disease, and health interventions. BM25-only index."
      },
      "properties": {
        "text": {
          "type": "text",
          "meta": {
            "description": "Full document body text."
          }
        },
        "title": {
          "type": "text",
          "meta": {
            "description": "Document or article title."
          }
        }
      }
    }
  }
}


{
  "beir-scifact": {
    "mappings": {
      "_meta": {
        "description": "SciFact: scientific fact-checking corpus of biomedical research abstracts used to verify factual claims in peer-reviewed literature. BM25-only index."
      },
      "properties": {
        "text": {
          "type": "text",
          "meta": {
            "description": "Full document body text."
          }
        },
        "title": {
          "type": "text",
          "meta": {
            "description": "Document or article title."
          }
        }
      }
    }
  }
}<p>Then, using the above convenience scripts, load a handful of documents into each index with the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk"><code>_bulk</code></a> API.</p><p>Now imagine an agent with a question and the indices we’ve just created. The agent has no idea which one is relevant at the start. Without pre-computed context, it either performs exploratory lookups (mappings, test searches) to figure out which source to use, or searches all three and hopes the merged results contain something useful. Either approach costs tokens, and if you multiply that inefficiency across every query an agent makes, it adds up.</p><h2>Create your AI Index</h2><p>Before generating any KIs, you need an index to store them. We call this an <strong>AI Index</strong>.</p><p>The naming convention is what triggers automatic configuration. Any index whose name starts with <code>ai-index-idx-</code> is a regular index; <code>ai-index-ds-</code> is a data stream. You’ll want to choose data streams for observability use cases, time series data, and when recency is important. Conversely, standard indices are a good choice for static data that will exist for a long while, where recency is not as much of a concern, and may need to occasionally be updated on demand. This naming convention is required for AI indices. </p><p>When Elasticsearch sees the <code>ai-index-</code> prefixes, it automatically applies component templates that configure the right mappings and settings.</p><p>Creating an AI Index is a single call:</p>PUT ai-index-idx-my-corpus<p>To see exactly what the component templates applied, inspect the mappings:</p>GET ai-index-idx-my-corpus/_mapping<p>The response shows the fields every AI Index gets out of the box:</p>{
  "ai-index-idx-my-corpus": {
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "attributes": {
          "type": "flattened"
        },
        "content": {
          "type": "text",
          "fields": {
            "semantic": {
              "type": "semantic_text",
              "inference_id": ".jina-embeddings-v5-text-small"
            }
          }
        },
        "description": {
          "type": "text",
          "fields": {
            "semantic": {
              "type": "semantic_text",
              "inference_id": ".jina-embeddings-v5-text-small"
            }
          }
        },
        "references": {
          "properties": {
            "uri": {
              "type": "keyword"
            }
          }
        },
        "tags": {
          "type": "keyword"
        },
        "title": {
          "type": "text",
          "fields": {
            "semantic": {
              "type": "semantic_text",
              "inference_id": ".jina-embeddings-v5-text-small"
            }
          }
        },
        "type": {
          "type": "keyword"
        }
      }
    }
  }
}<p><code>title</code>, <code>description</code>, and <code>content</code> are each a <code>text</code> field with a <code>.semantic</code> sub-field of type <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a>, supporting hybrid retrieval.</p><p>Data stream indices (<code>ai-index-ds-*</code>) additionally carry a default 90-day data retention policy. This blog uses a standard index (<code>ai-index-idx-*</code>).</p><h2>Index Metadata as a Knowledge Indicator</h2><p>The target use case for this example is how the <code>query-index-metadata-ki</code> skill can route an agent to the correct Elasticsearch index, even when index or field names are vague. This reduces mistakes from choosing the wrong index or formulating queries based on incomplete schema exploration.</p><p>Since we're creating KIs for our own indices, we can give the LLM a head start: annotate index mappings with human-written <code>_meta.description</code> content. The workflow generates better KIs with more context to work from.</p><p>To address this, we'll manually create a <a href="https://www.elastic.co/docs/reference/kibana">Kibana Workflow</a> that profiles each index and writes routing KIs into the AI Index. The workflow chains four steps:</p><p></p><p><strong>Step</strong></p><p><strong>Type</strong></p><p><strong>What it does</strong></p><p><code>get_mapping</code></p><p><code>elasticsearch.request</code></p><p>Read the mapping, including <code>_meta.description</code> and per-field descriptions.</p><p><code>sample_docs</code></p><p><code>elasticsearch.search</code></p><p>Pull a few real documents so the profile reflects actual value shapes.</p><p><code>profile_index</code></p><p><code>ai.agent</code></p><p>Generate a structured index profile as structured output.</p><p><code>sink_index_ki</code></p><p><code>elasticsearch.bulk</code></p><p>Write the profile into the AI Index as a KI.</p><p>Paste the following YAML into the <a href="https://www.elastic.co/docs/explore-analyze/workflows">Workflows</a> editor:</p>version: '1'
name: beir-index-profile-ki
description: Profile an index into an index-selection Knowledge Indicator.
enabled: true
tags:
  - context-management
  - index-selection

triggers:
  - type: manual

consts:
  indices:
    - beir-fiqa
    - beir-nfcorpus
    - beir-scifact

steps:
  - name: loop_indices
    type: foreach
    foreach: '{{ consts.indices | json }}'
    iteration-on-failure:
      continue: true
    steps:
      - name: get_mapping
        type: elasticsearch.request
        with:
          method: GET
          path: '/{{ foreach.item }}/_mapping'

      - name: sample_docs
        type: elasticsearch.search
        with:
          index: '{{ foreach.item }}'
          size: 3
          query:
            match_all: {}

      - name: profile_index
        type: ai.agent
        timeout: 120s
        with:
          message: &gt;
            You are a data steward building an INDEX PROFILE for an enterprise
            data catalog. Downstream, an AI agent uses these profiles to decide
            WHICH Elasticsearch index to query for a given user question -- this
            is an index-SELECTION aid, not a place to answer the question itself.

            You are given (a) the index name, (b) its Elasticsearch mapping
            including human-written descriptions in `_meta.description` and each
            field's `meta.description`, and (c) a few sample documents. Produce a
            faithful, decision-useful profile. Rules:
            - Ground everything in the provided mapping + samples. Never invent
              fields, values, or purpose. If unknown, use an empty string/array.
            - Optimize for routing: make it obvious what kinds of questions this
              index can authoritatively answer, and what it canNOT.
            - Prefer concrete field names and real example values from the
              samples over vague phrasing.
            - For joins, surface shared keys (e.g. *_id fields) that link this
              index to sibling indices, since cross-index questions hinge on them.

            Index name: {{ foreach.item }}

            Elasticsearch mapping (JSON):
            {{ steps.get_mapping.output | json }}

            Sample documents (JSON):
            {{ steps.sample_docs.output.hits.hits | map: '_source' | json }}
          schema:
            type: object
            properties:
              display_name:
                type: string
                description: A concise human-readable name for what this index represents (&lt;= 8 words).
              purpose:
                type: string
                description: 2-4 sentences describing what this index stores and its role. PRIMARY semantic surface for matching a question to this index.
              answers_questions:
                type: array
                items:
                  type: string
                description: 3-7 representative natural-language questions this index can authoritatively answer.
              does_not_contain:
                type: array
                items:
                  type: string
                description: 1-4 things a searcher might wrongly expect here but that live elsewhere, to prevent mis-routing.
              key_fields:
                type: array
                items:
                  type: string
                description: 3-10 of the most query-relevant fields as "field_name - what it is".
              when_to_use:
                type: string
                description: A single crisp routing heuristic - when should an agent pick THIS index? (&lt;= 30 words).
              example_esql:
                type: string
                description: One realistic, runnable ES|QL query against this index answering one of answers_questions.
            required:
              - display_name
              - purpose
              - answers_questions
              - key_fields
              - when_to_use

      - name: sink_index_ki
        type: elasticsearch.request
        with:
          method: PUT
          path: '/ai-index-idx-my-corpus/_doc/{{ foreach.item | url_encode }}'
          body:
            '@timestamp': '{{ "now" | date: "%Y-%m-%dT%H:%M:%S.%LZ" }}'
            type: index_metadata_entry
            title: '{{ steps.profile_index.output.structured_output.display_name | default: foreach.item }}'
            tags:
              - index-profile
              - '{{ foreach.item }}'
            attributes:
              display_name: '{{ steps.profile_index.output.structured_output.display_name }}'
              purpose: '{{ steps.profile_index.output.structured_output.purpose }}'
              when_to_use: '{{ steps.profile_index.output.structured_output.when_to_use }}'
              answers_questions: '{{ steps.profile_index.output.structured_output.answers_questions | json }}'
              does_not_contain: '{{ steps.profile_index.output.structured_output.does_not_contain | json }}'
              key_fields: '{{ steps.profile_index.output.structured_output.key_fields | json }}'
              example_esql: '{{ steps.profile_index.output.structured_output.example_esql }}'
              source_index: '{{ foreach.item }}'
            content: &gt;
              === SOURCE / PROVENANCE ===
              This is an INDEX PROFILE for routing/index-selection.
              Backing Elasticsearch index: {{ foreach.item }}
              Inspect it directly with ES|QL:
              FROM {{ foreach.item }} | LIMIT 10
              === WHAT THIS INDEX IS ===
              {{ steps.profile_index.output.structured_output.purpose }}
              Questions this index can answer: {{ steps.profile_index.output.structured_output.answers_questions | join: " | " }}
              When to use this index: {{ steps.profile_index.output.structured_output.when_to_use }}
              Example query:
              {{ steps.profile_index.output.structured_output.example_esql }}
            description: &gt;
              Index profile: {{ steps.profile_index.output.structured_output.display_name }}.
              Does NOT contain: {{ steps.profile_index.output.structured_output.does_not_contain | join: "; " }}.
              Key fields: {{ steps.profile_index.output.structured_output.key_fields | join: "; " }}.<p>Let's walk through what this workflow does. We loop over three specified indices with a <code>foreach</code> loop. For each:</p><ol><li><p><code>get_mapping</code> fetches the Elasticsearch index mappings, including any <code>_meta.description</code> annotations we added earlier.</p></li><li><p><code>sample_docs</code> pulls 3 real documents. Concrete examples give the LLM much better signal than schema alone.</p></li><li><p><code>profile_index</code> calls <code>ai.agent</code> with the index name, mappings, and sample documents. The LLM returns structured output describing the index's purpose, key fields, and an example ES|QL query showing how to use it.</p></li><li><p><code>sink_index_ki</code> writes the result into the AI Index as a KI of type <code>index_metadata_entry</code>, keyed on the index name so re-runs are idempotent.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfe75c3b235a08332/6a7b387e28883919bf07de96/image1.png" alt="Kibana Workflow generating Knowledge Indicators: get_mapping, sample_docs, profile_index, and sink to AI Index" /><p>A few things to point out: </p><ul><li><p>This workflow hard-codes a specific set of indices. In practice, you could derive the list from an index pattern or a dynamic source. </p></li><li><p>The <code>foreach</code> loop also runs iterations sequentially, which is fine for this guide but slow in production because each iteration involves an LLM call. For scale, use <a href="https://www.elastic.co/docs/explore-analyze/workflows/steps/composition">workflow.executeAsync</a> or native parallel support. The <a href="https://www.elastic.co/docs/explore-analyze/workflows/reference/cheat-sheet">cheat sheet</a> has tips on both.</p></li><li><p>In the <code>profile_index</code> step, the agent prompt is the special sauce. This is what shapes the accuracy and usefulness of the KIs. </p></li><li><p>Using <a href="https://www.elastic.co/docs/explore-analyze/workflows/steps/ai-steps#ai-prompt"><code>ai.prompt</code></a> can improve workflow efficiency (and cost) if you don’t need to load other tools. </p></li><li><p>Cost can be controlled in multiple ways. Richer prompts and structured output often result in higher token utilization, and of course the model you choose significantly impacts total costs. The <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service</a> (EIS) can be a great playground to test different models against the <code>profile_index</code>’s <code>ai.agent</code> step to compare how different models stack up against each other when generating KIs. </p></li></ul><h3>Query your AI Index to verify Knowledge Indicators</h3><p>Once the beir-index-profile-ki workflow runs, query the AI Index directly in the Discover tab using the following ES|QL query to confirm what got written:</p><p></p><p>This will result in the following output: </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a497f27ccbb343c/6a7b38b81f7b5adf6678fdf1/image2.png" alt="ES|QL query results from an AI Index showing three index metadata Knowledge Indicators in Kibana Discover" /><h3>Build a portable skill to retrieve AI agent context</h3><p>Retrieval is a critical component in an AI index. A KI is a document in the AI Index, and finding one is a single ES|QL query. We package that query as a small, portable skill so any agent can call it, regardless of the harness it runs in.</p><p>We write the skill as a SKILL.md: a YAML header with a name and description, followed by markdown instructions. This is the same Agent Skills format that many harnesses, including Claude Code, LangChain's Deep Agents, and others, load directly. </p><p>The harness reads the header content up front, and only pulls in the full instructions when a question matches the description. The one thing the skill asks of the harness is a way to run ES|QL against Elasticsearch.</p><p>Here is a sample <code>query-index-metadata-ki</code> skill: </p>---
name: query-index-metadata-ki
description: &gt;-
  Retrieve Knowledge Indicators (pre-computed context) from the Elasticsearch AI
  Index before answering. Use it to find which index to search (routing profiles).
  Trigger on any question that depends on choosing a data source.
allowed-tools: esql_query
---

# Retrieving Knowledge Indicators

Knowledge Indicators (KIs) live in Elasticsearch indices named `ai-index-*`.
Retrieve them by calling the `esql_query` tool with the query below. Substitute
the user's question for `&lt;query&gt;`, and `index_metadata_entry` as the `&lt;ki_type&gt;` for routing profiles.

```esql
FROM ai-index-idx-* METADATA _id, _index, _score
| WHERE type == "&lt;ki_type&gt;"
| FORK
    (WHERE MATCH(content, "&lt;query&gt;") OR MATCH(description, "&lt;query&gt;")
     | SORT _score DESC | LIMIT 20)
    (WHERE MATCH(content.semantic, "&lt;query&gt;") OR MATCH(description.semantic, "&lt;query&gt;")
     | SORT _score DESC | LIMIT 20)
| FUSE
| SORT _score DESC
| KEEP title, content, description, tags
| LIMIT 5
```

Ground your answer in what the query returns, and cite the KI titles you used. If
nothing relevant comes back, say so rather than guessing.<p>Let’s break down what the skill is doing: </p><ul><li><p>We’re defining <code>index-metadata-entry</code> as a KI type/use case.</p></li><li><p>We’re performing a hybrid ES|QL search on our AI indices, filtering by the appropriate <code>type</code> using RRF as the default method to fuse results.</p></li><li><p>The KI results will directly ground the agent’s answer when determining what indices are relevant to the query.</p></li></ul><p>Because the skill is just instructions plus a query, it travels wherever your agent does. You can point the same file at a Kibana Workflow agent, Claude Code, LangChain Deep Agents, or any other harness without changing a line of it.</p><h3>Connect your AI Index to an agent harness </h3><p>We want to demonstrate how you can use AI indices to query your data with any harness. For these examples, we’ll use LangChain Deep Agents and an OpenAI-compatible key, but any other agent harness can be easily substituted in, including Elastic Agent Builder.</p><p>First, let’s create a baseline to see how an agent will perform without using KIs:</p># Example question: Is there scientific evidence that vitamin D supplementation prevents cancer?
import os
import sys
import time
from elasticsearch import Elasticsearch
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

if len(sys.argv) &lt; 2:
    sys.exit(f'Usage: python {sys.argv[0]} "your question"')

es = Elasticsearch(os.environ["ES_URL"], api_key=os.environ["ES_API_KEY"])


@tool
def esql_query(query: str) -&gt; list[dict] | str:
    """Execute an ES|QL query against Elasticsearch and return the matching rows.

    Args:
        query: A complete ES|QL query string, e.g. 'FROM beir-fiqa | LIMIT 5'.
               Full-text search syntax: WHERE MATCH(field, "value") — not field MATCH "value".
    """
    try:
        resp = es.esql.query(query=query, format="json")
        cols = [c["name"] for c in resp["columns"]]
        return [dict(zip(cols, row)) for row in resp["values"]]
    except Exception as e:
        return f"ES|QL error: {e}"


@tool
def get_mapping(index: str) -&gt; dict:
    """Return the field mapping for an Elasticsearch index or pattern."""
    return es.indices.get_mapping(index=index).body


baseline_agent = create_deep_agent(
    model=ChatOpenAI(  # any OpenAI-compatible endpoint; configure via LLM_* env vars
        base_url=os.environ.get("LLM_BASE_URL", "https://openrouter.ai/api/v1"),
        model=os.environ.get("LLM_MODEL", "anthropic/claude-sonnet-4.5"),
        api_key=os.environ["LLM_API_KEY"],
    ),
    tools=[esql_query, get_mapping],
    system_prompt=(
        "You are a research assistant with access to three Elasticsearch indices: "
        "beir-fiqa, beir-nfcorpus, and beir-scifact. "
        "You do NOT know which index is relevant for a given question. "
        "Use get_mapping to inspect an index's description and fields, "
        "then query the most relevant one with esql_query. "
        "Ground your answer strictly in what the queries return."
    ),
)

start = time.perf_counter()
result = baseline_agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": sys.argv[1],
            }
        ]
    }
)
latency = time.perf_counter() - start

print("\n--- Tool calls ---")
for m in result["messages"]:
    if isinstance(m, AIMessage) and m.tool_calls:
        for tc in m.tool_calls:
            print(f"  [{tc['name']}] {str(tc['args'])[:120]}")
total = sum(
    len(m.tool_calls)
    for m in result["messages"]
    if isinstance(m, AIMessage) and m.tool_calls
)
print(f"Total: {total}\n")

print("--- Usage ---")
input_tokens = sum(
    (m.usage_metadata or {}).get("input_tokens", 0)
    for m in result["messages"]
    if isinstance(m, AIMessage) and m.usage_metadata
)
output_tokens = sum(
    (m.usage_metadata or {}).get("output_tokens", 0)
    for m in result["messages"]
    if isinstance(m, AIMessage) and m.usage_metadata
)
print(f"Tokens: {input_tokens + output_tokens} (input {input_tokens}, output {output_tokens})")
print(f"Latency: {latency:.2f}s\n")

print("--- Answer ---")
print(result["messages"][-1].content)<p>Here’s a modified example that could run the same agent, but now with the ability to search AI indices to return KIs: </p># Example question: Is there scientific evidence that vitamin D supplementation prevents cancer?
import os
import sys
import time
from elasticsearch import Elasticsearch
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent
from deepagents.backends.filesystem import FilesystemBackend

if len(sys.argv) &lt; 2:
    sys.exit(f'Usage: python {sys.argv[0]} "your question"')

es = Elasticsearch(os.environ["ES_URL"], api_key=os.environ["ES_API_KEY"])


@tool
def esql_query(query: str) -&gt; list[dict] | str:
    """Execute an ES|QL query against Elasticsearch and return the matching rows.

    Args:
        query: A complete ES|QL query string, e.g. 'FROM beir-fiqa | LIMIT 5'.
               Full-text search syntax: WHERE MATCH(field, "value") — not field MATCH "value".
    """
    try:
        resp = es.esql.query(query=query, format="json")
        cols = [c["name"] for c in resp["columns"]]
        return [dict(zip(cols, row)) for row in resp["values"]]
    except Exception as e:
        return f"ES|QL error: {e}"


backend = FilesystemBackend(root_dir=".", virtual_mode=False)

agent = create_deep_agent(
    model=ChatOpenAI(  # any OpenAI-compatible endpoint; configure via LLM_* env vars
        base_url=os.environ.get("LLM_BASE_URL", "https://openrouter.ai/api/v1"),
        model=os.environ.get("LLM_MODEL", "anthropic/claude-sonnet-4.5"),
        api_key=os.environ["LLM_API_KEY"],
    ),
    tools=[esql_query],
    skills=["skills"],
    backend=backend,
    system_prompt=(
        "You are a research assistant with access to several Elasticsearch indices. "
        "You do NOT know which index is relevant for a given question. "
        "Before searching, always use the query-ki skill with type 'index_metadata_entry' "
        "to retrieve the routing profile for the right index, then query that index directly. "
        "Ground your answer strictly in what the queries return and cite the KI you used for routing."
    ),
)

start = time.perf_counter()
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": sys.argv[1],
            }
        ]
    }
)
latency = time.perf_counter() - start

print("\n--- Tool calls ---")
for m in result["messages"]:
    if isinstance(m, AIMessage) and m.tool_calls:
        for tc in m.tool_calls:
            print(f"  [{tc['name']}] {str(tc['args'])[:120]}")
total = sum(
    len(m.tool_calls)
    for m in result["messages"]
    if isinstance(m, AIMessage) and m.tool_calls
)
print(f"Total: {total}\n")

print("--- Usage ---")
input_tokens = sum(
    (m.usage_metadata or {}).get("input_tokens", 0)
    for m in result["messages"]
    if isinstance(m, AIMessage) and m.usage_metadata
)
output_tokens = sum(
    (m.usage_metadata or {}).get("output_tokens", 0)
    for m in result["messages"]
    if isinstance(m, AIMessage) and m.usage_metadata
)
print(f"Tokens: {input_tokens + output_tokens} (input {input_tokens}, output {output_tokens})")
print(f"Latency: {latency:.2f}s\n")

print("--- Answer ---")
print(result["messages"][-1].content)<p>This agent will always query the KI indices to get the answer.</p><h2>How much do Knowledge Indicators reduce agent token usage?</h2><p>Since we’re using agents, the results of these scripts are non-deterministic. However, when I ran these results against the query <code>Is there scientific evidence that vitamin D supplementation prevents cancer?</code>, both agents led to the same conclusion, but they took different paths to get there: </p><p>
</p><p>Baseline (No AI Index)</p><p>With AI Index</p><p>Total tool calls</p><p>12</p><p>8</p><p><code>read_file</code> calls</p><p>0</p><p>2</p><p><code>get_mapping</code> calls</p><p>3</p><p>0</p><p><code>esql_query</code> calls</p><p>9</p><p>6 </p><p>Total indices queried</p><p>2 (bounced between <code>beir-scifact</code> and <code>beir-nfcorpus</code>)</p><p>1 (<code>beir-nfcorpus</code>)</p><p>Tokens consumed</p><p>167,763</p><p>92,711</p><p>Latency</p><p>39.58s</p><p>36.15s</p><p>Answer</p><p>Grounded, correct</p><p>Grounded, correct</p><p>The KI answers were both grounded and correct, but an interesting datapoint is the fact that the overall tool usage and token utilization was smaller when using KIs (latency was roughly equivalent). Here’s how both paths went, side by side: </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcce26759ded49327/6a7b3983288839683507dea0/image5.png" alt="Agent comparison: 12 tool calls without KIs vs 8 with KIs, 45% fewer tokens, same grounded answer" /><h2>Run the full AI Index pipeline in Serverless</h2><p>This walkthrough offered a deep dive into the build-it-yourself version of AI indices and KIs. In production, you wouldn't hand-write these workflows; a setup agent would generate them, and a feedback loop would refine KIs from the agent's own traces. But the primitives are exactly what you just used: extract KIs with a workflow, store them in an AI Index, and retrieve them with a skill.</p><p>Managing context is key to a relevant and efficient agentic search system, and AI indices are a way to manage this context with the full power of the Elastic stack. Try it out in Serverless and let us know what you think in our <a href="https://discuss.elastic.co/top?period=monthly">Discuss forums</a> or the <code>#stack-kibana</code> channel in our <a href="https://elasticstack.slack.com/signup#/domain-signup">Community Slack</a>! </p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-index-building-context-agents</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-index-building-context-agents</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Agentic AI]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Kathleen DeRusso,Matt Nowzari ,Apostolos Matsagkas,Peter Pisljar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt82d8e9495cde2e30/6a7b37020c5aa95ac5f88c47/image3.png" length="0" type="image/png"/>
    <pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch DiskBBQ delivers 7x faster vector search than Qdrant on network-attached storage]]></title>
    <description><![CDATA[Elasticsearch DiskBBQ achieves up to 7x higher vector search throughput than Qdrant at comparable recall on network-attached storage. Explore the benchmark methodology and full results.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch DiskBBQ delivers up to 7x higher throughput than Qdrant at comparable recall, tested on network-attached persistent storage, the topology most managed-cloud deployments actually use. The gap is consistent across recall levels from 0.93 to 0.97, and it widens as recall increases. DiskBBQ keeps latency nearly flat as search breadth grows; Qdrant's latency rises sharply as <code>hnsw_ef</code> increases, driven by random reads of original vectors from disk during rescoring. If you're running vector search in Kubernetes or a managed cloud environment, this is what the tradeoff looks like.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf417e30d37bbe73a/6a46976e151035764d202f02/057e4d34719f4ca87c86f1b2a36b06d0839ad275-800x500.png" alt="Bar chart comparing throughput in queries per second between Elasticsearch 9.4.1 and Qdrant 1.18.1 at recall levels 0.93, 0.95, 0.96 and 0.97, showing Elasticsearch delivering approximately 7x higher throughput across all recall levels." /><p>Vector search is a critical foundation for large language model (LLM) applications, retrieval augmented generation (RAG), and other AI workloads. In this benchmark, Elasticsearch achieved up to 7x higher throughput than Qdrant at comparable recall on the same storage topology. Elasticsearch as a vector database offers strong vector search performance even when network-attached persistent storage remains on the query path.</p><p>The difference reflects how the two systems interact with disk. Elasticsearch DiskBBQ is designed to keep vector search efficient when persistent storage remains on the query path, using a compact quantized representation and limiting costly access to full precision vectors during search. In this setup, Qdrant relies on a graph-based search path with rescoring against original vectors stored on disk. On network-attached persistent storage, that random access cost becomes much more significant, which is why the performance gap widens as recall increases. This benchmark therefore focuses specifically on network-attached persistent storage, a common deployment model in managed cloud and Kubernetes environments.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte01d96b1db40d0e9/6a46977131bdbb595d8b33e7/3a39012cb09a841468a5295e226955769c4d18f0-800x500.png" alt="Line chart showing recall versus average latency in milliseconds for Elasticsearch 9.4.1 and Qdrant 1.18.1. Elasticsearch maintains low latency between 120 and 150ms across all recall levels, while Qdrant latency rises steeply from 315ms to 900ms as recall increases." /><p>The key pattern in the latency curve is not only the size of the gap but also its shape. Elasticsearch latency remains comparatively flat as recall increases, suggesting that higher recall doesn’t require a dramatic increase in expensive storage activity. Qdrant’s latency rises sharply as <code>hnsw_ef</code> increases, which is consistent with broader candidate exploration leading to more rescoring work against original vectors on disk.</p><h2>Full results table</h2><p>The table below shows the full parameter sweep for both Elasticsearch and Qdrant. Because the two engines expose different tuning controls for vector search, the results are reported using each engine’s full parameter key rather than attempting a one-to-one mapping between settings.</p><p>A few notes on the metrics:</p><ul><li><p>ParamKey: The complete parameter setting used for a given run.</p></li><li><p>Recall: Recall@100 against a ground-truth top-100 result set for the benchmark queries. Values range from 0 to 1, and higher is better.</p></li><li><p>Latency_Avg: The average end-to-end latency per query measured from the benchmarking client across the full run, in milliseconds. Lower is better.</p></li><li><p>Latency_P95: The 95th percentile query latency, in milliseconds, showing the upper range of typical slow queries. Lower is better.</p></li><li><p>Throughput: The average number of queries processed per second across the full run. Higher is better.</p></li></ul><p>Engine</p><p>ParamKey</p><p>Recall</p><p>Latency_Avg</p><p>Latency_P95</p><p>Throughput</p><p>qdrant</p><p>hnsw_ef=50, oversampling=1, size=100</p><p>0.8694</p><p>315.7849</p><p>503.4754</p><p>12.629</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=1</p><p>0.8789</p><p>135.0802</p><p>218.494</p><p>29.343</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=1.5</p><p>0.9123</p><p>127.8286</p><p>195.2318</p><p>31.1107</p><p>qdrant</p><p>hnsw_ef=100, oversampling=1, size=100</p><p>0.9287</p><p>895.9933</p><p>1213.0448</p><p>4.4493</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=2</p><p>0.9317</p><p>124.846</p><p>183.6314</p><p>31.8225</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=2.5</p><p>0.9444</p><p>123.517</p><p>180.4831</p><p>32.1883</p><p>qdrant</p><p>hnsw_ef=150, oversampling=1, size=100</p><p>0.9518</p><p>884.7236</p><p>1195.2603</p><p>4.5066</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=3</p><p>0.9532</p><p>123.276</p><p>183.8379</p><p>32.2364</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=3.5</p><p>0.9599</p><p>122.5559</p><p>184.2858</p><p>32.4469</p><p>qdrant</p><p>hnsw_ef=200, oversampling=1, size=100</p><p>0.964</p><p>883.2114</p><p>1188.6597</p><p>4.5143</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=4</p><p>0.965</p><p>122.7946</p><p>184.9058</p><p>32.3635</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=4.5</p><p>0.9689</p><p>122.7062</p><p>182.9559</p><p>32.3976</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=5</p><p>0.9722</p><p>122.5761</p><p>187.3536</p><p>32.4221</p><p>qdrant</p><p>hnsw_ef=256, oversampling=1, size=100</p><p>0.9722</p><p>881.9643</p><p>1185.4948</p><p>4.5192</p><p>elasticsearch</p><p>k=100, oversample=1, size=100, visit_percentage=5.5</p><p>0.9747</p><p>122.5609</p><p>184.5128</p><p>32.4176</p><p>Each row pairs the closest measured Elasticsearch and Qdrant configurations in the sweep by achieved recall.</p><h3>Matched comparisons at similar recall</h3><p>To make the comparison fair, speedup is calculated only between configurations that achieve similar recall. This avoids comparing settings that trade off accuracy very differently.</p><p>Recall band</p><p>Elasticsearch recall</p><p>Elasticsearch Latency_Avg</p><p>Elasticsearch throughput</p><p>Qdrant recall</p><p>Qdrant Latency_Avg</p><p>Qdrant throughput</p><p>Throughput speedup</p><p>~0.87</p><p>0.8789</p><p>135.0802</p><p>29.343</p><p>0.8694</p><p>315.7849</p><p>12.629</p><p>2.32x</p><p>~0.93</p><p>0.9317</p><p>124.846</p><p>31.8225</p><p>0.9287</p><p>895.9933</p><p>4.4493</p><p>7.15x</p><p>~0.95</p><p>0.9532</p><p>123.276</p><p>32.2364</p><p>0.9518</p><p>884.7236</p><p>4.5066</p><p>7.15x</p><p>~0.96</p><p>0.9599</p><p>122.5559</p><p>32.4469</p><p>0.964</p><p>883.2114</p><p>4.5143</p><p>7.19x</p><p>~0.97</p><p>0.9722</p><p>122.5761</p><p>32.4221</p><p>0.9722</p><p>881.9643</p><p>4.5192</p><p>7.17x</p><p>This matched-recall view is the clearest expression of the underlying systems difference. At similar recall levels, Elasticsearch delivers both lower latency and much higher throughput, and the gap widens as recall rises. The recall-throughput pattern matters because higher recall in this benchmark requires broader search. DiskBBQ absorbs that increase with relatively little additional cost, while Qdrant’s graph plus rescoring path becomes much more constrained by random access to original vectors on persistent storage.</p><h2>Benchmark methodology</h2><p><a href="https://github.com/elastic/jingra">Jingra</a>, the benchmarking tool used for these tests, was originally written in Python and has since been rebuilt as a Java project. For these tests, Jingra runs in a Kubernetes pod within the same cluster as the engine being measured. This helps reduce external network variability and keeps the test environment consistent across runs. For each run, Jingra executed the query set at a fixed client concurrency, recorded end-to-end client-side latency and throughput, and computed recall against a precomputed ground-truth top-100 set.</p><p>This benchmark was intentionally run on network-attached persistent storage rather than local NVMe. For the published results, the storage used the baseline performance allocation for a 200 GiB GCP Hyperdisk Balanced volume, with no explicit IOPS or throughput provisioning. We chose this topology on purpose because it’s a relevant cloud deployment model and because it keeps storage efficiency materially on the query path.</p><p>Qdrant often performs better on local NVMe, so deployments using local NVMe should expect different results than the ones shown here. This benchmark specifically tests network-attached persistent storage because that’s a common managed-cloud deployment model and because it makes storage-path efficiency visible in end-to-end query performance.</p><p>Because Elasticsearch and Qdrant expose different query parameters for controlling vector search behavior, there’s no clean one-to-one mapping between their tuning settings. Instead of comparing equivalent parameter values directly, we use recall as the primary point of comparison. The matched comparisons below therefore pair configurations that achieve similar recall, rather than configurations with superficially similar parameter values.</p><p>Recall cannot be known in advance for a given parameter setting, so we sweep across a range of search configurations for each engine and then compare results at similar recall levels. In the published results, oversampling was fixed at 1 for both engines so that recall was primarily tuned via search breadth rather than rescoring expansion.</p><h3>How does Elasticsearch configure vector search?</h3>{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector": "{{query_vector}}",
      "k": "{{k}}",
      "visit_percentage": "{{visit_percentage}}",
      "rescore_vector": {
        "oversample": "{{oversample}}"
      }
    }
  },
  "size": "{{size}}",
  "_source": false
}<ul><li><p><code>query_vector</code>: The input vector used for similarity search. Elasticsearch compares this vector against the stored vectors in the field.</p></li><li><p><code>k</code>: The number of nearest neighbors to retrieve.</p></li><li><p><code>visit_percentage</code>: Controls how much of the DiskBBQ, Elasticsearch’s disk optimized vector index, is explored during the approximate search phase. Higher values usually improve recall but increase latency.</p></li><li><p><code>oversample</code>: Controls how many extra candidate vectors are passed into rescoring relative to k. Higher values can improve recall, but usually at additional cost.</p></li><li><p><code>size</code>: The number of hits returned in the final response.</p></li><li><p><code>_source: false</code>: Disables returning the document _source field, reducing response size and avoiding extra retrieval overhead during benchmarking.</p></li></ul><p>Example</p>{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector": [ -0.0095683, 0.0072035934, ... ],
      "k": "100",
      "visit_percentage": "3",
      "rescore_vector": {
        "oversample": "1"
      }
    }
  },
  "size": "100",
  "_source": false
}<p>Params</p>  recall@100:
    - { size: 100, k: 100, visit_percentage: 1, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 1.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 2, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 2.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 3, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 3.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 4, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 4.5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 5, oversample: 1 }
    - { size: 100, k: 100, visit_percentage: 5.5, oversample: 1 }<p>We keep <code>k = size = 100</code> so the search request is aligned with the benchmark target: returning the top 100 results. To improve recall, we tune <code>visit_percentage</code> rather than inflating the final result count, while keeping <code>oversample = 1</code> fixed across runs.</p><h3>How does Qdrant configure vector search?</h3>{
  "vector": "{{query_vector}}",
  "limit": "{{size}}",
  "with_payload": false,
  "with_vector": false,
  "params": {
    "hnsw_ef": "{{hnsw_ef}}",
    "quantization": {
      "rescore": true,
      "oversampling": "{{oversampling}}"
    }
  }
}<ul><li><p><code>query_vector / vector</code>: The input vector used for similarity search. Qdrant compares this vector against the stored vectors in the collection.</p></li><li><p><code>size / limit</code>: The number of nearest neighbor results returned in the response.</p></li><li><p><code>with_payload: false</code>: Disables returning payload fields, reducing response size and avoiding additional retrieval overhead during benchmarking.</p></li><li><p><code>with_vector: false</code>: Disables returning stored vectors in the response, again reducing response size and keeping the benchmark focused on search performance.</p></li><li><p>hnsw_ef: Controls the number of candidates explored during HNSW search. Higher values usually improve recall but increase latency. Like visit_percentage in Elasticsearch, it affects search breadth, but the two controls are engine-specific and not directly equivalent.</p></li><li><p><code>quantization.rescore: true</code>: Enables rescoring of the candidate set using the original vectors after quantized search.</p></li><li><p><code>oversampling</code>: Controls how many extra candidates are considered during rescoring relative to the final result count. Higher values can improve recall, but usually at additional cost.</p></li></ul><p>Example</p>{
  "vector":  [ -0.0095683, 0.0072035934, ... ],
  "limit": "100",
  "with_payload": false,
  "with_vector": false,
  "params": {
    "hnsw_ef": "150",
    "quantization": {
      "rescore": true,
      "oversampling": "1"
    }
  }
}<p>Params</p>  recall@100:
    - { size: 100, hnsw_ef: 50, oversampling: 1 }
    - { size: 100, hnsw_ef: 100, oversampling: 1 }
    - { size: 100, hnsw_ef: 150, oversampling: 1 }
    - { size: 100, hnsw_ef: 200, oversampling: 1 }
    - { size: 100, hnsw_ef: 256, oversampling: 1 }<p>We keep <code>size = 100</code> so that each request is aligned with the evaluation target, in this case top 100 retrieval. Recall is then tuned by sweeping <code>hnsw_ef</code>, which controls how many candidates are explored during search. Higher <code>hnsw_ef</code> values generally improve recall but also increase latency and reduce throughput. We keep <code>oversampling = 1</code> fixed across runs so that the main tuning variable is the search breadth rather than the rescoring expansion.</p><h2>Cluster setup and DiskBBQ configuration</h2><p>We ran the benchmark on GCP using three n4-standard-8 nodes, with each pod allocated 7 vCPUs and 26 GB of RAM, and using 200 GiB GCP Hyperdisk Balanced volumes at baseline performance allocation. The corpus contains 21 million vectors, (see dataset section below for more details and download links), which account for about 60.1 GiB of raw float vector data. With 2-bit quantization, the vector payload drops to roughly 3.8 to 4.0 GB. However, the full index footprint is much larger once graph and other index structures are included. That means the workload remains meaningfully sensitive to network-attached storage performance, especially because exact vector values still need to be read from disk during rescoring.</p><p>We chose this node size intentionally to keep the benchmark in a regime where network-attached persistent storage remains on the query path rather than allowing the full working set to remain comfortably memory-resident. Each system was therefore configured using the best-performing setup we identified for this workload within the tuning scope described in this post. In Elasticsearch, this meant <code>bbq_disk</code>. In Qdrant, the original vectors were stored on disk, while the 2-bit quantized representation used for approximate search was kept in RAM with <code>always_ram: true</code>. Because the two systems expose different search strategies and tuning controls, we compare them at matched recall rather than trying to map parameters one to one.</p><p>Elasticsearch was configured to use DiskBBQ, its disk-optimized approach for approximate nearest neighbor vector search, with 2-bit quantization. DiskBBQ uses aggressive quantization to keep the searchable index compact and then rescores with the original vectors to preserve accuracy. This helps maintain strong recall while keeping disk-based search efficient.</p><p><code>bbq_disk</code> is an Elasticsearch Enterprise feature. We used it here because the goal of this benchmark was to compare the strongest disk-oriented vector search configuration available in each engine for this workload, rather than licensing tiers or default features.</p><p>We didn’t include <code>bbq_hnsw</code> in this comparison because the benchmark was specifically designed to evaluate disk-oriented vector search under a disk-sensitive workload.</p><p>This storage topology matters because Qdrant’s rescore step reads the original <code>float32</code> vectors from disk with random access on each query. On local NVMe, those reads are much faster, and Qdrant correspondingly performs better. On network-attached persistent storage, the results are consistent with that random-read rescore path becoming a more important bottleneck. Qdrant latency rises sharply as <code>hnsw_ef</code> increases, while Elasticsearch remains comparatively flat across the same recall progression.</p><p>We chose 2-bit quantization because Qdrant couldn’t reach the target recall range with 1-bit binary quantization. Since the two systems expose different disk-oriented vector search strategies, we tuned each one to the strongest configuration available within its current feature set.</p><p>Both systems were configured with three shards distributed across the three nodes and with two total copies of each shard in the cluster. In Elasticsearch, <code>number_of_shards: 3</code> and <code>number_of_replicas: 1</code> means one primary plus one replica, for two total copies. In Qdrant, <code>shard_number: 3</code> and <code>replication_factor: 2</code> also means two total copies, since Qdrant’s replication factor refers to the total number of copies rather than the number of additional replicas. So although the field names differ, the effective replication level was the same in both systems.</p><p>Setting</p><p>Elasticsearch</p><p>Qdrant</p><p>Shards</p><p>number_of_shards: 3</p><p>shard_number: 3</p><p>Copies</p><p>number_of_replicas: 1 (1 primary + 1 replica = 2 total)</p><p>replication_factor: 2 (2 total)</p><p>Elasticsearch mapping</p>{
  "mappings": {
    "properties": {
      "embedding": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 768,
        "index": true,
        "similarity": "cosine",
        "index_options": {
          "type": "bbq_disk",
          "bits": 2
        }
      }
    }
  },
  "settings": {
    "number_of_shards": "3",
    "number_of_replicas": "1"
  }
}<p>Qdrant mapping</p>{
  "vectors": {
    "size": 768,
    "distance": "Cosine",
    "on_disk": true
  },
  "shard_number": 3,
  "replication_factor": 2,
  "hnsw_config": {
    "m": 16,
    "ef_construct": 256
  },
  "quantization_config": {
    "turbo": {
      "bits": "bits2",
      "always_ram": true
    }
  }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ff77646baa598e4/6a4697742d406b1032ba2bd9/8d6f3e8d3e2c620187d8d2841cc09b813cd77e57-881x401.png" alt="Architecture diagram showing the benchmark cluster setup on GCP. Two Kubernetes clusters side by side: the left contains three Elasticsearch nodes behind an ES Service, with Jingra as the benchmarking client. The right mirrors this with three Qdrant nodes behind a QD Service, also driven by Jingra." /><h2>Dataset</h2><p>For this benchmark, we used the <a href="https://huggingface.co/datasets/kenhktsui/wiki_dpr_e5"><code>kenhktsui/wiki_dpr_e5</code></a> dataset from Hugging Face, a large-scale Wikipedia passage retrieval dataset designed for dense vector search. The corpus contains 21 million embedded passages, each represented as a 768-dimensional float32 vector, or 3,072 bytes per vector. That corresponds to about 60.1 GiB of raw vector data, before accounting for additional fields and file format overhead in the source dataset. The downloadable <code>data.parquet</code> file is larger at 85.2 GB for that reason.</p><p>We chose this dataset because it reflects a common production pattern in LLM, RAG, and retrieval systems: searching a large corpus of semantically embedded text while balancing recall, latency, and throughput. At 21 million vectors and roughly 60 GiB of raw vector data, it’s large enough to make disk-based vector search a relevant operating mode to evaluate.</p><p>Both engines used 2-bit quantization, reducing each vector from 3,072 bytes to 192 bytes, a 16x reduction that brings the quantized vector corpus to around 4 GB. In Qdrant, that quantized representation was kept in RAM for search, while the original vectors remained on disk. Even so, the workload remained meaningfully sensitive to network-attached storage performance because rescoring still required access to the original vectors on disk.</p><p>You can download the dataset and query files from the links below:</p><ul><li><p><a href="https://storage.googleapis.com/elastic-benchmark-datasets/wiki-dpr-e5-768/data.parquet">data.parquet</a></p></li><li><p><a href="https://storage.googleapis.com/elastic-benchmark-datasets/wiki-dpr-e5-768/queries.parquet">queries.parquet</a></p></li></ul><h2>Jingra and recreating the benchmark</h2><p>For this benchmark, we used <a href="https://github.com/elastic/jingra/releases/tag/v0.2.3">Jingra v0.2.3</a> with the configurations described <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.4-vs-qd-1.18-vector-search">es-9.4-vs-qd-1.18-vector-search</a>. Jingra handled data loading, query execution, parameter sweeps, and metric collection for both Elasticsearch and Qdrant, making the benchmark repeatable and easier to compare.</p><p>To reproduce the experiment, you need the published dataset, query set, engine configurations, and comparable cluster hardware. With those in place, Jingra can rerun the benchmark and generate similar recall, latency, and throughput measurements shown in this post.</p><h2>Conclusion</h2><p>At comparable recall levels, Elasticsearch DiskBBQ consistently delivered faster vector search than Qdrant in this benchmark, with higher throughput and lower latency across the recall range we tested. These results are especially notable because the comparison was made on network-attached persistent storage, where efficient storage-aware vector search becomes critical. Elasticsearch as a vector database allows organizations to achieve high recall with lower latency and higher throughput on slower persistent storage.</p><p>Just as importantly, this benchmark highlights the value of comparing engines at matched recall rather than by nominal parameter settings. Elasticsearch and Qdrant expose different controls, so the fairest comparison isn’t parameter to parameter but outcome to outcome. Across the recall range tested here, Elasticsearch maintained a clear advantage in both latency and throughput.</p><p>If you want to reproduce the experiment yourself, we’re publishing the dataset and query set used in this benchmark so others can validate the results and build on them.</p><p>Further reading:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">Introducing a new vector storage format: DiskBBQ</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-bbq-osq-vs-turbo">Elasticsearch BBQ vs TurboQuant</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-benchmark-elasticsearch-vs-qdrant</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-benchmark-elasticsearch-vs-qdrant</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[ML Research]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf58ffc7bd7f3c826/6a469777945073eeed30d267/0fa30e54796aeb49baaa760590fa6dd3ee863c2d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Your FAQ bot doesn't need a PhD: LLM query routing with Elastic Workflows]]></title>
    <description><![CDATA[Route LLM queries by complexity using Elasticsearch search metadata: Mistral Small for FAQ questions, Claude Sonnet for multi-source synthesis.]]></description>
    <content:encoded><![CDATA[<p>Sending every customer support query to a large model means your simple FAQ answers are as slow and as expensive as your most complex ones. This post shows how to build a two-model routing system in <a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflows</a>: <a href="https://mistral.ai/news/mistral-small-4/">Mistral Small</a> handles straightforward questions directly from a single FAQ article; <a href="https://www.anthropic.com/claude/sonnet">Claude Sonnet</a> synthesizes answers across multiple knowledge base sources when the query needs it. The routing decision is made from search metadata alone, keeping classification cheap and fast on every query.</p><h2>Prerequisites</h2><ul><li><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a> deployment running Elasticsearch 9.3+ or <a href="https://cloud.elastic.co/registration">start a free trial</a></p></li><li><p><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started#workflows-prerequisites">Workflows enabled</a> (Advanced Settings)</p></li><li><p>Python 3.9+</p></li><li><p>A <a href="https://console.mistral.ai/">Mistral API key</a></p></li></ul><h2>How LLM query routing works in this system</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4665bd674708a1cb/6a3e41fab0216c28c26945d6/71973e55db974ef7eca637539c3c49859759d2a3-1280x720.png" alt="Flowchart diagram showing how customer queries are processed. It begins with “Customer Query” and then moves to “Search Knowledge Base (Elasticsearch)” and “Classify Query with ES context.” From there, two branches appear: one labeled “simple FAQ match” leading to “Direct answer from FAQ snippet (Mistral Small) Fast &amp; cheap,” and another labeled “complex (needs synthesis)” leading to “Synthesize from multiple articles (Claude Sonnet).” Both paths converge at “Final Response" /><p>We're going to build a two-stage system: a router that decides how to answer, and an answering model that produces the response.</p><p>The router looks at the query and the metadata of the top search hits, like scores, categories, and complexity labels. From that, it picks one of two strategies: Answer directly from the top FAQ article, or synthesize across multiple articles with citations. That decision can be made from structured signals alone, so a small, fast model handles it.</p><p>The answering step varies. A single-article answer is bounded work that a small model does well and returns quickly. A multisource synthesis with citations benefits from a more capable model, and the extra time is worth it. Matching each query to the model that fits keeps simple answers fast and complex answers good.</p><h3>Why use a small model for routing instead of the large model?</h3><p>Because the router runs on every query, including the simple ones. A slow router makes every answer slow, even the ones a small model could have produced in a fraction of the time.</p><p>The key design choice is that the routing step only sees metadata, not full documents. A query like "my OTG isn't heating evenly" only needs to know that the top hits are in the "Product Troubleshooting - Appliances" category with <code>issue_complexity: medium</code>, not the full conversation transcripts. This keeps the classification prompt tiny (a few hundred tokens) and cheap. The full article content is only loaded in the response step once.</p><h2>Set up AI connectors</h2><p>We use two AI connectors for the workflow:</p><p>Connector</p><p>Model</p><p>Type</p><p>Role</p><p>Mistral Small</p><p>mistral-small-latest</p><p>Custom (OpenAI-compatible)</p><p>Classify query complexity from metadata, answer simple FAQ-style questions</p><p>Anthropic Claude Sonnet 4.6</p><p>Claude Sonnet</p><p>Elastic Managed LLM</p><p>Synthesize complex answers from multiple articles, with citations</p><p>Both connectors are billed per million tokens, with the smaller model costing significantly less. Routing simple queries to it saves money on top of the latency win. To learn more about Elastic Managed LLM, see this <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">documentation</a>.</p><p>The Claude Sonnet connector is already available as an Elastic Managed large language model (LLM). We only need to create a custom connector for Mistral using the <code>.gen-ai</code> connector type, which supports any <a href="https://developers.openai.com/api/reference/overview">OpenAI-compatible API</a>. You can also <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/ai-connector#set-up-an-ai-connector">create it through the Kibana UI</a>.</p><p>All the setup code in this article is available in the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/routing-queries-right-model-elasticsearch/notebook.ipynb">companion notebook</a>. You can run each section there as you follow along.</p>SMALL_LLM_CONNECTOR = "Mistral Small"

headers = {
    "Authorization": f"ApiKey {ELASTICSEARCH_API_KEY}",
    "kbn-xsrf": "true",
    "Content-Type": "application/json",
}

mistral_connector_payload = {
    "connector_type_id": ".gen-ai",
    "name": SMALL_LLM_CONNECTOR,
    "config": {
        "apiProvider": "Other",
        "apiUrl": "https://api.mistral.ai/v1/chat/completions",
        "defaultModel": "mistral-small-latest",
    },
    "secrets": {
        "apiKey": MISTRAL_API_KEY,
    },
}

response = requests.post(
    f"{KIBANA_URL}/api/actions/connector",
    headers=headers,
    json=mistral_connector_payload,
)
result = response.json()
MISTRAL_CONNECTOR_ID = result.get("id")<p>The connector ID is auto-generated by Kibana. We let the platform handle this instead of trying to set it manually.</p><p>Once created, the connector appears in the Connectors UI:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt690e9245c0f19580/6a3e41fdb0216c2b556945dc/77179f792bb585dd060deac4ac9bc1c87ec1027a-1999x872.png" alt="Screenshot of a Connectors dashboard showing third‑party integrations for alerting data. The table lists AI connectors including Google Gemini 2.5 Pro, Google Gemini 3.0 Flash, Google Gemini 3.1 Pro (Preview), Mistral Small, and OpenAI GPT‑4.1. Each row displays type, compatibility, and authentication method. The focus is on the Mistral Small row." /><h2>Load and index the dataset</h2><p>We use the <a href="https://huggingface.co/datasets/rjac/e-commerce-customer-support-qa">e-commerce-customer-support-qa</a> dataset from Hugging Face. It contains 1,000 real customer support interactions from an ecommerce platform (BrownBox) with customer questions, agent solutions, issue categories, complexity levels, and customer sentiment.</p><p>The index mapping uses <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html"><code>semantic_text</code></a> with the <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><code>.jina-embeddings-v5-text-small</code></a> model from <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service</a>. This field handles <a href="https://www.elastic.co/docs/solutions/search/semantic-search">semantic search</a> end-to-end: <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">embedding generation</a>, <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">chunking</a>, and querying. We use <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> to aggregate the conversation and QA summary into a single searchable field:</p>es_client.indices.create(
    index="support-knowledge-base",
    mappings={
        "properties": {
            "conversation": {
                "type": "text",
                "copy_to": "semantic_content",
            },
            "qa": {
                "type": "text",
                "copy_to": "semantic_content",
            },
            "issue_area": {"type": "keyword"},
            "issue_category": {"type": "keyword"},
            "issue_complexity": {"type": "keyword"},
            "product_category": {"type": "keyword"},
            "semantic_content": {
                "type": "semantic_text",
                "inference_id": ".jina-embeddings-v5-text-small",
            },
        }
    },
)<h2>Defining the query routing workflow in Elastic Workflows YAML</h2><p>The routing workflow has four steps: semantic search, metadata-only classification, conditional branching, and a model-appropriate response step.</p><p>We use <a href="https://www.elastic.co/search-labs/blog/elastic-workflows-automation">Elastic Workflows</a> to encapsulate this routing logic. Workflows let us:</p><ol><li><p><strong>Expose the triaging as a tool</strong> in <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">Elastic Agent Builder</a>, so a conversational agent can call it.</p></li><li><p><strong>Trigger it directly</strong> via manual execution, schedules, or alerts.</p></li></ol><p>This flexibility means the same logic serves both programmatic and conversational interfaces without duplicating code.</p><p>Workflows are defined in YAML and configured directly in the Workflow UI (<strong>Elasticsearch &gt; Workflows &gt; Create a New Workflow</strong>). Each step can query Elasticsearch, call Kibana APIs, or prompt an LLM.</p><p>Here’s the complete workflow definition:</p>name: support_query_router
description: &gt;
  Routes customer queries to the appropriate LLM based on complexity.
  Searches the KB, classifies using only result metadata (cheap),
  then routes to a small or large model depending on complexity.
enabled: true

inputs:
  - name: query
    type: string
    description: The customer support query
    required: true

consts:
  indexName: support-knowledge-base

triggers:
  - type: manual

steps:
  # Step 1: Search the knowledge base using semantic search
  - name: search_es
    type: elasticsearch.search
    with:
      index: "{{ consts.indexName }}"
      query:
        semantic:
          field: semantic_content
          query: "{{ inputs.query }}"
      size: 5

  # Step 2: Classify using only METADATA (Mistral Small - cheap)
  # We deliberately do NOT pass the full documents here. The routing
  # decision only needs to know the shape of the results: which
  # categories they hit, their complexity labels, and their scores.
  - name: classify_query
    type: ai.prompt
    with:
      connectorId: Mistral Small
      prompt: &gt;
        You are a support query classifier. Based on the customer query
        and the metadata of the top knowledge base hits below, decide
        how this query should be handled.

        Return ONLY a JSON object with:
        - "complexity": "simple" if the top hit clearly matches a single
          FAQ (high score, low-complexity category, single product area),
          or "complex" if the query spans multiple categories, the top
          hits have medium/high complexity labels, or the results are
          weakly matched.
        - "reasoning": one-line explanation.

        Customer query: {{ inputs.query }}

        Top 5 results (metadata only):
        1. score={{ steps.search_es.output.hits.hits[0]._score }}, category={{ steps.search_es.output.hits.hits[0]._source.issue_category_sub_category }}, complexity={{ steps.search_es.output.hits.hits[0]._source.issue_complexity }}, product={{ steps.search_es.output.hits.hits[0]._source.product_category }}
        2. score={{ steps.search_es.output.hits.hits[1]._score }}, category={{ steps.search_es.output.hits.hits[1]._source.issue_category_sub_category }}, complexity={{ steps.search_es.output.hits.hits[1]._source.issue_complexity }}, product={{ steps.search_es.output.hits.hits[1]._source.product_category }}
        3. score={{ steps.search_es.output.hits.hits[2]._score }}, category={{ steps.search_es.output.hits.hits[2]._source.issue_category_sub_category }}, complexity={{ steps.search_es.output.hits.hits[2]._source.issue_complexity }}, product={{ steps.search_es.output.hits.hits[2]._source.product_category }}
        4. score={{ steps.search_es.output.hits.hits[3]._score }}, category={{ steps.search_es.output.hits.hits[3]._source.issue_category_sub_category }}, complexity={{ steps.search_es.output.hits.hits[3]._source.issue_complexity }}, product={{ steps.search_es.output.hits.hits[3]._source.product_category }}
        5. score={{ steps.search_es.output.hits.hits[4]._score }}, category={{ steps.search_es.output.hits.hits[4]._source.issue_category_sub_category }}, complexity={{ steps.search_es.output.hits.hits[4]._source.issue_complexity }}, product={{ steps.search_es.output.hits.hits[4]._source.product_category }}

  # Step 3: Route based on complexity
  - name: route_by_complexity
    type: if
    condition: "${{ steps.classify_query.output.complexity == 'simple' }}"
    steps:
      # Simple: answer directly from FAQ snippet (Mistral Small)
      - name: answer_from_faq
        type: ai.prompt
        with:
          connectorId: Mistral Small
          prompt: &gt;
            You are a customer support agent. Answer the customer's question
            using ONLY the FAQ article below. Be concise, friendly, and
            include specific steps if applicable.

            Customer query: {{ inputs.query }}

            FAQ article:
            {{ steps.search_es.output.hits.hits[0]._source | json }}
    else:
      # Complex: synthesize from multiple articles (Claude Sonnet)
      - name: synthesize_answer
        type: ai.prompt
        with:
          connectorId: Anthropic Claude Sonnet 4.6
          prompt: &gt;
            You are a senior customer support specialist. The customer's query
            requires careful analysis across multiple knowledge base articles.

            Provide a detailed, empathetic response that:
            1. Addresses all aspects of the customer's question
            2. Cites specific articles from the knowledge base (reference them
               by their question/title)
            3. Provides clear resolution steps
            4. Notes if any part of the query isn't covered by the KB

            Customer query: {{ inputs.query }}

            Knowledge base articles:
            {{ steps.search_es.output.hits.hits | json }}<p>The workflow has four key parts:</p><p></p><ol><li><p><strong><code>search_es</code></strong> uses <code>elasticsearch.search</code> with a <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">semantic query</a> to find the five most relevant articles.</p></li><li><p><strong><code>classify_query</code></strong>sends the customer query plus <strong>only metadata</strong> from the search results to Mistral Small. The prompt includes scores, categories, complexity labels, and product categories. This keeps the classification step cheap, preventing the use of large amounts of tokens.</p></li><li><p><strong><code>route_by_complexity</code></strong> uses an <code>if</code> step to branch based on the classifier's output.</p></li><li><p><strong>The response step</strong> depends on the route. For simple queries, Mistral Small gets the top FAQ article and rephrases it. For complex queries, Claude Sonnet gets all five articles and synthesizes a detailed response with citations. This is the only step where full document content is loaded.</p></li></ol><h2>Using the workflow as a tool in Agent Builder</h2><p>Beyond the default triggers (manual, schedule, alerts), workflows can also be exposed as <strong>tools in </strong><a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder"><strong>Agent Builder</strong></a>. This adds a conversational layer where users interact through a chat interface, and the agent decides when to call the workflow.</p><p>We use the <a href="https://www.elastic.co/docs/solutions/search/agent-builder/kibana-api">Agent Builder APIs</a> to create the tool and the agent. After creating the workflow in the Kibana UI, copy its ID and use it to register the workflow as a tool:</p>WORKFLOW_ID = "workflow-aaf77e41-37cf-48a8-973b-c853f71e4fae"

# Create the workflow tool
workflow_tool_payload = {
    "id": "run_support_query_router",
    "type": "workflow",
    "description": (
        "Routes a customer support query through the triage workflow. "
        "Searches the knowledge base, classifies query complexity, and "
        "generates a response using the appropriate model. Use this tool "
        "whenever a customer asks a support question."
    ),
    "tags": ["support", "triage", "workflow"],
    "configuration": {
        "workflow_id": WORKFLOW_ID,
    },
}

response = requests.post(
    f"{KIBANA_URL}/api/agent_builder/tools",
    headers=headers,
    json=workflow_tool_payload,
)<p>Then create an agent that uses the tool:</p>agent_payload = {
    "id": "support-query-agent",
    "name": "Support Query Agent",
    "description": "Customer support agent that routes queries through a multi-model workflow.",
    "labels": ["support", "e-commerce"],
    "configuration": {
        "instructions": (
            "You are a customer support assistant for BrownBox, an e-commerce platform. "
            "When a customer asks a support question, use the `run_support_query_router` tool "
            "to process it. The tool will search the knowledge base, classify the query, "
            "and generate an appropriate response.\n\n"
            "Present the response to the customer in a friendly, professional tone."
        ),
        "tools": [{"tool_ids": ["run_support_query_router"]}],
    },
}

response = requests.post(
    f"{KIBANA_URL}/api/agent_builder/agents",
    headers=headers,
    json=agent_payload,
)<p>The agent is now available in the <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">Agent Builder</a> UI in Kibana. You can also create the agent and its tools directly through the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/agent-builder-agents#custom-agents">Agent Builder UI</a>.</p><p>Once created, the agent appears in the Agent Builder UI with the workflow tool assigned:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec33d305e3d75727/6a3e4200517ae220fcf2fbfa/cc1b7c60969568bfe96d12ecc0c7174869144a75-1999x961.png" alt="" /><h2>Testing simple vs. complex query routing</h2><h3>Simple query</h3>"How do I track my order?"<p>The workflow searches the knowledge base, finds a direct match in the FAQ articles about order tracking, classifies it as <strong>simple</strong>, and routes to Mistral Small. The response is concise and drawn directly from the matched article: instructions for using the "My Orders" section or the tracking number from the confirmation email.</p><h3>Complex query</h3>"I ordered an OTG last week and it arrived damaged. I also noticed I was
charged twice on my credit card. I want a replacement for the OTG and a
refund for the duplicate charge. Also, my account shows the wrong delivery
address - can you update it?"<p>This query involves three distinct issues (damaged product, duplicate charge, address update) across different support categories. The workflow classifies it as <strong>complex</strong> and routes to Claude Sonnet, which synthesizes information from multiple knowledge base articles, addresses each issue separately, cites the relevant articles, and provides clear resolution steps for each.</p><h2>Conclusion</h2><p>Routing LLM queries by complexity in Elasticsearch reduces latency and cost for simple queries without sacrificing quality on complex ones. The small model answers FAQ-style queries in a fraction of the time the larger model would take, and the larger model is reserved for the queries that actually benefit from its capabilities. Cost savings come along for the ride: Simple queries routed to the smaller model are cheaper, too.</p><p>The pattern that makes this work is searching the knowledge base before routing. Without that context, the router is guessing based on surface-level cues. With it, the structure of the search results, like scores, categories, and complexity labels, tells the router whether the answer lives in a single article or needs synthesis across several. That's the actual signal for how to handle the query.</p><p>Elastic Workflows makes this possible without writing orchestration code. The entire routing logic lives in YAML inside Kibana, using native steps for search, LLM prompts, and conditional branching. Combined with Agent Builder, the same workflow serves programmatic triggers and conversational interfaces.</p><h2>Next steps</h2><ul><li><p>Try the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/routing-queries-right-model-elasticsearch/notebook.ipynb">notebook</a> with the complete implementation.</p></li><li><p>Add <a href="https://www.elastic.co/search-labs/blog/llm-monitoring-openrouter-agent-builder">LLM monitoring with OpenRouter</a> to track cost per routing tier.</p></li><li><p>Explore <a href="https://www.elastic.co/search-labs/blog/elastic-workflows-automation">Elastic Workflows</a> for other automation patterns.</p></li><li><p>Learn more about <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Agent Builder</a> and how to expose workflows as conversational tools.</p></li><li><p>Read about <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">building AI agentic workflows</a> with Elastic Agent Builder.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/llm-query-routing-elastic-workflows</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/llm-query-routing-elastic-workflows</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Integrations]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt469309ae05518d1c/6a3e4203d473dd14db0cb146/5a9cb32bda53bcb51b45e0bcf8a64ac184d45588-1672x941.png" length="0" type="image/png"/>
    <pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Entity resolution with Elasticsearch, part 4: The ultimate challenge]]></title>
    <description><![CDATA[Solving and evaluating entity resolution challenges in a highly diverse “ultimate challenge” dataset designed to prevent shortcuts.]]></description>
    <content:encoded><![CDATA[<p>We’ve now seen intelligent entity resolution implemented in two ways. Both approaches begin the same way: entity preparation and extraction, followed by candidate retrieval with Elasticsearch. From there, we evaluate those candidates using a large language model (LLM), either through prompt-based JSON generation or through function calling, and require the model to provide a transparent explanation for its judgment.</p><p>As we saw in the <a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">previous post</a>, the consistency provided by function calling is not just a nice optimization; it’s essential. Once we removed structural errors from the evaluation loop, results on standard scenarios (such as those in the tier 4 dataset) improved dramatically.</p><p>Yet there’s an obvious question left to answer:</p><p><em>Does this approach still work when things get genuinely messy?</em></p><p>Real-world entity resolution rarely fails because of simple cases. It fails when names cross languages, cultures, writing systems, time periods, and organizational boundaries. It fails when people are referenced by titles instead of names, when companies change names, when transliterations aren’t consistent, and when context (not spelling) is the only thing tying a mention to a real-world entity.</p><p>So, for the final post in this series, we put the system through what we called <strong>the ultimate challenge</strong>.</p><h2>What makes this the ultimate challenge?</h2><p>In earlier evaluations, we tested the system using increasingly complex datasets. By the time we reached tier 4, discussed in the previous post, we were already dealing with a mix of nicknames, titles, multilingual names, and semantic references. Those tests showed that the architecture itself was sound, but that reliability issues, especially malformed JSON, were suppressing recall.</p><p>With function calling in place, we finally had a stable foundation. That gave us the opportunity to ask a more interesting question:</p><p><em>Can one unified pipeline handle </em><em><strong>many different kinds</strong></em><em> of entity resolution problems at once?</em></p><p>The ultimate challenge dataset was designed to push precisely on that dimension.</p><p>Instead of focusing on a single difficulty (like nicknames or transliteration), this dataset combines <strong>50+ distinct challenge types</strong>, including:</p><ul><li><p>Cultural naming conventions.</p></li><li><p>Title-based references.</p></li><li><p>Business relationships and historical name changes.</p></li><li><p>Multilingual and cross-script mentions.</p></li><li><p>Compound challenges that mix several of the above.</p></li></ul><p>Crucially, this isn’t about optimizing for any one narrow use case. It’s about testing whether the <em>design pattern</em> holds up when the rules change from entity to entity.</p><h2>The dataset at a glance</h2><p>The ultimate challenge dataset consists of:</p><ul><li><p><strong>50 entities</strong>, spanning people, organizations, and institutions.</p></li><li><p><strong>~60 articles</strong>, with varying structure and linguistic complexity.</p></li><li><p><strong>51 distinct challenge categories</strong>, grouped broadly into:</p><ul><li><p>Cultural naming conventions.</p></li><li><p>Titles and professional context.</p></li><li><p>Business and organizational relationships.</p></li><li><p>Multilingual and transliteration challenges.</p></li><li><p>Combined and edge‑case scenarios.</p></li></ul></li></ul><p>Earlier in the series, we saw that using generative AI (GenAI) to create datasets can be a mixed blessing. Without it, assembling sufficiently large and diverse test data would be extremely difficult. But left unchecked, the model has a tendency to make things too easy.</p><p>On an early generation pass, for example, we discovered that the model had included phrases like “the Russian president” as explicit aliases for Vladimir Putin. That might seem reasonable today, but it defeats the purpose of testing contextual resolution. What happens if the article is discussing Russia in the 1990s? The system should infer the correct entity from context, not rely on a hard-coded alias.</p><p>For that reason, this dataset was deliberately designed so that <strong>shortcuts don’t work</strong>. Aliases are not explicitly listed when the system is expected to infer meaning. Descriptive phrases are not prelinked to entities. Correct matches often depend on article-level context, not just local text.</p><p><strong>Important note:</strong> Although we demonstrate the system’s capabilities across diverse scenarios, this is still an educational prototype. Production systems handling real-world sanctioned-entity monitoring would require additional validation, compliance checks, audit trails, and specialized handling for sensitive use cases.</p><h2>Why these scenarios are hard</h2><p>Back in the first post in this series, we introduced a simple but ambiguous example: “The new Swift update is here!” The challenge is that “Swift” can resolve to multiple real-world entities, depending on context. That example captures a broader truth: Natural language is inherently ambiguous.</p><p>Entity resolution, therefore, is not just a string-matching problem. Humans routinely rely on shared knowledge, cultural norms, and situational context to resolve references, and we rarely even notice we’re doing it.</p><p>Consider a few common cases:</p><ul><li><p>A title like “the president” is meaningless without geopolitical and temporal context.</p></li><li><p>A company name may refer to a parent, a subsidiary, or a former brand depending on when the article was written.</p></li><li><p>A person’s name may appear in different orders, scripts, or transliterations, depending on language and culture.</p></li><li><p>The same phrase can legitimately refer to different entities in different contexts, and the system must be able to <em>reject</em> matches just as confidently as it accepts them.</p></li></ul><p>There is no single rule set that handles all of this cleanly. That’s why this prototype separates concerns so aggressively:</p><ul><li><p>Elasticsearch narrows the candidate space efficiently and transparently.</p></li><li><p>The LLM is used only where judgment is required and is forced to explain itself.</p></li><li><p>Retrieval and reasoning remain distinct steps.</p></li></ul><p>This separation becomes even more important as the diversity of challenge types increases.</p><h2>How the system handles diversity without special cases</h2><p>One of the most interesting outcomes of this evaluation is what <em>didn’t</em> change:</p><ul><li><p>We did <strong>not</strong> add special logic for Japanese names.</p></li><li><p>We did <strong>not</strong> add custom rules for Arabic patronymics.</p></li><li><p>We did <strong>not</strong> add hard-coded mappings for historical company names.</p></li></ul><p>Instead, the system relied on the same core ingredients introduced earlier in the series:</p><ul><li><p>Context-enriched entities indexed for semantic search.</p></li><li><p>Hybrid retrieval (exact, alias, and semantic) in Elasticsearch.</p></li><li><p>A small, well-defined set of candidate matches.</p></li><li><p>LLM judgment constrained by function calling and minimal schemas.</p></li></ul><p>This suggests that the system’s flexibility comes from <strong>representation and architecture</strong>, not from an ever-growing collection of rules.</p><p>When the system succeeds, it’s because the right candidates are retrieved and the LLM has enough context to explain why a reference does (or does not) map to a specific entity.</p><h2>Results: How did it perform?</h2><p>On the ultimate challenge dataset, the system produced the following overall results:</p><ul><li><p><strong>Precision:</strong> ~91%</p></li><li><p><strong>Recall:</strong> ~86%</p></li><li><p><strong>F1 Score:</strong> ~89%</p></li><li><p><strong>LLM acceptance rate:</strong> ~72%</p></li></ul><h3>Performance across challenge types</h3><p>Breaking down results by challenge type reveals strengths and limitations:</p><p><strong>Strongest performance (100% F1 score)</strong> was observed in areas such as:</p><ul><li><p>Cross-script matching (Cyrillic, Korean, Chinese business entities).</p></li><li><p>Hebrew scenarios (patronymics, professional titles, religious titles, transliteration).</p></li><li><p>Business hierarchies (aerospace, diversified manufacturing, multidivision corporations).</p></li><li><p>Professional titles (academic, military, political, religious).</p></li><li><p>Combined Japanese scenarios involving multiple writing systems.</p></li></ul><p><strong>Strong performance (80–99% F1 score)</strong> included:</p><ul><li><p>International political figures (98%).</p></li><li><p>Historical name changes (90%).</p></li><li><p>Complex business hierarchies (89%).</p></li><li><p>Japanese company names (93%).</p></li><li><p>Cross-script transliteration (86%).</p></li><li><p>Arabic patronymics (86%).</p></li></ul><p><strong>More challenging areas</strong> included:</p><ul><li><p>Advanced transliteration (Chinese, Korean): 0% F1.</p></li><li><p>Certain Japanese scenarios (honorifics, name order, writing system variation): ~67% F1.</p></li><li><p>Some Arabic scenarios (company names, institutional references): ~40% F1.</p></li></ul><p>What’s important here is <em>why</em> the system struggled in these cases. The failures were not due to the overall approach breaking down, but to limitations in specific components, most notably the dense vector model used for semantic search in certain multilingual scenarios.</p><p>Because retrieval and judgment are cleanly separated, improving performance does not require rewriting the system. Swapping in a more capable multilingual embedding model, enriching entity context, or refining retrieval strategies would improve results across these categories without changing the core architecture.</p><p>From an architectural standpoint, that’s the real success metric.</p><h2>What this tells us about the design</h2><p>Looking back across the series, a few patterns stand out:</p><ul><li><p><strong>Preparation matters more than clever matching. </strong>Enriching entities with context up front dramatically reduces ambiguity later.</p></li><li><p><strong>LLMs are most valuable as judges, not retrievers. </strong>Asking them to explain <em>why</em> a match makes sense is far more powerful than asking them to search.</p></li><li><p><strong>Reliability enables accuracy. </strong>Function calling didn’t just clean up JSON; it unlocked recall that was already latent in the retrieval step.</p></li><li><p><strong>Generalization beats specialization. </strong>A small number of well-chosen abstractions handled dozens of challenge types without custom logic.</p></li></ul><p>This is why the prototype is intentionally Elasticsearch-native and intentionally conservative in how it uses LLMs. The goal isn’t to replace search; it’s to make search explainable in situations where meaning matters.</p><h2>Final thoughts</h2><p>The ultimate challenge wasn’t about chasing perfect metrics; it was about answering a more fundamental question:</p><p><em>Can a transparent, search-first, LLM-assisted architecture handle real-world entity ambiguity without collapsing into rules or black boxes?</em></p><p>For this educational prototype, the answer is yes, with clear caveats around production hardening, compliance, monitoring, and data quality. If you’re building systems that need to justify <em>why</em> an entity match was made, this pattern is worth serious consideration. I hope this series has shown that entity resolution doesn’t have to be mysterious. With the right separation of concerns, it becomes something you can reason about, measure, and improve.</p><p>This work also suggests a broader architectural pattern. What emerges is a slight but important evolution of classic retrieval augmented generation (RAG). Instead of allowing retrieval to feed generation directly, we introduce an explicit evaluation step. The LLM is first used to judge and sanity-check retrieved candidates, and only those approved results are allowed to augment generation. You can think of this as Generation-Augmented Retrieval-Augmented Generation with Evaluation, or GARAGE, because who doesn’t love a good acronym.</p><p>What other use cases could benefit from this pattern? Systems that require trust, transparency, and defensible reasoning are natural candidates. Future work in this area should prove as compelling as the results we’ve seen here, and I’m excited to see where the community takes it next.</p><h2>Next steps: Try it yourself</h2><p>Want to see the ultimate challenge in action? Check out the <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>Ultimate Challenge notebook</strong></a> for a complete walkthrough, with real implementations, detailed explanations, and hands-on examples.</p><p>The complete entity resolution pipeline demonstrates the core concepts and architecture needed for production use. You can use it as a foundation to build systems that monitor news articles, track entity mentions, and answer questions about which entities appear in which articles, all while retaining transparency and explainability.
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Hybrid Search]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Entity resolution with Elasticsearch, part 3: Optimizing LLM integration with function calling]]></title>
    <description><![CDATA[Learn how function calling enhances LLM integration, enabling a reliable and cost-efficient entity resolution pipeline in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>In <a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">part 1</a> and <a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search">part 2</a> of this series, we built a complete entity resolution pipeline that included preparing entities with context and indexing them for semantic search, extracting entities from articles using hybrid named entity recognition (NER), and matching entities using semantic search and large language model (LLM) judgment. The results were promising, but JSON parsing errors significantly lowered measured accuracy by causing otherwise valid judgments to be discarded. The system wasn’t failing because it made bad judgments; it was failing because it couldn’t reliably express them.</p><p>The root of this problem was our somewhat naive choice to use prompt-based JSON generation in which the LLM generates JSON responses in text format. If we asked the LLM to judge more than a couple of matches at a time, the generated JSON was often ill-formed. To mitigate this, we were forced to reduce the processing batch size, which simply won't scale in a production system.</p><p>So the prompt-based JSON generation helped validate our approach to entity resolution, but we need a more systematic and reliable method. OpenAI function calling provides a better path by guaranteeing structure and type safety while reducing errors and costs. We chose OpenAI's functions for the educational prototype, but other LLM providers typically provide similar functionality (for example, Claude tools).</p><p><strong>Note:</strong> While we discuss production challenges here, this is still an educational prototype demonstrating optimization techniques. Real production systems would need additional considerations, like monitoring, alerting, fallback strategies, and comprehensive error handling.</p><h2>Key concepts: Function calling, schema design, and cost benefits</h2><p><strong>What is function calling?</strong> <em>Function calling</em> is OpenAI's structured output API. With it, we can define schemas for LLM responses, so we always know exactly what we're going to get. By enforcing the JSON format rather than trying to define it in the LLM prompt, we should be able to eliminate parsing errors.</p><p><strong>Why is it better than prompt-based JSON?</strong> LLMs generate nondeterministic output. One hopes that they'll at least generate content that contains the correct response, but the presentation of that response is unpredictable. With a chatbot, this is often not a problem, but our prototype is trying to programmatically process the output. Computer programs demand consistency, so when the LLM generates what we expect, everything is fine, but as soon as it goes off script, so to speak, the code errors out. We could try to account for the different possibilities, but it would be very difficult to catch everything. We could try to enforce more consistent behavior by adding something like "Always return parsable JSON". We tried this exact technique in the prototype's prompt, but we've seen that prompt-based JSON still goes off the rails pretty quickly, particularly if we try to process a batch of matches.</p><p>Function calling makes the LLM generation controllable and predictable, exactly what we need for entity resolution. To aid in the definition of the functions, we’ll also follow minimal schema design principles.</p><p><strong>What are minimal schema design principles?</strong> <em>Minimal schema design</em> means defining only the fields you need, using simple types, and avoiding nested structures when possible. This reduces token usage (smaller schemas mean fewer tokens), improves reliability (simpler schemas are easier for the LLM to follow), and lowers costs (fewer tokens mean lower API costs).</p><p><strong>What are the cost and reliability benefits?</strong> Since fewer errors means match processing is much more likely to succeed, even with large batch sizes, we don't have to retry judging matches. The elimination of retries reduces costs by reducing token usage, but using minimal schemas also keeps our token count down. This all leads to a less expensive and more reliable approach that’s much more suitable to use in production.</p><p>We need to check one more thing, though. While matches may be getting processed without error, are the errorless results actually correct? How does this new approach compare to the promising results we saw with the prompt-based approach?</p><h2>Real-world results: Side-by-side comparison</h2><p>As we did in the previous blog, we ran the function calling approach against the tier 4 dataset, which consists of 206 expected matches across 69 articles. The results demonstrate a dramatic improvement:</p><p>Metric</p><p>Prompt-based</p><p>Function calling</p><p>Improvement</p><p>Error rate</p><p>30.2%</p><p>0.0%</p><p>100% elimination</p><p>Precision</p><p>83.8%</p><p>90.3%</p><p>+6.5pp</p><p>Recall</p><p>62.6%</p><p>90.8%</p><p>+28.2pp</p><p>F1 score</p><p>71.7%</p><p>90.6%</p><p>+18.9pp</p><p>Acceptance rate</p><p>44.8%</p><p>60.2%</p><p>+15.4pp</p><p>True positives</p><p>129</p><p>187</p><p>+45.0%</p><p>False negatives</p><p>77</p><p>19</p><p>-75.3%</p><h3>Error elimination: The key differentiator</h3><p>The most striking difference is the <strong>complete elimination of JSON parsing errors</strong>. This resulted in a modest precision improvement and a far more dramatic recall improvement. The precision metric captures how often the matches the system accepts were expected in the golden document. So the prototype was decent at judging matches correctly in the prompt-based approach, but function calling does that even better.</p><p></p><p>Conversely, recall tells us how many of the expected matches were found. When a batch of matches comes back with malformed JSON, the system loses all of those matches. It's likely that Elasticsearch sends many of these matches for judgment, but we lose those matches if judgment fails. The significant recall improvement shows that this hypothesis is correct. Elasticsearch identifies the potential matches and function calling verifies which of those matches are correct.</p><p></p><p><strong>Note:</strong> It’s expected that Elasticsearch will find some incorrect matches because we look at the top two or three results from hybrid search. Most of the time, hybrid search returns the correct match as the top result, but having the LLM judge the top few hits ensures that we see how the LLM handles incorrect matches. If we move from the educational prototype to a production system, we’ll likely tune the Elasticsearch queries more carefully so that we only send promising matches to the LLM, further optimizing our LLM costs.</p><h2>What's next: The ultimate challenge</h2><p>Now that we've optimized our LLM integration with function calling, we have a complete entity resolution pipeline with improved reliability and cost efficiency. However, can it handle the ultimate challenge? In the next post, we'll explore how the system handles diverse entity resolution scenarios across 50 different challenge types, including cultural naming conventions, business relationships, titles, and multilingual variations.</p><h2>Try it yourself</h2><p>Want to see function calling optimization in action? Check out the <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,04_function_calling_optimization_v3.ipynb,-Initial%20public%20lab">Function Calling Optimization notebook</a> for a complete walkthrough with real implementations, detailed explanations, and hands-on examples. The notebook shows you exactly how to use function calling for structured output, compare it with prompt-based JSON, and analyze cost and reliability benefits.</p><p><strong>Remember:</strong> This is an educational prototype designed to teach optimization concepts. When building production systems, consider additional factors, like multi-provider support, advanced caching strategies, monitoring and alerting, comprehensive error handling, and compliance requirements that aren't covered in this learning-focused prototype.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Hybrid Search]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3b4daaf48985d75/6a170cf360084b0d2c3c45be/b2afa90c1b863c716008f3f5bbdd2866fa1c3577-720x420.png" length="0" type="image/png"/>
    <pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Better text analysis for complex languages with Elasticsearch and neural models]]></title>
    <description><![CDATA[Using neural models and the Elasticsearch inference API to improve search in Hebrew, German, Arabic, and other morphologically complex languages.]]></description>
    <content:encoded><![CDATA[<p>If you work with English search, standard text analysis usually just works. You index “running,” the analyzer strips the suffix to store “run,” and a user searching for “run” finds the document. Simple.</p><p>But if you work with languages like Hebrew, Arabic, German, or Polish, you know that standard rule-based analyzers often fail. They either under-analyze (missing relevant matches) or overanalyze (returning garbage results).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b9673440a589d20/6a170e620e2e490ba741a1ce/2484b1f7ce600fbbf75b76a12a67cdfdf9b6e6ab-800x600.jpg" alt="Text analysis for complex languages" /><p>For years, we’ve had to rely on complex dictionaries and fragile regex rules. Today, we can do better. By replacing rule-based logic with <strong>neural models for text analysis</strong> (small, efficient language models that understand context), we can drastically improve search quality.</p><p>Here’s how to solve the morphology challenge by using the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom"><strong>Elasticsearch inference API</strong></a> and a custom model service.</p><h2><strong>The problem: Why rules fail</strong></h2><p>Most standard analyzers are <strong>context-free</strong>. They look at one word at a time and apply a static set of rules.</p><ul><li><p><strong>Algorithmic analyzers</strong> (like Snowball) strip suffixes based on patterns.</p></li><li><p><strong>Dictionary analyzers</strong> (like Hunspell) look up words in a list.</p></li></ul><p>This approach breaks down when the structure of a word (its root and affixes) changes based on the sentence it lives in.</p><h3><strong>1. The semitic ambiguity (roots versus prefixes)</strong></h3><p>Semitic languages, like Hebrew and Arabic, are built on root systems and often attach prepositions (such as, in, to, or from) directly to the word. This creates ambiguous tokens that rule-based systems cannot solve.</p><ul><li><p><strong>Word:</strong> <code>בצל</code> (B-Tz-L).</p></li><li><p><strong>Context A:</strong> “The soup tastes better with <strong>onion</strong> (<em>batzal</em>).”</p></li><li><p><strong>Context B:</strong> “We sat <strong>in the shadow</strong> (<em>ba-tzel</em>) of the tree.”</p></li></ul><p>In Context A, <code>בצל</code> is a noun (onion). In Context B, it’s a preposition ב (in) attached to the noun <code>צל</code> (shadow).</p><p>A standard analyzer is forced to guess. If it aggressively strips the ב prefix, it turns "onion" into "shadow." If it’s conservative and leaves it alone, a user searching for "shadow" (<em>tzel</em>) will fail to find documents containing "in the shadow" (<em>batzel</em>). Neural models solve this by reading the sentence to determine whether the ב is part of the root or a separate preposition.</p><h3><strong>2. The compound problem (German, Dutch, and more)</strong></h3><p>Languages like German, Dutch, Swedish, and Finnish concatenate nouns without spaces to form new concepts. This results in a theoretically infinite vocabulary. To search effectively, you must split (decompound) these words.</p><ul><li><p><strong>Word:</strong> <code>Wachstube</code>.</p></li><li><p><strong>Split A:</strong> <code>Wach</code> (guard) + <code>Stube</code> (room) = guardroom.</p></li><li><p><strong>Split B:</strong> <code>Wachs</code> (wax) + <code>Tube</code> (tube) = wax tube.</p></li></ul><p>A dictionary-based decompounder acts blindly. If both “Wach” and “Wachs” are in its dictionary, it might pick the wrong split, polluting your index with irrelevant tokens.</p><p>To see this problem in English: A naive algorithm might split “carpet” into “car” + “pet.” Without understanding meaning, rules fail.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0b12fffeaaf9b52/6a170e64cdacbf69597d2a98/eefee9dc6206452d362f8f58dc35c793021dcb1e-800x524.jpg" alt="Compound words in search" /><h2><strong>The solution: “Neural analyzers” (neural models for text analysis)</strong></h2><p>We don’t need to abandon the inverted index. We just need to feed it better tokens.</p><p>Instead of a regex rule, we use a <strong>neural model</strong> (like BERT or T5) to perform the analysis. Because these models are trained on massive datasets, they understand context. They look at the surrounding words to decide whether <code>בצל</code> means "onion" or "in shadow" or if <code>Wachstube</code> belongs in a military or cosmetic context.</p><h3><strong>Architecture: The inference sidecar</strong></h3><p>We can integrate these Python-based models directly into the Elasticsearch ingestion pipeline using the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom"><strong>inference API</strong></a>.</p><p><strong>The pattern:</strong></p><ol><li><p><strong>External model service:</strong> A simple Python service (for example, FastAPI) hosts the model.</p></li><li><p><a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom"><strong>Elasticsearch inference API</strong></a><strong>:</strong> Defines this service as a custom model within Elasticsearch.</p></li><li><p><strong>Ingest pipeline:</strong> Sends text to the inference processor, which calls your Python service.</p></li><li><p><strong>Index mapping: </strong>Create a <code>whitespace</code> target field for the analyzed text.</p></li><li><p><strong>Indexing:</strong> The service returns the cleaned text, which Elasticsearch stores in the target field.</p></li><li><p><strong>Search:</strong> Queries are analyzed via the inference API before matching.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e93cf10d9a93e21/6a170e66839dfa475cdcff90/5c3055a1594f267c676347da36b1b8b2b187220c-1600x1248.png" alt=" Text analysis for complex languages in Elasticsearch architecture" /><h2><strong>Implementation guide</strong></h2><p>Let’s build this for <strong>Hebrew</strong> (using <code>DictaBERT</code>) and <strong>German</strong> (using <code>CompoundPiece</code>).</p><p>To follow along, you’ll need:</p><ul><li><p>Python 3.10+.</p></li><li><p>Elasticsearch 8.9.x+.</p></li></ul><p>Install the Python dependencies:</p>pip3 install fastapi uvicorn torch transformers<h3><strong>Step 1: External model service</strong></h3><p>To connect Elasticsearch to our neural model, we need a simple API service that:</p><ol><li><p>Receives text from the Elasticsearch inference API.</p></li><li><p>Passes it through the neural model.</p></li><li><p>Returns analyzed text in a format Elasticsearch understands.</p></li></ol><p>This service interfaces Elasticsearch with the neural model. At ingest time, the Elasticsearch pipeline calls this API to analyze and store document fields; at search time, the application calls it to process the user's query. You can deploy this on any infrastructure, including EC2, Lambda, or SageMaker.</p><p>The code below loads both models at startup and exposes <code>/analyze/hebrew</code> and <code>/analyze/german</code> endpoints:</p>from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Union
from transformers import AutoTokenizer, AutoModel, AutoModelForSeq2SeqLM
from contextlib import asynccontextmanager
import torch

# Global models (loaded once at startup)
he_model = None
he_tokenizer = None
de_model = None
de_tokenizer = None


@asynccontextmanager
async def lifespan(app: FastAPI):
   """Load models at startup."""
   global he_model, he_tokenizer, de_model, de_tokenizer

   print("Loading Hebrew model (DictaBERT-Lex)...")
   he_tokenizer = AutoTokenizer.from_pretrained("dicta-il/dictabert-lex")
   he_model = AutoModel.from_pretrained("dicta-il/dictabert-lex", trust_remote_code=True)
   he_model.eval()

   print("Loading German model (CompoundPiece)...")
   de_tokenizer = AutoTokenizer.from_pretrained("benjamin/compoundpiece")
   de_model = AutoModelForSeq2SeqLM.from_pretrained("benjamin/compoundpiece")

   if torch.cuda.is_available():
       he_model.to("cuda")
       de_model.to("cuda")

   print("Models loaded successfully!")
   yield
   print("Shutting down...")


app = FastAPI(
   title="Neural Text Analyzer",
   description="Multi-language text normalization service",
   version="1.0.0",
   lifespan=lifespan
)


class InferenceRequest(BaseModel):
   """ES Inference API sends: {"input": ["text1", "text2"]} or {"input": "text"}"""
   input: Union[str, List[str]]


def format_response(normalized_text: str) -&gt; dict:
   """
   Normalize output to OpenAI-compatible format for ES Inference API.
   ES extracts: $.choices[*].message.content You do not need to stick
   with the OpenAI output format.
   Using it here for consistency reasons, since using the completions API.
   """
   return {
       "choices": [
           {"message": {"content": normalized_text}}
       ]
   }


@app.post("/analyze/hebrew")
async def analyze_hebrew(request: InferenceRequest):
   """Hebrew lemmatization using DictaBERT-Lex."""
   global he_model, he_tokenizer

   if he_model is None:
       raise HTTPException(status_code=503, detail="Model not loaded")

   # Handle input (can be string or list)
   if isinstance(request.input, str):
       texts = [request.input]
   else:
       texts = request.input

   # Run prediction
   with torch.no_grad():
       results = he_model.predict(texts, he_tokenizer)

   # results format: [[[word, lemma], [word, lemma], ...]]
   if results and results[0]:
       lemmas = []
       for word, lemma in results[0]:
           if lemma == '[BLANK]':
               lemma = word
           lemmas.append(lemma)
       normalized = " ".join(lemmas)
   else:
       normalized = ""

   return format_response(normalized)


@app.post("/analyze/german")
async def analyze_german(request: InferenceRequest):
   """German decompounding using CompoundPiece (supports 56 languages)."""
   global de_model, de_tokenizer

   if de_model is None:
       raise HTTPException(status_code=503, detail="Model not loaded")

   # Handle input
   if isinstance(request.input, str):
       text = request.input
   else:
       text = request.input[0] if request.input else ""

   # Format: "de: &lt;word&gt;" for German
   input_text = f"de: {text}"

   inputs = de_tokenizer(input_text, return_tensors="pt")
   if torch.cuda.is_available():
       inputs = {k: v.to("cuda") for k, v in inputs.items()}

   with torch.no_grad():
       outputs = de_model.generate(**inputs, max_length=128)

   # IMPORTANT: decode outputs[0], not outputs
   result = de_tokenizer.decode(outputs[0], skip_special_tokens=True)

   # Clean up: "de: Donau-Dampf-Schiff" -&gt; "Donau Dampf Schiff"
   # Note: model returns "de: " (with space after colon)
   if result.startswith("de: "):
       clean_result = result[4:].replace("-", " ")
   elif result.startswith("de:-"):
       clean_result = result[4:].replace("-", " ")
   elif result.startswith("de:"):
       clean_result = result[3:].replace("-", " ")
   else:
       clean_result = result.replace("-", " ")

   return format_response(clean_result.strip())


@app.get("/health")
async def health():
   return {"status": "healthy"}<p>Save the code above to a file (for example, <code>analyzer_service.py</code>), and run:</p>python3 -m uvicorn analyzer_service:app --port 8000<p>Wait for “<em>Models loaded successfully!</em>” (takes ~30–60 seconds for models to download on first run).</p><p>Test locally:</p>#Hebrew
curl -X POST http://localhost:8000/analyze/hebrew \
 -H "Content-Type: application/json" \
 -d '{"input": "הילדים אכלו גלידה בגינה"}'#German
curl -X POST http://localhost:8000/analyze/german \
 -H "Content-Type: application/json" \
 -d '{"input": "Donaudampfschifffahrt"}'<p>Expected output:</p>- Hebrew: `{"choices":[{"message":{"content":"ילד אוכל גלידה גינה"}}]}`
- German: `{"choices":[{"message":{"content":"Donau Dampf Schiff Fahrt"}}]}`<h3><strong>Step 2: Configure Elasticsearch inference API</strong></h3><p>We’ll use the<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom"> </a><a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom"><code>custom</code></a> inference endpoint. This allows us to define exactly how Elasticsearch talks to our Python endpoint.</p><p><strong>Note:</strong> Use <code>response.json_parser</code> to extract the content from our normalized JSON structure. You <strong>do not</strong> need to stick with the OpenAI output format. We’re using it here for consistency reasons, since we’re using the <em>completion</em> task type, which is text to text.</p><h4><strong>Exposing your local service</strong></h4><p>For testing, we’ll use <a href="https://ngrok.com">ngrok</a> to expose the local Python service to the internet. This allows any Elasticsearch deployment (self-managed, Elastic Cloud, or Elastic Cloud Serverless) to reach your service.</p><p>Install and run ngrok:</p># Install ngrok (macOS) (Or download from https://ngrok.com/download)
brew install ngrok<p>Expose your local service:</p>ngrok http 8000<p>ngrok will display a forwarding URL like:</p><p>Forwarding <a href="https://abc123.ngrok.io">https://abc123.ngrok.io</a> -&gt; <a href="http://localhost:8000">http://localhost:8000</a></p><p>Copy the HTTPS URL. You’ll use this in the Elasticsearch configuration.</p><p><strong>Configure the inference endpoint</strong></p> PUT _inference/completion/hebrew-analyzer                           
 {                                  
   "service": "custom",                                              
   "service_settings": {                             
     "url": "https://abc123.ngrok.io/analyze/hebrew",  
     "headers": {                    
       "Content-Type": "application/json"               
     },                                                
     "request": "{\"input\": ${input}}",                     
     "response": {                                
       "json_parser": {                         
         "completion_result": "$.choices[*].message.content"     
       }                               
     }                                 
   }                                   
 }<p>Replace <a href="https://abc123.ngrok.io">https://abc123.ngrok.io</a> with your actual ngrok URL.</p><p><strong>Note:</strong> ngrok is used here for fast testing and development. The free tier has request limits, and URLs change on restart. For production, deploy your service to a persistent infrastructure.</p><h4><strong>For production (with API Gateway)</strong></h4><p>In production, deploy your Python service to a secure, persistent endpoint (such as AWS API Gateway + Lambda, EC2, ECS, or any cloud provider). Use <code>secret_parameters</code> to securely store API keys:</p> PUT _inference/completion/hebrew-analyzer                        
 {                                     
   "service": "custom",                  
   "service_settings": {                
     "url": "https://your-api-gateway.execute-api.region.amazonaws.com/prod/analyze/hebrew",                 
     "headers": {                      
       "x-api-key": "${api_key}",       
       "Content-Type": "application/json"  
     },                              
     "secret_parameters": {           
       "api_key": "YOUR-API-KEY"     
     },                           
     "request": "{\"input\": ${input}}",      
     "response": {                    
       "json_parser": {               
         "completion_result": "$.choices[*].message.content"  
       }                             
     }                               
   }                                 
 }<h3><strong>Step 3: Ingest pipeline</strong></h3><p>Create a pipeline that passes the raw text field to our model and stores the result in a new field.</p>PUT _ingest/pipeline/hebrew_analysis_pipeline
{
 "description": "Lemmatizes Hebrew text using a custom inference endpoint",
 "processors": [
   {
     "inference": {
       "model_id": "hebrew-analyzer",
       "input_output": {
         "input_field": "content",
         "output_field": "content_analyzed"
       }
     }
   }
 ]
}<h3><strong>Step 4: Index mapping</strong></h3><p>This is the most critical step. The output from our neural model is already analyzed. We <strong>do not</strong> want a standard analyzer to mess it up again. We use the <code>whitespace</code> analyzer to simply tokenize the text we received.</p>PUT /my-hebrew-index
{
 "mappings": {
   "properties": {
     "content": {
       "type": "text",
       "analyzer": "standard"
     },
     "content_analyzed": {
       "type": "text",
       "analyzer": "whitespace"
     }
   }
 }
}<h3><strong>Step 5: Indexing</strong></h3><p><strong>Option A: Single document.</strong></p>POST /my-hebrew-index/_doc?pipeline=hebrew_analysis_pipeline
{
"content": "הילדים אכלו גלידה בגינה"
}<p><strong>Option B: Reindex existing data.</strong></p><p>If you have existing data in another index, reindex it through the pipeline:</p>POST _reindex
{
 "source": {
   "index": "my-old-index"
 },
 "dest": {
   "index": "my-hebrew-index",
   "pipeline": "hebrew_analysis_pipeline"
 }
}<p><strong>Option C: Set pipeline as default for index.</strong></p><p>Make all future documents automatically use the pipeline:</p>PUT /my-hebrew-index/_settings
{
"index.default_pipeline": "hebrew_analysis_pipeline"
}<p>Then index normally (no <code>?pipeline=</code> needed):</p>POST /my-hebrew-index/_doc
{
"content": "הילדים אכלו גלידה בגינה"
}<h3><strong>Step 6: Search</strong></h3><p>Search using a neural analyzer in Elasticsearch is a two-step process, so analyze the query first using the inference API, and then search with the result:</p><p><strong>A. Analyze the query.</strong></p> POST _inference/completion/hebrew-analyzer
 {
   "input": "הילדים אכלו גלידה בגינה"
 }<p><strong>B. Search with the result.</strong></p> GET /my-hebrew-index/_search
 {
   "query": {
     "match": {
       "content_analyzed": "ילד אוכל גלידה גינה"
     }
   }
 }<p>In production, wrap these two calls in your application code for a seamless experience.</p><h2><strong>Available models</strong></h2><p>The architecture above works for any language. You simply swap the Python model and adjust the post-processing of the output. Here are verified models for common complex languages:</p><ul><li><p><strong>Hebrew:</strong> Context-aware lemmatization. Handles prefix ambiguity (ב, ה, ל, and more) <a href="https://huggingface.co/dicta-il/dictabert-lex">dicta-il/dictabert-lex</a>.</p></li><li><p><strong>German: </strong>Generative decompounding. Supports 56 languages, including Dutch, Swedish, Finnish, and Turkish. <a href="https://huggingface.co/benjamin/compoundpiece">benjamin/compoundpiece</a>.</p></li><li><p><strong>Arabic:</strong> BERT-based disambiguation and lemmatization for Modern Standard Arabic. <a href="https://github.com/CAMeL-Lab/camel_tools">CAMeL Tools</a>.</p></li><li><p><strong>Polish:</strong> Case-sensitive lemmatization for Polish inflections. <a href="https://huggingface.co/amu-cai/polemma-large">amu-cai/polemma-large</a>.</p></li></ul><h2><strong>Conclusion</strong></h2><p>You don’t need to choose between the precision of lexical search and the intelligence of AI. By moving the “smart” part of the process into the analysis phase using the inference API, you fix the root cause of poor search relevance in complex languages.</p><p>The tools are here. The models are open-source. The pipelines are configurable. It’s time to teach our search engines to read.</p><h3><strong>Code</strong></h3><p>All code snippets from this article are available at <a href="https://github.com/noamschwartz/neural-text-analyzer">https://github.com/noamschwartz/neural-text-analyzer</a>.</p><p></p><p><strong>References</strong>:</p><ul><li><p><a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom">https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-custom</a></p></li><li><p><a href="https://www.elastic.co/docs/manage-data/ingest/transform-enrich/ingest-pipelines">https://www.elastic.co/docs/manage-data/ingest/transform-enrich/ingest-pipelines</a></p></li><li><p><a href="https://ngrok.com">https://ngrok.com</a></p></li><li><p><a href="https://huggingface.co/dicta-il/dictabert-lex">https://huggingface.co/dicta-il/dictabert-lex</a></p></li><li><p><a href="https://huggingface.co/benjamin/compoundpiece">https://huggingface.co/benjamin/compoundpiece</a></p></li><li><p><a href="https://arxiv.org/pdf/2305.14214">https://arxiv.org/pdf/2305.14214</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-text-analysis-neural-model</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-text-analysis-neural-model</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Noam Schwartz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09154d73719c99b2/6a170e68ab7f080f19db9f3e/a572f9832d8ebc603b70743ac8f2d6e4ea8d2e11-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Automating log parsing in Streams with ML]]></title>
    <description><![CDATA[Learn how a hybrid ML approach achieved 94% log parsing and 91% log partitioning accuracy through automation experiments with log format fingerprinting in Streams.]]></description>
    <content:encoded><![CDATA[<p>In modern observability stacks, ingesting unstructured logs from diverse data providers into platforms like Elasticsearch remains a challenge. Reliance on manually crafted parsing rules creates brittle pipelines, where even minor upstream code updates lead to parsing failures and unindexed data. This fragility is compounded by the scalability challenge: in dynamic microservices environments, the continuous addition of new services turns manual rule maintenance into an operational nightmare.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8f5bd0e4986b04c/6a170e6acdacbf612e7d2a9e/9108ec303339dd091faa3c363c7cf5c228155f49-3840x2160.png" alt="" /><p>Our goal was to transition to an automated, adaptive approach capable of handling both log parsing (field extraction) and log partitioning (source identification). We hypothesized that Large Language Models (LLMs), with their inherent understanding of code syntax and semantic patterns, could automate these tasks with minimal human intervention.</p><p>We are happy to announce that this feature is already available in <a href="http://elastic.co/elasticsearch/streams"><u>Streams</u></a>!</p><h2>Dataset description</h2><p>We chose a <a href="https://github.com/logpai/loghub"><strong>Loghub</strong></a>collection of logs for PoC purposes. For our investigation, we selected representative samples from the following key areas:</p><ul><li><p>Distributed systems: We used the HDFS (Hadoop Distributed File System) and Spark datasets. These contain a mix of info, debug, and error messages typical of big data platforms.</p></li><li><p>Server &amp; web applications: Logs from Apache web servers and OpenSSH provided a valuable source of access, error, and security-relevant events. These are critical for monitoring web traffic and detecting potential threats.</p></li><li><p>Operating systems: We included logs from Linux and Windows. These datasets represent the common, semi-structured system-level events that operations teams encounter daily.</p></li><li><p>Mobile systems: To ensure our model could handle logs from mobile environments, we included the Android dataset. These logs are often verbose and capture a wide range of application and system-level activities on mobile devices.</p></li><li><p>Supercomputers: To test performance on high-performance computing (HPC) environments, we incorporated the BGL (Blue Gene/L) dataset, which features highly structured logs with specific domain terminology.</p></li></ul><p>A key advantage of the Loghub collection is that the logs are largely unsanitized and unlabeled, mirroring a noisy live production environment with microservice architecture.</p><p>Log examples:</p>[Sun Dec 04 20:34:21 2005] [notice] jk2_init() Found child 2008 in scoreboard slot 6
[Sun Dec 04 20:34:25 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Mon Dec 05 11:06:51 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
17/06/09 20:10:58 INFO output.FileOutputCommitter: Saved output of task 'attempt_201706092018_0024_m_000083_1138' to hdfs://10.10.34.11:9000/pjhe/test/1/_temporary/0/task_201706092018_0024_m_000083
17/06/09 20:10:58 INFO mapred.SparkHadoopMapRedUtil: attempt_201706092018_0024_m_000083_1138: Committed<p>In addition, we created a Kubernetes cluster with a typical web application + database set up to mine extra logs in the most common domain.</p><p>Example of common log fields: timestamp, log level (INFO, WARN, ERROR), source, message.</p><h2>Few-shot log parsing with an LLM</h2><p>Our first set of experiments focused on a fundamental question: <strong>Can an LLM reliably identify key fields and generate consistent parsing rules to extract them?</strong></p><p>We asked a model to analyse raw log samples and generate log parsing rules in regular expression (regex) and <a href="https://www.elastic.co/docs/explore-analyze/scripting/grok">Grok</a> formats. Our results showed that this approach has a lot of potential, but also significant implementation challenges.</p><h3>High confidence &amp; context awareness</h3><p>Initial results were promising. The LLM demonstrated a strong ability to generate parsing rules that matched the provided few-shot examples with high confidence. Besides simple pattern matching, the model showed a capacity for log understanding —it could correctly identify and name the log source (e.g., health tracking app, Nginx web app, Mongo database).</p><h3>The "Goldilocks" dilemma of input samples</h3><p>Our experiments quickly surfaced a significant lack of robustness because of extreme<strong> sensitivity to the input sample.</strong> The model's performance fluctuates wildly based on the specific log examples included in the prompt. We observed a log similarity problem where the log sample needs to include <em>just diverse enough </em>logs:</p><ul><li><p>Too homogeneous (overfitting)<strong>:</strong> If the input logs are too similar, the LLM tends to <strong>overspecify</strong>. It treats variable data—such as specific Java class names in a stack trace—as static parts of the template. This results in brittle rules that cover a tiny ratio of logs and extract unusable fields.</p></li><li><p>Too heterogeneous (confusion): Conversely, if the sample contains significant formatting variance—or worse, "trash logs" like progress bars, memory tables, or ASCII art—the model struggles to find a common denominator. It often resorts to generating complex, broken regexes or lazily over-generalizing the entire line into a single message blob field.</p></li></ul><h3>The context window constraint</h3><p>We also encountered a context window bottleneck. When input logs were long, heterogeneous, or rich in extractable fields, the model's output often deteriorated, becoming "messy" or too long to fit into the output context window. Naturally, chunking helps in this case. By splitting logs using character-based and entity-based delimiters, we could help the model focus on extracting the main fields without being overwhelmed by noise.</p><h3>The consistency &amp; standardization gap</h3><p>Even when the model successfully generated rules, we noted slight inconsistencies:</p><ul><li><p>Service naming variations: The model proposes different names for the same entity (e.g., labeling the source as "Spark," "Apache Spark," and "Spark Log Analytics" in different runs).</p></li><li><p>Field naming variations: Field names lacked standardization (e.g., <code>id</code> vs. <code>service.id</code> vs. <code>device.id</code>). We normalized names using a standardized <a href="https://www.elastic.co/docs/reference/ecs/ecs-field-reference">Elastic field naming</a>.</p></li><li><p>Resolution variance: The resolution of the field extraction varied depending on how similar the input logs were to one another.</p></li></ul><h2>Log format fingerprint</h2><p>To address the challenge of log similarity, we introduce a high-performance heuristic: <strong>log format fingerprint (LFF)</strong>.</p><p>Instead of feeding raw, noisy logs directly into an LLM, we first apply a deterministic transformation to reveal the underlying structure of each message. This pre-processing step abstracts away variable data, generating a simplified "fingerprint" that allows us to group related logs.</p><p>The mapping logic is simple to ensure speed and consistency:</p><ol><li><p>Digit abstraction: Any sequence of digits (0-9) is replaced by a single ‘0’.</p></li><li><p>Text abstraction: Any sequence of alphabetical characters with whitespace is replaced by a single ‘a’.</p></li><li><p>Whitespace normalization: All sequences of whitespace (spaces, tabs, newlines) are collapsed into a single space.</p></li><li><p>Symbol preservation: Punctuation and special characters (e.g., :, [, ], /) are preserved, as they are often the strongest indicators of log structure.</p></li></ol><p>We introduce the log mapping approach. The basic mapping patterns include the following:</p><ul><li><p>Digits 0-9 of any length -&gt; to ‘0.’</p></li><li><p>Text (alphabetical characters with spaces) of any length -&gt; to ‘a’.</p></li><li><p>White spaces, tabs, and new lines -&gt; to a single space.</p></li></ul><p>Let's look at an example of how this mapping allows us to transform the logs.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf91eebab0ad79ccd/6a170e6c67045ba94f45c29c/78fa2887486eb9417804354ee3bf2a4fdb0f6383-846x252.png" alt="" /><p>As a result, we obtain the following log masks:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt438d74dcb921578b/6a170e6d1949f74aa0e7aae3/ec439a3d3a25002498b97defcff733ea5ebc6b55-826x94.png" alt="" /><p>Notice the fingerprints of the first two logs. Despite different timestamps, source classes, and message content, their prefixes (<code>0/0/0 0:0:0 a a.a:</code>) are identical. This structural alignment allows us to automatically bucket these logs into the same cluster.</p><p>The third log, however, produces a completely divergent fingerprint (<code>0-0-0...</code>). This allows us to algorithmically separate it from the first group <em>before</em> we ever invoke an LLM.</p><h2>Bonus part: Instant implementation with ES|QL</h2><p>It’s as easy as passing this query in Discover.</p><p><strong>Query breakdown:</strong></p><p><strong>FROM</strong> loghub: Targets our index containing the raw log data.</p><p><strong>EVAL</strong> pattern = …: The core mapping logic. We chain REPLACE functions to perform the abstraction (e.g., digits to '0', text to 'a', etc.) and save the result in a “pattern” field.</p><p><strong>STATS </strong>[column1 =] expression1, …<strong> BY </strong>SUBSTRING(pattern, 0, 15):</p><p>This is a clustering step. We group logs that share the first 15 characters of their pattern and create aggregated fields such as total log count per group, list of log datasources, pattern prefix, 3 log examples</p><p><strong>SORT</strong> total_count DESC | <strong>LIMIT</strong> 100 : Surfaces the top 100 most frequent log patterns</p><p>The query results on LogHub are displayed below:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa3960cf94ccf331/6a170e6fdc55decfa3e00e7c/b119498f124376c41d242a099bf9081fd6536be8-1600x394.png" alt="Log parsing query results on LogHub." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dbcde2a22e06367/6a170e71961e693a18c4cfb6/4dcfc0a5b7fa753497cc5def5ea3cd54449c0481-1600x719.png" alt="" /><p>As demonstrated in the visualization, this “LLM-free” approach partitions logs with high accuracy. It successfully clustered 10 out of 16 data sources (based on LogHub labels) completely (&gt;90%) and achieved majority clustering in 13 out of 16 sources (&gt;60%) —all without requiring additional cleaning, preprocessing, or fine-tuning.</p><p>Log format fingerprint offers a pragmatic, high-impact alternative and addition to sophisticated ML solutions like <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-categorize-text-aggregation">log pattern analysis</a>. It provides immediate insights into log relationships and effectively manages large log clusters.</p><ul><li><p>Versatility as a primitive </p></li></ul><p>Thanks to <a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">ES|QL</a> implementation, LFF serves both as a standalone tool for fast data diagnostics/visualisations, and as a building block in log analysis pipelines for high-volume use cases. </p><ul><li><p>Flexibility</p></li></ul><p>LFF is easy to customize and extend to capture specific patterns, i.e. hexadecimal numbers and IP addresses.</p><ul><li><p>Deterministic stability</p></li></ul><p>Unlike ML-based clustering algorithms, LFF logic is straightforward and deterministic. New incoming logs do not retroactively affect existing log clusters.</p><ul><li><p>Performance and mMemory</p></li></ul><p>It requires minimal memory, no training or GPU making it ideal for real-time high-throughput environments.</p><h2>Combining log format fingerprint with an LLM</h2><p>To validate the proposed hybrid architecture, each experiment contained a random 20% subset of the logs from each data source. This constraint simulates a real-world production environment where logs are processed in batches rather than as a monolithic historical dump.</p><p>The objective was to demonstrate that LFF acts as an effective compression layer. We aimed to prove that high-coverage parsing rules could be generated from small, curated samples and successfully generalized to the entire dataset.</p><h2>Execution pipeline</h2><p>We implemented a multi-stage pipeline that filters, clusters, and applies stratified sampling to the data before it reaches the LLM.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26635762891b3a41/6a170e73509168eea4e1bb91/b3f46ea471760b406a32fc7d4bc74cc03faaced2-3840x1660.png" alt="" /><p>1. Two-stage hierarchical clustering</p><ul><li><p>Subclasses (exact match): Logs are aggregated by identical fingerprints. Every log in one subclass shares the exact same format structure.</p></li><li><p>Outlier cleaning. We discard any subclasses that represent less than 5% of the total log volume. This ensures the LLM focuses on the dominant signal and won’t be sidetracked by noise or malformed logs.</p></li><li><p>Metaclasses (prefix match): Remaining subclasses are grouped into Metaclasses by the first N characters of the format fingerprint match. This grouping strategy effectively splits lexically similar formats under a single umbrella.We chose N=5 for Log parsing and N=15 for Log partitioning when data sources are unknown.</p></li></ul><p>2. Stratified sampling. Once the hierarchical tree is built, we construct the log sample for the LLM. The strategic goal is to maximize variance coverage while minimizing token usage.</p><ul><li><p>We select representative logs from <em>each</em> valid subclass within the broader metaclass.</p></li><li><p>To manage an edge case of too numerous subclasses, we apply random down-sampling to fit the target window size.</p></li></ul><p>3. Rule generation Finally, we prompt the LLM to generate a regex parsing rule that fits all logs in the provided sample for each Metaclass. For our PoC, we used the GPT-4o mini model.</p><h2>Experimental results &amp; observations</h2><p>We achieved 94% parsing accuracy and 91% partitioning accuracy on the Loghub dataset.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b896b41b3b70e7e/6a170e757d8d67601a70e7d9/49b2b6a1401dd1f33951da68e5a3fac37d0b5aaa-1600x1506.png" alt="94% parsing accuracy and 91% partitioning accuracy on the Loghub dataset." /><p>The confusion matrix above illustrates log partitioning results. The vertical axis represents the actual data sources, and the horizontal axis represents the predicted data sources. The heatmap intensity corresponds to log volume, with lighter tiles indicating a higher count. The diagonal alignment demonstrates the model's high fidelity in source attribution, with minimal scattering.</p><h2>Our performance benchmarks insights:</h2><ul><li><p><strong>Optimal baseline:</strong> a context window of <strong>30–40 log samples</strong> per category proved to be the "sweet spot," consistently producing robust parsing with both Regex and Grok patterns.</p></li><li><p><strong>Input minimisation:</strong> we pushed the input size to 10 logs per category for Regex patterns and observed only 2% drop in parsing performance, confirming that diversity-based sampling is more critical than raw volume.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/log-parsing-partitioning-automation-experiments-streams</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/log-parsing-partitioning-automation-experiments-streams</guid>
    <category><![CDATA[ML Research]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Nastia Havriushenko]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1df5a7cae463d59/6a170e76a6c2b907d7e797ab/965c58f19742361160593c38fcaa8b2f4b0d6cc5-3838x2159.png" length="0" type="image/png"/>
    <pubDate>Fri, 02 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Building an AI agent for HR with Elastic Agent Builder and GPT-OSS]]></title>
    <description><![CDATA[Discover how to build an AI agent that can answer natural language queries about your employee HR data using Elastic Agent Builder and GPT-OSS.]]></description>
    <content:encoded><![CDATA[<h2>Introduction</h2><p>This article will show you how to build an AI agent for HR using <a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS</a> and Elastic Agent Builder. The agent can answer your questions without sending data to OpenAI, Anthropic, or any external service.</p><p>We’ll use LM Studio to serve GPT-OSS locally and connect it to Elastic Agent Builder.</p><p>By the end of this article, you’ll have a custom AI agent that can answer natural language questions about your employee data while maintaining full control over your information and model.</p><h2>Prerequisites</h2><p>For this article, you need:</p><ul><li><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a> hosted 9.2, serverless or <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">local</a> deployment</p></li><li><p>Machine with 32GB RAM recommended (minimum 16GB for GPT-OSS 20B)</p></li><li><p><a href="https://lmstudio.ai/">LM Studio</a> installed</p></li><li><p><a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a> Installed</p></li></ul><h2>Why use GPT-OSS?</h2><p>With a local LLM you have the control to deploy it in your own infrastructure and fine-tune it to fit your own needs. All this while maintaining control over the data that you share with the model, and of course, you don’t have to pay a license fee to an external provider.</p><p>OpenAI <a href="https://openai.com/index/introducing-gpt-oss/">released GPT-OSS</a> on August 5, 2025, as part of their commitment to the open model ecosystem.</p><p>The 20B parameter model offers:</p><ul><li><p><strong>Tool use capabilities</strong></p></li><li><p><strong>Efficient inference</strong></p></li><li><p><strong>OpenAI SDK compatible</strong></p></li><li><p><strong>Compatible with agentic workflows</strong></p></li></ul><p>Benchmark comparison:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58fab956edb40412/6a170cfcb0367da43a72bd80/29160e3345352088e8213297630882f252b00c47-1600x680.png" alt="" /><h2>Solution architecture</h2><p>The architecture runs entirely on your local machine. Elastic (running in Docker) communicates directly with your local LLM through LM Studio, and the Elastic Agent Builder uses this connection to create custom AI agents that can query your employee data.</p><p>For more details, refer to this <a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">documentation</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80db5bb0a797f51b/6a170cfd0e2e492f2c41a16f/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" alt="" /><h2>Building an AI agent for HR: Steps</h2><p>We’ll divide the implementation into 5 steps:</p><ol><li><p>Configure LM studio with a local model</p></li><li><p>Deploy Local Elastic with Docker</p></li><li><p>Create the OpenAI connector in Elastic</p></li><li><p>Upload employee data to Elasticsearch</p></li><li><p>Build and test your AI Agent</p></li></ol><h2>Step 1: Configure LM Studio with GPT-OSS 20B</h2><p>LM Studio is a user-friendly application that allows you to run large language models locally on your computer. It provides an OpenAI-compatible API server, making it easy to integrate with tools like Elastic without a complex setup process. For more details, refer to the <a href="https://lmstudio.ai/docs/app">LM Studio Docs</a>.</p><p>First, download and install <a href="https://lmstudio.ai/">LM Studio</a> from the official website. Once installed, open the application.</p><h3>In the LM Studio interface:</h3><ol><li><p>Go to the search tab and search for “GPT-OSS”</p></li><li><p>Select the <code>openai/gpt-oss-20b</code> from OpenAI</p></li><li><p>Click download</p></li></ol><p>The size of this model should be approximately <strong>12.10GB</strong>. The download may take a few minutes, depending on your internet connection.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dc341a6625e34b7/6a170cff839dfa2eb4dcff44/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" alt="" /><h4>Once the model is downloaded:</h4><ol><li><p>Go to the local server tab</p></li><li><p>Select the openai/gpt-oss-20b</p></li><li><p>Use the default port 1234</p></li><li><p>On the right panel, go to <strong>Load </strong>and set the Context Length to <strong>40K</strong> or higher</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3704ca1b28465cc4/6a170d00d7c022ed8fde64ef/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" alt="" /><p>5. Click start server</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b9170a4945ff857/6a170d0266c4f9ffadf8c0a6/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" alt="" /><p>You should see this if the server is running.</p>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] -&gt;	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/embeddings
Server started.<h2>Step 2: Deploy Local Elastic with Docker</h2><p>Now we’ll set up Elasticsearch and Kibana locally using Docker. Elastic provides a convenient script that handles the entire setup process. For more details refer to the <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">official documentation</a>.</p><h3>Run the start-local script</h3><p>Execute the following command in your terminal:</p>curl -fsSL https://elastic.co/start-local | sh<p>This script will:</p><ul><li><p>Download and configure Elasticsearch and Kibana</p></li><li><p>Start both services using Docker Compose</p></li><li><p>Automatically activate a 30-day Platinum trial license</p></li></ul><h3>Expected output</h3><p>Just wait for the following message and save the password and API key shown; you’ll need them to access Kibana:</p>🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: KSUlOMNr
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: cnJGX0pwb0JhOG00cmNJVklUNXg6cnNJdXZWMnM4bncwMllpQlFlUTlWdw==
Learn more at https://github.com/elastic/start-local<h3>Access Kibana</h3><p>Open your browser and navigate to:</p>http://localhost:5601<p>Log in using the credentials obtained in the terminal output.</p><h3>Enable Agent Builder</h3><p>Once logged in to Kibana, navigate to <strong>Management </strong>&gt;<strong> AI </strong>&gt;<strong> Agent Builder </strong>and activate the Agent Builder.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a934bd99fa6a0ce/6a170d046234e019c3db1a5a/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" alt="" /><h2>Step 3: Create the OpenAI connector in Elastic</h2><p>Now we’ll configure Elastic to use your local LLM.</p><h3>Access Connectors</h3><ol><li><p>In Kibana</p></li><li><p>Go to <strong>Project Settings</strong> &gt; <strong>Management</strong></p></li><li><p>Under <strong>Alerts and Insights</strong>, select <strong>Connectors</strong></p></li><li><p>Click Create Connector</p></li></ol><h3>Configure the connector</h3><p>Select <strong>OpenAI</strong> from the list of connectors. LM Studio uses the OpenAI SDK, making it compatible.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762023c39781eb78/6a170d06a29299a59ed01087/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" alt="" /><p>Fill in the fields with these values:</p><ul><li><p><strong>Connector name: </strong>LM Studio - GPT-OSS 20B</p></li><li><p><strong>Select an OpenAI provider: </strong>Other (OpenAI Compatible Service)</p></li><li><p><strong>URL: </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></p></li><li><p><strong>Default model: </strong>openai/gpt-oss-20b</p></li><li><p><strong>API Key:</strong> testkey-123 (any text works, because LM Studio Server doesn't require authentication.)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt980e595f80e2be2e/6a170d086f7f0468a19148cc/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" alt="" /><p>To finish the configuration, click <strong>Save &amp; test</strong>.</p><p><strong>Important:</strong> Toggle ON the “<strong>Enable native function calling</strong>”; this is required for the Agent Builder to work properly. If you don’t enable this, you’ll get a <strong><code>No tool calls found in the response</code></strong> error.</p><h3>Test the connection</h3><p>Elastic should automatically test the connection. If everything is configured correctly, you’ll see a success message like this:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d2e815dd558f881/6a170d090e2e49076541a177/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" alt="" /><p>Response:</p>{
  "status": "ok",
  "data": {
    "id": "chatcmpl-flj9h0hy4wcx4bfson00an",
    "object": "chat.completion",
    "created": 1761189456,
    "model": "openai/gpt-oss-20b",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Hello! 👋 How can I assist you today?",
          "reasoning": "Just greet.",
          "tool_calls": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 69,
      "completion_tokens": 23,
      "total_tokens": 92
    },
    "stats": {},
    "system_fingerprint": "openai/gpt-oss-20b"
  },
  "actionId": "ee1c3aaf-bad0-4ada-8149-118f52dad757"
}<h2>Step 4: Upload employee data to Elasticsearch</h2><p>Now we’ll upload the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">HR employee dataset</a> to demonstrate how the agent works with sensitive data. I generated a fictional dataset with this structure.</p><h3>Dataset structure</h3>{
  "employee_id": "0f4dce68-2a09-4cb1-b2af-6bcb4821539b",
  "full_name": "Daffi Stiebler",
  "email": "lscutchings0@huffingtonpost.com",
  "date_of_birth": "1975-06-20T15:39:36Z",
  "hire_date": "2025-07-28T00:10:45Z",
  "job_title": "Physical Therapy Assistant",
  "department": "HR",
  "salary": "108455",
  "performance_rating": "Needs Improvement",
  "years_of_experience": 2,
  "skills": "Java",
  "education_level": "Master's Degree",
  "manager": "Carl MacGibbon",
  "emergency_contact": "Leigha Scutchings",
  "home_address": "5571 6th Park"
}<h3>Create the index with mappings</h3><p>First, create the index with proper mappings. Note that we’re using <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> fields for some key fields; this enables semantic search capabilities for our index.</p>​​PUT hr-employees
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "employee_id": {
        "type": "keyword"
      },
      "full_name": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "email": {
        "type": "keyword"
      },
      "date_of_birth": {
        "type": "date",
        "format": "iso8601"
      },
      "hire_date": {
        "type": "date",
        "format": "iso8601"
      },
      "job_title": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "department": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "salary": {
        "type": "double"
      },
      "performance_rating": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "years_of_experience": {
        "type": "long"
      },
      "skills": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "education_level": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "manager": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "emergency_contact": {
        "type": "keyword"
      },
      "home_address": {
        "type": "keyword"
      },
      "employee_semantic": {
        "type": "semantic_text"
      }
    }
  }
}<h3>Index with Bulk API</h3><p>Copy and paste the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">dataset</a> into your Dev Tools in Kibana and execute it:</p>POST hr-employees/_bulk
{"index": {}}
{"employee_id": "57728b91-e5d7-4fa8-954a-2384040d3886", "full_name": "Filide Gane", "email": "vhallahan1@booking.com", "job_title": "Business Systems Development Analyst", "department": "Marketing", "salary": "$52330.27", "performance_rating": "Meets Expectations", "years_of_experience": 12, "skills": "Java", "education_level": "Bachelor's Degree", "date_of_birth": "2000-02-07T16:49:32Z", "hire_date": "2023-11-07T13:03:16Z", "manager": "Freedman Kings", "emergency_contact": "Vilhelmina Hallahan", "home_address": "75 Dennis Junction"}
{"index": {}}
{"employee_id": "...", ...}<h3>Verify the data</h3><p>Run a query to verify:</p>GET hr-employees/_search<h2>Step 5: Build and test your AI agent</h2><p>With everything configured, it’s time to build a custom AI agent using Elastic Agent Builder. For more details refer to the <a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">Elastic documentation</a>.</p><h3>Add the connector</h3><p>Before we can create our new agent, we have to set our Agent builder to use our custom connector called <code>LM Studio - GPT-OSS 20B</code> because the default one is the <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>. For that, we need to go to <strong>Project Setting</strong> &gt; <strong>Management</strong> &gt; <strong>GenAI Settings</strong>; now we select the one we created and click <strong>Save</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc42f079c5e756057/6a170d0acf4f2501d9b2d1c7/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" alt="" /><h3>Access Agent Builder</h3><ol><li><p>Go to <strong>Agents</strong></p></li><li><p>Click on <strong>Create a new agent</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8e734817c5a7c6a/6a170d0ca929cf867cae0a34/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" alt="" /><h3>Configure the agent</h3><p>To create a new agent, the required fields are the <strong>Agent ID</strong>, <strong>Display Name</strong>, and <strong>Display Instructions</strong>.</p><p>But there are more customization options, like the Custom Instructions that guide how your agent is going to behave and interact with your tools, similar to a system prompt, but for our custom agent. Labels help organize your agents, avatar color, and avatar symbol.</p><p>The ones that I chose for our agent based on the dataset are:

<strong>Agent ID:</strong> <code>hr_assistant</code></p><p><strong>Custom instructions:</strong></p>You are an HR Analytics Assistant that helps answer questions about employee data.
When responding to queries:
- Provide clear, concise answers
- Include relevant employee details (name, department, salary, skills)
- Format monetary values with currency symbols
- Be professional and maintain data confidentiality<p>
Labels: <code>Human Resources</code> and <code>GPT-OSS</code></p><p>Display name: <code>HR Analytics Assistant</code></p><p>Display description:</p>A specialized AI assistant for Human Resources that helps analyze employee data, compensation, performance metrics, and talent management. Ask questions about employees, departments, salaries, or performance analytics.<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23fb011e5b4f4d49/6a170d0e7d8d67f47a70e77f/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" alt="" /><p>With all the data in there, we can click on <strong>Save</strong> our new agent.</p><h3>Test the agent</h3><p>Now you can ask natural language questions about your employee data, and GPT-OSS 20B will understand the intent and generate an appropriate response.</p><h4>Prompt:</h4>Which employee is the one with the highest salary in the hr-employees index?<h4>Answer:</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0c52faacf63b583/6a170d0f0e2e497bfd41a17b/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" alt="" /><p>The Agent process was:</p><p>1. Understand your question using the GPT-OSS connector</p><p>2. Generate the appropriate Elasticsearch query (using the built-in tools or custom <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a>)</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte32a8a7e6363c7f2/6a170d115091680077e1bb44/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" alt="" /><p>3. Retrieve matching employee records</p><p>4. Present results in natural language with proper formatting</p><p>Unlike traditional lexical search, the agent powered by GPT-OSS understands intent and context, making it easier to find information without knowing exact field names or query syntax. For more details on the agent's thinking process, refer to this <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">article</a>.</p><h2>Conclusion</h2><p>In this article, we built a custom AI agent using Elastic’s Agent Builder to connect to the OpenAI GPT-OSS model running locally. By deploying both Elastic and the LLM on your local machine, this architecture allows you to leverage generative AI capabilities while maintaining full control over your data, all without sending information to external services.</p><p>We used GPT-OSS 20B as an experiment, but the officially recommended models for Elastic Agent Builder are referenced <a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">here</a>. If you need more advanced reasoning capabilities, there's also the <a href="https://huggingface.co/openai/gpt-oss-120b">120B parameter variant</a> that performs better for complex scenarios, though it requires a higher-spec machine to run locally. For more details, refer to the <a href="https://openai.com/open-models/">official OpenAI documentation</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
    <category><![CDATA[Agentic AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt664f490053e46e6b/6a170d13b0367d2d7e72bd84/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png" length="0" type="image/png"/>
    <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Transforming data interaction: Deploying Elastic’s MCP server on Amazon Bedrock AgentCore Runtime for crafting agentic AI applications]]></title>
    <description><![CDATA[Transform complex database queries into simple conversations by deploying Elastic's search capabilities on Amazon Bedrock AgentCore Runtime platform.]]></description>
    <content:encoded><![CDATA[<p>Imagine asking your data questions in plain English: "Fitness/diet recommendations based on my health goals," or "Help find investment opportunities based on my risk level?" and getting accurate answers without writing a single query. Today, we'll explore how to achieve this by deploying Elastic's <a href="https://www.anthropic.com/news/model-context-protocol">Model Context Protocol</a> (MCP) server on <a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-how-it-works.html">Amazon Bedrock AgentCore Runtime</a>, creating a powerful bridge between conversational AI and your data.</p><p>At its core, this solution combines the power of Elasticsearch's search capabilities with Amazon's serverless AI infrastructure. Here's how it works:</p><ul><li><p>Your natural language questions are processed through the MCP server that is deployed on Amazon Bedrock AgentCore Runtime</p></li><li><p>The MCP server translates these questions into precise Elasticsearch queries</p></li><li><p>Results are returned in human-readable format, making your data instantly accessible</p></li><li><p>All of this happens in a secure, scalable environment that's production-ready, deployed on Amazon Bedrock AgentCore Runtime</p></li></ul><p>In this blog post, we'll explore how to:</p><ul><li><p>Deploy Elastic's MCP server on Amazon Bedrock AgentCore Runtime</p></li><li><p>Transform local MCP prototypes into production-ready solutions</p></li><li><p>How to Build Conversational Interfaces on top of Elasticsearch</p></li><li><p>Implement secure, scalable AI agent architectures</p></li></ul><h2>Background</h2><h3>Model Context Protocol (MCP)</h3><p>MCP is an open protocol that revolutionizes how businesses interact with their data through AI. Unlike traditional Retrieval-Augmented Generation (RAG) systems that simply retrieve documents, MCP enables AI agents to dynamically construct and execute complex tasks in real-time, mirroring the flexibility of human problem-solving</p><p>In practice, this means a business analyst can ask a series of increasingly specific questions about market trends, and the MCP-powered system will intelligently select and combine the appropriate data sources and analytical tools to provide comprehensive answers, while still maintaining context, allowing for follow-up questions without repetition.</p><p>For instance, when analyzing a product launch, the AI might integrate data from sales reports, customer feedback, and social media sentiment, orchestrating multiple tools simultaneously to provide a holistic view, thus enabling businesses to uncover deeper insights and make informed decisions, all through natural language interactions with their data systems.</p><h3>Agents</h3><p>Agents are AI-powered software applications that can think, plan, and act to achieve specific goals with minimal human supervision. They use foundation models (advanced AI models) to understand and complete complex tasks.</p><p>There are two types of AI agents.</p><p><strong>Knowledge AI agents</strong></p><p>These agents focus on enterprise knowledge. They gather context from company data — documents, logs, dashboards, communications, and customer records — and use that information to complete business tasks.</p><p><em>Example: A knowledge AI agent that can search across contracts, policies, and past tickets to help a customer support representative instantly resolve an issue.</em></p><p><strong>General AI agents</strong></p><p>These agents go further. They can understand goals and autonomously execute tasks on behalf of a user in broader, cross-domain workflows.</p><p><em>Example: A general AI agent that books travel, manages schedules, and negotiates with other systems to complete a user’s request end-to-end.</em></p><h3>Elastic’s role in agentic AI</h3><p><strong>For knowledge AI agents</strong>: Elastic enables secure access to enterprise data, retrieves relevant context, and grounds responses in facts.</p><p><strong>For general AI agents</strong>: Elastic serves as the knowledge store and context engine, providing trusted information so agents can perform more complex, goal-driven tasks.</p><p>In short, Elastic isn’t just storing data; it’s making enterprise knowledge usable, actionable, and AI-ready. This is the foundation for building intelligent agents that both understand and act.</p><h3>AWS partnership and MCP server</h3><p>Elastic has earned the AWS Generative AI Competency status. This recognition is awarded to AWS partners who deliver cutting-edge generative AI solutions that drive measurable gains in business efficiency, creativity, and productivity</p><p>Elastic also integrates with the Model Context Protocol (MCP), providing a seamless way for AI agents and applications to interact with Elasticsearch data through natural language conversations.</p><p>With the MCP server, you can connect to Elasticsearch directly from any MCP client — such as Claude Desktop, MCP Inspector, or an agentic application. The Elasticsearch MCP server is free to use (though infrastructure and Elasticsearch cluster costs may apply).</p><p>And with Amazon Bedrock models (such as Anthropic’s Claude) supporting MCP clients, organizations can now deploy intelligent, data-aware agents more easily and powerfully than ever before.</p><h3>Amazon Bedrock AgentCore</h3><p><a href="https://aws.amazon.com/bedrock/agentcore/?trk=e61dee65-4ce8-4738-84db-75305c9cd4fe&amp;sc_channel=el">Amazon Bedrock AgentCore</a> is an enterprise-grade orchestration platform designed for scalable AI agent deployment and management. </p><ul><li><p>The platform provides serverless runtime environments with session isolation capabilities, enabling concurrent agent operations across multiple frameworks.</p></li><li><p>It implements memory management systems for both session-state and persistent storage, facilitating context-aware model interactions and learning capabilities.</p></li><li><p>The architecture includes observability features with granular logging, metrics collection, and advanced debugging capabilities for agent trajectory analysis.</p></li><li><p>The platform's robust identity and access management layer enables secure service-to-service authentication and fine-grained authorization controls for AWS and third-party service integrations.</p></li></ul><p>It features a protocol-agnostic gateway for API transformations and tool discovery, supporting MCP-compliant interfaces. The infrastructure also includes containerized browser instances for web automation workflows and isolated compute environments for secure code execution. This end-to-end solution eliminates the need for building custom infrastructure components while maintaining enterprise security and compliance standards.</p><h2><strong>Solution overview</strong></h2><h3><strong>High-level architecture</strong></h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb55c578d65fd5085/6a17f3046864a47abeb688cc/40812bd3424c21232c6d03e24b74e01034ab2c76-1600x503.png" alt="" /><p>The architecture consists of four main components:</p><ol><li><p><strong> Python client: </strong>Handles user interactions and AWS authentication</p></li><li><p><strong>Amazon Bedrock</strong> AgentCore Runtime: Provides serverless hosting and session management</p></li><li><p><strong>Elastic MCP server</strong>: Processes MCP protocol requests and queries Elasticsearch</p></li><li><p><strong>Elasticsearch cluster</strong>: Stores and indexes the searchable data</p></li></ol><h3>Step-by-step workflow walkthrough</h3><ol><li><p>User authenticates using an authentication mechanism such as OAuth.</p></li><li><p>User access secured Client application running in a Customer AWS account using authenticated credentials.</p></li><li><p>The client application invokes a Supervisor Agent that further invokes and orchestrates other Agents.</p></li><li><p>All the agents are deployed on Amazon AgentCore Runtime and their tools are made available for the Agents, including Elastic’s MCP server and its tools.</p></li><li><p>Foundation Models are available for the agentic AI application through Amazon Bedrock.</p></li><li><p>Elastic Cloud is deployed on AWS and its endpoints are accessed by the Elastic MCP Server. Elastic MCP server automatically crafts the required queries, runs the queries against the Elastic data and fetches the response back to the Supervisor Agent.</p></li><li><p>Supervisor Agent responds back to the end user via the Client Application.</p></li></ol><h2>Implementation guide</h2><p>Please refer to <a href="https://github.com/aws-samples/aws-generativeai-partner-samples/tree/main/elastic/mcp/elastic-mcp-on-agentcore">this GitHub repo</a> to get a hands-on experience of how this solution can be implemented. Pay close attention to the <a href="https://github.com/aws-samples/aws-generativeai-partner-samples/tree/main/elastic/mcp/elastic-mcp-on-agentcore#-prerequisites">prerequisites</a> before getting started.</p><h3>Step 1: Deploy Elastic MCP server to ECR</h3><p>The <a href="https://github.com/aws-samples/aws-generativeai-partner-samples/blob/main/elastic/mcp/elastic-mcp-on-agentcore/deploy-elastic-mcp.sh">automated deployment script</a> handles the entire container build and upload process:</p>./deploy-elastic-mcp.sh<p>Here is what the script does when you execute:</p><ol><li><p>Downloads the official Elastic MCP server repository</p></li><li><p>Builds Docker container using <code>Dockerfile-8000</code></p></li><li><p>Creates ECR repository with image scanning enabled</p></li><li><p>Uploads container image to ECR with proper tagging</p></li></ol><h3>Step 2: Create AgentCore Runtime host</h3><p>Navigate to the AWS Console and configure your AgentCore Runtime:</p><p>1. Access AgentCore: Go to Amazon Bedrock AgentCore &gt; Build and Deploy &gt; Agent Runtime &gt; Host Agent</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4750c7fd2c1cdc5c/6a17f3062f4a5c7105fa8a1a/6818bafc00fdcba4c311b8ea1199c3d4ba9e979c-1428x396.png" alt="" /><p>2. Basic configuration: Click on “Host Agent” and give it a meaningful name if you prefer. Point to the Container Image you have uploaded to Amazon ECR.</p>   Name: hosted_agent_elastic_mcp
   Container Image: [ECR URI from Step 1]
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22fa13daf65713e3/6a17f30896142a918aeb1c29/beea587f4d1c022216c03f8f2895072c7075c1aa-1600x654.png" alt="" /><p>3. Service role: Choose "Create and use a new service role"</p><p>4. Protocol settings: Choose MCP, and for the Inbound Identity, select <code>Use IAM username</code></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4e24abbc56b21b2/6a17f309414c647a149452a9/a0cc50ebdfaf657cb2a5fbb0e60f1b5b45aa60fe-1600x505.png" alt="" /><p>5. Environment variables: Finally, configure your Elasticsearch endpoints and pass them as environment variables to your Docker container.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdec0c051b141511c/6a17f30b414c6499b59452ad/b3c1e97513b516bf5604c87950811a444b93745e-1432x358.png" alt="" /><p>6. After creating the host agent, go ahead and copy the Agent Runtime ARN from the "View invocation code" section. Here is an example:</p>arn:aws:bedrock-agentcore:us-west-2:XXXXXXXXXX:runtime/hosted_agent_elastic_mcp-xWSaxNGjf5<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte88f4a7205373342/6a17f30d96142a177feb1c2d/27330cf1d735d1240f4ce292f032d0a22cf6d986-1600x651.png" alt="" /><h3>Step 3: Configure Python client</h3><p><strong>Install dependencies:</strong></p><p>Go ahead and initialize a virtual environment and install the Python libraries.</p>python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -r requirements.txt<p>Update Agent ARN in <code>my_mcp_client_remote.py</code>:</p><p>Next, update the Python MCP client with the agent ARN you obtained in the previous steps.</p>agent_arn = "arn:aws:bedrock-agentcore:us-west-2:XXXXXXXX:runtime/hosted_agent_elastic_mcp-xWSbYNGjf5"<p>Here are the key client components in this Python file.</p><p><strong>AWS authentication class:</strong></p>class AWSAuth:
    def __init__(self, service='bedrock-agentcore', region='us-west-2'):
        self.session = boto3.Session()
        self.credentials = self.session.get_credentials()
        self.region = region
        self.service = service
        
    def get_auth_headers(self, url, method='POST', body=None):
        request = AWSRequest(method=method, url=url, data=body)
        SigV4Auth(self.credentials, self.service, self.region).add_auth(request)
        return dict(request.headers)<p><strong>MCP request formation / payload:</strong></p>chat_request = {
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
        "name": "search",
        "arguments": {
            "index": "events",
            "query_body": {
                "query": {
                    "bool": {
                        "should": [
                            {"match": {"name": "paris"}},
                            {"match": {"description": "paris"}},
                            {"match": {"venue": "paris"}},
                            {"match": {"address": "paris"}}
                        ]
                    }
                },
                "size": 10
            }
        }
    }
}<h3>Step 4: Run the client</h3><p>Execute the <a href="https://github.com/aws-samples/aws-generativeai-partner-samples/blob/main/elastic/mcp/elastic-mcp-on-agentcore/my_mcp_client_remote.py">Python client</a> to test the integration. This Python program implements an asynchronous client for interacting with Amazon Bedrock AgentCore, specifically designed to query event information. The code utilizes AWS SigV4 authentication and consists of two main functions: <code>test_mcp_endpoint()</code> and <code>chat_with_agentcore()</code>. The first function demonstrates basic API interaction by listing available tools and performing a search query, while the second function implements a more sophisticated search functionality specifically for events in Paris.</p><p>The program uses the <code>httpx</code> library for async HTTP requests and handles Server-Sent Events (SSE) responses, parsing and displaying event details including names, venues, dates, and descriptions. The authentication is managed through a custom <code>AWSAuth</code> class that handles AWS SigV4 signing of requests. The code includes comprehensive error handling and formatted output display, making it suitable for both testing and production use cases.</p>python my_mcp_client_remote.py<h2>Use case demonstrations</h2><h3>Use case 1: Data discovery</h3><p>Scenario: Finding events in a specific city using natural language.</p><p>Query: "Events in Paris"</p><p>MCP request: Here is the payload you supply to the Amazon Bedrock Agentcore Runtime.</p>{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "index": "events",
      "query_body": {
        "query": {
          "bool": {
            "should": [
              {"match": {"name": "paris"}},
              {"match": {"description": "paris"}},
              {"match": {"venue": "paris"}},
              {"match": {"address": "paris"}}
            ]
          }
        }
      }
    }
  }
}<p><strong>Response</strong>: And here is the response you get after Elastic’s MCP server runs a query in Elastic Search AI Platform and returns the result.</p>🎉 I found 1 events in Paris:


1. Paris Fashion Week
   📍 Murray-Howell Theater - 17814 Mills Mountains Apt. 815, Poncetown, DE 29241
   📅 2026-04-02
   📝 Major fashion event showcasing the latest collections from top designers.
   💰 $$$
   🎫 https://tickets.reed.net/event/DEST0001_EVT002<h3>Use case 2: Elastic MCP tool discovery</h3><p><strong>Scenario</strong>: Discovering the available MCP tools that Elastic’s MCP server offers.</p><p><strong>MCP request:</strong></p>{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "id": 1
}<p>This returns a list of available tools that the MCP server provides, enabling dynamic tool discovery.</p><h3>Use case 3: Complex search queries</h3><p><strong>Scenario</strong>: Advanced filtering with multiple criteria.
You can run more advanced Elastic Search Query Language based queries, like one shown below.</p><p><strong>Query</strong>: Events with specific price ranges, dates, and categories.</p><p><strong>MCP request:</strong></p>{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "index": "events",
      "query_body": {
        "query": {
          "bool": {
            "must": [
              {"range": {"start_date": {"gte": "2026-01-01"}}},
              {"term": {"price_range": "$$$"}}
            ],
            "should": [
              {"match": {"type": "Fashion"}},
              {"match": {"type": "Music"}}
            ]
          }
        },
        "size": 20
      }
    }
  }
}<h2>How it works (technical deep dive)</h2><h3>MCP implementation</h3><p>The Model Context Protocol uses JSON-RPC 2.0 format for all communications:</p>Client Request → AgentCore → MCP Server → Elasticsearch → Response Chain<p><strong>Key protocol features:</strong></p><ul><li><p>Stateless operation: Each request is independent with session isolation</p></li><li><p>Tool discovery: Dynamic discovery of available capabilities</p></li><li><p>Structured responses: Consistent response format across all tools</p></li><li><p>Error handling: Standardized error reporting and recovery</p></li></ul><p><strong>AWS authentication flow:</strong></p># 1. Create AWS request object
request = AWSRequest(method='POST', url=mcp_url, data=body)


# 2. Apply SigV4 authentication
SigV4Auth(credentials, 'bedrock-agentcore', region).add_auth(request)


# 3. Extract headers for HTTP client
headers = dict(request.headers)
headers["Content-Type"] = "application/json"
headers["Accept"] = "application/json, text/event-stream"<h3>Session management</h3><p>AgentCore automatically adds <code>Mcp-Session-Id</code> headers for session isolation:</p><ul><li><p>Each client session gets a unique identifier</p></li><li><p>Stateless servers can maintain conversation context</p></li><li><p>Automatic cleanup of inactive sessions</p></li></ul><h3>Response processing pipeline</h3><ol><li><p>Server-Sent Events (SSE): Responses come as <code>data: {...}</code> in JSON format</p></li><li><p>JSON parsing: Extract JSON from SSE wrapper</p></li><li><p>Content extraction: Parse the MCP result structure</p></li><li><p>Data formatting: Convert Elasticsearch results to user-friendly format</p></li></ol># Parse SSE response
if response.text.startswith('data: '):
    json_part = response.text[6:]  # Remove 'data: ' prefix
    response_json = json.loads(json_part)
    
    # Extract search results
    result = response_json.get('result', {})
    for content_item in result['content']:
        if content_item['type'] == 'text':
            # Process and format results
            search_results = json.loads(content_item['text'])<h3>Cleanup</h3><p>After you have played around with this setup, if you would like to clean up the environment, please follow the steps outlined below.</p><p><strong>Delete AgentCore Runtime:</strong></p><ol><li><p>Navigate to Amazon Bedrock AgentCore in AWS Console</p></li><li><p>Select your agent runtime</p></li><li><p>Click "Delete" and confirm</p></li></ol><p><strong>Remove ECR repository:</strong></p>aws ecr delete-repository \
    --repository-name elastic-mcp-server \
    --region us-west-2 \
    --force<p><strong>Clean local environment:</strong></p># Remove virtual environment
deactivate
rm -rf venv

# Remove cloned repository
rm -rf mcp-server-elasticsearch

# Remove Docker images
docker rmi elastic-mcp-server:latest
docker rmi [ECR_URI]:latest<h2>Conclusion</h2><p>By deploying Elastic's MCP server on Amazon Bedrock AgentCore Runtime, we've created a powerful, scalable, production-ready solution for natural language interaction with Elasticsearch data. This implementation opens up new possibilities for data exploration and analysis, making complex queries accessible through simple conversations.</p><p>Key takeaways include:</p><ul><li><p>Seamless integration: MCP protocol enables natural language querying of complex data</p></li><li><p>Production scalability: AgentCore provides enterprise-grade hosting with minimal configuration</p></li><li><p>Developer productivity: Transform local prototypes to production with minimal code changes</p></li><li><p>Security first: Built-in AWS security and authentication mechanisms</p></li></ul><p>Potential applications can be in any of the following areas of implementation: </p><ul><li><p>Customer support: Natural language querying of support ticket databases</p></li><li><p>Business intelligence: Conversational analytics for business metrics</p></li><li><p>Content discovery: Intelligent search across document repositories</p></li><li><p>IoT data analysis: Natural language queries for sensor and telemetry data</p></li></ul><p>Additional resources:</p><ul><li><p><a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-amazonbedrock">Amazon Bedrock integration documentation</a></p></li><li><p><a href="https://github.com/elastic/mcp-server-elasticsearch?tab=readme-ov-file#elasticsearch-mcp-server">Elastic MCP server documentation</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elastic-mcp-server-amazon-bedrock-agentcore-runtime</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elastic-mcp-server-amazon-bedrock-agentcore-runtime</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Udayasimha Theepireddy,Srinivas Pendyala,Matt Ryan,Ganesh Ramesh Shenoy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt576d87464937da1e/6a17f30e0b0bed0469dd36b1/7086754859ba2cbbaf673c843013462892738c30-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using ES|QL COMPLETION + an LLM to write a Chuck Norris fact generator in 5 minutes]]></title>
    <description><![CDATA[Discover how to use the ES|QL COMPLETION command to turn your Elasticsearch data into creative output using an LLM in just a few lines of code.]]></description>
    <content:encoded><![CDATA[<p>What if you could turn your Elasticsearch data into creative output using an LLM—in just a few lines of code? With the new <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/completion">COMPLETION command</a> in <strong>ES|QL</strong>, now you can.</p><p>Let’s build something fun to show it off: a Chuck Norris fact generator. We'll combine movie descriptions with a GPT model to generate facts so legendary even Rambo would be impressed.</p><h2>What you'll need</h2><ul><li><p>Access to an LLM (like OpenAI’s GPT-4o in our example below)</p></li><li><p>A dataset of movie descriptions </p></li></ul><p>You can download a <a href="https://www.kaggle.com/datasets/ursmaheshj/top-10000-popular-movies-tmdb-05-2023?resource=download">sample dataset</a> from Kaggle and upload it to your Elasticsearch cluster using the Data Visualizer in Kibana or the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk"><code>_bulk</code></a><a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk"> API</a>.</p><h2>Setting up the inference endpoint</h2><p>Before you can run the <code>COMPLETION</code> command, you need to create an inference endpoint for the model you want to use via the <code>_inference</code>  API.</p><p>Here’s how to set up GPT-4o with OpenAI:</p><p>Once this is in place, you can reference <code>my-gpt-4o-endpoint</code> directly in your query.</p><h2>The query</h2><p>Here’s the magic in action. This single <strong>ES|QL</strong> query handles the entire workflow: it finds a movie based on your input, constructs a prompt from its description, and then calls the LLM to generate a legendary Chuck Norris fact. Below is the full <strong>ES|QL</strong> query that powers our Chuck Norris fact generator. It takes in a movie query, retrieves the most relevant description, turns it into a prompt, and sends it off to the LLM—all in a single, piped query.</p><p>Here’s what comes back:</p><p>Yes, the model really said that. 💪🐐🚁</p><h2>Dissecting the query</h2><p>Let’s dissect the query and break down what’s happening, step by step.</p><h3>Step 1: Retrieve relevant movie data</h3><p>We begin by searching for the most relevant movie for the user query.
We use the <code>MATCH</code> function to search both the title and overview fields for the text provided by the <code>query</code> parameter, keeping only the first result, sorted by relevance using the metadata <code>_score</code> field:</p><p>This narrows down our dataset to the best match, giving us the movie's title and description, which will become the context for the LLM.</p><h3>Step 2: Build the prompt from the context</h3><p>Now we create the input prompt for the LLM by concatenating a static instruction provided as a query parameter, denoted by <code>?instruction</code>, with the movie’s overview:</p><p>This creates a new <code>prompt</code> column combining the provided instruction with the overview field from the returned document, which for our request looks a bit like this:</p>Generate a Chuck Norris Fact from the following description:
Combat has taken its toll on Rambo, but he's finally begun to find inner peace in a monastery. When Rambo's friend and mentor Col. Trautman asks for his help on a top secret mission to Afghanistan, Rambo declines but must reconsider when Trautman is captured.<p>You can easily swap in different instructions to change the tone or style of what the LLM generates by tweaking the instruction parameter. And because the prompt is just another <strong>ES|QL</strong> expression, you can compose it with any string-generating function—whether it’s simple concatenation, conditional logic, or even formatting based on your document content.</p><h3>Step 3: Generate text using the LLM</h3><p>Finally, we pass the prompt to the inference endpoint connected to our model using our new <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/completion"><code>COMPLETION</code></a><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/completion"> command</a>, and select which fields to return:</p><p>The result? A Chuck Norris fact, rooted in your movie data without any extra tooling required.</p><p>This example also demonstrates the full power of ES|QL's piped structure. Each step flows naturally into the next, letting you express a full retrieval augmented generation (RAG) pipeline in a single, declarative query. It’s clean, composable, and stays entirely inside Elasticsearch.</p><h2>What’s next?</h2><p>While the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/completion">COMPLETION command</a> is still a tech preview, this new feature unlocks a whole new world of possibilities—from summarization and content generation to enrichment and storytelling. Try it yourself! Point it at your favorite movie, tweak the prompt, or go wild and generate haikus from SQL errors. The power is yours.</p><p>Let us know what you build! 💬</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-completion-command-llm-fact-generator</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-completion-command-llm-fact-generator</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Aurélien Foucret]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbca7a25e3c272aa8/6a17ffb1be60868d100049d2/6494d88d51edf6a5b31a92b8439792354eae7190-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Thu, 28 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[RAG and the value of grounding in Elasticsearch]]></title>
    <description><![CDATA[Learn about RAG, grounding, and how to reduce hallucinations by connecting an LLM to your documents.]]></description>
    <content:encoded><![CDATA[<p>Large language models (<a href="https://www.elastic.co/what-is/large-language-models">LLMs</a>) are able to generate coherent answers, but when you need real and updated information, they might hallucinate (make up data) and give unreliable answers. To prevent this, we use grounding to provide the models with specialized, use-case-specific, and context-relevant information that goes beyond the LLM’s training.</p><p><em><strong>Grounding</strong></em> is the process by which you connect specific data sources to a model to “ground” it to truthful content instead of only relying on the patterns learned during the model’s training, thus giving more reliable and accurate answers.</p><p>Grounding helps reduce model hallucinations, generate responses based on your data sources, and allows you to examine the answers by providing citations for them.</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/_retrieval_augmented_generation.html"><em>Retrieval Augmented Generation</em></a>(RAG) is a <em><strong>grounding</strong></em> technique where you use search algorithms to retrieve relevant information from external sources, then you use that information as context for the LLM, and finally the model uses the augmented context together with their original training data to generate an answer.</p><p>Flow diagram of how RAG works:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f44f3376a784119/6a170b718b73cb357818a04f/b542f2e6e2af19e59c8877aa5dfbe3b85c78651b-564x311.png" alt="Flow diagram of how RAG works" /><p>RAG allows you to easily scale by updating or expanding the external data sources the model has access to. It is also a cost-effective <a href="https://www.elastic.co/search-labs/blog/rag-vs-fine-tuning">alternative to fine-tuning</a> LLMs since you can just add data without extensive customization.</p><p>And since RAG can access and utilize up-to-date information, it is ideal for use cases when the latest information is key.</p><h2>Hallucination example</h2><p>For this example, we’ll use DeepSeek and ask, <em><strong>“Who is the author who won the Chilean National Literature Prize in 1932?”</strong></em> This is a tricky question since the prize was created in 1942. Let’s see how the model answers:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte368ecbb808e84a1/6a170b735091682d2ee1bafa/60f86813a36f7521efce94c55124c3423e3d35ab-1600x699.png" alt="RAG hallucination example" /><p>As you can see, since the AI did not have all the information, it hallucinated and provided a made-up answer. Though the information is real in the sense that both the author and work exist, the other parts of the answer are wrong.</p><p>Now, let’s see how the model does when we ground it using RAG. For this, we will upload the Wikipedia page about the Chilean National Prize for Literature:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7abceb12479d7a59/6a170b7550916832b7e1bafe/86f8a8cb1e838650bfe946098d595bca0ae56340-1151x1600.png" alt="Grounding a model using RAG based on this document." /><p>Now, let’s ask the same question and check the answer:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bfa5bf9368b6a3a/6a170b770c4857204401aaa5/d9907035f53f6270796e88ce32205b907c2b9592-1600x865.png" alt="Asking the model questions to test RAG grounding." /><p>As you can see, with RAG we got the right answer. It says there was no prize in 1932 and asks for clarification from the user.</p><h2>Using RAG in Playground</h2><p>By using Elasticsearch, you can easily scale with only having your cluster capacity as a limit. You can use different data sources and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/es-connectors.html">connectors</a> to get access to the data you need. Additionally, you have total ownership of your data since it stays in your infrastructure and is not uploaded to a 3rd party service; if you run a local LLM, your data won’t even leave your network. Finally, you have control over search by designing the queries and how to filter data based on access control (<a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">RBAC</a>).</p><p>We will use <a href="https://www.elastic.co/guide/en/kibana/current/playground.html">Playground</a>, our low-code platform that allows you to quickly and simply create a RAG application using your Elasticsearch content.</p><p>Here’s a step-by-step guide on <a href="https://www.elastic.co/search-labs/blog/chat-with-pdf-elastic-playground">how to upload your PDFs or other documents into Playground</a>. You can also read more about it here and try Playground <a href="https://www.elastic.co/demo-gallery/ai-playground">here</a>.</p><h3>Upload the PDF</h3><p>We’ll index into Kibana the <a href="https://en.wikipedia.org/w/index.php?title=Special:DownloadAsPdf&amp;page=National_Prize_for_Literature_%28Chile%29&amp;action=show-download-screen">same PDF file</a> we provided to DeepSeek. If you followed the instructions in <a href="https://www.elastic.co/search-labs/blog/chat-with-pdf-elastic-playground">the article above</a> and created the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search-semantic-text.html">semantic_text</a> field, you’ll be creating a <a href="https://www.elastic.co/what-is/vector-database">vector database</a> with its corresponding <a href="https://www.elastic.co/what-is/vector-embedding">embeddings</a>, ready to be used.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b8b8fd1584e8f45/6a170b79a929cf0c49ae09e6/8776e1b83e3e042dd3150f706de3cd15af65dcd5-1600x1002.png" alt="Uploading the sample PDF for RAG to Elasticsearch Playground." /><h3>Ask the question</h3><p>Ask the following question:</p><p><em><strong>“Who is the author who won the Chilean National Literature Prize in 1932?”</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2824a121c0c0416/6a170b7aacf0881ccebe9b7c/382655b7858c525a36ce540c6b28bfc725703e00-765x1125.png" alt="Asking a RAG question in Elasticsearch Playground." /><p>Playground sends this query to Elasticsearch, which in turn, runs a <a href="https://www.elastic.co/what-is/semantic-search">semantic search</a> and localizes the fragments with information that is relevant to the question. Then, these fragments are included as context in the prompt sent to the LLM to ground the answer to the information source we provided.</p><p>Finally, Playground generated an answer saying that <strong>there was no prize</strong> in 1932 and provides citations for the relevant fragments as evidence.</p><p>Playground also offers two very useful features to understand the RAG system underlying components:</p><h3>Query</h3><p>You can see the query Elasticsearch is running to retrieve the relevant documents, and you can enable/disable fields based on your needs.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta24ee951f7df3924/6a170b7c4a531b153336a979/ec02d879a850d3d5519cf68391a39f0e0cd992ac-1440x534.png" alt="How Elasticsearch runs a query to retrieve relevant documents for RAG." /><h3>View code</h3><p>If you can deploy your RAG application, Playground got you covered. Under the View Code tab, you can see the code used under the hood to create the entire RAG workflow. You can choose between two Python alternatives: Elasticsearch Client with <em><strong>OpenAI</strong></em>, or a <em><strong>Langchain</strong></em> based implementation.</p><p>If you want to customize the experience and deploy the code elsewhere, you can use this snippet as a starting point.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5055df7029dee861/6a170b7ea929cffc90ae09ec/04b092ef3b75f97efffce96daecfccdce36d9e06-1439x954.png" alt="Deploying a RAG application in Elasticsearch Playground." /><h2>Conclusion</h2><p>Grounding is a process that connects LLMs to external data sources so they can go beyond their training to provide more accurate and trustworthy answers. Retrieval Augmented Generation (RAG) is a grounding method that is scalable, cost-effective, and ensures access to up-to-date information.</p><p>Tools like Playground simplify RAG implementation by enabling large-scale indexing, customized searches, and responses with citations, which allow you to easily verify an answer and make sure you’re getting accurate and trustworthy results.</p><p>If you want to read more in-depth articles about RAG features, you can start with this one to get a <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">more technical definition of RAG</a>. You can also check <a href="https://www.elastic.co/search-labs/blog/rag-vs-fine-tuning">Rag vs. fine-tuning: When RAG is the best decision</a>, <a href="https://www.elastic.co/search-labs/blog/sharepoint-federated-searches-azure">How to leverage document security using RAG</a> and <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">RAG systems in production</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/grounding-rag</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/grounding-rag</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt54210f630536d9bf/6a170b806234e080a5db1a01/8f02b6b264d8a26be3ae983a8f6d2013a21835a2-1324x742.png" length="0" type="image/png"/>
    <pubDate>Thu, 01 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[High Quality RAG with Aryn DocPrep, DocParse and Elasticsearch vector database]]></title>
    <description><![CDATA[Learn how to achieve high-quality RAG with effective data preparation using  Aryn.ai DocParse, DocPrep, and Elasticsearch vector database.]]></description>
    <content:encoded><![CDATA[<p>Organizations rely on natural language queries to gain insights from unstructured data, but achieving high-quality answers starts with effective data preparation. <a href="https://www.aryn.ai/">Aryn DocParse and DocPrep</a> streamline this process by converting complex documents into structured JSON or markdown, <a href="https://www.aryn.ai/post/an-evaluation-of-pdf-segmentation-and-layout-analysis-models">delivering up to 6x better data chunking and 2x improved recall</a> for hybrid search and Retrieval-Augmented Generation (RAG) applications. Powered by the open-source Aryn Partitioner and <a href="https://huggingface.co/Aryn/deformable-detr-DocLayNet">effective, deep learning DETR AI</a> model trained on 80K+ enterprise documents, these tools ensure higher accuracy and relevance <a href="https://www.aryn.ai/post/an-evaluation-of-pdf-segmentation-and-layout-analysis-models">compared to off-the-shelf solutions</a>.</p><p>In this blog, we’ll demonstrate how to use DocParse and DocPrep to prepare and load a dataset of complex PDFs into Elasticsearch for a RAG application. We will use ~75 PDF reports from the National Transportation Safety Board (NTSB) about aircraft incidents. An example document from the collection is <a href="https://data.ntsb.gov/carol-repgen/api/Aviation/ReportMain/GenerateNewestReport/103753/pdf">here</a>.</p><h2>What is Aryn DocParse and DocPrep</h2><p>Aryn DocParse segments and labels documents, extracts tables, and images, and does OCR – turning 30+ document types into structured JSON. It runs the open-source Aryn Partitioner and its <a href="https://huggingface.co/Aryn/deformable-detr-DocLayNet">open-source deep learning DETR AI model</a> trained on 80k+ enterprise documents. This leads to <a href="https://www.aryn.ai/post/an-evaluation-of-pdf-segmentation-and-layout-analysis-models">up to 6x more accurate data chunking and 2x improved recall</a> on hybrid search or RAG compared to off-the-shelf systems.</p><p><a href="https://docs.aryn.ai/docprep/getting_started">Aryn DocPrep</a> is a tool for creating document ETL pipelines to prepare and load this data into vector databases and hybrid search indexes like Elasticsearch. The first step in a pipeline is using DocParse to process each document. DocPrep creates Python code using <a href="https://sycamore.readthedocs.io/en/stable/">Sycamore</a>, an open-source, scalable, LLM-powered document ETL library. Though DocPrep can easily create ETL pipelines using Sycamore code, you will likely need to customize the pipeline using additional <a href="https://sycamore.readthedocs.io/en/stable/">Sycamore data transforms, chunking/merging, extraction, and cleaning functions</a>.</p><p>As can be seen, these documents are complex, containing tables, images, section headings, and complicated layouts. Let’s begin!</p><h2>Building high-quality RAG apps with effective data preparation</h2><h3>Launch an Elasticsearch vector database container</h3><p>We’ll install Elasticsearch locally using a Docker container for the demo RAG application. Follow <a href="https://github.com/elastic/start-local">these instructions</a> to deploy it.</p><h3>Prepare data for RAG using Aryn DocPrep and DocParse</h3><p>Aryn <a href="https://docs.aryn.ai/docprep/getting_started">DocPrep</a> is a tool for creating document ETL pipelines that prepare and load data into vector databases and hybrid search indexes like Elasticsearch. The first step in a pipeline is using DocParse to process each document.</p><p>We will use Aryn DocParse in Aryn Cloud to generate our initial ETL pipeline code. You can <a href="https://www.aryn.ai/get-started">sign up for free</a> to use Aryn Cloud and go to the <a href="https://console.aryn.cloud/docprep/">DocPrep UI in the Aryn Cloud console</a>.</p><p>You can also write an ETL pipeline and run a version of the Aryn Partitioner (used in DocParse) locally. <a href="https://sycamore.readthedocs.io/en/stable/">Visit the Sycamore documentation</a> to learn more.</p><h3>Create ETL pipeline with Aryn DocPrep</h3><p>DocPrep creates Python code using <a href="https://sycamore.readthedocs.io/en/stable/">Sycamore</a>, an open-source, scalable, LLM-powered document ETL library. While DocPrep can easily create ETL pipelines using Sycamore code, you may need to customize the pipeline further with additional <a href="https://sycamore.readthedocs.io/en/stable/">Sycamore data transforms, extraction, and cleaning functions</a>.</p><p>DocPrep simplifies the creation of a base ETL pipeline to prepare unstructured data for RAG and semantic search.</p><p>First, we provide the document type (PDF) and the source location of our PDFs in Amazon S3 (<code>s3://aryn-public/ntsb/</code>):</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt688c73411f105c89/6a17e25a63173062e45859f0/799421fc6a5544dbba666a81fe88845bfe5532d2-738x410.png" alt="Select document type and source" /><p></p><p>Next, we will select MiniLM for our embedding model to create our vector embeddings locally. DocPrep uses DocParse for document segmentation, extraction, and other processing, but we don’t need to change the default configuration.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b3959439d5478d1/6a17e25be31791b3a42d5759/4f84fe3e9d0130f815ecd380e011c036c5c506dd-936x452.png" alt="Select chunking options" /><p>Finally, we select Elasticsearch as our target database and add the Host URL and Index name. Note that the URL is set to “localhost” because we are running Elasticsearch locally. We will also run DocPrep/Sycamore ETL pipeline locally so it can easily load the cluster.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31d8160e920700df/6a17e25da2929981afd02bda/345545b1264db36bb113071e5baf69c51fe90fb1-936x728.png" alt="Configure Elasticsearch Connector" /><p>Now, click “Generate pipeline” to create the ETL pipeline. Click “Download notebook” on the next page to download the code as a Jupyter notebook file.</p><p></p><h3>Install Jupyter and Sycamore</h3><p>We will run the ETL pipeline locally in a Jupyter notebook and use the Sycamore document ETL library. As a first step, install Jupyter and Sycamore with the Elasticsearch connector and local inference libraries to create vector embeddings.</p>pip install jupyter
pip install 'sycamore-ai[elasticsearch,local-inference]'<p></p><h3>Run Pipeline</h3><p>Run Jupyter and open the notebook with the ETL pipeline downloaded in the earlier step.</p><p>If you haven’t set your Aryn Cloud API key as an environmental variable called <code>ARYN_API_KEY</code>, you can set it directly in the notebook.</p><p>In the second-to-last cell, update the Elasticsearch loading configuration. Replace the es_client_args from setting an Elasticsearch password to the Elasticsearch basic auth config from your container:</p><p></p>es_client_args={"basic_auth": (“&lt;YOUR-USERNAME&gt;”, os.getenv("ELASTIC_PASSWORD"))}<p></p><p>If the password isn’t set as an environment variable, you can add it directly here.</p><p>Now, run the cells in the notebook. Each of the ~75 PDFs is sent to DocParse for processing, and this step in the pipeline will take a few minutes. One of the cells will output three pages of a document with bounding boxes to show how DocParse segments the data.</p><p>The final cell runs a read query to verify if the data has been loaded correctly. Now, you can use the prepared data in the Elasticsearch index with your RAG application.</p><p></p><h3>Add additional data enrichment and transforms</h3><p>The code generated in DocPrep is great for a basic ETL pipeline, however, you may want to extract metadata and perform data cleaning. The pipeline code is fully customizable, and you can use additional transformations in Sycamore or arbitrary Python code.</p><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/Aryn-elasticsearch-RAG-data-preparation-demo/aryn-elasticsearch-blog-dataprep.ipynb">Here is an example notebook</a> with additional data transforms, metadata extraction, and data cleaning steps. You can use this metadata in your RAG applications to filter your results.</p><h2>Conclusion</h2><p>This blog used Aryn DocParse, DocPrep, and Sycamore to parse, extract, enrich, clean, embed, and load data into vector and keyword indexes in the Elasticsearch vector database. We used DocPrep to create an initial ETL pipeline and then used a notebook with additional Sycamore code to demonstrate additional data enrichment and cleaning.</p><p>How your documents are parsed, enriched, and processed significantly impacts the quality of your RAG queries. Use the examples in this blog post to quickly and easily build your own RAG systems with Aryn and Elasticsearch and iterate on the processing and retrieval strategies as you build your GenAI application.</p><p>Below are some resources for your next steps:</p><ul><li><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/Aryn-elasticsearch-RAG-data-preparation-demo/aryn-elasticsearch-blog-dataprep.ipynb">Sample notebook with Aryn DataPrep and Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local with Elasticsearch vector database</a></p></li><li><p><a href="https://www.aryn.ai/get-started">Get started with Aryn Cloud DocPrep</a></p></li><li><p><a href="https://sycamore.readthedocs.io/en/stable/">Sycamore documentation</a></p></li><li><p><a href="https://www.elastic.co/search-labs/integrations">Elasticsearch vector database ecosystem integrations</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/rag-aryn-elasticsearch-data-prep</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/rag-aryn-elasticsearch-data-prep</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Hemant Malik,Jonathan Fritz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3979255ddfc7f45/6a17e25ffaa913812f93c7cb/92c517a2e7b36122a18feee317a0215981b62b6b-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 21 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to use Elasticsearch Vector Store Connector for Microsoft Semantic Kernel for AI Agent development]]></title>
    <description><![CDATA[Microsoft Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase. With the release of Semantic Kernel Elasticsearch Vector Store Connector, developers using Semantic Kernel for building AI agents can now plugin Elasticsearch as a scalable enterprise-grade vector store while continuing to use Semantic Kernel abstractions.]]></description>
    <content:encoded><![CDATA[<p>In collaboration with the <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">Microsoft Semantic Kernel</a> team, we are announcing the availability of <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector</a>, for <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">Microsoft Semantic Kernel</a> (.NET) users. Semantic Kernel simplifies building enterprise-grade AI agents, including the capability to enhance large language models (LLMs) with more relevant, data-driven responses from a Vector Store. Semantic Kernel provides a seamless abstraction layer for interacting with Vector Stores like Elasticsearch, offering essential features such as creating, listing, and deleting collections of records and uploading, retrieving, deleting individual records.</p><p>The <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">out-of-the-box Semantic Kernel Elasticsearch Vector Store Connector</a> supports the Semantic Kernel <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">vector store abstractions</a> which make it very easy for developers to plugin Elasticsearch as a vector store while building AI agents.</p><p>Elasticsearch has a strong foundation in the open-source community and recently adopted the <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">AGPL license</a>. Combined with the open-source Microsoft Semantic Kernel, these tools offer a powerful, enterprise-ready solution. You can get started locally by spinning up Elasticsearch in a few minutes by running this command <code>curl -fsSL https://elastic.co/start-local | sh </code>(refer <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a> for details) and move to <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">cloud-hosted</a> or <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">self-hosted</a> versions while productionizing your AI agents.</p><p>In this blog we look at how to use <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector</a> when using Semantic Kernel. A Python version of the connector will be made available in the future.</p><h2>High-level scenario: Building a RAG app with Semantic Kernel &amp; Elasticsearch</h2><p>In the following section we go through an example. At a high-level we are building a RAG (Retrieval Augmented Generation) application which takes a user's question as input and returns an answer. We will use Azure OpenAI (<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">local LLM</a> can be used as well) as the LLM, Elasticsearch as the vector store and Semantic Kernel (.net) as the framework to tie all components together.</p><p>If you are not familiar with RAG architectures, you can have a quick introduction with this article: <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag</a>.</p><p>The answer is generated by the LLM which is fed with context, relevant to the question, retrieved from Elasticsearch vectorstore. The response also includes the source that was used as the context by the LLM.</p><h3>RAG example</h3><p>In this specific example, we build an application that allows users to ask questions about hotels stored in an internal hotel database. The user could e.g. search for a specific hotel, based on different criteria, or ask for a list of hotels.</p><p>For the example database, we generated a <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">list of hotels</a> containing 100 entries. The sample size is intentionally small to allow you to try out the connector demo as easily as possible. In a real-world application, the Elasticsearch connector would show its advantages over other options, such as the `InMemory` vector store implementation, especially when working with extremely large amounts of data.</p><p>The complete demo application can be found in the Elasticsearch vector store connector <a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">repository</a>.</p><p>Let’s start with adding the required NuGet packages and using directives to our project:</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>We can now create our data model and provide it with Semantic Kernel specific attributes to define the storage model schema and some hints for the text search:</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>The Storage Model Schema attributes (`VectorStore*`) are most relevant for the actual use of the Elasticsearch Vector Store Connector, namely:</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> to mark a property on a record class as the key under which the record is stored in a vector store.</p></li><li><p><code>VectorStoreRecordData</code> to mark a property on a record class as 'data'.</p></li><li><p><code>VectorStoreRecordVector</code> to mark a property on a record class as a vector.</p></li></ul><p>All of these attributes accept various optional parameters that can be used to further customize the storage model. In the case of <code>VectorStoreRecordKey </code>, for example, it is possible to specify a different distance function or a different index type.</p><p>The text search attributes (<code>TextSearch*</code>) will be important in the last step of this example. We will come back to them later.</p><p>In the next step, we initialize the Semantic Kernel engine and obtain references to the core services. In a real world application, <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">dependency injection</a> should be used instead of directly accessing the service collection. The same thing applies to the hardcoded configuration and secrets, which should be read using a <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">configuration provider</a> instead:</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>The <code>vectorStoreCollection</code> service can now be used to create the collection and to ingest a few <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">demo records</a>:</p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>This shows how Semantic Kernel reduces the use of a vector store with all its complexity to a few simple method calls.</p><p>Under the hood, a new index is created in Elasticsearch and all the necessary property mappings are created. Our data set is then mapped completely transparently into the storage model and finally stored in the index. Below is how the mappings look in Elasticsearch.</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p>The <code>embeddings.GenerateEmbeddingsAsync()</code> calls transparently called the configured Azure AI Embeddings Generation service.</p><p>Even more magic can be observed in the last step of this demo.</p><p>With just a single call to <code>InvokePromptAsync</code>, all of the following operations are performed when the user asks a question about the data:</p><p>1. An embedding for the user's question is generated</p><p>2. The vector store is searched for relevant entries</p><p>3. The results of the query are inserted into a prompt template</p><p>4. The actual query in the form of the final prompt is sent to the AI chat completion service</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>Remember the <code>TextSearch*</code> attributes, we previously defined on our data model? These attributes enable us to use corresponding placeholders in our prompt template which are automatically populated with the information from our entries in the vector store.</p><p>The final response to our question "Please show me all hotels that have a rooftop bar." is as follows:</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>The answer correctly refers to the following entry in our hotels.csv</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>This example shows very well how the use of Microsoft Semantic Kernel achieves a significant reduction in complexity through its well thought abstractions, as well as enabling a very high level of flexibility. By changing a single line of code, for example, the vector store or the AI services used can be replaced without having to refactor any other part of the code.</p><p>At the same time, the framework provides an enormous set of high-level functionality, such as the `InvokePrompt` function, or the template or search plugin system.</p><p>The complete demo application can be found in the Elasticsearch vector store connector repository.</p><h2>What else is possible with Elasticsearch</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch new semantic_text mapping: Simplifying semantic search</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">Semantic reranking in Elasticsearch with retrievers</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">Advanced RAG techniques part 1: Data processing</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">Advanced RAG techniques part 2: Querying and testing</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Building RAG with Llama 3 open-source and Elastic</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">A tutorial on building local agent using LangGraph, LLaMA3 and Elasticsearch vector store from scratch</a></p></li></ul><h2>Elasticsearch &amp; Semantic Kernel: What's next?</h2><ul><li><p>We showed how the Elasticsearch vector store can be easily plugged into Semantic Kernel while building GenAI applications in .NET. Stay tuned for a Python integration next.</p></li><li><p>As Semantic Kernel builds abstractions for advanced search features like <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">hybrid search</a>, the Elasticsearch connect will enable .NET developers to easily implement them while using Semantic Kernel.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch open inference API for Anthropic’s Claude]]></title>
    <description><![CDATA[Interact with Anthropic's Claude 3.5 Sonnet and other models to generate content and perform question &amp; answering.]]></description>
    <content:encoded><![CDATA[<p>We are excited to announce our latest addition to the Elasticsearch Open <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-apis.html">Inference API</a>: the integration of Anthropic's Claude. This work enables Elastic users to connect directly with the Anthropic platform, and use large language models like Claude 3.5 Sonnet to build GenAI applications with use cases such as question answering. Previously customers could access this capability from providers like Amazon Bedrock, but now can utilize their Anthropic account for these purposes.</p><h2>Using Anthropic’s messages to answer questions</h2><p>In this blog, we’ll use the Claude Messages API to answer questions during ingestion to have answers ready ahead of searching. Before we start interacting with Elasticsearch, make sure you have an Anthropic API key by creating an <a href="https://console.anthropic.com/login">evaluation account</a> first and <a href="https://console.anthropic.com/settings/keys">generating a key</a>. We’ll use <a href="https://www.elastic.co/guide/en/kibana/current/console-kibana.html">Kibana's Console</a> to execute these next steps in Elasticsearch without setting up an IDE.</p><p>First, we configure an inference endpoint, which will interact with Anthropic’s messages API:</p>PUT _inference/completion/anthropic_completion
{
  "service": "anthropic",
  "service_settings": {
    "api_key": "&lt;api key&gt;",
    "model_id": "claude-3-5-sonnet-20240620"
  },
  "task_settings": {
    "max_tokens": 1024
  }
}
<p>We’ll get back a response similar to the following with status code <code>200 OK</code> on successful inference endpoint creation:</p>{
  "model_id": "anthropic_completion",
  "task_type": "completion",
  "service": "anthropic",
  "service_settings": {
    "model_id": "claude-3-5-sonnet-20240620",
    "rate_limit": {
      "requests_per_minute": 50
    }
  },
  "task_settings": {
    "max_tokens": 1024
  }
}
<p>We can now call the configured endpoint to perform completion on any text input. Let’s ask the model for a short description of GenAI:</p>POST _inference/completion/anthropic_completion
{
  "input": "What is a short description of GenAI?"
}
<p>We should get a response back with a status code <code>200 OK</code> providing a short description of GenAI:</p>{
  "completion": [
    {
      "result": "GenAI, short for Generative Artificial Intelligence, refers to AI systems that can create new content, such as text, images, audio, or video, based on patterns learned from existing data. These systems use advanced machine learning techniques, often involving deep neural networks, to generate human-like outputs in response to prompts or inputs. GenAI has diverse applications across industries, including content creation, design, coding, and problem-solving."
    }
  ]
}
<p>Now we can set up a catalog of questions which we want to be answered during ingestion. We’ll use the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Elasticsearch Bulk API</a> to index these questions about Elastic products:</p>POST _bulk
{ "index" : { "_index" : "questions" } }
{"question": "What is Elasticsearch?"}
{ "index" : { "_index" : "questions" } }
{"question": "What is Kibana?"}
{ "index" : { "_index" : "questions" } }
{"question": "What is Logstash?"}
<p>A response similar to the one below should be returned upon successful indexing:</p>{
  "errors": false,
  "took": 1552829728,
  "items": [
    {
      "index": {
        "_index": "questions",
        "_id": "ipR_qJABkw3SJM5Tm3IC",
        "_version": 1,
        "result": "created",
        "_shards": {
          "total": 2,
          "successful": 1,
          "failed": 0
        },
        "_seq_no": 0,
        "_primary_term": 1,
        "status": 201
      }
    },
    {
      "index": {
        "_index": "questions",
        "_id": "i5R_qJABkw3SJM5Tm3IC",
        "_version": 1,
        "result": "created",
        "_shards": {
          "total": 2,
          "successful": 1,
          "failed": 0
        },
        "_seq_no": 1,
        "_primary_term": 1,
        "status": 201
      }
    },
    {
      "index": {
        "_index": "questions",
        "_id": "jJR_qJABkw3SJM5Tm3IC",
        "_version": 1,
        "result": "created",
        "_shards": {
          "total": 2,
          "successful": 1,
          "failed": 0
        },
        "_seq_no": 2,
        "_primary_term": 1,
        "status": 201
      }
    }
  ]
}
<p>We’ll now create our question and answering <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html">ingest pipeline</a> using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/script-processor.html">script</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html">inference</a>, and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/remove-processor.html">remove</a> processors:</p>PUT _ingest/pipeline/question_answering_pipeline
{
  "processors": [
    {
      "script": {
        "source": "ctx.prompt = 'Please answer the following question: ' + ctx.question"
      }
    },
    {
      "inference": {
        "model_id": "anthropic_completion",
        "input_output": {
          "input_field": "prompt",
          "output_field": "answer"
        }
      }
    },
    {
      "remove": {
        "field": "prompt"
      }
    }
  ]
}
<p>The pipeline prefixes the <code>question</code> field with the text: <code>“Please answer the following question: “</code> in a temporary field called <code>prompt</code>. The content of the temporary <code>prompt</code> field is sent to the Anthropic service via the inference API. Using an ingest pipeline provides extensive flexibility as you can set the pre-prompt to fit your needs. This approach can be used to summarize documents as well.</p><p>Next, we’ll send our documents containing the questions through the question and answering pipeline by calling the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html">reindex API</a>.</p>POST _reindex
{
  "source": {
    "index": "questions",
    "size": 50
  },
  "dest": {
    "index": "answers",
    "pipeline": "question_answering_pipeline"
  }
}
<p>We should get back a response similar to the following:</p>{
  "took": 9571,
  "timed_out": false,
  "total": 3,
  "updated": 0,
  "created": 3,
  "deleted": 0,
  "batches": 1,
  "version_conflicts": 0,
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "throttled_millis": 0,
  "requests_per_second": -1,
  "throttled_until_millis": 0,
  "failures": []
}
<p>In a production setup, you’ll likely use another ingestion mechanism to ingest your documents in an automated manner. Check out our <a href="https://www.elastic.co/guide/en/cloud/current/ec-cloud-ingest-data.html">Adding data to Elasticsearch guide</a> to learn more about the various options offered by Elastic to ingest data into Elasticsearch. We’re also committed to showcasing ingest mechanisms and providing guidance on bringing data into Elasticsearch using 3rd party tools. For example, take a look at <a href="https://www.elastic.co/search-labs/blog/data-ingestion-from-snowflake-to-elasticsearch-using-meltano">Ingest Data from Snowflake to Elasticsearch using Meltano: A developer’s journey</a> to see how to use Meltano for ingesting data.</p><p>We can now search for our pre-generated answers using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API</a>:</p>POST answers/_search
{
  "query": {
    "match_all": {}
  }
}
<p>The response will contain the pre-generated answers:</p>{
  "took": 11,
  "timed_out": false,
  "_shards": { ... },
  "hits": {
    "total": { ... },
    "max_score": 1.0,
    "hits": [
      {
        "_index": "answers",
        "_id": "4RO6YY8Bv2OsAP2iNusn",
        "_score": 1.0,
        "_ignored": [
          "answer.keyword"
        ],
        "_source": {
          "model_id": "azure_openai_completion",
          "question": "What is Elasticsearch?",
          "answer": "Elasticsearch is an open-source, RESTful, distributed search and analytics engine built on Apache Lucene. It can handle a wide variety of data types, including textual, numerical, geospatial, structured, and unstructured data. Elasticsearch is scalable and designed to operate in real-time, making it an ideal choice for use cases such as application search, log and event data analysis, and anomaly detection."
        }
      },
      { ... },
      { ... }
    ]
  }
}
<p>Pre-generating answers for frequently asked questions is particularly effective in reducing operational costs. By minimizing the need for on-the-fly response generation, you can significantly cut down on the amount of computational resources required. Additionally, this method ensures that every user receives the same precise information. Consistency is critical, especially in fields requiring high reliability and accuracy such as medical, legal, or technical support.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-anthropic-support</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-anthropic-support</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jonathan Buttner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d3c715e5832af68/6a1712292b835f4c7bf4b33f/d030a3b1f4c719792c5c11ba06f6547d2197343c-1440x810.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 26 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ChatGPT and Elasticsearch revisited: Building a chatbot using RAG]]></title>
    <description><![CDATA[Learn how to create a chatbot using ChatGPT and Elasticsearch, utilizing all of the newest RAG features.]]></description>
    <content:encoded><![CDATA[<p>Follow up to the blog <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT and Elasticsearch: OpenAI meets private data</a>.</p><p>In this blog, you will learn how to:</p><ul><li><p>Create an Elasticsearch Serverless project</p></li><li><p>Create an Inference Endpoint to generate embeddings with ELSER</p></li><li><p>Use a Semantic Text field for auto-chunking and calling the Inference Endpoint</p></li><li><p>Use the Open Crawler to crawl blogs</p></li><li><p>Connect to an LLM using Elastic’s Playground to test prompts and context settings for a RAG chat application.</p></li></ul><p>If you want to jump right into the code, you can view the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/rag-ties-the-app-together/ChatGPT_and_Elasticsearch__The_RAG_Really_Tied_the_App_Together.ipynb">accompanying Jupyter Notebook here</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2779150d12271ef1/6a1711e460084bdf023c4688/e0e3d205d5b4cb58c4b4b5f22aab57c8ef659ed6-1440x807.png" alt="The Dude Abides" /><h2>ChatGPT and Elasticsearch (April 2023)</h2><p>A lot has changed since I wrote the initial <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT and Elasticsearch: OpenAI meets private data</a>. Most people were just playing around with ChatGPT, if they had tried it at all. And every booth at every tech conference didn’t feature the letters “AI” (whether it is a useful fit or not).</p><h2>Updates in Elasticsearch (August 2024)</h2><p>Since then, Elastic has embraced being a full featured vector database and is putting a lot of engineering effort into making it the best vector database option for anyone building a search application. So as not to spend several pages talking about all the enhancements to Elasticsearch, here is a non-exhaustive list in no particular order:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/introducing-elser-v2-part-1">ELSER - The Elastic Learned Sparse Encoder</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/building-elastic-cloud-serverless">Elastic Serverless Service</a> was built and is in public beta</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-completion-support">Elasticsearch open Inference API</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-amazon-bedrock-support">Embeddings</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-openai-completion-support">Chat completion</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank">Semantic rerankers</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Semantic_text type</a> - Simplify semantic search</p><ul><li><p>Automatic chunking</p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/rag-playground-introduction">Playground</a> - Visually experiment with RAG application building in Elasticsearch</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">Retrievers</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elastic-open-crawler-release">Open web crawler</a></p></li></ul><p>With all that change and more, the original blog needs a rewrite. So let’s get started.</p><h2>Updated flow: ChatGPT, Elasticsearch &amp; RAG</h2><p>The plan for this updated flow will be:</p><ol><li><p>Setup  </p><ol><li><p>Create a new Elasticsearch serverless search project</p></li><li><p>Create an embedding inference API using ELSER</p></li><li><p>Configure an index template with a <code>semantic_text</code> field</p></li><li><p>Create a new LLM connector</p></li><li><p>Configure a chat completion inference service using our LLM connector</p></li></ol></li><li><p>Ingest and Test</p><ol><li><p>Crawl the Elastic Labs sites (Search, Observability, Security) with the Elastic Open Web Crawler.</p></li><li><p>Use Playground to test prompts using our indexed Labs content</p></li></ol></li><li><p>Configure and deploy our App </p><ol><li><p>Export the generated code from Playground to an application using FastAPI as the backend and React as the front end.</p></li><li><p>Run it locally</p></li><li><p>Optionally deploy our chatbot to Google Cloud Run</p></li></ol></li></ol><h2>Setup</h2><h3>Elasticsearch Serverless Project</h3><p>We will be using an Elastic serverless project for our chatbot. Serverless removes much of the complexity of running an Elasticsearch cluster and lets you focus on actually using and gaining value from your data. Read more about the <a href="https://www.elastic.co/search-labs/blog/building-elastic-cloud-serverless">architecture of Serverless here</a>.</p><p>If you don’t have an Elastic Cloud account, you can create a free two-week trial at <a href="https://cloud.elastic.co/registration">elastic.co</a> (Serverless pricing <a href="https://www.elastic.co/pricing/serverless-search">available here</a>). If you already have one, you can simply log in.</p><p>Once logged in, you will need to <a href="https://cloud.elastic.co/account/keys">create a cloud API key</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt03e19072d48a28bb/6a1711e5dc55def695e00f03/d8121ed3d0fb4bbd5927a78aee20619589106df8-1300x1920.png" alt="alt_text" /><p><strong>NOTE: In the steps below, I will show the relevant parts of Python code. For the sake of brevity, I’m not going to show complete code that will import required libraries, wait for steps to complete, catch errors, etc.</strong></p><p><strong>For more robust code you can run, please see the </strong><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/rag-ties-the-app-together/ChatGPT_and_Elasticsearch__The_RAG_Really_Tied_the_App_Together.ipynb"><strong>accompanying Jypyter notebook</strong></a><strong>!</strong></p><h3>Create Serverless Project</h3><p>We will use our newly created API key to perform the next setup steps.</p><p>First off, create a new Elasticsearch project.</p>url = "https://api.elastic-cloud.com/api/v1/serverless/projects/elasticsearch" 

project_data = {
    "name": "The RAG Really Tied the App Together",
    "region_id": "aws-us-east-1",
    "optimized_for": "vector"
}

auth_header = f"ApiKey {api_key}"  # seeing what a comment lokos like with pound
headers = {
    "Content-Type": "application/json",
    "Authorization": auth_header
}

es_project = requests.post(url, json=project_data, headers=headers)  :four:
<ul><li><p><code>url</code> - This is the standard Serverless endpoint for Elastic Cloud</p></li><li><p><code>project_data</code> - Your Elasticsearch Serverless project settings </p><ul><li><p><code>name</code> - Name we want for the project</p></li><li><p><code>region_id</code> - Region to deploy</p></li><li><p><code>optimized_for</code> - Configuration type - We are using <code>vector</code> which isn’t strictly required for the ELSER model but can be suitable if you select a dense vector model such as e5.</p></li></ul></li></ul><h3>Create Elasticsearch Python client</h3><p>One nice thing about creating a programmatic project is that you will get back the connection information and credentials you need to interact with it!</p>es = Elasticsearch(es_project_keys['endpoints']['elasticsearch'],
                   basic_auth=(es_project_keys['credentials']['username'],
                              es_project_keys['credentials']['password']
                              )
                   )
<h3>ELSER Embedding API</h3><p>Once the project is created, which usually takes less than a few minutes, we can prepare it to handle our labs’ data.</p><p>The first step is to configure the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/put-inference-api.html#inference-example-elser">inference API for embedding</a>. We will be using the <a href="https://www.elastic.co/search-labs/blog/introducing-elser-v2-part-2">Elastic Learned Sparse Encoder</a> (ELSER).</p><ul><li><p>Command to create the inference endpoint</p></li><li><p>Specify this endpoint will be for generating sparse embeddings</p></li></ul>model_config = {
    "service": "elser",
    "service_settings": {
        "num_allocations": 8,
        "num_threads": 1
    }
}

inference_id = "my-elser-model"

create_endpoint = es.inference.put_model(
    inference_id=inference_id,
    task_type="sparse_embedding",
    body=model_config
)
<ul><li><p><code>model_config</code> - Settings we want to use for deploying our semantic reranking model </p><ul><li><p><code>service</code> - Use the pre-defined <code>elser</code> inference service</p></li><li><p><code>service_settings.num_allocations</code> - <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-model.html">Deploy the model</a> with 8 allocations</p></li><li><p><code>service_settings.num_threads</code> - Deploy with one thread per allocation</p></li></ul></li><li><p><code>inference_id</code> - The name you want to give to you inference endpoint</p></li><li><p><code>task_type</code>- Specifies this endpoint will be for generating sparse embeddings</p></li></ul><p>This single command will trigger Elasticsearch to perform a couple of tasks:</p><ol><li><p>It will download the ELSER model.</p></li><li><p>It will deploy (start) the ELSER model with eight allocations and one thread per allocation.</p></li><li><p>It will create an inference API we use in our field mapping in the next step.</p></li></ol><h3>Index Mapping</h3><p>With our ELSER API created, we will create our index template.</p>template_body = {
    "index_patterns": ["elastic-labs*"],
    "template": {
        "mappings": {
            "properties": {
                "body": {
                    "type": "text",
                    "copy_to": "semantic_body"
                },
                "semantic_body": {
                    "type": "semantic_text",
                    "inference_id": "my-elser-model"
                },
                "headings": {
                    "type": "text"
                },
                "id": {
                    "type": "keyword"
                },
                "meta_description": {
                    "type": "text"
                },
                "title": {
                    "type": "text"
                }
            }
        }
    }
}

template_resp = es.indices.put_index_template(  :eight:
    name="labs_template",
    body=template_body
)
<ul><li><p><code>index_patterns</code> - The pattern of indices we want this template to apply to.</p></li><li><p><code>body</code> - The main content of a web page the crawler collects will be written to</p><ul><li><p><code>type</code> - It is a text field</p></li><li><p><code>copy_to</code> - We need to copy that text to our semantic text field for semantic processing</p></li></ul></li><li><p><code>semantic_body</code> is our <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">semantic text field</a> </p><ul><li><p>This field will automatically handle chunking of long text and generating embeddings which we will later use for semantic search</p></li><li><p><code>inference_id</code> specifies the name of the inference endpoint we created above, allowing us to generate embeddings from our ELSER model</p></li></ul></li><li><p><code>headings</code> - Heading tags from the html</p></li><li><p><code>id</code> - crawl id for this document</p></li><li><p><code>meta_description</code> - value of the description meta tag from the html</p></li><li><p><code>title</code> is the title of the web page the content is from</p></li></ul><p>Other fields will be indexed but auto-mapped. The ones we are focused on pre-defining in the template will not need to be both keyword and text type, which is defined automatically otherwise.</p><p>Most importantly, for this guide, we must define our <code>semantic_text</code> field and set a source field to copy from with <code>copy_to</code>. In this case, we are interested in performing semantic search on the body of the text, which the crawler indexes into the <code>body</code>.</p><h2>Crawl All the Labs!</h2><p>We can now install and configure the crawler to crawl the Elastic * Labs. We will loosely follow the excellent guide from the <a href="https://www.elastic.co/search-labs/blog/elastic-open-crawler-release#how-do-i-use-it">Open Crawler released for tech-preview</a> Search Labs blog.</p><p>The steps below will use docker and run on a MacBook Pro. To run this with a different setup, consult the <a href="https://github.com/elastic/crawler?tab=readme-ov-file#elastic-open-web-crawler">Open Crawler Github readme</a>.</p><h3>Clone the repo</h3><p>
Open the command line tool of your choice. I’ll be using Iterm2. Clone the <a href="https://github.com/elastic/crawler">crawler repo</a> to your machine.</p>~/repos
❯ git clone git@github.com:elastic/crawler.git
Cloning into 'crawler'...
remote: Enumerating objects: 1944, done.
remote: Counting objects: 100% (418/418), done.
remote: Compressing objects: 100% (243/243), done.
remote: Total 1944 (delta 237), reused 238 (delta 170), pack-reused 1526
Receiving objects: 100% (1944/1944), 84.85 MiB | 31.32 MiB/s, done.
Resolving deltas: 100% (727/727), done.
<h3>Build the crawler container</h3><p>Run the following command to build and run the crawler.</p>docker build -t crawler-image . &amp;&amp; docker run -i -d --name crawler crawler-image
~/repos
 ❯ cd crawler
~/repos/crawler main
 ❯ docker build -t crawler-image . &amp;&amp; docker run -i -d --name crawler crawler-image

[+] Building 66.9s (6/10)                                                                                                                                                                docker:desktop-linux
 =&gt; [internal] load build definition from Dockerfile					0.0s
 =&gt; =&gt; transferring dockerfile: 333B							0.0s
 =&gt; [internal] load .dockerignore							0.0s
 =&gt; =&gt; transferring context: 2B								0.0s
 =&gt; [internal] load metadata for docker.io/library/jruby:9.4.7.0-jdk21		1.7s
 =&gt; [auth] library/jruby:pull token for registry-1.docker.io			0.0s
...
...
 =&gt; [5/5] RUN make clean install								50.7s
 =&gt; exporting to image									0.9s
 =&gt; =&gt; exporting layers									0.9s
 =&gt; =&gt; writing image sha256:6b3f4000a121e76aba76fdbbf11b53f53a3fabba61c0b7cf3fdcdb21e244f1d8	0.0s
 =&gt; =&gt; naming to docker.io/library/crawler-image					0.0s
cc6c16941de04355c050ef5f5fd0041ee7f3505b8cf8448c7223f0d2e80b5498
<h3>Configure the crawler</h3><p>Create a new YAML in your favorite editor (vim):</p>~/repos/crawler main
 ❯ vim config/elastic-labs.yml
<p>We want to crawl all the documents on the three labs’ sites, but since blogs and tutorials on those sites tend to link out to other parts of elastic.co, we need to set a couple of runs to restrict the scope. We will allow crawling the three paths for our site and then deny anything else.</p><p>Paste the following in the file and save</p>domains:
  - url: https://www.elastic.co
    seed_urls:
      - https://www.elastic.co/search-labs
      - https://www.elastic.co/observability-labs
      - https://www.elastic.co/security-labs
    crawl_rules:
      - policy: allow
        type: begins
        pattern: /search-labs
      - policy: allow
        type: begins
        pattern: /observability-labs
      - policy: allow
        type: begins
        pattern: /security-labs
      - policy:deny
        type: regex
        pattern: .*/author/.*
      - policy: deny
        type: regex
        pattern: .*

output_sink: elasticsearch
output_index: elastic-labs
max_crawl_depth: 2

elasticsearch:
  host: "https://&lt;your_serverless_project&gt;.es.&lt;region&gt;.aws.elastic.cloud"
  port: "443"
  api_key: "&lt;API Key generated above&gt;"
<p>Copy the configuration into the Docker container:</p>~/repos/crawler main ⇣
 ❯ docker cp config/elastic-labs.yml crawler:/app/config/elastic-labs.yml

Successfully copied 2.05kB to crawler:/app/config/elastic-labs.yml
<h3>Validate the domain</h3><p>Ensure the config file has no issues by running:</p> ❯ docker exec -it crawler bin/crawler validate config/elastic-labs.yml
Domain https://www.elastic.co is valid
<h3>Start the crawler</h3><p>When you first run the crawler, processing all the articles on the three lab sites may take several minutes.</p>docker exec -it crawler bin/crawler crawl config/elastic-labs.yml
~/repos/crawler/config main ⇣
 ❯ docker exec -it crawler bin/crawler crawl config/elastic-labs.yml
[crawl:6692c3b584f98612e3a465ce] [primary] Initialized an in-memory URL queue for up to 10000 URLs
[crawl:6692c3b584f98612e3a465ce] [primary] ES connections will be authorized with configured API key
[crawl:6692c3b584f98612e3a465ce] [primary] ES connections will use SSL without ca_fingerprint
[crawl:6692c3b584f98612e3a465ce] [primary] Elasticsearch sink initialized for index [elastic-labs] with pipeline [ent-search-generic-ingestion]
[crawl:6692c3b584f98612e3a465ce] [primary] Starting the crawl with up to 10 parallel thread(s)...
[crawl:6692c3b584f98612e3a465ce] [primary] Crawl status: queue_size=11, pages_visited=1, urls_allowed=12, urls_denied={}, crawl_duration_msec=847, crawling_time_msec=635.0, avg_response_time_msec=635.0, active_threads=1, http_client={:max_connections=&gt;100, :used_connections=&gt;1}, status_codes={"200"=&gt;1}
<h3>Confirm articles have been indexed</h3><p>We will confirm two ways.</p><p>First, we will look at a sample document to ensure that ELSER embeddings have been generated. We just want to look at any doc so we can search without any arguments:</p>GET elastic-labs/_search
<p>Ensure you get results and then check that the field <code>body</code> contains text and <code>semantic_body.inference.chunks.0.embeddings</code> contains tokens.</p>    "hits": [
      {
        "_index": "elastic-labs",
...
        "_source": {
          "body": "Tutorials Integrations Blog Start Free Trial Contact Sales Open navigation menu Overview ...
          "semantic_body": {
            "inference": {
              "inference_id": "my-elser-model",
              "model_settings": {
                "task_type": "sparse_embedding"
              },
              "chunks": [
                {
                  "text": "Tutorials Integrations Blog Start Free Trial Contact Sales Open navigation menu Overview ...
                  "embeddings": {
                    "##her": 2.1016746,
                    "elastic": 2.084594,
                    "##ai": 1.6336359,
                    "dock": 1.5765089,
                    ...
<p>We can check we are gathering data from each of the three sites with a <code>terms</code> aggregation:</p>GET elastic-labs/_search
{
  "size": 0,
  "aggs": {
    "url_path_dir1": {
      "terms": {
        "field": "url_path_dir1.keyword"
      }
    }
  }
}
<p>You should see results that start with one of our three site paths.</p>      "buckets": [
        {
          "key": "security-labs",
          "doc_count": 37
        },
        {
          "key": "observability-labs",
          "doc_count": 30
        },
        {
          "key": "search-labs",
          "doc_count": 6
        }
      ]
<h2>To the Playground!</h2><p>With our data ingested, chunked, and inference, we can start working on the backend application code that will interact with the LLM for our RAG app.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1ceed13b358b1bf/6a1711e767045b096445c2fd/abd9cb1460436f0e658f654f76ab90828892a671-494x144.png" alt="alt_text" /><h3>LLM Connection</h3><p>We need to configure a connection for Playground to make API calls to an LLM. As of this writing, Playground supports chat completion connections to OpenAI, AWS Bedrock, and Google Gemini. More connections are planned, so check the docs for the latest list.</p><p>When you first enter the Playground UI, click on “Connect to an LLM”</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8338faedbc15c2a5/6a1711e9961e69ce8ac4d021/ff5dbf52272a53ffe1c97136cf6bc02e0b05ff45-1146x872.png" alt="alt_text" /><p>Since I used OpenAI for the original blog, we’ll stick with that. The great thing about the Playground is that you can switch connections to a different service, and the Playground code will generate code specifically to that service’s API specification. You only need to select which one you want to use today.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt472f08d397464176/6a1711eb4a531b73db36aa9b/cf27f69cd578b936d78476bdf8ee5c387e725061-1440x480.png" alt="alt_text" /><p>In this step, you must fill out the fields depending on which LLM you wish to use. As mentioned above, since Playground will abstract away the API differences, you can use whichever supported LLM service works for you, and the rest of the steps in this guide will work the same.</p><p>If you don’t have an Azure OpenAI account or OpenAI API account, you can get one <a href="https://platform.openai.com/signup/">here</a> (OpenAI now requires a $5 minimum to fund the API account).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd63e7d871549ab9/6a1711ed1949f72e3fe7ab52/1ac508303f4cc9d9427ae039f354b8ae0ac4473d-1370x1642.png" alt="alt_text" /><p>Once you have completed that, hit “Save,” and you will get confirmation that the connector has been added. After that, you just need to select the indices we will use in our app. You can select multiple, but since all our crawler data is going into <code>elastic-labs,</code> you can choose that one.</p><p>Click “Add data sources” and you can start using Playground!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a1b97d9677c2dd4/6a1711ee0e2e496c2a41a266/365855fbca95613171777e9171d2c3dd65b11694-1128x840.png" alt="alt_text" /><p>Select the “restaurant_reviews” index created earlier.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4cdedc79bcf7e160/6a1711f01949f787f3e7ab56/4355652e648e3e69915fad0afcade2a1a55ab1f7-740x524.png" alt="alt_text" /><h2>Playing in the Playground</h2><p>After adding your data source you will be in the Playground UI.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8d1b52a4bffced1/6a1711f12b835f39adf4b329/6ad316fb6dfcd815d1f66844a2b23e02f8cf0826-1440x874.png" alt="alt_text" /><p>To keep getting started as simple as possible, we will stick with all the default settings other than the prompt. However, for more details on Playground components and how to use them, check out the <a href="https://www.elastic.co/search-labs/blog/rag-playground-introduction">Playground: Experiment with RAG applications with Elasticsearch in minutes</a> blog and the <a href="https://www.elastic.co/guide/en/kibana/current/playground.html">Playground documentation</a>.</p><p>Experimenting with different settings to fit your particular data and application needs is an important part of setting up a RAG-backed application.</p><p>The defaults we will be using are:</p><ul><li><p>Querying the <code>semantic_body</code> chunks</p></li><li><p>Using the three nearest semantic chunks as context to pass to the LLM</p></li></ul><h3>Creating a more detailed prompt</h3><p>The default prompt in Playground is simply a placeholder. Prompt engineering continues to develop as LLMs become more capable. Exploring the ever-changing world of prompt engineering is a blog, but there are a few basic concepts to remember when creating a system prompt:</p><ul><li><p>Be detailed when describing the app or service the LLM response is part of. This includes what data will be provided and who will consume the responses.</p></li><li><p>Provide example questions and responses. This technique, called <em>few-shot-prompting</em>, helps the LLM structure its responses.</p></li><li><p>Clearly state how the LLM should behave.</p></li><li><p>Specify the Desired Output Format.</p></li><li><p>Test and Iterate on Prompts.</p></li></ul><p>With this in mind, we can create a more detailed system prompt:</p>You are a helpful and knowledgeable assistant designed to assist users in querying information related to Search, Observability, and Security. Your primary goal is to provide clear, concise, and accurate responses based on semantically relevant documents retrieved using Elasticsearch.

Guidelines:

Audience:
Assume the user could be of any experience level but lean towards a technical slant in your explanations.
Avoid overly complex jargon unless it is common in the context of Elasticsearch, Search, Observability, or Security.

Response Structure:
Clarity: Responses should be clear and concise, avoiding unnecessary verbosity.
Conciseness: Provide information in the most direct way possible, using bullet points when appropriate.

Formatting: Use Markdown formatting for:
Bullet points to organize information
Code blocks for any code snippets, configurations, or commands
Relevance: Ensure the information provided is directly relevant to the user's query, prioritizing accuracy.

Content:
Technical Depth: Offer sufficient technical depth while remaining accessible. Tailor the complexity based on the user's apparent knowledge level inferred from their query.

Examples: Where appropriate, provide examples or scenarios to clarify concepts or illustrate use cases.
Documentation Links: When applicable, suggest additional resources or documentation from Elastic.co that can further assist the user.

Tone and Style:
Maintain a professional yet approachable tone.
Encourage curiosity by being supportive and patient with all user queries, regardless of complexity.

Example Queries:
"How can I optimize my Elasticsearch cluster for large-scale data?"
"What are the best practices for implementing observability in a microservices architecture?"
"How can I secure sensitive data in Elasticsearch?"
<p>Feel free to to test out different prompts and context settings to see what results you feel are best for your particular data. For more examples on advanced techiques, check out the <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">Prompt section on the two part blog Advanced RAG Techniques</a>. Again, see the <a href="https://www.elastic.co/search-labs/blog/rag-playground-introduction">Playground blog post</a> for more details on the various settings you can tweak.</p><h2>Export the Code</h2><p>Behind the scenes, Playground generates all the backend chat code we need to perform semantic search, parse the relevant contextual fields, and make a chat completion call to the LLM. No coding work from us required!</p><p>In the upper right corner click on the “View Code” button to expand the code flyout</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf523ed7f26673f46/6a1711f35091687af1e1bbee/aca43cd5554f5a35cc4a336557b0497675c044a2-962x406.png" alt="alt_text" /><p>You will see the generated python code with all the settings your configured as well as the the functions to make a semantic call to Elasticsearch, parse the results, built the complete prompt, make the call to the LLM, and parse those results.</p><p>Click the copy icon to copy the code.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt841e18f13ddd4145/6a1711f514b270564ce3c6f9/ff6a3a012c9a2f5f760efe78f7b663ae6261ec52-1440x1449.png" alt="alt_text" /><p>You can now incorporate the code into your own chat application!</p><h2>Wrapup</h2><p>A lot has changed since the first iteration of this blog over a year ago, and we covered a lot in this blog. You started from a cloud API key, created an Elasticsearch Serverless project, generated a cloud API key, configured the Open Web Crawler, crawled three Elastic Lab sites, chunked the long text, generated embeddings, tested out the optimal chat settings for a RAG application, and exported the code!</p><p><em>Where’s the UI, Vestal?</em></p><p>Be on the lookout for part two where we will integrate the playground code into a python backend with a React frontend. We will also look at deploying the full chat application.</p><p>For a complete set of code for everything above, see the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/rag-ties-the-app-together/ChatGPT_and_Elasticsearch__The_RAG_Really_Tied_the_App_Together.ipynb">accompanying Jypyter notebook</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-rag-enhancements</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-rag-enhancements</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2779150d12271ef1/6a1711e460084bdf023c4688/e0e3d205d5b4cb58c4b4b5f22aab57c8ef659ed6-1440x807.png" length="0" type="image/png"/>
    <pubDate>Mon, 19 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Vector embeddings made simple with the Elasticsearch-DSL client for Python]]></title>
    <description><![CDATA[Learn how to ingest and search dense vectors in Python using the Elasticsearch-DSL client.]]></description>
    <content:encoded><![CDATA[<p>In this article we'll take a look at the <a href="https://elasticsearch-dsl.readthedocs.io/en/latest/index.html">Elasticsearch-DSL</a> client for Python, with a focus on how it simplifies the task of building a vector search solution.</p><p>The <a href="https://github.com/miguelgrinberg/quotes">code</a> that accompanies this article implements a database of famous quotes. It includes a back end written in Python with the <a href="https://fastapi.tiangolo.com/">FastAPI</a> web framework, and a front end written in <a href="https://www.typescriptlang.org/">TypeScript</a> and <a href="https://react.dev/">React</a>. Regarding vector search, this application demonstrates how to:</p><ul><li><p>run a local Elasticsearch service using Docker,</p></li><li><p>bulk-ingest a large number of documents efficiently,</p></li><li><p>generate vector embeddings for documents as they are ingested,</p></li><li><p>leverage the power of a GPU to accelerate the generation of vector embeddings through parallelization,</p></li><li><p>run vector search queries using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">approximate kNN algorithm</a>,</p></li><li><p>aggregate results from vector search,</p></li><li><p>compare vector search results against those resulting from a standard <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html">match</a> (BM25) query.</p></li></ul><p>Below you can see a screenshot of the application. In this article you will find a detailed explanation of how the ingest and search features work. You then have the option to install and run the code on your own computer to experiment and learn!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a680a6bf95f4421/6a17122b66c4f9358ef8c157/1225861f71cf9ccbb2102216a9365dd07ff71e9c-1440x858.png" alt="Application screenshot" /><h2>What is the Elasticsearch-DSL client for Python?</h2><p>Sometimes called the "high-level" Python client, <a href="https://elasticsearch-dsl.readthedocs.io/en/latest/index.html">Elasticsearch-DSL</a> offers idiomatic (or "Pythonic") access to your Elasticsearch database, in contrast with the official (or "low-level") Python client, which provides direct access to the complete range of Elasticsearch features and endpoints.</p><p>When using Elasticsearch-DSL, the structure (or "mappings") of Elasticsearch indices are defined as classes, with a syntax that is similar to that of Python <a href="https://docs.python.org/3/library/dataclasses.html">dataclasses</a>. The documents stored in these indices are represented by instances of these classes. All the transformations that are necessary to map between Python objects and Elasticsearch documents are automatically and transparently carried out, resulting in application code that is simple and idiomatic.</p><p>To add Elasticsearch-DSL to your Python project, you can install it with <code>pip</code>:</p>pip install elasticsearch-dsl
<p>If your project is asynchronous, then there are additional dependencies that need to be installed, so in that case use the following command instead:</p>pip install "elasticsearch-dsl[async]"
<h2>Index definition</h2><p>As stated above, with Elasticsearch-DSL the structure of an Elasticsearch index is defined as a Python class. The example application featured in this article uses a dataset of famous quotes that have the following fields:</p><ul><li><p><code>quote</code>: the text of the quote, as a string</p></li><li><p><code>author</code>: the name of the author, as a string</p></li><li><p><code>tags</code>: a list of tag names that apply to the quote, each a string</p></li></ul><p>As part of this application we are going to add one additional field, the vector embedding that we will use to search for quotes:</p><ul><li><p><code>embedding</code>: a list of floating point numbers representing a vector embedding for the quote</p></li></ul><p>Let's write an initial document class to describe our famous quotes index:</p>import elasticsearch_dsl as dsl

class QuoteDoc(dsl.AsyncDocument):
    quote: str
    author: str
    tags: list[str]
    embedding: list[float]

    class Index:
        name = 'quotes'
<p>The <code>AsyncDocument</code> class that is used as a base class for our <code>QuoteDoc</code> class implements all the functionality to connect the class to an Elasticsearch index. The choice of an asynchronous document base class was made because this examples uses the FastAPI web framework, which is also asynchronous. For projects that do not use asynchronous Python, the <code>Document</code> base class must be used when declaring document classes.</p><p>The <code>name</code> attribute given in the <code>Index</code> inner class defines the name of the Elasticsearch index that will be used with documents of this class.</p><p>If you have used Python dataclasses before, you likely find the way fields are defined very familiar, with each field being given a Python type hint. These Python types are mapped to the closest Elasticsearch type, so for example, in the case of <code>str</code>, the corresponding field in the Elasticsearch index will be given the type <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html#text-field-type"><code>text</code></a>, the standard type that is used for text that needs to be indexed for full-text search, while <code>float</code> is mapped to the equally named <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html"><code>float</code></a> on the Elasticsearch side.</p><p>While it can be useful to leave the <code>quote</code> field as is so that we can use it for both vector and full-text searches, the <code>author</code> and <code>tags</code> fields do not really need all the extra work associated with full-text search. The best Elasticsearch type for these fields is <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html#keyword-field-type"><code>keyword</code></a>, which just stores the text, without doing any indexing. Likewise, the <code>embedding</code> field is not just a simple list of floating point numbers, we are going to use it for vector search, which is a behavior associated with the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html"><code>dense_vector</code></a> type in Elasticsearch.</p><p>To assign a type override to a field, we add an assignment with the <code>mapped_field()</code> function, as shown in the improved version of the <code>QuoteDoc</code> class that follows:</p>class QuoteDoc(dsl.AsyncDocument):
    quote: str
    author: str = dsl.mapped_field(dsl.Keyword())
    tags: list[str] = dsl.mapped_field(dsl.Keyword())
    embedding: list[float] = dsl.mapped_field(dsl.DenseVector(), init=False)

    class Index:
        name = 'quotes'
<p>As you can see in this updated version, the <code>elasticsearch_dsl</code> package includes classes such as <code>Keyword</code> and <code>DenseVector</code> to represent all the native Elasticsearch field types.</p><p>Did you notice the <code>init=False</code> argument given in this new definition of the <code>embedding</code> field? If you are familiar with Python dataclasses you may recognize <code>init</code> as one of the options available in the dataclasses <a href="https://docs.python.org/3/library/dataclasses.html#dataclasses.field"><code>field()</code></a> function, used to indicate that the given attribute should be omitted from the constructor for instances of the class. The behavior is the same here, which means that when creating an instance of <code>QuoteDoc</code>, this argument should not be given.</p><p>How will the vector embeddings be generated if they will not be passed down to the document constructor? Elasticsearch-DSL always calls the <code>clean()</code> method in all documents before serializing them and sending them to Elasticsearch. This method is a convenience entry point where the application can add any custom field processing logic. For example, fields that are optional or auto-generated can be added in this method. Here is the final version of the <code>QuoteDoc</code> document class, including the logic that generates the embeddings:</p>from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")

class QuoteDoc(dsl.AsyncDocument):
    quote: str
    author: str = dsl.mapped_field(dsl.Keyword())
    tags: list[str] = dsl.mapped_field(dsl.Keyword())
    embedding: list[float] = dsl.mapped_field(dsl.DenseVector(), init=False)

    class Index:
        name = 'quotes'

    def clean(self):
        if not self.embedding:
            self.embedding = model.encode(self.quote).tolist()
<p>For this example we are going to use embeddings from a <a href="https://sbert.net/">SentenceTransformers</a> model. These embeddings are easy to generate locally and being open source and free they are convenient to use when experimenting. The <a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">all-MiniLM-L6-v2</a> model is a great general purpose embedding model for English text. There are many other models that are also compatible with the SentenceTransformers framework, so feel free to use a different one if you prefer.</p><p>The <code>clean()</code> method can be used for more advanced use cases as well. For example, it is common when working with large bodies of text to split the text into smaller chunks, and then generate embeddings for each chunk. Elasticsearch accommodates this use case through nested objects. If you want to see an advanced example that implements this type of solution, check out the <a href="https://github.com/elastic/elasticsearch-dsl-py/blob/main/examples/vectors.py">vectors</a> example in the Elasticsearch-DSL repository.</p><h2>Document ingestion</h2><p>With the structure of the index in place, we can now create the index. This is done with the <code>init()</code> class method:</p>async def ingest_quotes():
    await QuoteDoc.init()
<p>In many cases it is useful to delete a previously existing index to make sure an ingest process begins from a clean starting point. This can be done using the <code>_index</code> class attribute, which provides access to the Elasticsearch index, along with its <code>exists()</code> and <code>delete()</code> methods:</p>async def ingest_quotes():
    if await QuoteDoc._index.exists():
        await QuoteDoc._index.delete()
    await QuoteDoc.init()
<p>The example dataset used by the example application is a collection of almost 37,000 famous quotes. It comes as a CSV file with the <code>quote</code>, <code>author</code> and <code>tags</code> columns. The tags are given as a comma-separated string. The dataset is available for <a href="https://raw.githubusercontent.com/miguelgrinberg/quotes/main/backend/quotes.csv">download</a> from the example GitHub repository.</p><p>To ingest the data contained in this dataset, Python's <code>csv</code> module can be used:</p>import csv

async def ingest_quotes():
    if await QuoteDoc._index.exists():
        await QuoteDoc._index.delete()
    await QuoteDoc.init()

    with open('quotes.csv') as f:
        reader = csv.DictReader(f)
        for row in reader:
            q = QuoteDoc(quote=row['quote'], author=row['author'],
                         tags=row['tags'].split(','))
            await q.save()
<p>The <code>csv.DictReader</code> class creates a CSV file importer that returns a dictionary for each row in the data file. For each row, we create a <code>QuoteDoc</code> instance and pass the <code>quote</code>, <code>author</code> and <code>tags</code> in the constructor. For the tags, the string that is read from the CSV file has to be split into a list, which is how it will be stored in the Elasticsearch index.</p><p>To write a document to the index, the <code>save()</code> method is invoked. This method will call the document's <code>clean()</code> method, which in turn will generate the vector embedding for the quote.</p><h3>Starting an Elasticsearch instance</h3><p>Before the above ingest script can be executed, you need to have access to a running instance of Elasticsearch. By far the easiest (and also 100% free) way to do this is with a <a href="https://www.docker.com/">Docker</a> container.</p><p>To start a single-node Elasticsearch service on your computer first make sure you have Docker running, and then execute the following command:</p>docker run -p 127.0.0.1:9200:9200 -d --name elasticsearch \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "xpack.license.self_generated.type=basic" \
  -v "./data:/usr/share/elasticsearch/data" \
  docker.elastic.co/elasticsearch/elasticsearch:8.15.0
<p>To make sure you are running the latest and greatest version, open the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/es-release-notes.html">release notes</a> page to find out what is the current version, then replace the version number in the last line of the above command.</p><p>The <code>-v</code> option in the command above sets up a mapping between a directory named <code>data</code> in your local system and the data directory in the Elasticsearch container. All the data files used by Elasticsearch will be saved in this directory, so that in case you need to restart your container you do not lose any data. If you prefer to not store the data files in your computer, then you can remove the <code>-v</code> line and the data will be stored ephemerally in the container.</p><p>Note that deploying Elasticsearch using this method is only adequate for local experimentation. If you intend to deploy Elasticsearch on a production server, consider using our <a href="https://www.elastic.co/blog/getting-started-with-the-elastic-stack-and-docker-compose">Elasticsearch on Docker Compose</a> or <a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-eck.html">Elasticsearch on Kubernetes</a> guides.</p><h3>Connecting to Elasticsearch</h3><p>The ingestion script needs to know how to connect to Elasticsearch. If you are running a Docker container as demonstrated in the previous section, add the following line between the imports and the definition of the <code>QuoteDoc</code> class:</p>dsl.async_connections.create_connection(hosts=['http://localhost:9200'])
<p>To complete the script, the <code>ingest_quotes()</code> function should be called. Add the following snippet at the bottom of your source file:</p>if __name__ == '__main__':
    asyncio.run(ingest_quotes())
<p>The <code>asyncio.run()</code> function will launch the asynchronous application. If your application is not asynchronous, then you would just call the ingest function directly.</p><p>For your convenience, below you can find the complete code for the script up to this point. You can save this file as <em>search.py</em>. You can find an example of this file <a href="https://github.com/miguelgrinberg/quotes/blob/main/backend/search.py">here</a>.</p>import asyncio
import csv
import elasticsearch_dsl as dsl
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")
dsl.async_connections.create_connection(hosts=['http://localhost:9200'], serializer=OrjsonSerializer())


class QuoteDoc(dsl.AsyncDocument):
    quote: str
    author: str = dsl.mapped_field(dsl.Keyword())
    tags: list[str] = dsl.mapped_field(dsl.Keyword())
    embedding: list[float] = dsl.mapped_field(dsl.DenseVector(), init=False)

    class Index:
        name = 'quotes'

    def clean(self):
        if not self.embedding:
            self.embedding = model.encode(self.quote).tolist()

async def ingest_quotes():
    if await QuoteDoc._index.exists():
        await QuoteDoc._index.delete()
    await QuoteDoc.init()

    with open('quotes.csv') as f:
        reader = csv.DictReader(f)
        for row in reader:
            q = QuoteDoc(quote=row['quote'], author=row['author'],
                         tags=row['tags'].split(','))
            await q.save()

if __name__ == '__main__':
    asyncio.run(ingest_quotes())
<p>Create a virtual environment for your project using the tool of your choice, and then install the dependencies on it:</p>pip install "elasticsearch-dsl[async]" sentence-transformers
<p>Make sure you have the <a href="https://raw.githubusercontent.com/miguelgrinberg/quotes/main/backend/quotes.csv">quotes.csv</a> file in the current directory, and then start the ingest by running the script:</p>python search.py
<p>The script does not print anything, so it will run for a while adding the quotes from the CSV file into your Elasticsearch index. The file has about 37,000 quotes, so expect the process to run for several minutes.</p><p>Luckily you do not need to wait that long. If you start the script and no error appears, that is confirmation that everything is working. You can press Ctrl-C to stop it and continue reading to learn about ingest performance.</p><h3>Performance tuning part 1: bulk processing</h3><p>If your dataset is small, then the above ingest solution will work just fine, and it has the benefit that it is simple to code and easy to understand.</p><p>For larger ingest jobs, however, it is necessary to sacrifice code clarity and pay attention to performance, so let's see what optimizations can be done in this application.</p><p>First of all, to evaluate performance we need to be able to measure the performance of the existing solution. Below is the updated <code>ingest_quotes()</code> function, which now calls <code>ingest_progress()</code> every 100 ingested documents to show how many documents have been ingested, along with an average document per second.</p>from time import time

# ...

def ingest_progress(count, start):
    elapsed = time() - start
    print(f'\rIngested {count} quotes. ({count / elapsed:.0f}/sec)', end='')

async def ingest_quotes():
    if await QuoteDoc._index.exists():
        await QuoteDoc._index.delete()
    await QuoteDoc.init()

    with open('quotes.csv') as f:
        reader = csv.DictReader(f)
        count = 0
        start = time()
        for row in reader:
            q = QuoteDoc(quote=row['quote'], author=row['author'],
                         tags=row['tags'].split(','))
            await q.save()
            count += 1
            if count % 100 == 0:
                ingest_progress(count, start)
        ingest_progress(count, start)

# ...
<p>This version of the ingest is nicer than the previous one because it prints regular status updates. If you let the script run for a while you may see an output similar to the one below:</p>❯ python search.py
Ingested 4900 quotes. (97/sec)
<p>The data file has close to 37,000 quotes, so now you can have a good idea of how long the ingest will take. Assuming the average of 97 ingested documents per second holds throughout the entire ingest job, it should take less than 7 minutes to ingest the entire dataset. You can press Ctrl-C to stop this ingest process, there is no need to let it run to completion yet.</p><p>Elasticsearch offers a very flexible bulk ingest feature, which is made available in the Elasticsearch-DSL package's <code>bulk()</code> method. Instead of saving each document, the entire import loop can be moved into a generator function which is given to the <code>bulk()</code> method as an argument:</p>async def ingest_quotes():
    if await QuoteDoc._index.exists():
        await QuoteDoc._index.delete()
    await QuoteDoc.init()

    async def get_next_quote():
        with open('quotes.csv') as f:
            reader = csv.DictReader(f)
            count = 0
            start = time()
            for row in reader:
                q = QuoteDoc(quote=row['quote'], author=row['author'],
                             tags=row['tags'].split(','))
                yield q
                count += 1
                if count % 100 == 0:
                    ingest_progress(count, start)
            ingest_progress(count, start)

    await QuoteDoc.bulk(get_next_quote())
<p>Here the <code>get_next_quote()</code> inner generator function yields <code>QuoteDoc</code> instances. The <code>QuoteDoc.bulk()</code> method will run the generator and issue batch updates to Elasticsearch. With this change, you can expect to see a small speed improvement:</p>❯ python s.py
Ingested 5500 quotes. (108/sec)
<p>For another small improvement, the JSON serializer used by the Elasticsearch client can be changed to the <a href="https://pypi.org/project/orjson/">orjson</a> library, which performs better than Python's own:</p>from elasticsearch import OrjsonSerializer
# ...

dsl.async_connections.create_connection(hosts=['http://localhost:9200'],
                                        serializer=OrjsonSerializer())

# ...
<p>This should lead to another small performance improvement:</p>❯ python s.py
Ingested 5100 quotes. (111/sec)
<h3>Performance tuning part 2: GPU accelerated embeddings</h3><p>You have seen in the previous section that we have obtained some modest performance improvements by processing ingest requests in bulk. But while ingestion requests are now being grouped, the embeddings continue to be generated one by one in the <code>clean()</code> method of the <code>QuoteDoc</code> class.</p><p>Is there a way to optimize embedding generation? The SentenceTransformers model uses PyTorch, which in turn uses a GPU if one is available. But the embeddings are generated individually, which does not lead to an optimal utilization of the GPU hardware. GPUs are very good at parallelization, so we can reorganize the ingest function to generate embeddings in batches. And once again the price we pay for this comes in increased code complexity.</p><p>So we are going to stop using the <code>clean()</code> method to generate document embeddings, and instead we are going to accumulate the <code>QuoteDoc</code> instances in a list, and once we reach a good number we'll generate embeddings for all of them in a single operation.</p><p>Let's start by writing a helper function that generates embeddings for a list of <code>QuoteDoc</code> instances:</p>def embed_quotes(quotes):
    embeddings = model.encode([q.quote for q in quotes])
    for q, e in zip(quotes, embeddings):
        q.embedding = e.tolist()
<p>Note how now the <code>model.encode()</code> method is given a list of quotes to embed instead of a single one. When the input argument is a list, the model generates an embedding for each list element. The method accepts an optional <a href="https://sbert.net/docs/package_reference/sentence_transformer/SentenceTransformer.html#sentence_transformers.SentenceTransformer.encode"><code>batch_size</code></a> argument (not used in the example above) that defaults to 32 that can be used to control the size of each batch of samples that are sent to the model for computation. Depending on the GPU hardware you may find that different values of this argument help tune performance to the best possible. Once the embeddings are generated, they are assigned to each quote using a for-loop.</p><p>Now the ingest function can be refactored to accumulate quotes and use the helper function to generate embeddings:</p>async def ingest_quotes():
    if await QuoteDoc._index.exists():
        await QuoteDoc._index.delete()
    await QuoteDoc.init()

    async def get_next_quote():
        quotes = []
        with open('quotes.csv') as f:
            reader = csv.DictReader(f)
            count = 0
            start = time()
            for row in reader:
                q = QuoteDoc(quote=row['quote'], author=row['author'],
                             tags=row['tags'].split(','))
                quotes.append(q)
                if len(quotes) == 512:
                    embed_quotes(quotes)
                    for q in quotes:
                        yield q
                    count += len(quotes)
                    ingest_progress(count, start)
                    quotes = []
            if len(quotes) &gt; 0:
                embed_quotes(quotes)
                for q in quotes:
                    yield q
            ingest_progress(count, start)
<p>In this version of <code>ingest_quotes()</code>, each <code>QuoteDoc</code> instance is added to the <code>quotes</code> list, and when 512 elements have accumulated the <code>embed_quotes()</code> function added above is used to generate the embeddings more efficiently. Once the objects have their embeddings, they are yielded, so that the <code>bulk()</code> method from Elasticsearch-DSL can add them to the index as before.</p><p>What is the significance of the 512 number? There isn't any. We know that the model uses a batch size of 32, so it makes sense to accumulate at least that many documents. Starting from 32, you can try if larger powers of 2 provide better performance. With the hardware available to me, I've found 512 to give the best performance.</p><p>Here is an example run using batched embeddings:</p>❯ python search.py
Ingested 36864 quotes. (481/sec)
<p>And now the ingestion process runs much faster, with the entire dataset ingested in about 1 minutes and 16 seconds.</p><p>If you decide to try to optimize your ingest, you are encouraged to try different options and see what works best with your hardware.</p><h2>Querying the index</h2><p>If you are following along, by now you have an Elasticsearch index called <code>quotes</code> that is populated with about 37K famous quotes, each with a searchable vector embedding. Now it is time to learn how to query this index.</p><p>When using Elasticsearch-DSL, the document classes return a search object from their <code>search()</code> method:</p>s = QuoteDoc.search()
<p>The search object has a large number of methods that map to the query options in the Elasticsearch <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html">query DSL</a>.</p><p>The simplest query that can be issued is the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-all-query.html">match all</a> query, which returns all the elements. With the class-based approach used by Elasticsearch-DSL, this is how to run the query:</p>s = QuoteDoc.search()
s = s.query(dsl.query.MatchAll())
async for q in s:
    print(q.quote)
<p>This would obviously print a listing of the entire list of quotes stored in the index, up to 10,000, which is the maximum number of results Elasticsearch returns by default.</p><p>In many cases it is useful to request a subset of the results. The search object uses Python style slicing for this. Here is how to request the first 25 results only:</p>async for q in s[:25]:
    print(q.quote)
<p>Here is how to request the second page of results, at 25 results per page:</p>async for q in s[25:50]:
    print(q.quote)
<p>Elasticsearch offers approximate and exact vector search queries, also called <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k-nearest neighbor (kNN) queries</a>. To run a vector search query with the approximate k-nearest neighbor algorithm, the <code>Knn</code> query should be used:</p>s = QuoteDoc.search()
s = s.query(dsl.query.Knn(field=QuoteDoc.embedding, query_vector=model.encode(q).tolist()))
<p>The <code>Knn</code> query class accepts the field that stores the embeddings and a search vector as arguments. In the above snippet the variable <code>q</code> has the search text entered by the user.</p><p>If instead you prefer to run a regular full-text search, the <code>Match</code> query class is used:</p>s = QuoteDoc.search()
s = s.query(dsl.query.Match(quote=q))
<h3>Filters</h3><p>One of the most important benefits of using Elasticsearch as a vector database is that it is a robust database system, and all the options you can expect to have from a database nicely integrates with your vector search queries.</p><p>A great example of this is <em>filters</em>. The famous quotes database stores a list of tags for each quote, so it is only natural to have the option to restrict a query to quotes that have a specific tag.</p><p>Given a list of tag filters stored in a <code>tags</code> variable, the following snippet configures a search object to only return results that include the given tags using a "terms" filter:</p>for tag in tags:
    s = s.filter(dsl.query.Terms(tags=[tag]))
<h3>Aggregations</h3><p>Another example of a useful database function that is fully integrated with vector search is <em>aggregations</em>. Given a query, Elasticsearch can aggregate the tags and provide the counts of quotes per tag.</p><p>The next snippet shows how to add a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">Terms</a> aggregation to an existing query, which will return the 100 most referenced tags in the results:</p>s.aggs.bucket('tags', dsl.aggs.Terms(field=QuoteDoc.tags, size=100))
<p>Recall that the <code>tags</code> field was declared with the <code>Keyword()</code> type, which means that the tags will be stored as is on the index, without any processing. This is required by the Terms aggregation, which will count the occurrences of each tag in the results.</p><h3>A complete query example</h3><p>You have seen a few isolated query examples. In this section you can see how they can all be integrated into a function that performs a query in the example application.</p><p>The <code>search_quotes()</code> function shown below accepts a query string <code>q</code>, a list of filters <code>tags</code> and a <code>use_knn</code> flag to choose between kNN or full-text search query. It also accepts <code>start</code> and <code>size</code> pagination arguments.</p><p>The function decides which of the three queries you've seen above to issue depending on the input arguments. If <code>q</code> is empty, then it selects a "match all" query, and in any other case it selects a kNN or match query depending on the <code>use_knn</code> flag, which the user can control from a checkbox in the application's user interface.</p><p>The function returns three results as a tuple:</p><ul><li><p>a list of <code>QuoteDoc</code> instances that are the search results,</p></li><li><p>the tag aggregations as a list of tuples, each with tag name and document count,</p></li><li><p>the total number of results, which is useful to show in paginated queries</p></li></ul><p>Here is the complete code of this function:</p>async def search_quotes(q, tags, use_knn=True, start=0, size=25):
    s = QuoteDoc.search()
    if q == '':
        s = s.query(dsl.query.MatchAll())
    elif use_knn:
        s = s.query(dsl.query.Knn(field=QuoteDoc.embedding, query_vector=model.encode(q).tolist()))
    else:
        s = s.query(dsl.query.Match(quote=q))
    for tag in tags:
        s = s.filter(dsl.query.Terms(tags=[tag]))
    s.aggs.bucket('tags', dsl.aggs.Terms(field=QuoteDoc.tags, size=100))
    r = await s[start:start + size].execute()
    tags = [(tag.key, tag.doc_count) for tag in r.aggs.tags.buckets]
    return r.hits, tags, r['hits'].total.value
<p>To be able to access both the search results and the aggregation results, we now issue the request explicitly through the <code>execute()</code> method and store the response is stored in <code>r</code>. The <code>hits</code> attribute of the response object contains the actual search results, and the <code>aggs</code> attribute provides access to the aggregations. The format in which the aggregation results is provided is described in the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">terms aggregation documentation</a>.</p><h2>Conclusion</h2><p>The complete quotes example is available in a <a href="https://github.com/miguelgrinberg/quotes">GitHub repository</a> that you can install and run on your computer. Follow the instructions on the <code>README.md</code> file to set it up.</p><p>You are welcome to use this example to experiment with vector embeddings and Elasticsearch!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-dsl-python-vectors</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-dsl-python-vectors</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Miguel Grinberg]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a680a6bf95f4421/6a17122b66c4f9358ef8c157/1225861f71cf9ccbb2102216a9365dd07ff71e9c-1440x858.png" length="0" type="image/png"/>
    <pubDate>Fri, 16 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Advanced RAG techniques part 2: Querying and testing]]></title>
    <description><![CDATA[Discussing and implementing techniques which may increase RAG performance. Part 2 of 2, focusing on querying and testing an advanced RAG pipeline.]]></description>
    <content:encoded><![CDATA[<p><em>All code may be found </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>in the Searchlabs repo, in the advanced-rag-techniques branch</em></a><em>.</em></p><p>Welcome to Part 2 of our article on Advanced RAG Techniques! In <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">part 1 of this series</a>, we set up, discussed, and implemented the data processing components of the advanced RAG pipeline:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="Advanced RAG pipeline" /><p>In this part, we're going to proceed with querying and testing out our implementation. Let's get right to it!</p><h3>Table of contents</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#searching-and-retrieving,-generating-answers">Searching and retrieving, generating answers</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#enriching-queries-with-synonyms">Enriching queries with synonyms</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-hypothetical-document-embedding">HyDE (Hypothetical Document Embedding)</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hybrid-search">Hybrid search</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#experiments">Experiments</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#summary-of-results">Summary of results</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-1-who-audits-elastic">Test 1: Who audits Elastic?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-2--total-revenue-2023">Test 2: total revenue 2023</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-1">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-1">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-3-what-product-does-growth-primarily-depend-on-how-much">Test 3: What product does growth primarily depend on? How much?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-2">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-2">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-4-describe-employee-benefit-plan">Test 4: Describe employee benefit plan</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-3">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-3">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-5-which-companies-did-elastic-acquire">Test 5: Which companies did Elastic acquire?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-4">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-4">SimpleRAG</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#conclusion">Conclusion</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#appendix">Appendix</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">Prompts</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#rag-question-answering-prompt">RAG question answering prompt</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#elastic-query-generator-prompt">Elastic query generator prompt</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#potential-questions-generator-prompt">Potential questions generator prompt</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-generator-prompt">HyDE generator prompt</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#sample-hybrid-search-query">Sample hybrid search query</a></p></li></ul></li></ul><h2>Searching and retrieving, generating answers</h2><p>Let's ask our first query, ideally some piece of information found primarily in the annual report. How about:</p>Who audits Elastic?"
<p>Now, let's apply a few of our techniques to enhance the query.</p><h3>Enriching queries with synonyms</h3><p>Firstly, let's enhance the diversity of the query wording, and turn it into a form that can be easily processed into an Elasticsearch query. We'll enlist the aid of GPT-4o to convert the query into a list of OR clauses. Let's write this prompt:</p>
ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<p>When applied to our query, GPT-4o generates synonyms of the base query and related vocabulary.</p>'audits elastic OR 
elasticsearch audits OR 
elastic auditor OR 
elasticsearch auditor OR 
elastic audit firm OR 
elastic audit company OR 
elastic audit organization OR 
elastic audit service'
<p>In the <code>ESQueryMaker</code> class, I've defined a function to split the query:</p>def parse_or_query(self, query_text: str) -&gt; List[str]:
    # Split the query by 'OR' and strip whitespace from each term
    # This converts a string like "term1 OR term2 OR term3" into a list ["term1", "term2", "term3"]
    return [term.strip() for term in query_text.split(' OR ')]
<p>Its role is to take this string of OR clauses and split them into a list of terms, allowing us do a multi-match on our key document fields:</p>["original_text", 'keyphrases', 'potential_questions', 'entities']
<p>Finally ending up with this query:</p> 'query': {
    'bool': {
        'must': [
            {
                'multi_match': {
                'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
                'fields': [
                    'original_text',
                'keyphrases',
                'potential_questions',
                'entities'
                ],
                'type': 'best_fields',
                'operator': 'or'
                }
            }
      ]
<p>This covers many more bases than the original query, hopefully reducing the risk of missing a search result because we forgot a synonym. But we can do more.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Back to top</a></p><h3>HyDE (Hypothetical Document Embedding)</h3><p>Let's enlist GPT-4o again, this time to implement <a href="https://arxiv.org/abs/2212.10496">HyDE</a>.</p><p>The basic premise of HyDE is to generate a hypothetical document - The kind of document that would likely contain the answer to the original query. The factuality or accuracy of the document is not a concern. With that in mind, let's write the following prompt:</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<p>Since vector search typically operates on cosine vector similarity, the premise of HyDE is that we can achieve better results by matching documents to documents instead of queries to documents.</p><p>What we care about is structure, flow, and terminology. Not so much factuality. GPT-4o outputs a HyDE document like this:</p>'Elastic N.V., the parent company of Elastic, the organization known for developing Elasticsearch, is subject to audits to ensure financial accuracy, 
regulatory compliance, and the integrity of its financial statements. The auditing of Elastic N.V. is typically conducted by an external, 
independent auditing firm. This is common practice for publicly traded companies to provide stakeholders with assurance regarding the company\'s 
financial position and operations.\n\nThe primary external auditor for Elastic is the audit firm Ernst &amp; Young LLP (EY). Ernst &amp; Young is one of the 
four largest professional services networks in the world, commonly referred to as the "Big Four" audit firms. These firms handle a substantial number 
of audits for major corporations around the globe, ensuring adherence to generally accepted accounting principles (GAAP) and international financial 
reporting standards (IFRS).\n\nThe audit process conducted by EY involves several steps. Initially, the auditors perform a risk assessment to identify 
areas where misstatements due to error or fraud could occur. They then design audit procedures to test the accuracy and completeness of financial statements,
 which include examining financial transactions, assessing internal controls, and reviewing compliance with relevant laws and regulations. Upon completion of 
 the audit, Ernst &amp; Young issues an audit report, which includes the auditor’s opinion on whether the financial statements are free from material misstatement 
 and are presented fairly in accordance with the applicable financial reporting framework.\n\nIn addition to external audits by firms like Ernst &amp; Young, 
 Elastic may also be subject to internal audits. Internal audits are performed by the company’s own internal auditors to evaluate the effectiveness of internal 
 controls, risk management, and governance processes.\n\nOverall, the auditing process plays a crucial role in maintaining the transparency and reliability of 
 Elastic\'s financial information, providing confidence to investors, regulators, and other stakeholders.'
<p>It looks pretty believable, like the ideal candidate for the kinds of documents we'd like to index. We're going to embed this and use it for hybrid search.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Back to top</a></p><h3>Hybrid search</h3><p>This is the core of our search logic. Our lexical search component will be the generated OR clause strings. Our dense vector component will be embedded HyDE Document (aka the search vector). We use KNN to efficiently identify several candidate documents closest to our search vector. We call our lexical search component <em>Scoring with TF-IDF and BM25</em> by default. Finally, the lexical and dense vector scores will be combined using the 30/70 ratio recommended by <a href="https://arxiv.org/abs/2407.01219">Wang et al</a>.</p>def hybrid_vector_search(self, index_name: str, query_text: str, query_vector: List[float], 
                         text_fields: List[str], vector_field: str, 
                         num_candidates: int = 100, num_results: int = 10) -&gt; Dict:
    """
    Perform a hybrid search combining text-based and vector-based similarity.

    Args:
        index_name (str): The name of the Elasticsearch index to search.
        query_text (str): The text query string, which may contain 'OR' separated terms.
        query_vector (List[float]): The query vector for semantic similarity search.
        text_fields (List[str]): List of text fields to search in the index.
        vector_field (str): The name of the field containing document vectors.
        num_candidates (int): Number of candidates to consider in the initial KNN search.
        num_results (int): Number of final results to return.

    Returns:
        Dict: A tuple containing the Elasticsearch response and the search body used.
    """
    try:
        # Parse the query_text into a list of individual search terms
        # This splits terms separated by 'OR' and removes any leading/trailing whitespace
        query_terms = self.parse_or_query(query_text)

        # Construct the search body for Elasticsearch
        search_body = {
            # KNN search component for vector similarity
            "knn": {
                "field": vector_field,  # The field containing document vectors
                "query_vector": query_vector,  # The query vector to compare against
                "k": num_candidates,  # Number of nearest neighbors to retrieve
                "num_candidates": num_candidates  # Number of candidates to consider in the KNN search
            },
            "query": {
                "bool": {
                    # The 'must' clause ensures that matching documents must satisfy this condition
                    # Documents that don't match this clause are excluded from the results
                    "must": [
                        {
                            # Multi-match query to search across multiple text fields
                            "multi_match": {
                                "query": " ".join(query_terms),  # Join all query terms into a single space-separated string
                                "fields": text_fields,  # List of fields to search in
                                "type": "best_fields",  # Use the best matching field for scoring
                                "operator": "or"  # Match any of the terms (equivalent to the original OR query)
                            }
                        }
                    ],
                    # The 'should' clause boosts relevance but doesn't exclude documents
                    # It's used here to combine vector similarity with text relevance
                    "should": [
                        {
                            # Custom scoring using a script to combine vector and text scores
                            "script_score": {
                                "query": {"match_all": {}},  # Apply this scoring to all documents that matched the 'must' clause
                                "script": {
                                    # Script to combine vector similarity and text relevance
                                    "source": """
                                    # Calculate vector similarity (cosine similarity + 1)
                                    # Adding 1 ensures the score is always positive
                                    double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;
                                    # Get the text-based relevance score from the multi_match query
                                    double text_score = _score;
                                    # Combine scores: 70% vector similarity, 30% text relevance
                                    # This weighting can be adjusted based on the importance of semantic vs keyword matching
                                    return 0.7 * vector_score + 0.3 * text_score;
                                    """,
                                    # Parameters passed to the script
                                    "params": {
                                        "query_vector": query_vector,  # Query vector for similarity calculation
                                        "vector_field": vector_field  # Field containing document vectors
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }

        # Execute the search request against the Elasticsearch index
        response = self.conn.search(index=index_name, body=search_body, size=num_results)
        # Log the successful execution of the search for monitoring and debugging
        logger.info(f"Hybrid search executed on index: {index_name} with text query: {query_text}")
        # Return both the response and the search body (useful for debugging and result analysis)
        return response, search_body
    except Exception as e:
        # Log any errors that occur during the search process
        logger.error(f"Error executing hybrid search on index: {index_name}. Error: {e}")
        # Re-raise the exception for further handling in the calling code
        raise e
<p>Finally, we can piece together a RAG function. Our RAG, from query to answer, will follow this flow:</p><ol><li><p>Convert Query to OR Clauses.</p></li><li><p>Generate HyDE document and embed it.</p></li><li><p>Pass both as inputs to Hybrid Search.</p></li><li><p>Retrieve top-n results, reverse them so that the most relevant score is the "most recent" in the LLM's contextual memory (Reverse Packing) Reverse Packing Example: Query: "Elasticsearch query optimization techniques" Retrieved documents (ordered by relevance):  Reversed order for LLM context:  By reversing the order, the most relevant information (1) appears last in the context, potentially receiving more attention from the LLM during answer generation.</p><ol><li><p>"Use bool queries to combine multiple search criteria efficiently."</p></li><li><p>"Implement caching strategies to improve query response times."</p></li><li><p>"Optimize index mappings for faster search performance."</p></li><li><p>"Optimize index mappings for faster search performance."</p></li><li><p>"Implement caching strategies to improve query response times."</p></li><li><p>"Use bool queries to combine multiple search criteria efficiently."</p></li></ol></li><li><p>Pass the context to the LLM for generation.</p></li></ol>def get_context(index_name, 
                match_query, 
                text_query, 
                fields, 
                num_candidates=100, 
                num_results=20, 
                text_fields=["original_text", 'keyphrases', 'potential_questions', 'entities'], 
                embedding_field="primary_embedding"):

    embedding=embedder.get_embeddings_from_text(text_query)

    results, search_body = es_query_maker.hybrid_vector_search(
        index_name=index_name,
        query_text=match_query,
        query_vector=embedding[0][0],
        text_fields=text_fields,
        vector_field=embedding_field,
        num_candidates=num_candidates,
        num_results=num_results
    )

    # Concatenates the text in each 'field' key of the search result objects into a single block of text.
    context_docs=['\n\n'.join([field+":\n\n"+j['_source'][field] for field in fields]) for j in results['hits']['hits']]

    # Reverse Packing to ensure that the highest ranking document is seen first by the LLM.
    context_docs.reverse()
    return context_docs, search_body

def retrieval_augmented_generation(query_text):
    match_query= gpt4o.generate_query(query_text)
    fields=['original_text']

    hyde_document=gpt4o.generate_HyDE(query_text)

    context, search_body=get_context(index_name, match_query, hyde_document, fields)

    answer= gpt4o.basic_qa(query=query_text, context=context)
    return answer, match_query, hyde_document, context, search_body

<p>Let's run our query and get back our answer:</p>According to the context, Elastic N.V. is audited by an independent registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of independent registered public accounting firm," which states:

"We have audited the accompanying consolidated balance sheets of Elastic N.V. [...] / s / pricewaterhouseco."
<p>Nice. That's correct.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Back to top</a></p><h2>Experiments</h2><p>There's an important question to answer now. What did we get out of investing so much effort and additional complexity into these implementations?</p><p>Let's do a little comparison. The RAG pipeline we've implemented versus baseline hybrid search, without any of the enhancements we've made. We'll run a small series of tests and see if we notice any substantial differences. We'll refer to the RAG we have just implemented as AdvancedRAG, and the basic pipeline as SimpleRAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" alt="Simple RAG Pipeline" /><h4>Summary of results</h4><p>This table summarizes the results of five tests of both RAG pipelines. I judged the relative superiority of each method based on answer detail and quality, but this is a totally subjective judgement. The actual answers are reproduced below this table for your consideration. With that said, let's take a look at how they did!</p><p>SimpleRAG was unable to answer questions 1 &amp; 5. AdvancedRAG also went into far greater detail on questions 2, 3, and 4. Based on the increased detail, I judged the quality of AdvancedRAG's answers better.</p><p>Test</p><p>Question</p><p>AdvancedRAG Performance</p><p>SimpleRAG Performance</p><p>AdvancedRAG Latency</p><p>SimpleRAG Latency</p><p>Winner</p><p>1</p><p>Who audits Elastic?</p><p>Correctly identified PwC as the auditor.</p><p>Failed to identify the auditor.</p><p>11.6s</p><p>4.4s</p><p>AdvancedRAG</p><p>2</p><p>What was the total revenue in 2023?</p><p>Provided the correct revenue figure. Included additional context with revenue from previous years.</p><p>Provided the correct revenue figure.</p><p>13.3s</p><p>2.8s</p><p>AdvancedRAG</p><p>3</p><p>What product does growth primarily depend on? How much?</p><p>Correctly identified Elastic Cloud as the key driver. Included overall revenue context &amp; greater detail.</p><p>Correctly identified Elastic Cloud as the key driver.</p><p>14.1s</p><p>12.8s</p><p>AdvancedRAG</p><p>4</p><p>Describe employee benefit plan</p><p>Gave a comprehensive description of retirement plans, health programs, and other benefits. Included specific contribution amounts for different years.</p><p>Provided a good overview of benefits, including compensation, retirement plans, work environment, and the Elastic Cares program.</p><p>26.6s</p><p>11.6s</p><p>AdvancedRAG</p><p>5</p><p>Which companies did Elastic acquire?</p><p>Correctly listed recent acquisitions mentioned in the report (CmdWatch, Build Security, Optimyze). Provided some acquisition dates and purchase prices.</p><p>Failed to retrieve relevant information from the provided context.</p><p>11.9s</p><p>2.7s</p><p>AdvancedRAG</p><h4>Test 1: Who audits Elastic?</h4><p>AdvancedRAG</p>According to the context, Elastic N.V. is audited by an independent 
registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of 
independent registered public accounting firm," which states:
"We have audited the accompanying consolidated balance sheets 
of Elastic N.V. [...] / s / pricewaterhouseco."
<p>SimpleRAG</p>According to the provided context, no specific auditing firm 
or company is mentioned as the auditor for Elastic. 
The context provided talks about various features, strengths, 
deployment options, and other details related to Elastic and 
its operations but does not specify which auditing firm is 
responsible for auditing Elastic's financial statements or 
internal controls.
<p><strong>Summary</strong>: SimpleRAG did not identify PWC as the auditor</p><p>Okay that's actually quite surprising. That looks like a search failure on SimpleRAG's part. No documents related to auditing were retrieved. Let's dial down the difficulty a little with the next test.</p><h4>Test 2: total revenue 2023</h4><p>AdvancedRAG</p>### Total Revenue for 2023

According to the provided context, the total revenue for the 
year ended April 30, 2023, was **$1,068,989,000**. This 
information can be found several times in the provided text. 
Specifically, one part states:

"The following table sets forth our results of operations 
for the periods presented in dollars and as a percentage of 
our total revenue. ... year ended April 30, 2023, ... 
total revenue 1,068,989".

Additionally, it is mentioned here as well:

"For the years ended April 30, 2023, 2022, and 2021, 
representing year-over-year growth of 24% for the year 
ended April 30, 2023... total revenue $1,068,989".

Therefore, the total revenue for 2023 is $1.069 billion.
<p>SimpleRAG</p>According to the context provided, the total revenue for the fiscal year ended April 30, 2023, is:

$1,068,989,000 (or approximately $1.1 billion).
<p><strong>Summary</strong>: Both RAGs got the right answer: $1,068,989,000 total revenue in 2023</p><p>Both of them were right here. It does seem like AdvancedRAG may have acquired a broader range of documents? Certainly the answer is more detailed and incorporates information from previous years. That is to be expected given the enhancements we made, but it's far too early to call.</p><p>Let's raise the difficulty.</p><h4>Test 3: What product does growth primarily depend on? How much?</h4><p>AdvancedRAG</p>### Product Growth Primarily Depends On:
According to the context, "our revenue is derived primarily from 
subscriptions of search, observability and security built into 
the elastic stack." The document mentions various strategies and 
factors contributing to the company’s growth which include investment 
in "elastic cloud," "elastic stack," and the conversion of free 
users to paid subscribers.

### Quantitative Data:
- **Revenue**: The document states, "Our total revenue was $1.1 billion, 
$862.4 million, and $608.5 million for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Subscription Revenue**: "Subscriptions accounted for 92%, 93%, 
and 93% of our total revenue for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Elastic Cloud Contribution**: "For the years ended April 30, 2023, 
2022, and 2021, Elastic Cloud contributed 40%, 35%, and 27% of our 
total revenue, respectively."
- **Customer Growth**: The number of customers increased from 
"over 15,000 as of April 30, 2021," to "approximately 20,200 as of 
April 30, 2023."

### Summary:
Product growth for the company primarily depends on the "Elastic Stack" 
(which includes search, observability, and security solutions) and 
particularly the "Elastic Cloud." The Elastic Cloud's growing contribution 
to revenue highlights its significance, which grew from 27% in 2021 to 40% in 2023.
<p>SimpleRAG</p>According to the provided context, growth primarily depends on **Elastic Cloud**, 
which is described as the company's "family of cloud-based offerings." 
The context also emphasizes the significance of Elastic Cloud in achieving 
the company's long-term growth potential and its role in increasing subscription revenue.

From the context:

- "Increasing adoption of Elastic Cloud... is an important growth opportunity 
for our business. Organizations are increasingly looking for hosted 
deployment alternatives with reduced administrative burdens."
- "We believe that offering Elastic Cloud is important for achieving 
our long-term growth potential, and we expect Elastic Cloud’s contribution 
to our subscription revenue to continue to increase."

In terms of the contribution to total revenue, here are the specific figures provided:

- Elastic Cloud contributed 40% of the total revenue for the year ended April 30, 2023.
- Elastic Cloud contributed 35% of the total revenue for the year ended April 30, 2022.
- Elastic Cloud contributed 27% of the total revenue for the year ended April 30, 2021.

This progression shows a clear upward trend in the importance and 
contribution of Elastic Cloud to the company's overall revenue.
<p><strong>Summary</strong>: Both RAGs correctly identified Elastic Cloud as the key growth driver. However, AdvancedRAG includes more detail, factoring in subscription revenues and customer growth, and explicitly mentions other Elastic offerings.</p><h4>Test 4: Describe employee benefit plan</h4><p>AdvancedRAG</p>### Employee Benefit Plan Overview

The provided context outlines various elements of the employee benefit plan at 
Elastic, often referred to as "Elasticians." Here are the key components 
described in the texts:

1. **401(k) Plan**:
   - **Coverage**: The 401(k) plan is available to substantially all U.S. 
   employees who meet minimum age and service requirements.
   - **Contributions**: Elastic makes contributions to the 401(k) plan up to 
   6% of the participating employee’s W-2 earnings and wages.
   - **Expenses**: For the fiscal years ended April 30, Elastic recorded 
   expenses of $17.9 million (2023), $15.2 million (2022), and $11.4 million (2021) 
   related to the 401(k) plan.
   - **Defined-Contribution Plans in Other Countries**: Elastic has 
   defined-contribution plans in various other countries and recorded respective 
   expenses of $9.4 million (2023), $7.2 million (2022), and $5.1 million (2021).

2. **Stock-Based Compensation**:
   - **Types of Awards**: Stock options, restricted stock units (RSUs), 
   and shares under the Employee Stock Purchase Plan (ESPP).
   - **Fair Value Measurement**: Fair value of these stock awards is 
   measured using models like Black-Scholes.
   - **Employee Stock Purchase Plan (2022 ESPP)**: 
     - Started in 2022, it allows employees to acquire ordinary 
     shares at a discount (85% of the market value at the beginning 
     or end of the offering period).
     - Offering periods are approximately six months long.

3. **Total Rewards Compensation**:
   - **Components**: Includes cash compensation as well as equity awards, 
   reflecting a comprehensive interest in the well-being of the employees.
   - **Programs**: Market competitive medical and dental programs, 
   mental health, and holistic well-being programs.
   - **Paid Time Off**: Features 16 weeks of paid leave for new 
   parents and life-planning benefits.

4. **Retirement and Income Protection Plans**:
   - **401(k) Match**: Elastic provides a dollar-for-dollar 
   match up to 6% of eligible earnings up to a plan-limit 
   maximum for U.S.-based employees.
   - **Similar Plans Outside the U.S.**: Competitive retirement 
   plans available for employees based outside the United States.

5. **Flexible Work Environment**: 
   - **Distributed Workforce**: Elastic operates with a global 
   distributed workforce model, allowing employees to work remotely 
   and providing a broad candidate pool.
   
6. **Learning and Development**:
   - **Programs**: Access to on-demand video-based learning, 
   leadership development programs, and other specific training 
   to develop managers and leaders.

7. **Employee Engagement and Surveys**:
   - **Mechanisms**: Annual employee engagement survey and 
   mid-year pulse survey.
   - **Objectives**: To monitor morale, develop action plans, 
   and maintain high engagement scores.

8. **Onboarding Program**:
   - **X-School**: An extensive new-hire orientation program to 
   help new employees meet peers and learn about the company's 
   products and solutions.

In summary, Elastic's employee benefit plan encompasses a broad 
spectrum of financial, health, well-being, and professional development 
benefits designed to support and enhance the lives of its employees. 
The plan is both competitive and comprehensive, aiming to attract,
retain, and engage its global workforce.
<p>SimpleRAG</p>The employee benefit plan at the Company includes several key 
components aimed at enhancing the overall well-being and 
professional development of employees, referred to as "Elasticians." 

1. **Compensation and Benefits:**
   - The Company provides market-competitive compensation 
   packages which typically include cash compensation and equity awards.
   - Comprehensive medical and dental programs are offered, 
   emphasizing mental health and holistic well-being.
   - Paid time off programs feature:
     - 16 weeks of paid leave for new parents.
     - Life-planning benefits.
     - Travel reimbursements for certain healthcare services.

2. **Retirement and Income Protection Plans:**
   - In the United States, employees are eligible for a 401k 
   plan with a dollar-for-dollar match by the Company up to 6% 
   of eligible earnings, subject to a plan limit.
   - Similar competitive retirement and income protection plans 
   are available for employees outside the United States.

3. **Work Environment:**
   - The Company offers a flexible work environment, allowing most 
   employees to work from anywhere as often as they would like.
   - Physical offices around the world are available for employees 
   who prefer to work on-site or need a space for face-to-face interactions.

4. **Community Involvement:**
   - The Elastic Cares Program supports charitable organizations 
   that matter to employees on both local and global levels.
   - The program includes:
     - Donation matching.
     - Providing the Company's technology for free to certain 
     nonprofit organizations.
     - Volunteer time off initiative.
<p><strong>Summary</strong>: AdvancedRAG goes into much greater depth and detail, mentioning the 401K plan for US-based employees, as well as defining contribution plans outside of the US. It also mentions Health and Well-Being plans but misses the Elastic Cares program, which SimpleRAG mentions.</p><h4>Test 5: Which companies did Elastic acquire?</h4><p>AdvancedRAG</p>Elastic acquired several companies over the years to enhance its 
technology and market presence. According to the provided context, 
the specific acquisitions include:

1. **CmdWatch Security Inc.**: Acquired on September 17, 2021. 
The total purchase consideration was $77.8 million.
2. **Build Security Ltd.**: Acquired on September 2, 2021. The 
combined purchase price for Build Security Ltd. and another acquisition was $57.2 million.
3. **Optimyze Cloud Inc.**: Acquired on November 1, 2021. Part 
of the combined purchase price mentioned above with Build Security Ltd.
4. **Endgame, Inc.**: Although the context mentions an amended 
and restated stock incentive plan, it indicates that Endgame, Inc. was 
associated with Elastic, suggesting it could have been acquired at some point.

These acquisitions are part of Elastic's strategy to 
enhance the technology underlying its security and 
observability offerings.
<p>SimpleRAG</p>The provided context does not specify any companies that Elastic has acquired. 
Therefore, based on the context, there is no information available about the companies acquired by Elastic.
<p><strong>Summary</strong>: SimpleRAG does not retrieve any relevant info about acquisitions, leading to a failed answer. AdvancedRAG correctly lists CmdWatch, Build Security, and Optimyze, which were the key acquisitions listed in the report.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Back to top</a></p><h2>Conclusion</h2><p>Based on our tests, our advanced techniques appear to increase the range and depth of the information presented, potentially enhancing quality of RAG answers.</p><p>Additionally, there may be improvements in reliability, as ambiguously worded questions such as <code>Which companies did Elastic acquire?</code> and <code>Who audits Elastic</code> were correctly answered by AdvancedRAG but not by SimpleRAG.</p><p>However, it is worth keeping in perspective that in 3 out of 5 cases, the basic RAG pipeline, incorporating Hybrid Search but no other techniques, managed to produce answers that captured most of the key information.</p><p>We should note that due to the incorporation of LLMs at the data preparation and query phases, the latency of AdvancedRAG is generally between 2-5x larger that of SimpleRAG. This is a significant cost which may make AdvancedRAG suitable only for situations where answer quality is prioritized over latency.</p><p>The significant latency costs can be alleviated using a smaller and cheaper LLM like Claude Haiku or GPT-4o-mini at the data preparation stage. Save the advanced models for answer generation.</p><p>This aligns with the findings of Wang et al. As their results show, any improvements made are relatively incremental. In short, simple baseline RAG gets you most of the way to a decent end-product, while being cheaper and faster to boot. For me, it's an interesting conclusion. For use cases where speed and efficiency are key, SimpleRAG is the sensible choice. For use cases where every last drop of performance needs squeezing out, the techniques incorporated into AdvancedRAG may offer a way forward.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56b7067a9d41d5a8/6a171119acf0886fb4be9c45/ea811706b6adc4731d90b925a9fefa0ac15901b4-1440x1060.jpg" alt="Wang Pipeline" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Back to top</a></p><h2>Appendix</h2><h3>Prompts</h3><h4>RAG question answering prompt</h4><p>Prompt for getting the LLM to generate answers based on query and context.</p>BASIC_RAG_PROMPT = '''
You are an AI assistant tasked with answering questions based primarily on the provided context, while also drawing on your own knowledge when appropriate. Your role is to accurately and comprehensively respond to queries, prioritizing the information given in the context but supplementing it with your own understanding when beneficial. Follow these guidelines:

1. Carefully read and analyze the entire context provided.
2. Primarily focus on the information present in the context to formulate your answer.
3. If the context doesn't contain sufficient information to fully answer the query, state this clearly and then supplement with your own knowledge if possible.
4. Use your own knowledge to provide additional context, explanations, or examples that enhance the answer.
5. Clearly distinguish between information from the provided context and your own knowledge. Use phrases like "According to the context..." or "The provided information states..." for context-based information, and "Based on my knowledge..." or "Drawing from my understanding..." for your own knowledge.
6. Provide comprehensive answers that address the query specifically, balancing conciseness with thoroughness.
7. When using information from the context, cite or quote relevant parts using quotation marks.
8. Maintain objectivity and clearly identify any opinions or interpretations as such.
9. If the context contains conflicting information, acknowledge this and use your knowledge to provide clarity if possible.
10. Make reasonable inferences based on the context and your knowledge, but clearly identify these as inferences.
11. If asked about the source of information, distinguish between the provided context and your own knowledge base.
12. If the query is ambiguous, ask for clarification before attempting to answer.
13. Use your judgment to determine when additional information from your knowledge base would be helpful or necessary to provide a complete and accurate answer.

Remember, your goal is to provide accurate, context-based responses, supplemented by your own knowledge when it adds value to the answer. Always prioritize the provided context, but don't hesitate to enhance it with your broader understanding when appropriate. Clearly differentiate between the two sources of information in your response.

Context:
[The concatenated documents will be inserted here]

Query:
[The user's question will be inserted here]

Please provide your answer based on the above guidelines, the given context, and your own knowledge where appropriate, clearly distinguishing between the two:
'''
<h4>Elastic query generator prompt</h4><p>Prompt for enriching queries with synonyms and converting them into the OR format.</p>ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<h4>Potential questions generator prompt</h4><p>Prompt for generating potential questions, enriching document metadata.</p>RAG_QUESTION_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating questions for Retrieval-Augmented Generation (RAG) systems. Your task is to analyze a given document and create 10 diverse questions that would effectively test a RAG system's ability to retrieve and synthesize information from this document.

Guidelines:
1. Thoroughly analyze the entire document.
2. Generate exactly 10 questions that cover various aspects and levels of complexity within the document's content.
3. Create questions that specifically target:
   a. Key facts and information
   b. Main concepts and ideas
   c. Relationships between different parts of the content
   d. Potential applications or implications of the information
   e. Comparisons or contrasts within the document
4. Ensure questions require answers of varying lengths and complexity, from simple retrieval to more complex synthesis.
5. Include questions that might require combining information from different parts of the document.
6. Frame questions to test both literal comprehension and inferential understanding.
7. Avoid yes/no questions; focus on open-ended questions that promote comprehensive answers.
8. Consider including questions that might require additional context or knowledge to fully answer, to test the RAG system's ability to combine retrieved information with broader knowledge.
9. Number the questions from 1 to 10.
10. Output only the ten questions, without any additional text, explanations, or answers.

Document:
[The document content will be inserted here]

Generate 10 questions optimized for testing a RAG system based on this document:
'''
<h4>HyDE generator prompt</h4><p>Prompt for generating hypothetical documents using HyDE</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<h3>Sample hybrid search query</h3>{'knn': {'field': 'primary_embedding',
  'query_vector': [0.4265527129173279,
   -0.1712949573993683,
   -0.042020395398139954,
   ...],
  'k': 100,
  'num_candidates': 100},
 'query': {'bool': {'must': [{'multi_match': {'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
      'fields': ['original_text',
       'keyphrases',
       'potential_questions',
       'entities'],
      'type': 'best_fields',
      'operator': 'or'}}],
   'should': [{'script_score': {'query': {'match_all': {}},
      'script': {'source': '\n                                        double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;\n                                        double text_score = _score;\n                                        return 0.7 * vector_score + 0.3 * text_score;\n                                        ',
       'params': {'query_vector': [0.4265527129173279,
         -0.1712949573993683,
         -0.042020395398139954,
        ...],
        'vector_field': 'primary_embedding'}}}}]}},
 'size': 10}
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Advanced RAG techniques part 1: Data processing]]></title>
    <description><![CDATA[Discussing and implementing techniques which may increase RAG performance. Part 1 of 2, focusing on the data processing and ingestion component of an advanced RAG pipeline.]]></description>
    <content:encoded><![CDATA[<p><em>This is Part 1 of our exploration into Advanced RAG Techniques. </em><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2"><em>Click here for Part 2!</em></a></p><p>The recent paper <a href="https://arxiv.org/abs/2407.01219">Searching for Best Practices in Retrieval-Augmented Generation</a> empirically assesses the efficacy of various RAG enhancing techniques, with the goal of converging on a set of best-practices for RAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt671704ff06a4011d/6a170b3ea929cf2d19ae09d8/dafa7250e7c4ead4d9b4aed7c407509131929749-1440x572.png" alt="RAG pipeline recommended by Wang" /><p>We'll implement a few of these proposed best-practices, namely the ones which aim to improve the quality of search <strong>(Sentence Chunking, HyDE, Reverse Packing)</strong>.</p><p>For brevity, we will omit those techniques focused on improving efficiency <strong>(Query Classification and Summarization)</strong>.</p><p>We will also implement a few techniques that were not covered, but which I personally find useful and interesting <strong>(Metadata Inclusion, Composite Multi-Field Embeddings, Query Enrichment)</strong>.</p><p>Finally, we'll run a short test to see if the quality of our search results and generated answers has improved versus the baseline. Let's get to it!</p><h2>RAG overview</h2><p>RAG aims to enhance LLMs by retrieving information from external knowledge bases to enrich generated answers. By providing domain-specific information, LLMs can be quickly adapted for use cases outside the scope of their training data; significantly cheaper than fine-tuning, and easier to keep up-to-date.</p><p>Measures to improve the quality of RAG typically focus on two tracks:</p><ol><li><p>Enhancing the quality and clarity of the knowledge base.</p></li><li><p>Improving the coverage and specificity of search queries.</p></li></ol><p>These two measures will achieve the goal of improving the odds that the LLM has access to relevant facts and information, and is thus less likely to hallucinate or draw upon its own knowledge - which may be outdated or irrelevant.</p><p>The diversity of methods is difficult to clarify in just a few sentences. Let's go straight to implementation to make things clearer.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="Advanced RAG pipeline" /><h3>Table of contents</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#overview">Overview</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Table of contents</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#set-up">Set-up</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#ingesting-processing-and-embedding-documents">Ingesting, processing, and embedding documents</a>  </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#data-ingestion">Data ingestion</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#sentence-level-token-wise-chunking">Sentence-level, token-wise chunking</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#metadata-inclusion-and-generation">Metadata inclusion and generation</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#keyphrases-extracted-by-textrank">Keyphrases extracted by TextRank</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#potential-questions-generated-by-gpt-4o">Potential questions generated by GPT-4o</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#entities-extracted-by-spacy">Entities extracted by Spacy</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#composite-multi-field-embeddings">Composite multi-field embeddings</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#indexing-to-elastic">Indexing to Elastic</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#cat-break">Cat break</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#appendix">Appendix</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#definitions">Definitions</a></p></li></ul></li></ul><h2>Set-up</h2><p><em>All code may be found </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>in the Searchlabs repo</em></a><em>.</em></p><p>First things first. You will need the following:</p><ol><li><p>An Elastic Cloud Deployment</p></li><li><p>An LLM API - We are using a GPT-4o deployment on Azure OpenAI in this notebook</p></li><li><p>Python Version 3.12.4 or later</p></li></ol><p>We will be running all the code from <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/main.ipynb">the main.ipynb notebook.</a></p><p>Go ahead and git clone the repo, navigate to supporting-blog-content/advanced-rag-techniques, then run the following commands:</p># Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt
<p>Once that's done, create a <em>.env</em> file and fill out the following fields (Referenced in <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/.env.example"><em>.env.example</em></a>). Credits to my co-author, Claude-3.5, for the helpful comments.</p># Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"
<p>Next, we'll choose the document to ingest, and place it in the documents folder. For this article, we'll be using the <a href="https://s201.q4cdn.com/217177842/files/doc_downloads/OtherDocuments/2023/AnnualMeeting/Annual-Report-Fiscal-Year-2023.pdf">Elastic N.V. Annual Report 2023</a>. It's a pretty challenging and dense document, perfect for stress testing our RAG techniques.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte292dc6030d496cc/6a170b40dc55de9b03e00dfc/e513b9d67adac43da794c25a5969b893127bbbe3-1440x395.jpg" alt="Elastic Annual Report 2023" /><p>Now we're all set, let's go to ingestion. Open <em>main.ipynb</em> and execute the first two cells to import all packages and intialize all services.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Back to top</a></p><h2>Ingesting, processing, and embedding documents</h2><h3>Data ingestion</h3><ul><li><p><em>Personal note: I am stunned by LlamaIndex's convenience. In the olden days before LLMs and LlamaIndex, ingesting documents of various formats was a painful process of collecting esoteric packages from all over. Now it's reduced to a single function call. Wild.</em></p></li></ul><p>The <code>SimpleDirectoryReader</code> will load every document in the <code>directory_path.</code> For <code>.pdf</code> files, it returns a list of document objects, which I convert to Python dictionaries because I find them easier to work with.</p># llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]
<p>Each dictionary contains the key content in the <code>text</code> field. It also contains useful metadata such as page number, filename, file size, and type.</p>{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Back to top</a></p><h3>Sentence-level, token-wise chunking</h3><p>The first thing to do is reduce our documents to chunks of a standard length (to ensure consistency and manageability). Embedding models have unique token limits (maximum input size they can process). Tokens are the basic units of text that models process. To prevent information loss (truncation or omission of content), we should provide text that does not exceed those limits (by splitting longer texts into smaller segments).</p><p>Chunking has a significant impact on performance. Ideally, each chunk would represent a self-contained piece of information, capturing contextual information about a single topic. Chunking methods include word-level chunking, where documents are split by word count, and semantic chunking which uses an LLM to identify logical breakpoints.</p><p>Word-level chunking is cheap, fast, and easy, but runs a risk of splitting sentences and thus breaking context. Semantic chunking gets slow and expensive, especially if you're dealing with documents like the 116-page Elastic Annual Report.</p><p>Let's choose a middleground approach. Sentence level chunking is still simple, but can preserve context more effectively than word-level chunking while being significantly cheaper and faster. Additionally, we'll implement a sliding window to capture some of the surrounding context, and alleviate the impact of splitting paragraphs.</p># chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?&lt;=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start &lt; len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i &gt; chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) &gt;= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)
<p>The <code>Chunker</code> class takes in the embedding model's tokenizer to encode and decode text. We'll now build chunks of 512 tokens each, with an overlap of 20 tokens. To do this, we'll split the text into sentences, tokenize those sentences, and then add the tokenized sentences to our current chunk until we cannot add more without breaching our token limit.</p><p>Finally, decode the sentences back to the original text for embedding, storing it in a field called <code>original_text</code>. Chunks are stored in a field called <code>chunk</code>. To reduce noise (aka useless documents), we will discard any documents smaller than 50 tokens in length.</p><p>Let's run it over our documents:</p>chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)
<p>And get back chunks of text that look like this:</p>print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Back to top</a></p><h3>Metadata inclusion and generation</h3><p>We've chunked our documents. Now it's time to enrich the data. I want to generate or extract additional metadata. This additional metadata can be used to influence and enhance search performance.</p><p>We'll define a <code>DocumentEnricher</code> class, whose role is to take in a list of documents (Python dictionaries), and a list of processor functions. These functions will run over the documents' <code>original_text</code> column, and store their outputs in new fields.</p><p>First, we extract keyphrases using <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/nltk_processor.py">TextRank</a>. TextRank is a graph-based algorithm that extracts key phrases and sentences from text by ranking their importance based on the relationships between words.</p><p>Next, we'll <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/llm.py">generate potential_questions using GPT-4o</a>.</p><p>Finally, we'll <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/entity_extractor.py">extract entities</a> using <a href="https://spacy.io/">Spacy</a>.</p><p>Since the code for each of these is quite lengthy and involved, I will refrain from reproducing it here. If you are interested, the files are marked in the code samples below.</p><p>Let's run the data enrichment:</p># documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)
<p>And take a look at the results:</p><h4>Keyphrases extracted by TextRank</h4><p>These keyphrases are a stand-in for the chunk's core topics. If a query has to do with cybersecurity, this chunk's score will be boosted.</p>print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'
<h4>Potential questions generated by GPT-4o</h4><p>These potential questions may directly match with user queries, offering a boost in score. We prompt GPT-4o to generate questions which can be answered using the information found in the current chunk.</p>print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?
<h4>Entities extracted by Spacy</h4><p>These entities serve a similar purpose to the keyphrases, but capture organizations' and individuals' names, which keyphrase extraction may miss.</p>print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Back to top</a></p><h3>Composite multi-field embeddings</h3><p>Now that we have enriched our documents with additional metadata, we can leverage this information to create more robust and context-aware embeddings.</p><p>Let's review our current point in the process. We've got four fields of interest in each document.</p>{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}
<p>Each field represents a different perspective on the document's context, potentially highlighting a key area for the LLM to focus on.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84cb328fce6aae23/6a170b42964cea3e4408bbc4/aea1f513009a0c7c8545a79fad8f072a5bcae24c-1440x1067.jpg" alt="Metadata Enrichment Pipeline in RAG" /><p>The plan is to embed each of these fields, and then create a weighted sum of the embeddings, known as a Composite Embedding.</p><p>With luck, this Composite Embedding will allow the system to become more context aware, in addition to introducing another tunable hyperparameter from controlling the search behavior.</p><p>First, let's embed each field and update each document in place, using our locally defined embedding model imported at the beginning of the main.ipynb notebook.</p># EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)
<p>Each embedding function returns the embedding's field, which is just the original input field with an <code>_embedding</code> postfix.</p><p>Let's now define the weightings of our composite embedding:</p>embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]
<p>The weightings allow you to assign priorities to each component, based on your usecase and the quality of your data. Intuitively, the size of these weightings is dependent on the semantic value of each component. Since the chunk text itself is by far the richest, I assign a weighting of 70%. Since the entities are the smallest, being just a list of org or person names, I assign it a weighting of 5%. The precise setting for these values has to be determined empirically, on a use-case by use-case basis.</p><p>Finally, let's write a function to apply the weightings, and create our composite embedding. We'll delete all the component embeddings as well to save space.</p>from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)
<p>With this, we've completed our document processing. We now have a list of document objects which look like this:</p>{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }
<h4>Indexing to Elastic</h4><p>Let's bulk upload our documents to Elastic Search. For this purpose, I long-ago defined a set of Elastic Helper functions in <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/elastic_helpers.py"><code>elastic_helpers.py</code></a>. It is a very lengthy piece of code so let's sticking to looking at the function calls.</p><p><code>es_bulk_indexer.bulk_upload_documents</code> works with any list of dictionary objects, taking advantage of Elasticsearch's convenient dynamic mappings.</p># Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)
<p>Head on over to Kibana and verify that all documents have been indexed. There should be 224 of them. Not bad for such a large document!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efeface6effe01d/6a170b447d8d67652870e72a/1b3b07f6b98ceb65f6594ce4be83c5b0ed7e7cf9-1440x1380.jpg" alt="Index Kibana" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Back to top</a></p><h2>Cat break</h2><p>Let's take a break, article's a little heavy, I know. Check out my cat:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1db5595f71c12ff/6a170b450e2e49940241a0fe/baca4eb52b801b21ced97352cc55462f0a12d6b0-969x996.jpg" alt="Han Pipeline" /><p>Adorable. The hat went missing and I half suspect she stole and hid it somewhere :(</p><p>Congrats on making it this far :)</p><p>Join me in <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">Part 2</a> for testing and evaluation of our RAG pipeline!</p><h2>Appendix</h2><h3>Definitions</h3><p><strong>1. Sentence Chunking</strong></p><ul><li><p>A preprocessing technique used in RAG systems to divide text into smaller, meaningful units.</p></li><li><p><em>Process:</em> </p><ol><li><p>Input: Large block of text (e.g., document, paragraph)</p></li><li><p>Output: Smaller text segments (typically sentences or small groups of sentences)</p></li></ol></li><li><p><em>Purpose:</em> </p><ul><li><p>Creates granular, context-specific text segments</p></li><li><p>Allows for more precise indexing and retrieval</p></li><li><p>Improves the relevance of retrieved information in RAG systems</p></li></ul></li><li><p><em>Characteristics:</em> </p><ul><li><p>Segments are semantically meaningful</p></li><li><p>Can be independently indexed and retrieved</p></li><li><p>Often preserves some context to ensure standalone comprehensibility</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Enhances retrieval precision</p></li><li><p>Enables more focused augmentation in RAG pipelines</p></li></ul></li></ul><p><strong>2. HyDE (Hypothetical Document Embedding)</strong></p><ul><li><p>A technique that uses an LLM to generate a hypothetical document for query expansion in RAG systems.</p></li><li><p><em>Process:</em>  </p><ol><li><p>Input query to an LLM</p></li><li><p>LLM generates a hypothetical document answering the query</p></li><li><p>Embed the generated document</p></li><li><p>Use the embedding for vector search</p></li></ol></li><li><p><em>Key difference:</em> </p><ul><li><p>Traditional RAG: Matches query to documents</p></li><li><p>HyDE: Matches documents to documents</p></li></ul></li><li><p><em>Purpose:</em> </p><ul><li><p>Improve retrieval performance, especially for complex or ambiguous queries</p></li><li><p>Capture richer semantic context than a short query</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Leverages LLM's knowledge to expand queries</p></li><li><p>Can potentially improve relevance of retrieved documents</p></li></ul></li><li><p><em>Challenges:</em> </p><ul><li><p>Requires additional LLM inference, increasing latency and cost</p></li><li><p>Performance depends on quality of generated hypothetical document</p></li></ul></li></ul><p><strong>3. Reverse Packing</strong></p><ul><li><p>A technique used in RAG systems to reorder search results before passing them to the LLM.</p></li><li><p><em>Process:</em> </p><ol><li><p>Search engine (e.g., Elasticsearch) returns documents in descending order of relevance.</p></li><li><p>The order is reversed, placing the most relevant document last.</p></li></ol></li><li><p><em>Purpose:</em> </p><ul><li><p>Exploits the recency bias of LLMs, which tend to focus more on the latest information in their context.</p></li><li><p>Ensures the most relevant information is "freshest" in the LLM's context window.</p></li></ul></li><li><p><em>Example:</em> Original order: [Most Relevant, Second Most, Third Most, ...] Reversed order: [..., Third Most, Second Most, Most Relevant]</p></li></ul><p><strong>4. Query Classification</strong></p><ul><li><p>A technique to optimize RAG system efficiency by determining whether a query requires RAG or can be answered directly by the LLM.</p></li><li><p><em>Process:</em> </p><ol><li><p>Develop a custom dataset specific to the LLM in use</p></li><li><p>Train a specialized classification model</p></li><li><p>Use the model to categorize incoming queries</p></li></ol></li><li><p><em>Purpose:</em> </p><ul><li><p>Improve system efficiency by avoiding unnecessary RAG processing</p></li><li><p>Direct queries to the most appropriate response mechanism</p></li></ul></li><li><p><em>Requirements:</em> </p><ul><li><p>LLM-specific dataset and model</p></li><li><p>Ongoing refinement to maintain accuracy</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Reduces computational overhead for simple queries</p></li><li><p>Potentially improves response time for non-RAG queries</p></li></ul></li></ul><p><strong>5. Summarization</strong></p><ul><li><p>A technique to condense retrieved documents in RAG systems.</p></li><li><p><em>Process:</em> </p><ol><li><p>Retrieve relevant documents</p></li><li><p>Generate concise summaries of each document</p></li><li><p>Use summaries instead of full documents in the RAG pipeline</p></li></ol></li><li><p><em>Purpose:</em> </p><ul><li><p>Improve RAG performance by focusing on essential information</p></li><li><p>Reduce noise and interference from less relevant content</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Potentially improves relevance of LLM responses</p></li><li><p>Allows for inclusion of more documents within context limits</p></li></ul></li><li><p><em>Challenges:</em> </p><ul><li><p>Risk of losing important details in summarization</p></li><li><p>Additional computational overhead for summary generation</p></li></ul></li></ul><p><strong>6. Metadata Inclusion</strong></p><ul><li><p>A technique to enrich documents with additional contextual information.</p></li><li><p><em>Types of metadata:</em>  </p><ul><li><p>Keyphrases</p></li><li><p>Titles</p></li><li><p>Dates</p></li><li><p>Authorship details</p></li><li><p>Blurbs</p></li></ul></li><li><p><em>Purpose:</em> </p><ul><li><p>Increase contextual information available to the RAG system</p></li><li><p>Provide LLMs with clearer understanding of document content and relevance</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Potentially improves retrieval accuracy</p></li><li><p>Enhances LLM's ability to assess document usefulness</p></li></ul></li><li><p><em>Implementation:</em> </p><ul><li><p>Can be done during document preprocessing</p></li><li><p>May require additional data extraction or generation steps</p></li></ul></li></ul><p><strong>7. Composite Multi-Field Embeddings</strong></p><ul><li><p>An advanced embedding technique for RAG systems that creates separate embeddings for different document components.</p></li><li><p><em>Process:</em> </p><ol><li><p>Identify relevant fields (e.g., title, keyphrases, blurb, main content)</p></li><li><p>Generate separate embeddings for each field</p></li><li><p>Combine or store these embeddings for use in retrieval</p></li></ol></li><li><p><em>Difference from standard approach:</em> </p><ul><li><p>Traditional: Single embedding for entire document</p></li><li><p>Composite: Multiple embeddings for different document aspects</p></li></ul></li><li><p><em>Purpose:</em> </p><ul><li><p>Create more nuanced and context-aware document representations</p></li><li><p>Capture information from a wider variety of sources within a document</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Potentially improves performance on ambiguous or multi-faceted queries</p></li><li><p>Allows for more flexible weighting of different document aspects in retrieval</p></li></ul></li><li><p><em>Challenges:</em> </p><ul><li><p>Increased complexity in embedding storage and retrieval processes</p></li><li><p>May require more sophisticated matching algorithms</p></li></ul></li></ul><p><strong>8. Query Enrichment</strong></p><ul><li><p>A technique to expand the original query with related terms to improve search coverage.</p></li><li><p><em>Process:</em> </p><ol><li><p>Analyze the original query</p></li><li><p>Generate synonyms and semantically related phrases</p></li><li><p>Augment the query with these additional terms</p></li></ol></li><li><p><em>Purpose:</em> </p><ul><li><p>Increase the range of potential matches in the document corpus</p></li><li><p>Improve retrieval performance for queries with specific or technical language</p></li></ul></li><li><p><em>Benefits:</em> </p><ul><li><p>Potentially retrieves relevant documents that don't exactly match the original query terms</p></li><li><p>Can help overcome vocabulary mismatch between queries and documents</p></li></ul></li><li><p><em>Challenges:</em> </p><ul><li><p>Risk of query drift if not carefully implemented</p></li><li><p>May increase computational overhead in the retrieval process</p></li></ul></li></ul><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Back to top</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 14 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Building multilingual RAG with Elastic and Mistral]]></title>
    <description><![CDATA[Building a multilingual RAG application using Elastic and Mixtral 8x22B model]]></description>
    <content:encoded><![CDATA[<p><a href="https://mistral.ai/news/mixtral-8x22b">Mixtral 8x22B</a> is the most performant open model, and one of its most powerful features is fluency in many languages; including English, Spanish, French, Italian, and German.</p><p>Imagine a multinational company with support tickets and solutions in different languages and wants to take advantage of that knowledge across divisions. Currently, knowledge is limited to the language the agent speaks. Let's fix that!</p><p>In this article, I’m going to show you how to test Mixtral’s language capabilities, by creating a multilingual RAG system.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4116efa3368e0387/6a17117a1949f76a59e7ab36/27ba7e0cdf3d484b5c9e697702b9a63bff49b82b-1440x868.png" alt="Building multilingual RAG with Elastic and Mistral diagram" /><p><em>You can follow the notebook to reproduce this article's example </em><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/building-multilingual-rag-with-elastic-and-mistral/building_multilingual_rag_with_elastic_and_mistral.ipynb"><em>here</em></a></p><h3>Steps</h3><ol><li><p><a href="https://www.elastic.co/search-labs/blog/building-multilingual-rag-with-elastic-and-mistral#creating-endpoints">Creating embeddings endpoint</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/building-multilingual-rag-with-elastic-and-mistral#creating-mappings">Creating mappings</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/building-multilingual-rag-with-elastic-and-mistral#indexing-data">Indexing data</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/building-multilingual-rag-with-elastic-and-mistral#asking-questions">Asking questions</a></p></li></ol><h2>Creating embeddings endpoint</h2><p>Our support tickets for this example will come in English, Spanish, and German. The Mistral embeddings model is not multilingual, but we can generate <a href="https://www.elastic.co/search-labs/blog/multilingual-vector-search-e5-embedding-model">multilingual embeddings</a> using the e5 model, so we can index text on different languages and manage it as a single source, giving us a much richer context.</p><p>To create e5 multilingual embeddings you can use Kibana:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0aadfb7eeddd9754/6a17117c6234e00fc6db1ae8/a691763d2976a23d7d82177b6a7e8ad31051b913-800x549.gif" alt="Creating a multilingual endpoint with Kibana" /><p>Or the _inference API:</p>PUT _inference/text_embedding/multilingual-embeddings
 {
    "service": "elasticsearch",
    "service_settings": {
        "model_id": ".multilingual-e5-small",
        "num_allocations": 1 ,
        "num_threads": 1
    }
}
<h2>Creating Mappings</h2><p>For the mappings we will use <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">semantic_text</a> mapping type, which is one of my favorite features. It handles the process of chunking the data, generating embeddings, and querying embeddings for you!</p>PUT multilingual-mistral
{
  "mappings": {
    "properties": {
      "super_body": {
        "type": "semantic_text",
        "inference_id": "multilingual-embeddings"
      }
    }
  }
}
<p>We call the text field <code>super_body</code> because with a single mapping type it will handle chunks and embeddings.</p><h2>Indexing data</h2><p>We will index a couple of support tickets with problems and solutions in two languages, and then ask a question about problems within many documents in a third.</p><p>The following documents will be added to the index:</p><p></p><p>1. English Support Ticket: Calendar Sync Issue</p><p></p><p><em>Support Ticket #EN1234</em> <strong>Subject</strong>: Calendar sync not working with Google Calendar</p><p><strong>Description</strong>: I'm having trouble syncing my project deadlines with Google Calendar. Whenever I try to sync, I get an error message saying "Unable to connect to external calendar service."</p><p><strong>Resolution</strong>: The issue was resolved by following these steps:</p><ol><li><p>Go to Settings &gt; Integrations</p></li></ol><p></p><ol><li><p>Disconnect the Google Calendar integration</p></li></ol><p></p><ol><li><p>Clear browser cache and cookies</p></li></ol><p></p><ol><li><p>Reconnect the Google Calendar integration</p></li></ol><p></p><ol><li><p>Authorize the app again in Google's security settings</p></li></ol><p>The sync should now work correctly. If problems persist, ensure that third-party cookies are enabled in your browser settings.</p><p></p><p>2. German Support Ticket: File Upload Problem</p><p></p><p><em>Support-Ticket #DE5678</em> <strong>Betreff</strong>: Datei-Upload funktioniert nicht</p><p><strong>Beschreibung</strong>: Ich kann keine Dateien mehr in meine Projekte hochladen. Jedes Mal, wenn ich es versuche, bleibt der Ladebalken bei 99% stehen und dann erscheint eine Fehlermeldung.</p><p><strong>Lösung</strong>: Das Problem wurde durch folgende Schritte gelöst:</p><ol><li><p>Überprüfen Sie die Dateigröße. Die maximale Uploadgröße beträgt 100 MB.</p></li></ol><p></p><ol><li><p>Deaktivieren Sie vorübergehend den Virenschutz oder die Firewall.</p></li></ol><p></p><ol><li><p>Versuchen Sie, die Datei im Inkognito-Modus hochzuladen.</p></li></ol><p></p><ol><li><p>Wenn das nicht funktioniert, leeren Sie den Browser-Cache und die Cookies.</p></li></ol><p></p><ol><li><p>Als letzten Ausweg, versuchen Sie einen anderen Browser zu verwenden.</p></li></ol><p>In den meisten Fällen lag das Problem an zu großen Dateien oder an Interferenzen durch Sicherheitssoftware. Nach Anwendung dieser Schritte sollte der Upload funktionieren.</p><p></p><p>3. Marketing Campaign Ideas (noise)</p><p></p><p><em>Q3 Marketing Campaign Ideas</em></p><ol><li><p>Social media contest: "Share Your Productivity Hack"</p><ul><li><p>Users share tips using our software, best entry wins a premium subscription.</p></li></ul></li></ol><p></p><ol><li><p>Webinar series: "Mastering Project Management"</p><ul><li><p>Invite industry experts to share insights using our tool.</p></li></ul></li></ol><p></p><ol><li><p>Email campaign: "Unlock Hidden Features"</p><ul><li><p>Series of emails highlighting lesser-known but powerful features.</p></li></ul></li></ol><p></p><ol><li><p>Partner with a productivity podcast for sponsored content.</p></li></ol><p></p><ol><li><p>Create a "Project Management Memes" social media account for lighter, shareable content.</p></li></ol><p></p><p>4. Mitarbeiter des Monats (noise)</p><p></p><p><em>Mitarbeiter des Monats: Juli 2023</em></p><p>Wir freuen uns, bekannt zu geben, dass Sarah Schmidt zur Mitarbeiterin des Monats Juli gewählt wurde!</p><p>Sarah hat außergewöhnliche Leistungen in folgenden Bereichen gezeigt:</p><ul><li><p>Kundenbetreuung: Sarah hat durchschnittlich 95% positive Bewertungen erhalten.</p></li></ul><p></p><ul><li><p>Teamarbeit: Sie hat maßgeblich zur Verbesserung unseres internen Wissensmanagementsystems beigetragen.</p></li></ul><p></p><ul><li><p>Innovation: Sarah hat eine neue Methode zur Priorisierung von Support-Tickets vorgeschlagen, die unsere Reaktionszeiten um 20% verbessert hat.</p></li></ul><p>Bitte gratulieren Sie Sarah zu dieser wohlverdienten Anerkennung!</p><p>This is how a document will look like inside Elasticsearch:</p>{
    "took": 9,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 0.9155389,
        "hits": [
            {
                "_index": "multilingual-mistral",
                "_id": "1",
                "_score": 0.9155389,
                "_source": {
                    "super_body": {
                        "text": "\n        _Support Ticket #EN1234_\n        **Subject**: Calendar sync not working with Google Calendar\n\n        **Description**:\n        I'm having trouble syncing my project deadlines with Google Calendar. Whenever I try to sync, I get an error message saying \"Unable to connect to external calendar service.\"\n\n        **Resolution**:\n        The issue was resolved by following these steps:\n        1. Go to Settings &gt; Integrations\n        2. Disconnect the Google Calendar integration\n        3. Clear browser cache and cookies\n        4. Reconnect the Google Calendar integration\n        5. Authorize the app again in Google's security settings\n\n        The sync should now work correctly. If problems persist, ensure that third-party cookies are enabled in your browser settings.\n    ",
                        "inference": {
                            "inference_id": "multilingual-embeddings",
                            "model_settings": {
                                "task_type": "text_embedding",
                                "dimensions": 384,
                                "similarity": "cosine",
                                "element_type": "float"
                            },
                            "chunks": [
                                {
                                    "text": "passage: \n        _Support Ticket #EN1234_\n        **Subject**: Calendar sync not working with Google Calendar\n\n        **Description**:\n        I'm having trouble syncing my project deadlines with Google Calendar. Whenever I try to sync, I get an error message saying \"Unable to connect to external calendar service.\"\n\n        **Resolution**:\n        The issue was resolved by following these steps:\n        1. Go to Settings &gt; Integrations\n        2. Disconnect the Google Calendar integration\n        3. Clear browser cache and cookies\n        4. Reconnect the Google Calendar integration\n        5. Authorize the app again in Google's security settings\n\n        The sync should now work correctly. If problems persist, ensure that third-party cookies are enabled in your browser settings.",
                                    "embeddings": [
                                        0.0059651174,
                                        0.0016363655,
                                        -0.064753555,
                                        0.0093298275,
                                        0.05689768,
                                        -0.049640983,
                                        0.02504726,
                                        0.0048340675,
                                        0.08093895,
                                        ...
                                    ]
                                }
                            ]
                        }
                    }
                }
            }
        ]
    }
}
<h2>Asking questions</h2><p>Now, we are going to ask a question in Spanish:</p>Hola, estoy teniendo problemas para ocupar su aplicación, estoy teniendo problemas para sincronizar mi calendario, y encima al intentar subir un archivo me da error.<p>The expectation is retrieving documents #1 and #2, then sending them to the LLM as additional context, and finally, getting an answer in Spanish.</p><h4>Retrieving documents</h4><p>To retrieve the relevant documents, we can use this nice and short query that will run a search on the embeddings, and return the support tickets most relevant to the question.</p>GET multilingual-mistral/_search
{
   "size": 2,
   "_source": {
    "excludes": ["*embeddings", "*chunks"]
   },
  "query": {
    "semantic": {
      "field": "super_body",
      "query": "Hola, estoy teniendo problemas para ocupar su aplicación, estoy teniendo problemas para sincronizar mi calendario, y encima al intentar subir un archivo me da error."
    }
  }
}
<p><em>Notes about the parameters set:</em> <code>size: 2</code> Because we know we want the top 2 documents. <code>excludes</code> For clarity in the response. Documents are short so each one will be one chunk long.</p><h4>Answering the question</h4><p>Now we can call the Mistral completion API using the Python library to answer the question.</p>from mistralai.client import MistralClient
from mistralai.models.chat_completion import ChatMessage

api_key = os.environ["MISTRAL_API_KEY"]
model = "open-mixtral-8x22b"

client = MistralClient(api_key=api_key)

system_message = "You are a helpful multilingual agent that helps users with their problems. You have access to a knowledge base of different languages and you must answer in the same language the question was asked."

user_message = """
## Question:

Hola, estoy teniendo problemas para ocupar su aplicación, estoy teniendo problemas para sincronizar mi calendario, y encima al intentar subir un archivo me da error. 

## Related knowledge:

Support Ticket #EN1234 Subject: Calendar sync not working with Google Calendar...
(the rest of the content of the document)

\n

Support-Ticket #DE5678 Betreff: Datei-Upload funktioniert nicht... 
(the rest of the content of the document)

ANSWER:

"""

messages = [
    ChatMessage(role="system", content=system_message),
    ChatMessage(role="user", content=user_message)
]

chat_response = client.chat(
    model=model,
    messages=messages,
)

print(chat_response.choices[0].message.content) 
<p>The answer is in perfect Spanish and on point!</p><p></p><p>Show answer</p><p></p><p>Hola, me alegra que te hayas comunicado con nosotros. Parece que hay dos problemas distintos.</p><p>En cuanto a la sincronización del calendario, puedes seguir estos pasos para resolver el problema:</p><ol><li><p>Ve a Configuración &gt; Integraciones</p></li></ol><p></p><ol><li><p>Desconecta la integración del Calendario de Google</p></li></ol><p></p><ol><li><p>Borra la caché y las cookies del navegador</p></li></ol><p></p><ol><li><p>Vuelve a conectar la integración del Calendario de Google</p></li></ol><p></p><ol><li><p>Autoriza de nuevo la aplicación en la configuración de seguridad de Google</p></li></ol><p>Si sigues teniendo problemas, asegúrate de que las cookies de terceros están habilitadas en la configuración de tu navegador.</p><p>En cuanto al problema de subir un archivo, hay varias cosas que puedes probar:</p><ol><li><p>Comprueba el tamaño del archivo. El tamaño máximo de carga es de 100 MB.</p></li></ol><p></p><ol><li><p>Desactiva temporalmente el antivirus o el cortafuegos.</p></li></ol><p></p><ol><li><p>Intenta cargar el archivo en modo incógnito.</p></li></ol><p></p><ol><li><p>Si eso no funciona, borra la caché y las cookies del navegador.</p></li></ol><p></p><ol><li><p>Como último recurso, prueba a usar un navegador diferente.</p></li></ol><p>En la mayoría de los casos, el problema se debe a archivos demasiado grandes o a interferencias causadas por software de seguridad. Al seguir estos pasos, deberías poder cargar el archivo correctamente.</p><p>¡Espero que esto te ayude a resolver tus problemas! Si tienes alguna otra pregunta, no dudes en preguntar.</p><h2>Conclusion</h2><p>Mixtral 8x22B is a powerful model that enables us to leverage data sources in different languages, being able to answer, understand, and translate in many languages. This ability– together with multilingual embeddings– allows you to have multilingual support both in the data retrieval and the answer generation stages, removing language barriers entirely.</p><p><em>If you are interested on reproducing the examples of this article, you can find the Python Notebook with the requests </em><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/building-multilingual-rag-with-elastic-and-mistral/building_multilingual_rag_with_elastic_and_mistral.ipynb"><em>here</em></a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/building-multilingual-rag-with-elastic-and-mistral</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/building-multilingual-rag-with-elastic-and-mistral</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9cf558f36ced44dc/6a17117dd7c022520cde65a2/7dd63f367670175590e30927ef432ff93e166c84-1440x809.png" length="0" type="image/png"/>
    <pubDate>Fri, 02 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Building RAG with Llama 3 open-source and Elastic]]></title>
    <description><![CDATA[Learn how to build a RAG system with Llama3 open source and Elastic. This blog provides practical examples of RAG using Llama3 as an LLM.]]></description>
    <content:encoded><![CDATA[<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt902f708eddf7ee15/6a17d71742022952cf29f44d/52f1c7bb9a419f6468f5ea92d28b8e28ef33afc7-966x321.png" alt="Building RAG with Llama 3 open-source and Elastic" /><p></p><p>This blog will walk through implementing RAG using two approaches.</p><ol><li><p>Elastic, Llamaindex, Llama 3 (8B) version running locally using Ollama.</p></li><li><p>Elastic, Langchain, ELSER v2, Llama 3 (8B) version running locally using Ollama.</p></li></ol><p>The notebooks are available at this <a href="https://github.com/elastic/elasticsearch-labs/tree/main/notebooks/integrations/llama3">GitHub</a> location.</p><p>Before we get started, let's take a quick dive into Llama 3.</p><h2>Llama 3 overview</h2><p>Llama 3 is an open source large language model recently launched by Meta. This is a successor to Llama 2 and based on published metrics, is a significant improvement. It has good evaluation metrics, when compared to some of the recently published models such as Gemma 7B Instruct, Mistral 7B Instruct, etc. The model has two variants, which are the 8 billion and 70 billion parameter. An interesting thing to note is that at the time of writing this blog, Meta was still in the process of training 400B+ variant of Llama 3.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d94caf1ea093092/6a17d718abe0f2e686dfe858/efe54db6b84b063d48708e47d323c3d37bc162cf-1440x810.png" alt="Meta Llama 3 Instruct Model Performance. (from https://ai.meta.com/blog/meta-llama-3/)" /><p>Meta Llama 3 Instruct Model Performance. (from<a href="https://ai.meta.com/blog/meta-llama-3/"> https://ai.meta.com/blog/meta-llama-3/</a>)</p><p>The above figure shows data on Llama3 performance across different datasets as compared to other models. In order to be optimized for performance for real world scenarios, Llama3 was also evaluated on a high quality human evaluation set.</p><p>Aggregated results of Human Evaluations across multiple categories and prompts (from<a href="https://ai.meta.com/blog/meta-llama-3/"> https://ai.meta.com/blog/meta-llama-3/</a>)</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt549890dd4cb8215c/6a17d71a1d1b83f13393e2d1/612561b2e8c35f19d3161ae867235fedf93b7e9e-1440x903.png" alt="" /><h2>How to build RAG with Llama 3 open-source and Elastic</h2><h3>Dataset</h3><p>For the dataset, we will use a fictional organization policy document in json format, available at this <a href="https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/datasets/workplace-documents.json">location</a>.</p><h3>Configure Ollama and Llama3</h3><p>As we are using the Llama 3 8B parameter size model, we will be running that using Ollama. Follow the steps below to install Ollama.</p><ol><li><p>Browse to the URL<a href="https://ollama.com/download"> https://ollama.com/download</a> to download the Ollama installer based on your platform.</p></li></ol><p><em>Note: The Windows version is in preview at the moment.</em></p><ol><li><p>Follow the instructions to install and run Ollama for your OS.</p></li><li><p>Once installed, follow the commands below to download the Llama3 model.</p></li></ol>    ollama run llama3
<p>This should take some time depending upon your network bandwidth. Once the run completes, you should end with the interface below.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte70e67e2dd184948/6a17d71b4b055d4b0843203b/6f9cce46c6a7442bbfd4cef6c6250e0b4e697376-777x595.png" alt="" /><p>To test Llama3, run the following command from a new terminal or enter the text at the prompt itself.</p>    curl -X POST http://localhost:11434/api/generate -d '{ "model": "llama3", "prompt":"Why is the sky blue?" }'
<p>At the prompt, the output looks like below.</p>    ❯ ollama run llama3
    &gt;&gt;&gt; Why is the sky blue?
    The color of the sky appears blue to our eyes because of a fascinating combination of scientific factors. Here's the short answer:

    **Scattering of Light**: When sunlight enters Earth's atmosphere, it encounters tiny molecules of gases like nitrogen (N2) and oxygen (O2).
    These molecules scatter the light in all directions, but they do so more efficiently for shorter wavelengths (like blue and violet light) than
    longer wavelengths (like red and orange light).

    **Rayleigh Scattering**: This scattering effect is known as Rayleigh scattering, named after the British physicist Lord Rayleigh, who first
    described it in the late 19th century. It's responsible for the blue color we see in the sky.

    **Atmospheric Composition**: The Earth's atmosphere is composed of approximately 78% nitrogen, 21% oxygen, and small amounts of other gases.
    These gases are more abundant at lower altitudes, where they scatter shorter wavelengths (like blue light) more effectively than longer
    wavelengths (like red light).

    **Sunlight's Wavelengths**: When sunlight enters the Earth's atmosphere, it contains a broad spectrum of wavelengths, including visible light
    with colors like red, orange, yellow, green, blue, indigo, and violet. The shorter wavelengths (blue and violet) are scattered more than the
    longer wavelengths (red and orange), due to Rayleigh scattering.

    **What We See**: As our eyes look up at the sky, we see the combined effect of these factors: the shorter wavelengths (blue light) being
    scattered in all directions by the atmospheric gases, while the longer wavelengths (red and orange light) continue to travel in a more direct
    path to our eyes. This results in the blue color we perceive as the sky.

    So, to summarize: the sky appears blue because of the scattering of sunlight's shorter wavelengths (blue light) by the tiny molecules in the
    Earth's atmosphere, combined with the atmospheric composition and the original wavelengths present in sunlight.

    Now, go enjoy that blue sky!

    &gt;&gt;&gt; Send a message (/? for help)
<p>We now have Llama3 running locally using Ollama.</p><h3>Elasticsearch setup</h3><p>We will use Elastic cloud setup for this. Please follow the instructions <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">here</a>. Once successfully deployed, note the API Key and the Cloud ID, we will require them as part of our setup.</p><h3>Application setup</h3><p>There are two notebooks, one for RAG implemented using Llamaindex and Llama3, the other one with Langchain, ELSER v2 and Llama3. In the first notebook, we use Llama3 as a local LLM as well as provide embeddings. For the second notebook, we use ELSER v2 for the embeddings and Llama3 as the local LLM.</p><h4>Method 1: Elastic, Llamaindex, Llama 3 (8B) version running locally using Ollama.</h4><p>Step 1 : Install required dependencies</p>    !pip install llama-index
    !pip install llama-index-cli
    !pip install llama-index-core
    !pip install llama-index-embeddings-elasticsearch
    !pip install llama-index-embeddings-ollama
    !pip install llama-index-legacy
    !pip install llama-index-llms-ollama
    !pip install llama-index-readers-elasticsearch
    !pip install llama-index-readers-file
    !pip install llama-index-vector-stores-elasticsearch
    !pip install llamaindex-py-client
<p>The above section installs the required llamaindex packages.</p><p>Step 2: Import required dependencies</p><p>We start with importing the required packages and classes for the app.</p>    from llama_index.core.node_parser import SentenceSplitter
    from llama_index.core.ingestion import IngestionPipeline
    from llama_index.embeddings.ollama import OllamaEmbedding
    from llama_index.vector_stores.elasticsearch import ElasticsearchStore
    from llama_index.core import VectorStoreIndex, QueryBundle
    from llama_index.llms.ollama import Ollama
    from llama_index.core import Document, Settings
    from getpass import getpass
    from urllib.request import urlopen
    import json
<p>We start with providing a prompt to the user to capture the Cloud ID and API Key values.</p>    #https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id
    ELASTIC_CLOUD_ID = getpass("Elastic Cloud ID: ")

    #https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#creating-an-api-key
    ELASTIC_API_KEY = getpass("Elastic Api Key: ")
<p>If you are not familiar with obtaining the Cloud ID and API Key, please follow the links in the code snippet above to guide you with the process.</p><p>Step 3: document processing</p><p>We start with downloading the json document and building out <a href="https://docs.llamaindex.ai/en/stable/module_guides/loading/documents_and_nodes/">Document</a> objects with the payload.</p>    url = "https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/datasets/workplace-documents.json"
    response = urlopen(url)
    workplace_docs = json.loads(response.read())
    documents = [Document(text=doc['content'],
                              metadata={"name": doc['name'],"summary": doc['summary'],"rolePermissions": doc['rolePermissions']})
                     for doc in workplace_docs]
<p>We now define the Elasticsearch vector store (<a href="https://docs.llamaindex.ai/en/stable/api_reference/storage/vector_store/elasticsearch/#llama_index.vector_stores.elasticsearch.ElasticsearchStore">ElasticsearchStore</a>), the embedding created using Llama3 and a <code>pipeline</code> to help process the payload constructed above and ingest into Elasticsearch.</p><p>The ingestion pipeline allows us to compose pipelines using different components, one of which allows us to generate embeddings using Llama3.</p>    es_vector_store = ElasticsearchStore(index_name="workplace_index",
                                         vector_field='content_vector',
                                         text_field='content',
                                         es_cloud_id=ELASTIC_CLOUD_ID,
                                         es_api_key=ELASTIC_API_KEY)

    # Embedding Model to do local embedding using Ollama.
    ollama_embedding = OllamaEmbedding("llama3")
    # LlamaIndex Pipeline configured to take care of chunking, embedding
    # and storing the embeddings in the vector store.
    pipeline = IngestionPipeline(
        transformations=[
            SentenceSplitter(chunk_size=512, chunk_overlap=100),
            ollama_embedding
        ], vector_store=es_vector_store
    )
<p><a href="https://docs.llamaindex.ai/en/stable/api_reference/storage/vector_store/elasticsearch/#llama_index.vector_stores.elasticsearch.ElasticsearchStore">ElasticsearchStore</a> is defined with the name of the index to be created, the vector field and the content field. And this index is created when we run the pipeline.</p><p>The index mapping created is as below:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta88debbd9af3ac96/6a17d71d3e03d7868c4f2ab7/5af30a28a7caa99c5fb4c5c691320d9f9d0644e9-367x589.png" alt="" /><p>The pipeline is executed using the step below. Once this pipeline run completes, the index <code>workplace_index</code> is now available for querying. Do note that the vector field <code>content_vector</code> is created as a dense vector with dimension <code>4096</code>. The dimension size comes from the size of the embeddings generated from Llama3.</p>    pipeline.run(show_progress=True,documents=documents)
<p>Step 4: LLM configuration</p><p>We now setup Llamaindex to use the Llama3 as the LLM. This as we covered before is done with the help of Ollama.</p>    Settings.embed_model = ollama_embedding
    local_llm = Ollama(model="llama3")
<p>Step 5: Semantic search</p><p>We now configure Elasticsearch as the vector store for the Llamaindex query engine. The query engine is then used to answer your questions with contextually relevant data from Elasticsearch.</p>    index = VectorStoreIndex.from_vector_store(es_vector_store)
    query_engine = index.as_query_engine(local_llm, similarity_top_k=10)

    # Customer Query
    query = "What are the organizations sales goals?"
    bundle = QueryBundle(query_str=query,
    embedding=Settings.embed_model.get_query_embedding(query=query))

    response = query_engine.query(bundle)

    print(response.response)
<p>The response I received with Llama3 as the LLM and Elasticsearch as the Vector database is below.</p>    According to the "Fy2024 Company Sales Strategy" document, the organization's primary goal is to:

    * Increase revenue by 20% compared to fiscal year 2023.
    * Expand market share in key segments by 15%.
    * Retain 95% of existing customers and increase customer satisfaction ratings.
    * Launch at least two new products or services in high-demand market segments.
<p>This concludes the RAG setup based on using Llama3 as a local LLM and to generate embeddings.</p><p>Let's now move to the second method, which uses Llama3 as a local LLM, but we use Elastic’s ELSER v2 to generate embeddings and for semantic search.</p><h4>Method 2: Elastic, Langchain, ELSER v2, Llama 3 (8B) version running locally using Ollama.</h4><p>Step 1: Install required dependencies</p>    !pip install langchain
    !pip install langchain-elasticsearch
    !pip install langchain-community
    !pip install tiktoken
<p>The above section installs the required langchain packages.</p><p>Step 2: Import required dependencies</p><p>We start with importing the required packages and classes for the app. This step is similar to Step 2 in Method 1 above.</p>    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain_elasticsearch import ElasticsearchStore
    from langchain_community.llms import Ollama
    from langchain.prompts import ChatPromptTemplate
    from langchain.schema.output_parser import StrOutputParser
    from langchain.schema.runnable import RunnablePassthrough
    from langchain_elasticsearch import ElasticsearchStore
    from langchain_elasticsearch import SparseVectorStrategy
    from getpass import getpass
    from urllib.request import urlopen
    import json
<p>Next, provide a prompt to the user to capture the Cloud ID and API Key values.</p>    #https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id
    ELASTIC_CLOUD_ID = getpass("Elastic Cloud ID: ")

    #https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#creating-an-api-key
    ELASTIC_API_KEY = getpass("Elastic Api Key: ")
<p>Step 3: Document processing</p><p>Next, we move to downloading the json document and building the payload.</p>    url = "https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/datasets/workplace-documents.json"

    response = urlopen(url)
    workplace_docs = json.loads(response.read())
    metadata = []
    content = []
    for doc in workplace_docs:
        content.append(doc["content"])
        metadata.append(
            {
                "name": doc["name"],
                "summary": doc["summary"],
                "rolePermissions": doc["rolePermissions"],
            }
        )
    text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
        chunk_size=512, chunk_overlap=256
    )
    docs = text_splitter.create_documents(content, metadatas=metadata)
<p>This step differs from the Method 1 approach, from how we use the LlamaIndex provided pipeline to process the document. Here we use the <code>RecursiveCharacterTextSplitter</code> to generate the chunks.</p><p>We now define the Elasticsearch vector store <a href="https://api.python.langchain.com/en/latest/vectorstores/langchain_elasticsearch.vectorstores.ElasticsearchStore.html">ElasticsearchStore</a>.</p>    es_vector_store = ElasticsearchStore(
        es_cloud_id=ELASTIC_CLOUD_ID,
        es_api_key=ELASTIC_API_KEY,
        index_name="workplace_index_elser",
        strategy=SparseVectorStrategy(
            model_id=".elser_model_2_linux-x86_64"
        )
    )
<p>The vector store is defined with the index to be created and the model to be used for embedding and retrieval. You can retrieve the <code>model_id</code> by navigating to Trained Models under Machine Learning.</p><p>This also results in the creation of an ingest pipeline in Elastic, which generates and stores the embeddings as the documents are ingested into Elastic.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt392d48d602cf0458/6a17d71f3e03d7e0754f2abb/d924e59ecb3f918be3ca7264a316a801d4ee551d-1440x487.png" alt="" /><p>We now add the documents processed above.</p>    es_vector_store.add_documents(documents=docs)
<p>Step 4: LLM configuration</p><p>We set up the LLM to be used with the following. This is again different from method 1, where we used Llama3 for embeddings too.</p>    llm = Ollama(model="llama3")
<p>Step 5: Semantic search</p><p>The necessary building blocks are all in place now. We tie them up together to perform semantic search using ELSER v2 and Llama3 as the LLM. Essentially, Elasticsearch ELSER v2 provides the contextually relevant response to the users question using its semantic search capabilities. The user's question is then enriched with the response from ELSER and structured using a template. This is then processed with Llama3 to generate relevant responses.</p>    def format_docs(docs):
        return "\n\n".join(doc.page_content for doc in docs)

    retriever = es_vector_store.as_retriever()
    template = """Answer the question based only on the following context:\n

                    {context}
                    
                    Question: {question}
                   """
    prompt = ChatPromptTemplate.from_template(template)
    chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )

    chain.invoke("What are the organizations sales goals?")
<p>The response with Llama3 as the LLM and ELSER v2 for semantic search is as below:</p>    According to the provided context, the organization's sales goals for Fiscal Year 2024 are:

    1. Increase revenue by 20% compared to fiscal year 2023.
    2. Expand market share in key segments by 15%.
    3. Retain 95% of existing customers and increase customer satisfaction ratings.

    These goals are outlined under "Objectives for Fiscal Year 2024" in the provided document.
<p>This concludes the RAG setup based on using Llama3 as a local LLM and ELSER v2 for semantic search.</p><h2>Conclusion</h2><p>In this blog we looked at two approaches to RAG with Llama3 and Elastic. We explored Llama3 as an LLM and to generate embeddings. Next we used Llama3 as the local LLM and ELSER for embeddings and semantic search. We utilized two different frameworks, LlamaIndex and Langchain. You could implement the two methods using either of these frameworks. The notebooks were tested with the Llama3 8B parameter version. Both the notebooks are available at this <a href="https://github.com/elastic/elasticsearch-labs/tree/main/notebooks/integrations/llama3">GitHub</a> location.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Rishikesh Radhakrishnan]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte43bb958addae113/6a17d720be60862c01004598/cdae9e56c803a0765f7fd0c2856bce018bbbaa59-1080x1080.png" length="0" type="image/png"/>
    <pubDate>Thu, 20 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Intelligent RAG data chunking: Fetch surrounding chunks]]></title>
    <description><![CDATA[Learn about data chunking in RAG and explore fetch surrounding chunking, a pattern in RAG that uses chunking and Elasticsearch to refine LLM responses.]]></description>
    <content:encoded><![CDATA[<p>In the realm of Retrieval-Augmented Generation (RAG), one persistent challenge is finding the optimal amount of data to feed into a Large Language Model (LLM). Too little data results in insufficient or inaccurate responses, while too much data leads to vague answers. This delicate balance inspired me to develop a <a href="https://ela.st/fetch-surrounding-chunks">notebook</a> focusing on intelligent chunking and leveraging Elasticsearch vector database.</p><p>This blog builds on that notebook and explores fetch surrounding chunking, an emerging pattern in RAG that uses intelligent chunking and Elasticsearch vector database to optimize LLM responses. The approach balances data input to enhance the accuracy and relevance of LLM-generated answers through semantic hybrid search.</p><h2>The motivation: A refined approach to RAG data chunking</h2><p>The primary motivation behind building <a href="https://ela.st/fetch-surrounding-chunks">this notebook</a> was to demonstrate a refined approach to RAG by addressing the challenge of data chunking. Traditional methods often fall short in dynamically adjusting the data size fed to LLMs, either overwhelming the model with too much context or starving it with too little. This notebook aims to strike the right balance, providing just enough information for the LLM to generate precise and contextually relevant responses. However, it must be noted that there is no one-size-fits-all solution.</p><p>This method works especially well with books and similar texts where content flows within longer sections or chapters. However, it may require adaptation for texts structured into shorter, distinct sections, such as research papers or articles, where each segment might cover a different topic. In such cases, additional strategies may be necessary to effectively chunk and retrieve related content.</p><h2>The methodology: Intelligent RAG data chunking</h2><h3>Fetch surrounding chunks</h3><p>The core idea is to partition the source text into manageable chunks, ensuring each chunk contains just the right amount of information. For this demonstration, I used text from "Harry Potter and the Sorcerer's Stone." The text was partitioned into chapters, and each chapter was further divided into smaller chunks. These chunks, along with their dense and sparse (ELSER) vector representations, were indexed in the Elasticsearch vector database.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6bcc905a1653ca3a/6a1711357d8d670d9970e846/23b210ce29f47f8a872d300ef01fca901d1e80ab-1163x548.png" alt="architecture" /><h3>Assigning numbers to chunks</h3><p>Each chunk within a chapter was assigned a sequential integer, allowing us to identify its position. When a matching chunk is found, the chapter number and chunk number are used to retrieve surrounding chunks, providing additional context for the LLM.</p><h3>Vector database in Elasticsearch</h3><p>These chunks and their vector representations were ingested into an Elasticsearch Cloud instance. Elasticsearch's robust vector search capabilities make it ideal for hosting these chunks, allowing for efficient retrieval of the most relevant chunks based on the semantic content or text match of a user's query.</p><h3>AI search</h3><p>To retrieve the relevant chunks, I employed a hybrid search strategy using dense vector comparisons, sparse vector comparisons, and text search in parallel. This multi-faceted approach ensures that the search results are both semantically rich and contextually accurate. A query is issued to find the matched chunk, which returns the chunk number and chapter. Surrounding chunks for that chapter are then fetched based on the matched chunk.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte879d45ec9558b9e/6a171137b339d50e9776a0be/62d3cb6d9cddcecda359bc1fd808b52cd0f23864-1440x778.png" alt="architecture" /><h2>The RAG pattern</h2><p>When a query is made, the search flow performs the following steps:</p><ol><li><p><strong>Query analysis:</strong> The user's query is translated into dense and sparse vectors to retrieve the most relevant chunks from the Elasticsearch index.</p></li><li><p><strong>Chunk retrieval:</strong> Using the AI search strategy, the system retrieves the top relevant chunks.</p></li><li><p><strong>Contextual expansion:</strong> Adjacent chunks (n-1 and n+1) are also retrieved to provide a more comprehensive context. If the chunk is the last in the chapter, it fetches n-1 and n-2; if it's the first, it fetches n+1 and n+2.</p></li><li><p><strong>LLM response:</strong> These intelligently selected chunks are then fed into the LLM, ensuring it receives the optimal amount of information to generate a precise and contextually relevant response.</p></li></ol><h2>Why intelligent RAG data chunking matters</h2><p>This approach addresses a critical aspect of RAG by optimizing the input data fed to LLMs. By leveraging intelligent chunking and hybrid semantic search, this method enhances the accuracy and relevance of the responses generated by LLMs. It showcases a pattern that can be widely applied in various applications within the RAG space, from customer support to content generation and beyond.</p><h2>Conclusion</h2><p><a href="https://ela.st/fetch-surrounding-chunks">This notebook</a> underscores the importance of intelligent data chunking in the RAG framework and demonstrates how Elasticsearch vector database can be leveraged to achieve optimal results. By ensuring the LLM receives just the right amount of information, this methodology paves the way for more accurate and contextually rich responses, enhancing the overall effectiveness of RAG systems.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-chunking-fetch-surrounding-chunks</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-chunking-fetch-surrounding-chunks</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Sunile Manjee]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt17ba5b693b94a883/6a171139acf0880723be9c49/4467ccd71baaae7422b9b5df9a8612eec4af1bd2-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 11 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch open inference API adds support for Azure OpenAI embeddings]]></title>
    <description><![CDATA[Elasticsearch open inference API adds support for Azure OpenAI embeddings to be stored in the world's most downloaded vector database.]]></description>
    <content:encoded><![CDATA[<p>We're happy to announce that Elasticsearch now supports <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/overview">Azure OpenAI embeddings</a> in our <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-apis.html">open inference API</a>, enabling developers to store generated embeddings into our highly scalable and performant <a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">vector database</a>.</p><p>This new functionality further solidifies our commitment to not only working with Microsoft and the Azure platform, but also toward our commitment to offering our customers more flexibility with their AI solutions.</p><h2>Ongoing Investment in AI at Elastic</h2><p>This is the latest in a series of additional features and integrations on AI enablement for Elasticsearch following on from:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-ai-studio-support">Elasticsearch open inference API adds Azure AI Studio support</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-completion-support">Elasticsearch open inference API adds support for Azure OpenAI chat completions</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-openai-completion-support">Elasticsearch open inference API adds support for OpenAI chat completions</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-embeddings-support">Elasticsearch open inference API adds support for Cohere Embeddings</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/azure-openai-on-your-data-elasticsearch-vector-database">Introducing Elasticsearch vector database to Azure OpenAI Service On Your Data (preview)</a></p></li></ul><p>The new <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-apis.html">inference</a> embeddings service provider for Azure OpenAI is already available in our <a href="https://www.elastic.co/search-labs/blog/building-elastic-cloud-serverless">stateless offering</a> on Elastic Cloud, and will be soon available to everyone in an upcoming Elastic release.</p><h2>Using Azure OpenAI Embeddings with the Elasticsearch Inference API</h2><h3>Deploying an Azure OpenAI Embeddings Model</h3><p>To get started, you will need a <a href="https://azure.microsoft.com/">Microsoft Azure Subscription</a> as well as access to <a href="https://aka.ms/oai/access">Azure OpenAI service</a>. Once you have registered and have access, you will need to create a resource in your <a href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure Portal</a>, and then deploy an embedding model to <a href="https://oai.azure.com/">Azure OpenAI Studio</a>. To do this, if you do not already have an Azure OpenAI resource in your Azure Portal, create a new one from the “Azure OpenAI” type which can be found in the Azure Marketplace, and take note of your resource name as you will need this later. When you create your resource, the region you choose may impact what models you have access to. See the Azure OpenAI deployment <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#standard-deployment-model-availability">model availability table</a> for additional details.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35aedf6095d45d06/6a17d706abe0f20405dfe854/53dfbc95b12448541a816f78f8d996c2a3c24399-416x220.png" alt="Azure OpenAI on Marketplace" /><p>Once you have your resource, you will also need one of your API keys which can be found in the “Keys and Endpoint” information from the Azure Portal's left side navigation:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d58e5f2c964df0a/6a17d707e31791350e2d5678/c45e937ec65b16818168264b30e0ec419f0f7eac-724x404.png" alt="Keys and Endpoint" /><p>Now, to deploy your Azure OpenAI Embedding model, go into your <a href="https://oai.azure.com/">Azure OpenAI Studio's</a> console and create your deployment using an <a href="https://platform.openai.com/docs/guides/embeddings/embedding-models">OpenAI Embeddings model</a> such as <code>text-embedding-ada-002</code>. Once your deployment is created, you should see the deployment overview. Also take note of the deployment name, in the example below it is “example-embeddings-model”.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9541eda646e4cfb3/6a17d709be608642de004594/ab8c1d64aaa3a41436f5c6be5a2ea867c648ae11-938x311.png" alt="Example Deployment" /><h3>Using your deployed Azure OpenAI embeddings model with the Elasticsearch Inference API</h3><p>With an Azure OpenAI embeddings model deployed, we can now configure your Elasticsearch deployment's <code>_inference</code> API and create a pipeline to index embeddings vectors in your documents. Please refer to the <a href="https://github.com/elastic/elasticsearch-labs/">Elastic Search Labs GitHub repository</a> for more in-depth guides and interactive notebooks.</p><p>To perform these tasks, you can use the Kibana Dev Console, or any REST console of your choice.</p><p>First, configure your inference endpoint using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.14/put-inference-api.html">create inference model endpoint</a> - we'll call this “example_model”:</p>PUT _inference/text_embedding/example_model
{
    "service": "azureopenai",
    "service_settings": {
        "api_key": "&lt;api-key&gt;",
        "resource_name": "&lt;resource-name&gt;",
        "deployment_id": "&lt;deployment-id&gt;",
        "api_version": "2024-02-01"
    },
    "task_settings": {
        "user": "&lt;optional-username&gt;"
    }
}
<p>For your inference endpoint, you will need your API key, your resource name, and the deployment id that you created above. For the “api_version”, you will want to use an available API version from the <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#embeddings">Azure OpenAI embeddings documentation</a> - we suggest always using the latest version which is “2024-02-01” as of this writing. You can also optionally add a username in the task setting's “user” field which should be a unique identifier representing your end-user to help Azure OpenAI to monitor and detect abuse. If you do not want to do this, omit the entire “task_settings” object.</p><p>After running this command you should receive a <code>200 OK</code> status indicating that the model is properly set up.</p><p>Using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.14/post-inference-api.html">perform inference endpoint</a>, we can see an example of your inference endpoint at work:</p>POST _inference/text_embedding/example_model
{
  "input": "What is Elastic?"
}
<p>The output from the above command should provide the embeddings vector for the input text:</p>{
    "text_embedding": [
        {
            "embedding": [
                -0.0038039694,
                0.0054465225,
                -0.0018359756,
                -0.02274399,
                -0.01969836,
                ...
            ]
        }
    ]
}
<p>Now that we know our inference endpoint works, we can create a pipeline that uses it:</p>PUT _ingest/pipeline/azureopenai_embeddings
{
  "processors": [
    {
      "inference": {
        "model_id": "example_model", 
        "input_output": { 
          "input_field": "name",
          "output_field": "name_embedding"
        }
      }
    }
  ]
}
<p>This will create an ingestion pipeline named “azureopenai_embeddings” that will read the contents of the “name” field upon ingestion and apply the embeddings inference from our model to the “name_embedding” output field. You can then use this ingestion pipeline when documents are ingested (e.g. via the _bulk ingest endpoint), or when reindexing an index that is already populated.</p><p>This is currently available through the open inference API in our <a href="https://www.elastic.co/search-labs/blog/building-elastic-cloud-serverless">stateless offering</a> on Elastic Cloud. It'll also be soon available to everyone in an upcoming versioned Elasticsearch release, with additional semantic text capabilites that will make this step even simpler to integrate into your existing workflows.</p><p>For an additional use case, you can walk through the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.14/semantic-search-inference.html">semantic search with inference tutorial</a> for how to perform ingestion and semantic search on a larger scale with Azure OpenAI and other services such as reranking or chat completions.</p><h2>Plenty more on the horizon</h2><p>This new extensibility is only one of many new features we are bringing to the AI table from Elastic. Bookmark <a href="https://www.elastic.co/search-labs">Elastic Search Labs</a> now to stay up to date! Ready to build RAG into your apps? Want to try different LLMs with a vector database? Check out our sample notebooks for LangChain, Cohere and more <a href="https://github.com/elastic/elasticsearch-labs">on Github</a>, and join the Elasticsearch <a href="https://www.elastic.co/training/elasticsearch-engineer">Engineer training</a> starting soon!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-embeddings-support</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-embeddings-support</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Mark Hoy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdf867d9327b3b843/6a17d70b3e9e450670ba12da/1ea2acd6fcfea41d4f57ce576c0aebd416724129-1440x660.png" length="0" type="image/png"/>
    <pubDate>Wed, 22 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch open inference API adds support for Azure OpenAI chat completions]]></title>
    <description><![CDATA[Azure OpenAI chat completions is available via the Elasticsearch inference API. Learn how to use this feature to answer questions.]]></description>
    <content:encoded><![CDATA[<p>We’ve integrated <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions">Azure OpenAI chat completions</a> in the inference API, which allows our customers to build powerful GenAI applications based on chat completion using large language models like GPT-4 Azure and Elasticsearch developers can utilize the unique capabilities of the Elasticsearch vector database and the Azure AI ecosystem to power unique GenAI applications with the model of their choice.</p><p>This blog quickly goes over the catalog of supported providers in the open inference API and explains how to use Azure’s OpenAI chat completions to answer questions through an example.</p><h2>The inference API is growing…fast!</h2><p>We’re heavily extending the catalog of supported providers in the open inference API. Check out some of our latest blog posts on <a href="https://www.elastic.co/search-labs">Elastic Search labs</a> to learn more about recent integrations around embeddings, completions and reranking:</p><ul><li><p><a href="https://elastic.co/search-labs/blog/elasticsearch-azure-ai-studio-support">Elasticsearch open inference API adds support for Azure Open AI Studio</a></p></li><li><p><a href="https://elastic.co/search-labs/blog/elasticsearch-azure-openai-embeddings-support">Elasticsearch open inference API adds support for Azure Open AI embeddings</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-openai-completion-support">Elasticsearch open inference API adds support for OpenAI chat completions</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank">Elasticsearch open Inference API adds support for Cohere’s Rerank 3 model</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-embeddings-support">Elasticsearch open inference API adds support for Cohere Embeddings</a></p></li><li><p>...more to come!</p></li></ul><p>Azure OpenAI chat completions support is available through the open inference API in our <a href="https://www.elastic.co/search-labs/blog/building-elastic-cloud-serverless">stateless offering</a> on Elastic Cloud. It’ll also be soon available to everyone in an upcoming versioned Elasticsearch release. This also complements the capability to use the <a href="https://www.elastic.co/search-labs/blog/azure-openai-on-your-data-elasticsearch-vector-database">Elasticsearch vector database in the Azure OpenAI service.</a></p><h2>Using Azure’s OpenAI chat completions to answer questions</h2><p>In my last blog post about <a href="https://www.elastic.co/search-labs/blog/elasticsearch-openai-completion-support">OpenAI chat completions</a> we’ve learned how to summarize text using OpenAI’s chat completions. In this guide we’ll use Azure OpenAI chat completions to answer questions during ingestion to have answers ready ahead of searching. Make sure you have your Azure OpenAI api key, deployment id and resource name ready by <a href="https://azure.microsoft.com/en-us/free">creating a free Azure account</a> first and setting up a model suited for chat completions. You can follow <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart">Azure's OpenAI Service GPT quickstart guide</a> to get a model up and running. In the following example we’ve used `gpt-4` with the version `2024-02-01`. You can read more about supported models and versions <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions">here</a>.</p><p>In Kibana, you'll have access to a console for you to input these next steps in Elasticsearch without even needing to set up an IDE.</p><p>First, we configure a model, which will perform completions:</p>PUT _inference/completion/azure_openai_completion
{
    "service": "azureopenai",
    "service_settings": {
        "resource_name":"&lt;resource-name&gt;",
        "deployment_id": "&lt;deployment-id&gt;",
        "api_version": "2024-02-01",
        "api_key": "&lt;api-key&gt;"
    }
}
<p>You’ll get back a response similar to the following with status code `200 OK` on successful inference creation:</p>{
    "model_id": "azure_openai_completion",
    "task_type": "completion",
    "service": "azureopenai",
    "service_settings": {
        "resource_name": "&lt;resource-name&gt;",
        "deployment_id": "&lt;deployment-id&gt;",
        "api_version": "2024-02-01"
    },
    "task_settings": {}
}
<p>You can now call the configured model to perform completion on any text input. Let’s ask the model what’s inference in the context of GenAI:</p>POST _inference/completion/azure_openai_completion
{
    "input": "What is inference in the context of GenAI?"
}
<p>You should get back a response with status code `200 OK` explaining what inference is:</p>{
    "completion": [
        {
            "result": "In the context of generative AI, inference refers to the process of generating new data based on the patterns, structures, and relationships the AI has learned from the training data. It involves using a model that has been trained on a lot of data to infer or generate new, similar data. For instance, a generative AI model trained on a collection of paintings might infer or generate new, similar paintings. This is the useful part of machine learning where the actual task is performed."
        }
    ]
}
<p>Now we can set up a small catalog of questions, which we want to be answered during ingestion. We’ll use the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API</a> to index three questions about products of Elastic:</p>POST _bulk
{ "index" : { "_index" : "questions" } }
{"question": "What is Elasticsearch?"}
{ "index" : { "_index" : "questions" } }
{"question": "What is Kibana?"}
{ "index" : { "_index" : "questions" } }
{"question": "What is Logstash?"}
<p>You’ll get back a response with status `200 OK` back similar to the following upon successful indexing:</p>{
    "errors": false,
    "took": 385,
    "items": [
        {
            "index": {
                "_index": "questions",
                "_id": "4RO6YY8Bv2OsAP2iNusn",
                "_version": 1,
                "result": "created",
                "_shards": {
                    "total": 2,
                    "successful": 1,
                    "failed": 0
                },
                "_seq_no": 0,
                "_primary_term": 1,
                "status": 201
            }
        },
        {
            "index": {
                "_index": "questions",
                "_id": "4hO6YY8Bv2OsAP2iNuso",
                "_version": 1,
                "result": "created",
                "_shards": {
                    "total": 2,
                    "successful": 1,
                    "failed": 0
                },
                "_seq_no": 1,
                "_primary_term": 1,
                "status": 201
            }
        },
        {
            "index": {
                "_index": "questions",
                "_id": "4xO6YY8Bv2OsAP2iNuso",
                "_version": 1,
                "result": "created",
                "_shards": {
                    "total": 2,
                    "successful": 1,
                    "failed": 0
                },
                "_seq_no": 2,
                "_primary_term": 1,
                "status": 201
            }
        }
    ]
}
<p>We’ll create now our question and answering <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html">ingest pipeline</a> using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/script-processor.html">script-</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html">inference-</a> and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/remove-processor.html">remove-processor</a>:</p>PUT _ingest/pipeline/question_answering_pipeline

{
    "processors": [
        {
            "script": {
                "source": "ctx.prompt = 'Please answer the following question: ' + ctx.question"
                }
        },
        {
            "inference": {
                "model_id": "azure_openai_completion",
                "input_output": {
                    "input_field": "prompt",
                    "output_field": "answer"
                }
            }
        },
        {
            "remove": {
                "field": "prompt"
            }
        }
    ]
}
<p>This pipeline prefixes the content with the instruction “Please answer the following question: “ in a temporary field named `prompt`. The content of this temporary `prompt` field will be sent to Azure’s OpenAI Service through the inference API to perform a completion. Using an ingest pipeline allows for immense flexibility as you can change the pre-prompt to anything you would like. This allows you to summarize documents for example, too. Check out <a href="https://www.elastic.co/search-labs/blog/elasticsearch-openai-completion-support">Elasticsearch open inference API adds support for OpenAI chat completions</a> to learn about how to build a summarisation ingest pipeline!</p><p>We now send our documents containing questions through the question and answering pipeline by calling the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html">reindex API</a>.</p>POST _reindex

{
  "source": {
    "index": "questions",
    "size": 50
  },
  "dest": {
    "index": "answers",
    "pipeline": "question_answering_pipeline"
  }
}
<p>You'll get back a response with status <code>200 OK</code> similar to the following:</p>{
    "took": 10651,
    "timed_out": false,
    "total": 3,
    "updated": 0,
    "created": 3,
    "deleted": 0,
    "batches": 1,
    "version_conflicts": 0,
    "noops": 0,
    "retries": {
        "bulk": 0,
        "search": 0
    },
    "throttled_millis": 0,
    "requests_per_second": -1.0,
    "throttled_until_millis": 0,
    "failures": []
}
<p>In a real world setup you’ll probably use another ingestion mechanism to ingest your documents in an automated way. Check out our <a href="https://www.elastic.co/guide/en/cloud/current/ec-cloud-ingest-data.html">Adding data to Elasticsearch guide</a> to learn more about the various options offered by Elastic to ingest data into Elasticsearch. We’re also committed to showcase ingest mechanisms and provide guidance on how to bring data into Elasticsearch using 3rd party tools. Take a look at <a href="https://www.elastic.co/search-labs/blog/data-ingestion-from-snowflake-to-elasticsearch-using-meltano">Ingest Data from Snowflake to Elasticsearch using Meltano: A developer’s journey</a> for example on how to use Meltano for ingesting data.</p><p>You're now able to search for your pre-generated answers using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API</a>:</p>POST answers/_search

{
  "query": {
    "match_all": { }
  }
}
<p>In the response you'll get back your pre-generated answers:</p>{
    "took": 11,
    "timed_out": false,
    "_shards": { ... },
    "hits": {
        "total": { ... },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "answers",
                "_id": "4RO6YY8Bv2OsAP2iNusn",
                "_score": 1.0,
                "_ignored": [
                    "answer.keyword"
                ],
                "_source": {
                    "model_id": "azure_openai_completion",
                    "question": "What is Elasticsearch?",
                    "answer": "Elasticsearch is an open-source, RESTful, distributed search and analytics engine built on Apache Lucene. It can handle a wide variety of data types, including textual, numerical, geospatial, structured, and unstructured data. Elasticsearch is scalable and designed to operate in real-time, making it an ideal choice for use cases such as application search, log and event data analysis, and anomaly detection."
                }
            },
            { ... },
            { ... }
        ]
    }
}
<p>Pre-generating answers for frequently asked questions is particularly effective in reducing operational costs. By minimizing the need for on-the-fly response generation, you can significantly cut down on the amount of computational resources required like token usage. Additionally, this method ensures that every user receives the same, precise information. Consistency is crucial, especially in fields requiring high reliability and accuracy such as medical, legal, or technical support.</p><h2>More to come!</h2><p>We’re already working on adding support for more task types using Cohere, Google Vertex AI and many more. Furthermore we’re actively developing an intuitive UI in Kibana for managing Inference endpoints. Lots of exciting stuff to come! Bookmark <a href="https://www.elastic.co/search-labs">Elastic Search Labs</a> now to keep with Elastic’s innovations in the GenAI space!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-completion-support</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-completion-support</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Tim Grein]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt348f5acc6a75137a/6a17d7041d1b83fac593e2cd/88c9d88ac1e3c32b8a91732cfcad2c2093b6a6f6-1440x747.png" length="0" type="image/png"/>
    <pubDate>Wed, 22 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch open inference API adds Azure AI Studio support]]></title>
    <description><![CDATA[Elasticsearch open inference API now supports Azure AI Studio. Learn how to use Azure AI Studio capabilities with Elasticsearch in this blog.]]></description>
    <content:encoded><![CDATA[<p>As part of our ongoing commitment to serve the Microsoft Azure developers with the tools of their choice, we are happy to announce that Elasticsearch now provides integration of the <a href="https://learn.microsoft.com/en-us/azure/ai-studio/how-to/model-catalog-overview">hosted model catalog</a> on Microsoft Azure AI Studio into our open inference API. This complements the ability for developers to bring their<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"> Elasticsearch vector database to be used in Azure OpenAI</a>.</p><p>Developers can use the capabilities of the world's most downloaded vector database to store and utilize embeddings generated from OpenAI models from Azure AI studio or access the wide array of chat completion model deployments for quick access to conversational models like <code>mistral-small</code>.</p><p>Just recently we've added support for Azure OpenAI <a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-embeddings-support">text embeddings</a> and <a href="https://www.elastic.co/search-labs/blog/elasticsearch-azure-openai-completion-support">completion</a>, and now we've added support for utilizing Azure AI Studio. Microsoft Azure developers have complete access to Azure OpenAI &amp; Microsoft Azure AI Studio service capabilities and can <a href="https://www.elastic.co/search-labs/blog/azure-openai-on-your-data-elasticsearch-vector-database">bring their Elasticsearch</a> data to <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">revolutionize conversational search</a>.</p><p>Let's walk you through just how easily you can use these capabilities with Elasticsearch.</p><h2>Deploying a model in Azure AI Studio</h2><p>To get started, you'll need a <a href="https://azure.microsoft.com/">Microsoft Azure</a> subscription as well as access to <a href="https://ai.azure.com/">Azure AI Studio</a>. Once you are set up, you'll need to deploy either a text embedding model or a chat completion model from the <a href="https://ai.azure.com/explore/models">Azure AI Studio model catalog</a>. Once your model is deployed, on the deployment overview page take note of the target URL and your deployment's API key - you'll need these later to create your inference endpoint in Elasticsearch.</p><p>Furthermore, when you deploy your model, Azure offers two different types of deployment options - a “pay as you go” model (where you pay by the token), and a “realtime” deployment which is a dedicated VM that is billed by the hour. Not all models will have both deployment types available, so be sure to take note as well as which deployment type is used.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51abed9b55573072/6a17d7016864a46975b685d7/fb111aa1bb5cf45f78b7eabb1c2eb0498c441773-763x319.png" alt="Azure AI Studio Deployment" /><h2>Creating an Inference API Endpoint in Elasticsearch</h2><p>Once your model is deployed, we can now create an endpoint for your inference task in Elasticsearch. For the examples below we are using the <a href="https://ai.azure.com/explore/models/Cohere-command-r/version/3/registry/azureml-cohere">Cohere Command R model</a> to perform chat completion.</p><p>In Elasticsearch, create your endpoint by providing the service as “azureaistudio”, and the service settings including your API key and target from your deployed model. You'll also need to provide the model provider, as well as the endpoint type from before (either “token” or “realtime”). In our example, we've deployed a Cohere model with a token type endpoint.</p>PUT _inference/completion/test_cohere_chat_completion
{
  "service": "azureaistudio",
  "service_settings": {
    "api_key": "&lt;&lt;API_KEY&gt;&gt;",
    "target": "&lt;&lt;TARGET_URL&gt;&gt;",
    "provider": "cohere",
    "endpoint_type": "token"
  }
}
<p>When you send Elasticsearch the command, it should return back the created model to confirm that it was successful. Note that the API key will never be returned and is stored in Elasticsearch's secure settings.</p>{
    "model_id": "test_cohere_chat_completion",
    "task_type": "completion",
    "service": "azureaistudio",
    "service_settings": {
        "target": "&lt;&lt;TARGET_URL&gt;&gt;",
        "provider": "cohere",
        "endpoint_type": "token"
    },
    "task_settings": {}
}
<p>Adding a model for using text embeddings is just as easy. For reference, if we had deployed the <a href="https://ai.azure.com/explore/models/Cohere-embed-v3-english/version/3/registry/azureml-cohere">Cohere-embed-v3-english model</a>, we can create our inference model in Elasticsearch with the “text_embeddings” task type by providing the appropriate API key and target URL from that deployment's overview page:</p>PUT _inference/text_embeddings/test_cohere_embeddings
{
  "service": "azureaistudio",
  "service_settings": {
    "api_key": "&lt;&lt;API_KEY&gt;&gt;",
    "target": "&lt;&lt;TARGET_URL&gt;&gt;",
    "provider": "cohere",
    "endpoint_type": "token"
  }
}
<h2>Let's perform some inference</h2><p>That's all there is to setting up your model. Now that that's out of the way, we can use the model. First, let's test the model out by asking it to provide some text given a simple prompt. To do this, we'll call the _inference API with our input text:</p>POST _inference/completion/test_cohere_chat_completion
{
  "input": "The answer to the universe is"
}
<p>And we should see Elasticsearch provide a response. Behind the scenes, Elasticsearch is calling out to Azure AI Studio with the input text and processes the results from the inference. In this case, we received the response:</p>{
    "completion": [
        {
            "result": "42. \n\nIn Douglas Adams' *The Hitchhiker's Guide to the Galaxy*, a super-computer named Deep Thought is asked what the answer to the ultimate question of life, the universe, and everything is. After calculating for 7.5-million years, Deep Thought announces that the answer is 42. \n\nThe number 42 has since become a reference to the novel, and many fans of the book series speculate as to what the actual question might be."
        }
    ]
}
<p>We've tried to make it easy for the end user to not have to deal with all the technical details behind the scenes, but we can also control our inference a bit more by providing additional parameters to control the processing such as sampling temperature and requesting the maximum number of tokens to be generated:</p>POST _inference/completion/test_cohere_chat_completion
{
  "input": "The answer to the universe is",
  "task_settings": {
    "temperature": 1.0,
    "do_sample": true,
    "max_new_tokens": 50
  }
}
<h2>That was easy. What else can we do?</h2><p>This becomes even more powerful when we are able to use our new model in other ways such as adding additional text to a document when it's used in an Elasticsearch ingestion pipeline. For example, the following pipeline definition will use our model and anytime a document using this pipeline is ingested, any text in the field “question_field” will be sent through the inference API and the response will be written to the “completed_text_answer” field in the document. This allows large batches of documents to be augmented.</p>PUT _ingest/pipeline/azure_ai_studio_cohere_completions
{
  "processors": [
    {
      "inference": {
        "model_id": "test_cohere_chat_completion", 
        "input_output": { 
          "input_field": "question_field",
          "output_field": "completed_text_answer"
        }
      }
    }
  ]
}
<h2>Limitless possibilities</h2><p>By harnessing the power of Azure AI Studio deployed models in your Elasticsearch inference pipelines, you can enhance your search experience's natural language processing and predictive analytics capabilities.</p><p>In upcoming versions of Elasticsearch, users can take advantage of new field mapping types that simplify the process even further where designing an ingest pipeline would no longer be necessary. Also, as alluded to in our <a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank#elasticsearchs-accelerated-roadmap-to-semantic-reranking-and-retrievers">accelerated roadmap for semantic search</a> the future will provide dramatically simplified support for inference tasks with Elasticsearch retrievers at query time.</p><p>These capabilities are available through the open inference API in our <a href="https://www.elastic.co/search-labs/blog/building-elastic-cloud-serverless">stateless offering</a> on Elastic Cloud. It'll also be soon available to everyone in an upcoming versioned Elasticsearch release.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-azure-ai-studio-support</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-azure-ai-studio-support</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Mark Hoy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857508512ce282f5/6a17d703e9ea87a89fa9c415/d5cfda5d59f5812a9819831938219a34c11a0bd9-1440x962.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 22 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Red Hat & Elastic: Red Hat OpenShift AI integration with Elasticsearch]]></title>
    <description><![CDATA[Red Hat OpenShift users can now implement Elasticsearch for vector search &amp; RAG applications via the Red Hat Ecosystem Catalog. Explore this integration here.]]></description>
    <content:encoded><![CDATA[<p>Red Hat and Elastic have <a href="https://www.redhat.com/en/about/press-releases/red-hat-and-elastic-fuel-retrieval-augmented-generation-genai-use-cases">collaborated</a> to enable integration for the Elasticsearch vector database on <a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/openshift-ai">Red Hat OpenShift AI</a>. Red Hat OpenShift users can implement Elasticsearch for vector search and Retrieval-Augmented Generation (RAG) applications via the <a href="https://catalog.redhat.com/software/container-stacks/detail/5f32f067651c4c0bcecf1bfe">Red Hat Ecosystem Catalog</a>.</p><p>Elastic Cloud on Kubernetes (ECK) is a certified offering on Red Hat OpenShift. Elastic is an IBM <a href="https://cloud.ibm.com/docs/databases-for-elasticsearch">partner</a>, and IBM Watsonx Assistant and Watsonx Discovery use Elastic <a href="https://www.ibm.com/docs/en/announcements/watsonx-discovery-10">vector search</a> for question-answering and retrieval augmentation use cases.</p><p>With this collaboration, Elasticsearch users can benefit from Red Hat OpenShift AI, a flexible, scalable MLOps platform for building, training, testing, and serving models for AI-enabled applications.</p><h2>Elasticsearch vector database for generative AI and RAG apps</h2><p>Elasticsearch Relevance Engine (ESRE) is a comprehensive suite of developer tools for building generative AI and RAG applications. ESRE incorporates a <a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">vector database</a> that stores embeddings for text, image, and video data. ESRE’s native hybrid search can effectively combine results containing text, vectors, and geospatial data, with filtering, aggregations, and document-level security.</p><p>With ESRE, developers can implement vector search and semantic search, including k-nearest neighbors (<a href="https://www.elastic.co/search-labs/blog/simplifying-knn-search?trk=feed-detail_main-feed-card_feed-article-content">kNN</a>) and approximate nearest neighbor (ANN) search, along with support for both built-in and third-party natural language processing (<a href="https://www.elastic.co/search-labs/blog/how-to-deploy-nlp-text-embeddings-and-vector-search">NLP</a>) models. ESRE also seamlessly integrates with key third-party ecosystem products from providers such as <a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank">Cohere</a>, LangChain, and LlamaIndex. Elasticsearch can be self-managed or deployed with <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc88e752ba4c25d75/6a17d774445de9105c4cff50/4387b921978cde8ce8cdcf9dcb435d4fdaec6229-1440x663.png" alt="Elasticsearch as the preferred vector database solution on Red Hat OpenShift AI" /><p>As part of this collaboration, users are now able to leverage ESRE capabilities by downloading Elasticsearch directly from the <a href="https://catalog.redhat.com/software/container-stacks/detail/5f32f067651c4c0bcecf1bfe">Red Hat Ecosystem Catalog</a>.</p><h2>What is Red Hat OpenShift AI for generative AI apps</h2><p>Red Hat OpenShift AI is a hybrid MLOps platform that brings IT, data science, and app dev teams together. Designed to simplify Generative AI application development and deployment, it provides a comprehensive infrastructure stack tailored for distributed workloads. This includes training, optimizing, fine-tuning, and deploying foundational and predictive AI models. Collaborating with model builders helps provide access to a variety of pre-built models. Developers and data scientists can work together on the same platform, greatly enhancing collaboration. The platform facilitates end-to-end AI lifecycle management—from model development and training to deployment, serving, and continuous monitoring.</p><ul><li><p><strong>Model development</strong>: Conduct exploratory data science in JupyterLab with access to core AI / ML libraries and frameworks, including TensorFlow and PyTorch using our notebook images or your own.</p></li><li><p><strong>Model serving &amp; monitoring</strong>: Deploy models across on-premise or any cloud, either in a fully managed or self-managed Red Hat OpenShift footprint and centrally monitor their performance.</p></li><li><p><strong>Lifecycle Management</strong>: Create repeatable data science pipelines for model training and validation and integrate them with DevOps pipelines for the delivery of models across your enterprise.</p></li><li><p><strong>Increased capabilities and collaboration</strong>: Create projects and share them across teams. Combine Red Hat components, open-source software, and ISV-certified software.</p></li></ul><h2>Get started with Red Hat and Elasticsearch</h2><p>To get started, just follow the installation instructions provided in the <a href="https://catalog.redhat.com/software/container-stacks/detail/5f32f067651c4c0bcecf1bfe">Red Hat Ecosystem Catalog</a>, and start building your next generative AI application with RAG!</p><p>Visit <a href="https://www.elastic.co/search-labs">Elasticsearch Labs</a> for articles and sample notebooks on vector search, RAG, and more.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-redhat-openshift-ai-vector-database</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-redhat-openshift-ai-vector-database</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Aditya Tripathi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd5dd849e60ee215c/6a17d776faa913959493c6d3/56eeb9068e892907fa03ccda7556f9c0eae66f0b-1401x841.png" length="0" type="image/png"/>
    <pubDate>Tue, 07 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Making Elasticsearch and Lucene the best vector database: up to 8x faster and 32x efficient]]></title>
    <description><![CDATA[Discover the recent enhancements and optimizations that notably improve vector search performance in Elasticsearch &amp; Lucene vector database.]]></description>
    <content:encoded><![CDATA[<h2>Elasticsearch and Lucene report card: noteworthy speed and efficiency investments</h2><p>Our mission at Elastic is to make Apache Lucene the best vector database out there, and to continue to make Elasticsearch the best retrieval platform out there for search and RAG. Our investments into Lucene are key to ensure that every release of Elasticsearch brings increasing faster performance and scale.</p><p>Customers are already building the next generation of AI enabled search applications with Elastic’s vector database and vector search technology. <a href="https://roboflow.com/">Roboflow</a> is used by over 500,000 engineers to create datasets, train models, and deploy computer vision models to production. Roboflow uses Elastic vector database to store and search billions of vector embeddings.</p><p>In this blog we summarize recent enhancements and optimisations that significantly improve vector search performance in Elasticsearch and Apache Lucene, over and above performance gains <a href="https://www.elastic.co/search-labs/blog/apache-lucene-9.9-search-speedups">delivered with Lucene 9.9</a> and Elasticsearch 8.12.x.</p><p>The integration of vector search into Elasticsearch relies on Apache Lucene, the layer that orchestrates data storage and retrieval. <a href="https://www.elastic.co/search-labs/blog/vector-search-elasticsearch-rationale">Lucene's architecture</a> organizes data into segments, immutable units that undergo periodic merging. This structure allows for efficient management of inverted indices, essential for text search. With vector search, Lucene extends its capabilities to handle multi-dimensional points, employing the hierarchical navigable small world (HNSW) algorithm to index vectors.</p><p>This approach facilitates scalability, enabling data sets to exceed available RAM size while maintaining performance. Additionally, Lucene's segment-based approach offers lock-free search operations, supporting incremental changes and ensuring visibility consistency across various data structures. The integration however comes with its own engineering challenges. Merging segments requires recomputing HNSW graphs, incurring index-time overhead. Searches must cover multiple segments, leading to possible latency overhead. Moreover, optimal performance requires scaling RAM as data grows, which may raise resource management concerns.</p><p>Lucene's integration into Elasticsearch comes with the benefit of robust vector search capabilities. This includes aggregations, document level security, geo-spatial queries, pre-filtering, to full compatibility with various Elasticsearch features. Imagine running vector searches using a geo bounding box, this is an example usecase enabled by Elasticsearch and Lucene.</p><p>Lucene's architecture lays a solid foundation for efficient and versatile vector search within Elasticsearch. Let’s explore optimization strategies and enhancements we have implemented to integrate vector search into Lucene, which delivers a high performance and comprehensive feature-set for developers.</p><h2>Harnessing Lucene's architecture for multi-threaded search</h2><p>Lucene's segmented architecture enables the implementation of multi-threaded search capabilities. Elasticsearch’s performance gains come from efficiently searching multiple segments simultaneously. Latency of individual searches is significantly reduced by using the processing power of all available CPU cores. While it may not directly improve overall throughput, this enhancement prioritizes minimizing response times, ensuring that users receive their search results as swiftly as possible.</p><p>Furthermore, this optimization is particularly beneficial for Hierarchical Navigable Small World (HNSW) searches, as each graph is independent of the others and can be searched in parallel, maximizing efficiency and speeding up retrieval times even further.</p><p>The advantage of having multiple independent segments extends to the architectural level, especially in serverless environments. In this <a href="https://www.elastic.co/blog/elastic-serverless-architecture">new architecture,</a> the indexing tier is responsible for creating new segments, each containing its own HSNW graph. The search tier can simply replicate these segments without incurring the CPU cost of indexation. This separation allows a significant portion of compute resources to be dedicated to searches, optimizing overall system performance and responsiveness.</p><h2>Accelerating multi-graph vector search</h2><p>In spite of gains achieved with parallelization, each segment's searches would remain independent, unaware of progress made by other segment searches. So our focus shifted towards optimizing the efficiency of concurrent searches across multiple segments.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35f29a49abcd2e22/6a17d78ae31791cd572d5682/103e9a7a97e9c219edb028e0fc675346920002cc-974x407.png" alt="" /><p>The graph shows that the number queries per second increased from 104 queries/sec to 219 queries/sec.</p><p>Recognizing the potential for further speedups, we leveraged our insights from optimizing lexical search, to enable information exchange among segment searches allowing for better coordination and efficiency in vector search.</p><p><a href="https://www.elastic.co/search-labs/blog/multi-graph-vector-search">Our strategy for accelerating multi-graph vector search</a> revolves around balancing exploration and exploitation within the proximity graph. By adjusting the size of the expanded match set, we control the trade-off between runtime and recall, crucial for achieving optimal search performance across multiple graphs.</p><p>In multi-graph search scenarios, the challenge lies in efficiently navigating individual graphs, while ensuring comprehensive exploration to avoid local minima. While searching multiple graphs independently yields higher recall, it incurs increased runtime due to redundant exploration efforts. To mitigate this, we devised a strategy to intelligently share state between searches, enabling informed traversal decisions based on global and local competitive thresholds.</p><p>This approach involves maintaining shared global and local queues of distances to closest vectors, dynamically adapting search parameters based on the competitiveness of each graph's local search. By synchronizing information exchange and adjusting search strategies accordingly, we achieve significant improvements in search latency while preserving recall rates comparable to single-graph searches.</p><p>The impact of these optimizations is evident in our benchmark results. In concurrent search and indexing scenarios, we notice up to 60% reduction in query latencies! Even for queries conducted outside of indexing operations, we observed notable speedups and a dramatic decrease in the number of vector operations required. These enhancements, integrated into Lucene 9.10 and subsequently Elasticsearch 8.13, mark significant strides towards enhancing vector database performance for search while maintaining excellent recall rates.</p><h2>Harnessing Java's latest advancements for ludicrous speed</h2><p>In the area of Java development, automatic vectorization has been a boon, optimizing scalar operations into SIMD (Single Instruction Multiple Data) instructions through the HotSpot C2 compiler. While this automatic optimization has been beneficial, it has its limitations, particularly in scenarios where explicit control over code shape yields superior performance. Enter Project Panama Vector API, a recent addition to the JDK offering an API for expressing computations reliably compiled to SIMD instructions at runtime.</p><p>Lucene's vector search implementation relies on fundamental operations like dot product, square, and cosine distance, both in floating point and binary variants. Traditionally, these operations were backed by scalar implementations, leaving performance enhancements to the JIT compiler. However, recent advancements introduce a paradigm shift, enabling developers to express these operations explicitly for optimal performance.</p><p>Consider the dot product operation, a fundamental vector computation. Traditionally implemented in Java with scalar arithmetic, recent innovations leverage the Panama Vector API to express dot product computations in a manner conducive to SIMD instructions. This revised implementation iterates over input arrays, multiplying and accumulating elements in batches, aligning with the underlying hardware capabilities.</p><p><a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">By harnessing Panama Vector API,</a> Java code now interfaces seamlessly with SIMD instructions, unlocking the potential for significant performance gains. The compiled code, when executed on compatible CPUs, leverages advanced vector instructions like AVX2 or AVX 512, resulting in accelerated computations. Disassembling the compiled code reveals optimized instructions tailored to the underlying hardware architecture.</p><p>Microbenchmarks comparing traditional Java implementations to those leveraging Panama Vector API illustrate dramatic performance improvements. Across various vector operations and dimension sizes, the optimized implementations outperform their predecessors by significant margins, offering a glimpse into the transformative power of SIMD instructions.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efb30efc7e6c157/6a17d78bfbc5f8285d49190c/d2a5f15bb0d16608b67753a82312d2f254370622-1204x120.png" alt="" /><p>Micro-benchmark comparing dot product with the new Panama API (dotProductNew) and the scalar implementation (dotProductOld).</p><p>Beyond microbenchmarks, the real-world impact of these optimizations is quite exciting to think about. Vector search benchmarks, such as <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">SO Vector,</a> demonstrate notable enhancements in indexing throughput, merge times, and query latencies. Elasticsearch, embracing these advancements, incorporates the faster implementations by default, ensuring users reap the performance benefits seamlessly.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd3a97bf5f0cd24a/6a17d78d3e9e452c84ba12e6/e82a7ee152fdebfc856106bdc1f68c6eab9b5798-1349x882.png" alt="" /><p>The graph shows indexing throughput increased from about 900 documents/sec to about 1300 documents/sec.</p><p>Despite the incubating status of Panama Vector API, its quality and potential benefits are undeniable. Lucene's pragmatic approach allows for selective adoption of non-final JDK APIs, balancing the promise of performance improvements with maintenance considerations. With Lucene and Elasticsearch, users can leverage these advancements effortlessly, with performance gains translating directly to real-world workloads.</p><p>The integration of Panama Vector API into Java development yields a new era of performance optimization, particularly in vector search scenarios. By embracing hardware-accelerated SIMD instructions, developers can unlock efficiency gains, visible both in microbenchmarks and macro-level benchmarks. As Java continues to evolve, leveraging its latest features promises to propel performance to new heights, enriching user experiences across diverse applications.</p><h2>Maximizing memory efficiency with scalar quantization</h2><p>Memory consumption has long been a concern for efficient vector database operations, particularly for searching large datasets. Lucene introduces a breakthrough optimization technique - scalar quantization - aimed at significantly reducing memory requirements without sacrificing search performance.</p><p>Consider a scenario where querying millions of float32 vectors of high dimensions demands substantial memory, leading to significant costs. By embracing byte quantization, Lucene slashes memory usage by approximately 75%, offering a viable solution to the memory-intensive nature of vector search operations.</p><p>For quantizing floats to bytes, Lucene implements <a href="https://www.elastic.co/search-labs/blog/scalar-quantization-in-lucene">Scalar quantization</a> a lossy compression technique that transforms raw data into a compressed form, sacrificing some information for space efficiency. Lucene's implementation of scalar quantization achieves remarkable space savings with minimal impact on recall, making it an ideal solution for memory-constrained environments.</p><p>Lucene's architecture, consisting of nodes, shards, and segments, which facilitates efficient distribution and management of documents for search. Each segment stores raw vectors, quantized vectors, and metadata, ensuring optimized storage and retrieval mechanisms.</p><p>Lucene's vector quantization adapts dynamically over time, adjusting quantiles during segment merge operations to maintain optimal recall. By intelligently handling quantization updates and re-quantization when necessary, Lucene ensures consistent performance while accommodating changes in data distribution.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4680278043366ea4/6a17d78e1d1b83ebd893e2d5/73fb017cce8096a108a7a7297c86cfb26866922c-1440x447.png" alt="" /><p>Example of merged quantiles where segments A and B have 1000 documents and C only has 100.</p><p>Experimental results demonstrate the efficacy of scalar quantization in reducing memory footprint while maintaining search performance. Despite minor differences in recall compared to raw vectors, Lucene's quantized vectors offer significant speed improvements and recall recovery with minimal additional vectors.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62d403efee1b4ebb/6a17d790e317918e322d5686/aacf329d8eb54a9b73a1e4722e14f27379dd80d7-576x455.png" alt="" /><p>Recall@10 for quantized vectors vs raw vectors. The search performance of quantized vectors is significantly faster than raw, and recall is quickly recoverable by gathering just 5 more vectors; visible by quantized@15.</p><p>Lucene's scalar quantization presents a revolutionary approach to memory optimization in vector search operations. With no need for training or optimization steps, Lucene seamlessly integrates quantization into its indexing process, automatically adapting to changes in data distribution over time. As Lucene and Elasticsearch continue to evolve, widespread adoption of scalar quantization will revolutionize memory efficiency for vector database applications, paving the way for enhanced search performance at scale.</p><h2>Achieving seamless compression with minimal impact on recall</h2><p>To make compression even better, we aimed to reduce each dimension from 7 bits to just 4 bits. Our main goal was to compress data further while still keeping search results accurate. By making some improvements, we managed to compress data by a factor of 8 without making search results worse. Here's how we did it.</p><p>We focused on keeping search results accurate while making data smaller. By making sure we didn't lose important information during compression, we could still find things well even with less detailed data. To make sure we didn't lose any important information, we added a smart error correction system.</p><p>We checked our compression improvements by testing them with different types of data and real search situations. This helped us see how well our searches worked with different compression levels and what we might lose in accuracy by compressing more.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta71070ad46fd4573/6a17d791be608665ca00459d/7c32834cfb733a6ad3deb64eb9813d4855539823-972x602.png" alt="" /><p>Comparison of int4 dot product values to the corresponding float values for a random sample of 100 documents and their 10 nearest neighbors.</p><p>These compression features were created to easily work with existing vector search systems. They help organizations and users save space without needing to change much in their setup. With this simple compression, organizations can expand their search systems without wasting resources.</p><p>In short, moving to 4 bits per dimension for scalar quantization was a big step in making compression more efficient. It lets users compress their original vectors by 8 times. By optimizing carefully, adding error correction, testing with real data, and offering scalable deployment, organizations could save a lot of storage space without making search results worse. This opens up new chances for efficient and scalable search applications.</p><h2>Paving the way for binary quantization</h2><p>The optimization to reduce each dimension to 4 bits not only delivers significant compression gains but also lays the groundwork for further advancements in compression efficiency. Specifically, future advancements like binary quantization into Lucene, a development that has the potential to revolutionize vector storage and retrieval.</p><p>In an ongoing effort to push the boundaries of compression in vector search, we are actively working on integrating binary quantization into Lucene using the same techniques and principles that underpin our existing optimization strategies. The goal is to achieve binary quantization of vector dimensions, thereby reducing the size of the vector representation by a factor of 32 compared to the original floating-point format.</p><p>Through our iterations and experiments, we want to deliver the full potential of vector search while maximizing resource utilization and scalability. Stay tuned for further updates on our progress towards integrating binary quantization into Lucene and Elasticsearch, and the transformative impact it will have on vector database storage and retrieval.</p><h2>Multi-vector integration in Lucene and Elasticsearch</h2><p>Several real world applications rely on text embedding models and large text inputs. Most embedding models have token limits, which necessitate chunking of longer text into passages. Therefore, instead of a single document, multiple passages and embeddings must be managed, potentially complicating metadata preservation.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7cf9100604bef40/6a17d793033c8d5c696baff7/b8a6073b44c078ef8ee5294e559cf8092bf40e38-1440x903.png" alt="" /><p>Now instead of having a single piece of metadata indicating, for example the first chapter of the book “Little Women”, you have to index that information data for every sentence.</p><p>Lucene's "join" functionality, integral to Elasticsearch's nested field type, offers a solution. This feature enables multiple nested documents within a top-level document, allowing searches across nested documents and subsequent joins with their parent documents. So, how do we deliver support for vectors in nested fields with Elasticsearch?</p><p>The key lies in how Lucene joins back to parent documents when searching child vector passages. The parallel concept here is the debate around pre-filtering versus post-filtering in kNN methods, as the timing of joining significantly impacts result quality and quantity. To address this, <a href="https://www.elastic.co/search-labs/blog/adding-passage-vector-search-to-lucene">recent enhancements to Lucene</a> enable pre-joining against parent documents while searching the HNSW graph.</p><p>Practically, pre-joining ensures that when retrieving the k nearest neighbors of a query vector, the algorithm returns the k nearest documents instead of passages. This approach diversifies results without complicating the HNSW algorithm, requiring only a minimal additional memory overhead per stored vector.</p><p>Efficiency is improved by leveraging certain restrictions, such as disjoint sets of parent and child documents and the monotonicity of document IDs. These restrictions allow for optimizations using bit sets, providing rapid identification of parent document IDs.</p><p>Searching through a vast number of documents efficiently required investing in nested fields and joins in Lucene. This work helps storage and search for dense vectors that represent passages within long texts, making document searches in Lucene more effective. Overall, these advancements represent an exciting step forward in the area of vector database retrieval within Lucene.</p><h2>Wrapping up (for now)</h2><p>We're dedicated to making Elasticsearch and Lucene the best vector database with every release. Our goal is to make it easier for people to search for things. With some of the investments we discuss in this blog, there is significant progress, but we're not done!</p><p>To say that the gen AI ecosystem is rapidly evolving is an understatement. At Elastic, we want to give developers the most flexible and open tools to keep up with all the innovation—with features available across recent releases until 8.13 and <a href="https://www.elastic.co/blog/elastic-serverless-architecture">serverless</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Mayya Sharipova,Benjamin Trent,Jim Ferenczi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35f29a49abcd2e22/6a17d78ae31791cd572d5682/103e9a7a97e9c219edb028e0fc675346920002cc-974x407.png" length="0" type="image/png"/>
    <pubDate>Fri, 26 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Cloud adds Elasticsearch Vector Database optimized instance to Google Cloud]]></title>
    <description><![CDATA[Elasticsearch's vector search optimized profile for GCP is available. Learn more about it and how to use it in this blog.]]></description>
    <content:encoded><![CDATA[<p>Elastic Cloud Vector Search optimized hardware profile is available for Google Elastic Cloud users. This hardware profile is optimized for applications that require the storage of dense or sparse embeddings for search and Generative AI use cases powered by RAG (retrieval augmented generation). This release follows the previous release of a Vector Search optimized hardware profile for AWS Elastic Cloud users in Nov 2023.</p><h2>GCP Vector Search optimized instances: what you need to know</h2><p>Elastic Cloud users benefit from having Elastic managed infrastructure across all major cloud providers (GCP, AWS and Azure) along with <a href="https://www.elastic.co/guide/en/cloud/current/ec-regions-templates-instances.html">wide region support</a> for GCP users. For more specific details on the instance configuration for this hardware profile, refer to our documentation for instance type: <a href="https://www.elastic.co/guide/en/cloud/current/ec-default-gcp-configurations.html">gcp.es.datahot.n2d.64x8x11</a></p><h2>Vector Search, HNSW, and memory</h2><p>Elasticsearch uses the <a href="https://www.elastic.co/search-labs/blog/vector-search-elasticsearch-rationale">Hierarchical Navigable Small World</a> graph (HNSW) data structure to implement its Approximate Nearest Neighbor search (ANN). Because of its layered approach, HNSW's hierarchical aspect offers excellent query latency. To be most performant, HNSW requires the vectors to be cached in the node's memory. This caching is done automatically and uses the available RAM not taken up by the Elasticsearch JVM. Because of this, memory optimizations are important steps for scalability.</p><p>Consult our vector search <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-knn-search.html#_ensure_data_nodes_have_enough_memory">tuning guide</a> to determine the right setup for your vector search embeddings and whether you have adequate memory for your deployment.</p><p>With this in mind, the Vector Search optimized hardware profile is configured with a smaller than standard Elasticsearch JVM heap setting. This provides more RAM for caching vectors on a node, allowing users to provision fewer nodes for their vector search use cases.</p><p>If you’re using compression techniques like <a href="https://www.elastic.co/search-labs/blog/scalar-quantization-in-lucene">scalar quantization</a>, the memory requirement is lowered by a factor of 4. To store quantized embeddings (available in versions Elasticsearch 8.12 and later) simply ensure that you’re storing in the correct <code>element_type: byte</code>. To utilize our <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-quantization">automatic quantization</a> of <code>float</code> vectors update your embeddings to use index type: <code>int8_hnsw</code> like in the following mapping example.</p>PUT my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_vector": {
        "type": "dense_vector",
        "dims": 512,
        "index_options": {
          "type": "int8_hnsw"
        }
      }
    }
  }
}
<p>In upcoming versions, Elasticsearch will provide this as the default mapping, removing the need for users to adjust their mapping.</p><p>Combining this optimized hardware profile with Elasticsearch’s <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-quantization">automatic quantization</a> are two examples where Elastic is focused on vector search to be cost-effective while still being extremely performant.</p><h2>Getting Started with Elastic Cloud vector search optimized profile for GCP</h2><p>Start a <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">free trial</a> on Elastic Cloud and simply select the new Vector Search optimized profile to get started.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a33219437748674/6a17d7823e9e458302ba12de/c0434f399ee75c99b290060d7b0e613cbcd0829b-1440x1390.png" alt="cloud UI view for new deployments" /><h2>Migrating existing Elastic Cloud deployments</h2><p>Migrating to this new Vector Search optimized hardware profile is a few clicks away. Simply navigate to your Elastic Cloud management UI, click to manage the specific deployment, and edit the hardware profile. In this example, we are migrating from a ‘Storage optimized’ profile to the new ‘Vector Search’ optimized profile. When choosing to do so, while there is a reduction to available storage and vCPU, what is gained is the ability to store more vectors per memory with vector search.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18139bf4f13d62da/6a17d7843e9e4537e0ba12e2/f13962f914d5d9a3be765bde2ac95a9e2d797d3f-1440x561.png" alt="cloud UI view for migrating deployments" /><p>Migrating to a new hardware profile uses the grow and shrink approach for deployment changes. This approach adds new instances, migrates data from old instances to the new ones, and then shrinks the deployment by removing the old instances. This approach allows for high availability during configuration changes even for single availability zones.</p><p>The following image shows a typical architecture for a deployment running in Elastic Cloud, where vector search will be the primary use case.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbaac029d716db095/6a17d785e9ea874ffca9c421/58e00f32bef1411dbc11849a78b5ecd3c334528a-1440x570.png" alt="deployment view" /><p>This example deployment uses our new Vector Search optimized hardware profile, now available in GCP. This setup includes:</p><ul><li><p>Two data nodes in our hot tier with our vector search profile</p></li><li><p>One Kibana node</p></li><li><p>One Machine Learning node</p></li><li><p>One integration server</p></li><li><p>One master tiebreaker</p></li></ul><p>By deploying these two “full-sized” data nodes with the Vector Search optimized hardware profile and while taking advantage of Elastic’s automatic dense vector <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-quantization">scalar quantization</a>, you can index roughly 60 million vectors, including one replica (with 768 dimensions).</p><h2>Conclusion</h2><p>Vector search is a powerful tool when building modern search applications, be it for semantic document retrieval on its own or integrating with an LLM service provider in a <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">RAG setup</a>. Elasticsearch provides a full-featured vector database natively integrated with a full-featured search platform. Along with improving vector search feature set and usability, Elastic continues to improve scalability. The vector search node type is the latest example, allowing users to scale their search application.</p><p>Elastic is committed to providing scalable, price effective infrastructure to support enterprise grade search experiences. Customers can depend on us for reliable and easy to maintain infrastructure and cost levers like vector compression, so you benefit from the lowest possible total cost of ownership for building search experiences powered by AI.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-vector-profile-gcp</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-vector-profile-gcp</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Elastic Cloud Hosted]]></category>
    <dc:creator><![CDATA[Serena Chou,Jeff Vestal,Yuvraj Gupta]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbaac029d716db095/6a17d785e9ea874ffca9c421/58e00f32bef1411dbc11849a78b5ecd3c334528a-1440x570.png" length="0" type="image/png"/>
    <pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ChatGPT and Elasticsearch: Creating custom GPTs with Elastic data]]></title>
    <description><![CDATA[Get started with custom GPTs using ChatGPT and Elasticsearch. Learn how to create custom GPTs that interact seamlessly with your Elasticsearch data.]]></description>
    <content:encoded><![CDATA[<p>ChatGPT Plus subscribers now have the opportunity to create their own customized versions of ChatGPT, known as <a href="https://openai.com/blog/introducing-gpts">GPTs</a>, replacing plugins as discussed in a <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-plugin-elastic-data">previous blog post</a>. Building upon a foundation from the <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-openai-meets-private-data">first installment of this series</a>—where we delved into setting up Elasticsearch data and creating vector embeddings in <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud</a>—this blog will guide you through the process of developing a custom GPT designed to interact seamlessly with your Elasticsearch data.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt476976f187bd3697/6a17118ca6c2b920c3e797f0/00df7e59eea6ddfd3b07f4033e24c440ec896785-1416x1150.png" alt="screenshot of custom gpt in chatgpt interface" /><h2>Custom GPTs</h2><p>GPTs mark a significant advancement from the plugin system, offering an easier way for users to create custom versions of ChatGPT. Facilitated by an intuitive user interface, this enhancement simplifies the customization process, often eliminating the need for coding skills for a broad range of applications. Beyond basic personalization, those aiming to integrate ChatGPT with external data can do so through custom actions. Users have the option to share these tailored GPTs on the GPT store, keep them private for personal use, or only share them within your company’s workspace with the ChatGPT Team plan.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1dd762c84ba56010/6a17118ec1e8a59961f883bd/a168cd7373e33094ba22ee20843e5015f89437fa-1428x2538.png" alt="screenshot of gpt store in chatgpt interface" /><h2>How ChatGPT communicates with Elasticsearch</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e1f6150f6b585ab/6a17118f8b73cbd5e118a127/2082b5c37896f56123ba834128f5aa36c5163f1b-1440x902.png" alt="illustration of architecture" /><ol><li><p>ChatGPT initiates a call to the <code>/search</code> endpoint in the Cloud Run service.</p></li><li><p>The service takes this input to create an Elasticsearch search request.</p></li><li><p>The query response with the documentation body and URL are returned to the service.</p></li><li><p>The service returns the document body and URL in text form to the custom ChatGPT.</p></li><li><p>This response is then relayed back to the GPT in text form, ready for interpretation.</p></li></ol><p>Again, this blog post assumes that you have set up your <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud</a> account, vectorized your content, and have an Elasticsearch cluster filled with data ready to be used. If you haven’t set all that up, see our <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-openai-meets-private-data">previous post</a> for detailed steps to follow.</p><h2>Code</h2><p>To bring our custom GPT to life, we create a service that acts as the intermediary between ChatGPT and our Elasticsearch data. The core of this service is a Python application which sets up a Quart app and defines the <code>/search</code> endpoint. In addition, we use a Dockerfile to facilitate the deployment of the app on Cloud Run.</p><p>The Python app connects to our Elastic Cloud cluster, executes a hybrid search combining BM25 and kNN queries, and returns the relevant documentation body and URL. This allows our custom GPT to access and utilize Elasticsearch data in real time.</p><p>For the complete code refer to the <a href="https://github.com/elastic/ElasticDocs_CustomGPT">GitHub repository</a>. This includes the Python app and the Dockerfile necessary for Cloud Run deployment.</p><h2>Deploy a service</h2><p>For detailed steps on deploying the service using Google Cloud Platform (GCP), refer to the <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-plugin-elastic-data#deploying-the-elastic-plugin-in-google-cloud-platform-gcp">deployment section in our previous blog post</a> on ChatGPT Plugins. There, you’ll find a step-by-step guide for setting up and deploying your service on GCP.</p><h2>Create a custom GPT</h2><p>After logging into your ChatGPT Plus account, navigate to “My GPTs” via your profile to find the “Create a GPT” link. Alternatively, the “Explore GPTs” section above your conversations also leads to the GPT store, where you can find a link to create a GPT.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d285f4d5cab9105/6a1711912b835f5e44f4b303/1ac7ca63ef7fb3f63c1225b2d35f570d5b5a7bc4-1406x302.png" alt="screenshot of create a gpt button in chatgpt" /><h3>Configure the custom GPT</h3><p>The GPT editor provides two ways to configure your GPT: the "Create" tab for a guided setup through conversational prompts and the "Configure" tab for direct configuration input. For configuring the Elastic Docs Assistant, we'll primarily use manual configuration to precisely define our GPT's settings.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt29c11c5416250a42/6a1711920e2e49050241a236/6f4329795f9f16246689a0c8e450cf46040e7a28-1440x1234.png" alt="screenshot of gpt editor in chatgpt" /><p>Assign a name to your GPT, such as “Elastic Docs Assistant,” and add a brief description highlighting its function.</p><p>Under instructions, define the primary role of your GPT and provide it with instructions on how to present information:</p>You are an Elasticsearch Docs Assistant.  Your function is to assist users with docs on Elastic products by querying the defined /search action. Answer the user's query using only the information from the /search action response. If the response contains no results, respond "I'm unable to answer the question based on the information I have from Elastic Docs." and nothing else.  Be sure to include the URL at the bottom of each response.
<p>Let’s switch to the “Create” tab and ask ChatGPT to generate conversation starters and a logo. Perhaps I’ll upload my own logo instead.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd9b3121bbc52543f/6a171194a6c2b94cdde797f4/5f4692207d02797db336d7e071fc158f6758aed0-1440x1278.png" alt="screenshot of gpt conversation editor in chatgpt" /><p>We won’t be uploading any knowledge files as all the data we use is in Elasticsearch. Instead, we'll define an action.</p><h3>Define an action</h3><p>This is where we connect our data to Elasticsearch. Clicking “Create a new action” will take us to the action editor.</p><p>First, I define my API key that I’m using in my endpoint service with a custom header name I set in my environment.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta6000165bc041729/6a171195dc55ded100e00eed/9c5ca2d2fa4266982eccb0ef03f626acb8082b3d-740x710.png" alt="screenshot of api key editor in chatgpt" /><p>Then I copy in my OpenAPI specification:</p>openapi: 3.0.1
info:
  title: ElasticDocs_CustomGPT
  description: Retrieve information from the most recent Elastic documentation
  version: 'v1'
servers:
  - url: YOUR_SERVICE_URL
paths:
  /search:
    get:
      operationId: search
      summary: retrieves the document matching the query
      parameters:
      - in: query
        name: query
        schema:
            type: string
        description: use to filter relevant part of the elasticsearch documentation
      responses:
        "200":
          description: OK
<p>Upon entering this information our schema will be automatically validated and display a search action, with any errors in red. If everything looks good, this is where the preview pane becomes particularly useful. Not only can you test the action to confirm its functionality, but the assistant also provides debugging information about the request. This is helpful for refining your GPT’s responses based on the service's response.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta65b77db6bd29180/6a17119714b270b574e3c6e1/912de55175b968f19a3d758b8b0e40e7e90471b8-1440x1010.png" alt="screenshot of chatgpt action editor" /><p>Further customization can be achieved by configuring the GPT instructions to dynamically modify its action requests, such as rewriting the user input before it's sent to the service or adding request query parameters based on some condition in the user input. This eliminates the need for traditional coding logic, assuming your endpoint is designed to support these modifications.</p><h3>Publish the custom GPT</h3><p>Click “Publish” in the top right corner above the preview pane to be taken to your newly created GPT.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt476976f187bd3697/6a17118ca6c2b920c3e797f0/00df7e59eea6ddfd3b07f4033e24c440ec896785-1416x1150.png" alt="screenshot of custom gpt in chatgpt interface" /><h2>What's next for custom GPTs</h2><p>This exploration of Custom GPTs, leveraging Elasticsearch for dynamic, data-driven conversations, has only begun to reveal the potential of what's possible. By harnessing the power of ChatGPT's interface and connecting it to external data, we introduce a new dimension of customization and contextually rich interactions with state-of-the-art AI models.</p><p>You can try all of the capabilities discussed in this blog today! Get started by signing up for a <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">free Elastic Cloud trial</a>.</p><p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-creating-custom-gpts-with-elastic-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-creating-custom-gpts-with-elastic-data</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Sandra Gonzales]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9839a0a8271bf582/6a171199ab7f088457db9fa7/bd6a04b5eac462c2096f6b27aeef58847159595d-1128x1020.png" length="0" type="image/png"/>
    <pubDate>Fri, 12 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch open Inference API adds support for Cohere’s Rerank 3 model]]></title>
    <description><![CDATA[“Learn about Cohere reranking, how to use Cohere's Rerank 3 model with the Elasticsearch open inference API and Elastic's roadmap for semantic reranking.”]]></description>
    <content:encoded><![CDATA[<p>Cohere's <a href="https://txt.cohere.com/rerank-3/">Rerank 3 model</a> <code>rerank-english-v3.0</code> is now available in their Rerank <a href="https://docs.cohere.com/reference/rerank-1">endpoint</a>. As the only vector database included in Cohere’s Rerank 3 launch, Elasticsearch has integrated seamless support for this new model into our open Inference API.</p><p>So briefly, what is reranking? Rerankers take the ‘top n’ search results from existing vector search and keyword search systems, and provide a semantic boost to those results. With good reranking in place, you have better ‘top n’ results without requiring you to change your model or your data indexes – ultimately providing better search results you can send to large language models (LLMs) as context.</p><p>Recently, we collaborated with the Cohere team to make it easy for Elasticsearch developers to use Cohere’s <a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-embeddings-support">embeddings</a> (available in <a href="https://www.elastic.co/blog/whats-new-elastic-search-8-13-0">Elasticsearch 8.13</a> and Serverless!). It is a natural evolution to include Cohere’s incredible reranking capabilities to unlock all of the tools necessary for true refinement of results past the first-stage of retrieval.</p><p>Cohere’s Rerank 3 model can be added to <em>any</em> existing Elasticsearch retrieval flow without requiring any significant code changes. Given Elastic’s vector database and hybrid search capabilities, users can also bring embeddings from any 3rd party model to Elastic, to use with Rerank 3.</p><h2>Elastic’s approach to hybrid search</h2><p>When looking to implement RAG (Retrieval Augmented Generation), the strategy for retrieval and reranking is a key optimization for customers to ground LLMs and achieve accurate results. Customers have trusted Elastic for years with their private data, and are able to leverage several first-stage retrieval algorithms (e.g. for BM25/keyword, dense, and sparse vector retrieval). More importantly, most real-world search use cases benefit from <a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">hybrid search</a> which we have supported since Elasticsearch <a href="https://www.elastic.co/blog/whats-new-elastic-enterprise-search-8-9-0">8.9</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1eee7a702c738a0a/6a171220d7c022784fde65d8/855663e958a2100d87f534883507bdd6cca46686-1440x897.png" alt="reranking" /><p>For mid-stage reranking, we also offer native support for <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/learning-to-rank.html">Learning To Rank </a>and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.17/filter-search-results.html#rescore">query rescore</a>. In this walkthrough, we will focus on Cohere’s last stage reranking capabilities, and will cover Elastic’s mid stage reranking capabilities in a subsequent blog post!</p><h2>Cohere’s approach to reranking</h2><p>Cohere has seen phenomenal results with their new Rerank model. In the testing, Cohere is reporting that reranking models in particular benefit from long context. Chunking for model token limits is a necessary constraint when preparing your document for dense vector retrieval. But with Cohere’s approach for reranking, a considerable benefit to reranking can be seen based on context contained in the full document, rather than a specific chunk within the document. Rerank has a 4k token limit to enable the input of more context to unlock the full relevance benefits of incorporating this model into your Elasticsearch based search system.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8acce02c4315035c/6a171221acf08840f7be9c79/485637d45e3b3aa0d6f7d7ad53434144ac590361-1440x883.png" alt="cohere results" /><p>(i) General retrieval based on BEIR benchmark; accuracy measured as nDCG@10</p><p>(ii) Code retrieval based on 6 common code benchmarks; accuracy measured as nDCG@10</p><p>(iii) Long context retrieval based on 7 common benchmarks; accuracy measured as nDCG@10</p><p>(iv) Semi-structured (JSON) retrieval based on 4 common benchmarks; accuracy measured as nDCG@10</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22ff716baf6d1636/6a1712231949f7ead1e7ab62/8b5f5438bcb5dbc103c2a2e34088dedf59697025-571x326.png" alt="rag" /><p>If you’re interested in how to chunk with <a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain</a> and <a href="https://www.elastic.co/search-labs/integrations/llama-index">LlamaIndex</a>, we provide chat application reference code, integrations and more in <a href="https://www.elastic.co/search-labs">Search Labs</a> and our open source <a href="https://github.com/elastic/elasticsearch-labs">repository</a>. Alternatively, you can leverage Elastic’s <a href="https://www.elastic.co/search-labs/blog/adding-passage-vector-search-to-lucene">passage retrieval</a> capabilities and chunk with <a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">ingest pipelines</a>.</p><h2>Building a RAG implementation with Elasticsearch and Cohere</h2><p>Now that you have a general understanding of how these capabilities can be leveraged, let’s jump into an example on building a RAG implementation with Elasticsearch and Cohere.</p><p>You'll need a <code>Cohere</code> account and some working knowledge of the Cohere <a href="https://docs.cohere.com/reference/rerank-1">Rerank endpoint</a>. If you’re intending to use Cohere’s newest generative model <code>Command R+</code> familiarize yourself with the <a href="https://docs.cohere.com/reference/chat">Chat endpoint</a>.</p><p>In <a href="https://www.elastic.co/kibana">Kibana</a>, you'll have access to a console for you to input these next steps in Elasticsearch even without an IDE set up. If you prefer to use a language client - you can revisit these steps in the <a href="https://docs.cohere.com/docs/elasticsearch-and-cohere">provided guide</a>.</p><h2>Elasticsearch vector database</h2><p>In an earlier announcement, we had some steps to get you started with the Elasticsearch vector database. You can review the steps to cover ingesting a sample <code>books</code> catalog, and generate embeddings using Cohere’s Embed capabilities by reading the <a href="https://www.elastic.co/search-labs/blog/elasticsearch-cohere-embeddings-support">announcement</a>. Alternatively, if you prefer we also provide a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search-inference.html">tutorial</a> and <a href="https://github.com/elastic/elasticsearch-labs/blob/main/notebooks/integrations/cohere/inference-cohere.ipynb">Jupyter notebook</a> to get you started on this process.</p><h2>Cohere reranking</h2><p>The following section assumes that you’ve ingested data and have issued your first search. This will give you a baseline as to how the search results are ranked with your first dense vector retrieval.</p><p>The previous announcement concluded with a query issued against the sample <code>books</code> catalog, and, and generated the following results in response to the query string “Snow”. These results are returned in descending order of relevance.</p>    {
      "took": 201,
      "timed_out": false,
      "_shards": {
        "total": 3,
        "successful": 3,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": {
          "value": 6,
          "relation": "eq"
        },
        "max_score": 0.80008936,
        "hits": [
          {
            "_index": "cohere-embeddings",
            "_id": "3VAixI4Bi8x57NL3O03c",
            "_score": 0.80008936,
            "_source": {
              "name": "Snow Crash",
              "author": "Neal Stephenson"
            }
          },
          {
            "_index": "cohere-embeddings",
            "_id": "4FAixI4Bi8x57NL3O03c",
            "_score": 0.6495671,
            "_source": {
              "name": "Fahrenheit 451",
              "author": "Ray Bradbury"
            }
          },
          {
            "_index": "cohere-embeddings",
            "_id": "31AixI4Bi8x57NL3O03c",
            "_score": 0.62768984,
            "_source": {
              "name": "1984",
              "author": "George Orwell"
            }
          },
          {
            "_index": "cohere-embeddings",
            "_id": "4VAixI4Bi8x57NL3O03c",
            "_score": 0.6197722,
            "_source": {
              "name": "Brave New World",
              "author": "Aldous Huxley"
            }
          },
          {
            "_index": "cohere-embeddings",
            "_id": "3lAixI4Bi8x57NL3O03c",
            "_score": 0.61449933,
            "_source": {
              "name": "Revelation Space",
              "author": "Alastair Reynolds"
            }
          },
          {
            "_index": "cohere-embeddings",
            "_id": "4lAixI4Bi8x57NL3O03c",
            "_score": 0.59593034,
            "_source": {
              "name": "The Handmaid's Tale",
              "author": "Margaret Atwood"
            }
          }
        ]
      }
    }
<p>You’ll next want to configure an inference endpoint for Cohere Rerank by specifying the Rerank 3 model and API key.</p>    PUT _inference/rerank/cohere_rerank 
    {
        "service": "cohere",
        "service_settings": {
            "api_key": &lt;API-KEY&gt;, 
            "model_id": "rerank-english-v3.0"
        },
        "task_settings": {
            "top_n": 10,
            "return_documents": true
        }
    }
<p>Once this inference endpoint is specified, you’ll now be able to rerank your results by passing in the original query used for retrieval, “Snow” along with the documents we just retrieved with the kNN search. Remember, you can repeat this with any <a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">hybrid</a> <a href="https://github.com/elastic/elasticsearch-labs/blob/main/notebooks/search/02-hybrid-search.ipynb">search</a> query as well!</p><p>To demonstrate this while still using the dev console, we’ll do a little cleanup on the JSON response above.</p><p>Take the <code>hits</code> from the JSON response and form the following JSON for the <code>input</code>, and then POST to the cohere_rerank endpoint we just configured.</p>    POST _inference/rerank/cohere_rerank
    {
      "input": ["Snow Crash", "Fahrenheit 451", "1984", "Brave New World","Revelation Space", "The Handmaid's Tale"], 
      "query": "Snow" 
    }
<p>And there you have it, your results have been reranked using Cohere's Rerank 3 model.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd73a44a4694ab658/6a171224964cea5e0108bcef/7b1f1c0c012efa2d029051bda0c72e4a75834793-1440x874.png" alt="Kibana rerank" /><p>The <code>books</code> corpus that we used to illustrate these capabilities does not contain large passages, and is a relatively simple example. When instrumenting this for your own search experience, we recommend that you follow Cohere’s approach to populate your <code>input</code> with the context from the full documents returned from the first retrieved result set, not just a retrieved chunk within the documents.</p><h2>Elasticsearch’s accelerated roadmap to semantic reranking and retrievers</h2><p>In <strong>upcoming</strong> versions of Elasticsearch we will continue to build seamless support for mid and final stage rerankers. Our end goal is to enable developers to have the ability to use semantic reranking to improve the results from any search whether it is BM25, dense or sparse vector retrieval, or a combination with hybrid retrieval. To provide this experience, we are building a concept called <code>retrievers</code> into the query DSL. Retrievers will provide an intuitive way to execute semantic reranking, and will also enable direct execution of what you’ve configured in the open inference API in the Elasticsearch stack without relying on you to execute this in your application logic.</p><p>When incorporating the use of retrievers in the earlier dense vector example, this is how different the reranking experience can be:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt436f1768726bf40d/6a171226e8fbcedab039fd7d/f80a4ef6f5793f1c3fb8f84706ca9a87336acdbd-611x223.png" alt="rag roadmap" /><p>(i) <strong>Elastic’s roadmap:</strong> The indexing step is simplified with the addition of Elastic’s future capabilities to automatically chunk indexed data</p><p>(ii) <strong>Elastic’s roadmap:</strong> The kNN retriever specifies the model (in this case Cohere’s Rerank 3) that was configured as an inference endpoint</p><p>(iii) <strong>Cohere’s roadmap:</strong> The step between sending the resulting data to Cohere’s Command R+ will benefit from a planned feature named <code>extractive snippets</code> which will enable the user to return a relevant chunk of the reranked document to the Command R+ model</p><p>This was our original kNN dense vector search executed on the <code>books</code> corpus to return the first set of results for “Snow”.</p>    GET cohere-embeddings/_search
    {
      "knn": {
        "field": "name_embedding",
        "query_vector_builder": {
          "text_embedding": {
            "model_id": "cohere_embeddings",
            "model_text": "Snow"
          }
        },
        "k": 10,
        "num_candidates": 100
      },
      "_source": [
        "name",
        "author"
      ]
    }
<p>As explained in this blog, there are a few steps to retrieve the documents and pass on the correct response to the inference endpoint. At the time of this publication, this logic should be handled in your application code.</p><p>In the future, retrievers can be configured to use the Cohere rerank inference endpoint directly within a single API call.</p>    {
      "retriever": {
        "text_similarity_rank": {
          "retriever": {
            "knn": {
              "field": "name_embedding",
              "query_vector_builder": {
                "text_embedding": {
                  "model_id": "cohere_embeddings",
                  "model_text": "Snow"
                }
              },
              "k": 10,
              "num_candidates": 100
            }
          },
          "field": "name",
          "window_size": 10,
          "inference_id": "cohere_rerank",
          "inference_text": "Snow"
        }
      },
      "_source": [
        "name",
        "author"
      ]
    }
<p>In this case, the kNN query is exactly the same as my original, but the cleansing of the response before input to the rerank endpoint will no longer be a necessary step. A retriever will know that a kNN query has been executed and seamlessly rerank using the Cohere rerank inference endpoint specified in the configuration. This same principle can be applied to <strong>any</strong> search, BM25, dense, sparse and hybrid.</p><p>Retrievers as an enabler of great semantic reranking is on our active and near term roadmap.</p><h2>Cohere’s generative model capabilities</h2><p>Now you’re ready with a semantically reranked set of documents that can be used to ground the responses for the large language model of your choice! We recommend Cohere’s newest generative model <code>Command R+</code>. When building the full RAG pipeline, in your application code you can easily issue a command to Cohere’s Chat API with the user query and the reranked documents.</p><p>An example of how this might be achieved in your <a href="https://elasticsearch-py.readthedocs.io/en/v8.13.0/">Python</a> application code can be seen below:</p>    response = co.chat(message=query, documents=documents, model='command-r-plus')

    source_documents = []
    for citation in response.citations:
        for document_id in citation.document_ids:
            if document_id not in source_documents:
                source_documents.append(document_id)

    print(f"Query: {query}")
    print(f"Response: {response.text}")
    print("Sources:")
    for document in response.documents:
        if document['id'] in source_documents:
            print(f"{document['title']}: {document['text']}")
<p>This integration with Cohere is offered in <a href="https://www.elastic.co/blog/elastic-serverless-architecture">Serverless</a> and soon will be available to try in a versioned Elasticsearch release either on Elastic Cloud or on your laptop or self-managed environment. We recommend you use our <a href="https://github.com/elastic/elasticsearch-serverless-python/releases/tag/v0.2.0.20231031">Elastic Python client v0.2.0</a> against your Serverless project to get started!</p><p>Happy reranking!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-cohere-rerank</guid>
    <category><![CDATA[Vector Database]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Serena Chou,Max Hniebergall]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte1f1748575c25298/6a1712272b835f8974f4b33b/808a666fc35b91149ce28e0a37769cff2554b6f5-1440x863.png" length="0" type="image/png"/>
    <pubDate>Thu, 11 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Introducing Elasticsearch vector database to Azure OpenAI Service On Your Data (preview)]]></title>
    <description><![CDATA[Microsoft and Elastic partner to add Elasticsearch (preview) as an officially supported vector database and retrieval augmentation technology for Azure OpenAI On Your Data, enabling users to build chat experiences with advanced AI models grounded by enterprise data.]]></description>
    <content:encoded><![CDATA[<p>Microsoft and Elastic are thrilled to announce that Elasticsearch, the world's most downloaded <a href="https://www.elastic.co/elasticsearch/vector-database">vector database</a> is an officially supported vector store and retrieval augmented search technology for Azure OpenAI Service On Your Data in public preview. The groundbreaking feature empowers you to leverage the power of OpenAI models, such as GPT-4, and incorporates the advanced capabilities of RAG (Retrieval Augmented Generation) model, directly on your data with enterprise-grade security on Azure. Read the announcement from Microsoft <a href="https://aka.ms/elasticsearch">here</a>.</p><p>Azure OpenAI Service On Your Data makes conversational experiences come alive for your employees, customers and users. With the addition of Elasticsearch vector database and vector search technology, LLMs are enriched by your business data, and conversations deliver superior quality responses out-of-the-box. All of this adds up to helping you better understand your data, and make more informed decisions.</p><h2>Build powerful conversational chat experiences, fast</h2><p>Business users, such as users on e-commerce teams, product managers, and others can add documents from an Elasticsearch index to build a conversational chat experience very quickly. All it takes is a few simple steps to configure the chat experience with parameters such as message history, and you're good to go! Customers can realize benefits pretty much right away..</p><ul><li><p>Quickly roll out conversational experiences to your users, customers, or employees--backed by context from your business data</p></li><li><p>Common use cases include offering internal knowledge search, users self-service, or chatbots that help process common business workflows</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51bcf5a45f392fd2/6a171175d7c022e889de659e/ff0350e74200eafb55193a2ad4e38f11992cc4ce-1440x776.png" alt="build a chatbot" /><h2>How Elasticsearch vector database works with On Your Data</h2><p>The new native experience within Azure OpenAI Studio makes adding an Elastic index a simple matter. Developers can pick Elasticsearch as their chosen vector database option from the drop-down menu..</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33a0d7ef3c3c8b4a/6a171176a2929923c6d01114/fe8a099b84c3f25a5f69b9b17e82e2932d4a6597-1224x1019.png" alt="pick Elastic as your vector database" /><p>You can bring your existing Elasticsearch indexes to On Your Data—whether those indexes live on Azure or on-prem. Just select Elasticsearch as your data source, add your Elastic endpoint and API key, add an Elastic index, and you're all set!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7399d9c89eb0f821/6a1711770e2e493d7741a232/378dcda8ebce6385979b358af8144f0ba5f9fb3b-1440x1184.png" alt="add your Elastic credentials and Elastic index" /><p>With the Elasticsearch vector database running in the background, users get all the Elastic advantages you'd expect.</p><ul><li><p>Precision of BM25 (text) search, the semantic understanding of vector search, and the best of both worlds with hybrid search</p></li><li><p>Document and field level security, so users can only access information they're entitled to based on their permissions</p></li><li><p>Filters, facets, and aggregations that add a real boost to how quickly relevant context is pulled from your organisation's data, and sent to an LLM</p></li><li><p>Choice of leveraging a range of large language model providers, including Azure OpenAI, Hugging Face, or other 3rd party models</p></li></ul><h2>Elastic on Microsoft Azure: a proven combination</h2><p>Elastic is a proud winner of the Worldwide Microsoft Partner of the Year award for Commercial Marketplace. Elastic and Microsoft customers have been using Elasticsearch and Azure OpenAI to build futuristic search experiences, that leverage the best of AI and machine learning, <a href="https://www.elastic.co/search-labs/blog/articles/relativity-elasticsearch-azure-openai">today</a>.</p><p>Ali Dalloul, VP, Azure AI Customer eXperience Engineering had this to say about the collaboration, "By harnessing the power of Azure Cloud and OpenAI, Elastic is driving the development of AI-driven solutions that redefine customer experiences. This partnership is more than just a collaboration; it's a feedback loop of innovation, benefiting customers, Elastic, and Microsoft, while empowering the broader partner ecosystem. We're delighted to offer customers Elasticsearch's strong vector database and retrieval augmentation capabilities to store and search vector embeddings for On Your Data."</p><p>"This really helps customers connect data wherever it lives. We are happy to open the spectrum of building conversational AI solutions, agnostic to location, including Elasticsearch. We are excited to see how developers build upon this integration." Adds Pavan Li, Principal Product Manager of Azure OpenAI Service On Your Data.</p><p>Elastic's clear strengths in hybrid search--combining BM25/text search with vector search for semantic relevance, was an important differentiator. With the backing of the open source Apache Lucene community, Elastic's vector database has already been widely adopted by large companies for enterprise scale use cases.</p><h2>Try On Your Data with Elasticsearch vector database today</h2><p>Unlock the insights with conversational AI, using Elasticsearch and Azure OpenAI On Your Data today!</p><ul><li><p>Visit <a href="http://oai.azure.com/">Azure OpenAI Studio</a> to build your first conversational copilot</p></li><li><p>Connect <a href="https://www.elastic.co/search-labs/blog/articles/chatgpt-elasticsearch-openai-meets-private-data">Elasticsearch with OpenAI models</a></p></li><li><p>Read more on the <a href="https://aka.ms/elasticsearch">Microsoft Tech Community blog</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/azure-openai-on-your-data-elasticsearch-vector-database</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/azure-openai-on-your-data-elasticsearch-vector-database</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Aditya Tripathi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33a0d7ef3c3c8b4a/6a171176a2929923c6d01114/fe8a099b84c3f25a5f69b9b17e82e2932d4a6597-1224x1019.png" length="0" type="image/png"/>
    <pubDate>Tue, 26 Mar 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Avatar assisted & dialogue driven voice to RAG search]]></title>
    <description><![CDATA[Create avatar-assisted voice search experience by integrating speech-to-text, semantic search, RAG and a synthesized avatar for responses.]]></description>
    <content:encoded><![CDATA[<h2>The evolution of search</h2><p>Search has evolved from simple text queries yielding straightforward results to a complex system accommodating various formats like text, images, videos, and questions.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24559d31d5b7b8f6/6a170b206f7f04db68914857/128bd2fe93461638e6ac28ca994a07c5a21e9c70-1440x659.png" alt="Legacy Search" /><p>Search not too long ago comprised of a text query and relevant results. Today's search results are enhanced with generative AI, machine learning, and interactive chat features, offering a richer, more dynamic, and contextually relevant user experience. Additionally, voice search and speech avatars have transformed traditional search, offering a more interactive and convenient user experience.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd19b4894abd43db2/6a170b22cf4f25868db2d18b/1ebe15a22c031bc439cb2500c21d1c60f79b008c-1440x781.png" alt="Search today" /><h3>The desire for dialogue in search</h3><p>In a realm where dialogue underpins every interaction, whether with fellow humans or bots, shouldn't our search experiences reflect this fundamental aspect? Envision the vast array of document corpora residing within an enterprise. Naturally, this environment sparks curiosity and a multitude of questions, leading to subsequent inquiries. This innate human trait drives us to seek answers, delve deeper following initial responses, and continuously explore. Yet, traditional question-and-answer mechanisms fall short, as they often disregard the context of preceding exchanges, leading to a disjointed and laborious process that feels unnatural and prompts users to disengage prematurely.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ec8ee317d02777e/6a170b238b73cb33a818a046/f3feb0843430a550151182aba95648ce5b45820b-1440x766.png" alt="you have questions" /><h3>Beyond question and answer search</h3><p>Consider the act of using a television to search for content, such as seeking action movies featuring Nicolas Cage. While most current systems adeptly provide relevant results, the inquiry rarely ends there. Subsequent questions, such as inquiring about the runtime or release dates of these movies, are a natural progression in our quest for information. However, standard search applications are not designed to facilitate a continuous dialogue; they are structured around isolated question-and-answer formats, which limits the depth of interaction and exploration.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ae209de746281fb/6a170b2566c4f994d9f8c043/60e914ec7dc1809b0f8a18eb2ec186fa3f2e890b-1006x430.png" alt="results" /><h2>Avatar assisted voice search experience</h2><p>This is where the concept of an avatar-assisted search experience comes into play, especially in scenarios where users, myself included, prefer direct answers without the need to sift through information. Occasionally, we desire the convenience of having answers delivered to us, bypassing the effort of reading through content. The development of an avatar to generate responses could further modernize this interaction, providing a more engaging, efficient, and natural user experience.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3a6145eda1e0487f/6a170b2714b2704159e3c625/4e77349fb83be542c9817a65cb6a905e18879f6c-1116x1174.png" alt="results" /><h2>Live demo: creating an avatar assisted voice search experience</h2><p>This demo showcases a seamless integration of speech-to-text, Elasticsearch's semantic search capabilities, Azure OpenAI's RAG, and a synthesized avatar for responses.</p><h2>Integration details</h2><h4>Speech to search</h4><p>The advanced search experience begins with user voice interactions, which are converted into text by Azure Speech to Text, forming the basis of the search query. This query is then processed through Elasticsearch, using the ELSER, to retrieve relevant documents, such as TV guides listing “action movies featuring Nicolas Cage.” This ensures precision and relevance in the search results.</p><h4>RAG &amp; cache</h4><p>In the enhanced search framework, merely fetching documents isn't enough. Azure OpenAI's GPT-4 refines raw data into understandable responses, ensuring smooth conversation flow. Additionally, Elasticsearch boosts efficiency as a GenAI caching layer, recycling answers for related queries, thus conserving resources. For example, if there's a cached response for "action movies featuring Nicolas Cage," the caching API will swiftly use this for similar questions like “Nicolas Cage high-intensity movies,” accelerating the search experience.</p><h4>Avatar response generation</h4><p>The experience is further enriched with an avatar response feature, powered by Azure Synthesizer, adding a visual and auditory dimension that surpasses traditional text-based interfaces. This creates a more engaging and interactive user experience, integrating various advanced technologies to deliver a dynamic, intuitive, and compelling search experience.</p><h2>Summary</h2><p>The shift from traditional Google searches to platforms like ChatGPT for answering queries illustrates a broader trend: our preference for dialogue over static information retrieval. This predilection underscores the importance for enterprises to adopt a more intuitive and conversational approach in their search functionalities. By embracing this paradigm, businesses can better align with the natural human inclination towards dialogue, thereby enhancing the overall search and discovery process within their data ecosystems.</p><h2>Demo assets</h2><p>Still curious, here is the <a href="https://github.com/sunileman/voice-movie-search">link to the source code</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/avatar-assisted-dialogue-driven-voice-to-rag-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/avatar-assisted-dialogue-driven-voice-to-rag-search</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Vector Database]]></category>
    <dc:creator><![CDATA[Sunile Manjee]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3572b96ad92d331/6a170b29b339d560d0769fd3/9b274d1191d203babb55dc7693897fd278df1a09-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Fri, 08 Mar 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Retrieval Augmented Generation (RAG) using Cohere Command model through Amazon Bedrock and domain data in Elasticsearch]]></title>
    <description><![CDATA[Learn how to implement Retrieval Augmented Generation (RAG) using Cohere Command model via Amazon Bedrock &amp; domain data in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p><strong>Generative AI</strong> is a type of Artificial Intelligence (AI) that can create new content and ideas, including conversations, stories, images, videos, and music. Like all AI, generative AI is powered by Machine Learning (ML) models—very large models that are pre-trained on vast corpora of data and commonly referred to as Foundation Models (FMs).</p><p>To the public, generative AI has seemingly appeared from nowhere. But if you dig deeper, you’ll note that the ideas underlying generative AI solutions trace their lineage back to inventions such as the Mark I perceptron in 1958 and neural networks in the late twentieth century.</p><p>Advancements in statistical techniques, the vast growth of publicly available data and advancements in Machine Learning (specifically the invention of the transformer-based neural network architecture) have led to the rise of models that contain billions of parameters or variables. To give a sense for the change in scale, the largest pre-trained model in 2019 was 330M parameters. Cohere's Command XL model, one of the leading models in Stanford’s <a href="https://crfm.stanford.edu/helm/latest/">Holistic Evaluation of Language Models (HELM) benchmark</a> is trained on 52.4 billion parameters - ~1580x increase in size in just a few years.</p><p><strong>Foundation Models (FMs)</strong> are ML models trained on massive quantities of structured and unstructured data, which can be fine-tuned or adapted for more specific tasks.</p><p><strong>Large Language Models (LLMs)</strong> are a subset of FMs focused on understanding and generating human-like text. These models are ideal for needs such as translation, answering questions, summarizing information, and creating or identifying images.</p><p><strong>LLMs</strong> can perform a wide range of tasks that span multiple domains, like writing blog posts, solving math problems, engaging in dialog, and answering questions based on a document. The size and general-purpose nature of FMs make them different from traditional ML models, which typically perform specific tasks, like analyzing text for sentiment, classifying images, and forecasting trends.</p><p>The primary goal of LLMs is to enable meaningful and engaging conversation between humans and machines and have become an immensely effective resource in countless industries, helping business to improve the customer experience.</p><h2>Limitations of LLMs</h2><p>LLMs have certain limitations. One notable constraint is that they are trained on general domain corpora, making them less effective on domain-specific tasks. There are scenarios when you want models to generate text based on specific data rather than generic data. For example, a health service provider company may want their chatbot to answer questions using the latest information stored in an enterprise document repository, so that the answers are specific to the health service provider’s business.</p><p>Also, these LLMs are trained offline until a <strong>knowledge cutoff date</strong>. It will be agnostic of any developments that have happened after the knowledge cutoff date. This may lead to inaccurate interpretations. For example, in 2021 San Francisco was the most expensive Bay Area City for renters. Today, it is Mountain view - <a href="https://www.nbcbayarea.com/news/local/south-bay/mountain-view-rent-report/3152054/#:~:text=A%20new%20report%20shows%20Mountain,almost%20always%20been%20San%20Francisco.">source</a>.</p><h2>Approaches to enhance LLMs</h2><p>There are two popular ways to reference contextual data in LLMs.</p><p>The first option is to <strong>fine-tune</strong> the base LLMs with contextual data. But, using this approach generating the correctly formatted information is time consuming. Also, it is costly to fine-tune a model. In addition to that, if the domain specific data is changing frequently, it would require frequent fine-tunings and retraining to provide accurate responses. This impacts time to market and also increases overall cost of the solution. In addition to that, not all LLMs provide an option to fine-tune.</p><p>To overcome these constraints, we can use a technique called <strong>Retrieval Augmented Generation (RAG)</strong>. RAG is a process in which the model retrieves contextual documents from an external data source like Elasticsearch as part of its execution. These contextual documents are used in conjunction with the original input to produce an output.</p><p>Below are a few examples of how RAG can be used in various applications to improve the quality and relevance of generated content.</p><ul><li><p><strong>Chatbot responses</strong>: In a chatbot system, RAG refers to combining a retrieval-based approach with a generative model. The retrieval component obtains relevant responses from a pre-defined database or knowledge base, while the generative model can add additional context or generate more fluent and diverse responses. This combination helps the chatbot provide more accurate and contextually appropriate answers to user queries.</p></li><li><p><strong>Content generation</strong>: RAG can be used in content generation tasks such as summarization or paraphrasing. The retrieval component can retrieve relevant sentences or paragraphs from existing documents or articles, and the generative model can then augment or rephrase the retrieved content to create new and original summaries or paraphrases.</p></li><li><p><strong>Recommendation systems</strong>: RAG can also be applied in recommendation systems. The retrieval component can retrieve a set of candidate items or products based on user preferences or history, and the generative model can then generate personalized recommendations or provide additional information about the recommended items to enhance the user’s decision-making process.</p></li></ul><h2>RAG using Elasticsearch and Cohere Command model through Amazon Bedrock</h2><h3>Why Cohere?</h3><p><a href="https://cohere.com/">Cohere</a> is the leading AI platform for enterprise. The company builds world-class LLMs that allow computers to search, understand meaning, and converse in text. Cohere's models are uniquely suited to the needs of business, providing ease of use and strong security and privacy controls across multiple deployment options. Companies can use the models out-of-the-box or tailor them to their particular needs using their own custom data.</p><p><a href="https://cohere.com/models/command">Command </a>is Cohere’s flagship text generation model. It is trained to follow user commands and to be instantly useful in practical business applications, such as text generation, summarization, RAG, and chat. Command ranks as one of the leading language models according to the <a href="https://crfm.stanford.edu/helm/latest/?group=core_scenarios">Stanford’s HELM website</a> an evaluation leaderboard comparing large language models on a wide number of tasks from Stanford University (March 2023 results). Customers can use Cohere's Command LLM through <a href="https://aws.amazon.com/sagemaker/jumpstart/?p=pm&amp;c=sm&amp;z=2">Amazon SageMaker Jumpstart</a> and Amazon Bedrock.</p><p><a href="https://cohere.com/embed">Embed</a> is Cohere’s representative model which translates text into numerical vectors that models can understand. Cohere provides industry-leading English and multilingual models (100+ languages) for a range of use cases, including semantic search, text classification, and semantic engine for RAG.</p><h3>Why Elasticsearch?</h3><p>To make the most of generative AI, it is essential to have a unified data platform where the organization's data is stored, making it easily (and safely) accessible and searchable in one centralized location.</p><p><a href="https://www.elastic.co/?utm_campaign=B-Stack-Trials-AMER-US-E-Exact&amp;utm_content=Stack-Core&amp;utm_source=google&amp;utm_medium=cpc&amp;device=c&amp;utm_term=elasticsearch&amp;gclid=Cj0KCQjwy4KqBhD0ARIsAEbCt6gQivnVj9HtKlnH2V-vRa9cXTQ-06y4DDUe_g2Rj3sunEAqQQNZ7qMaArfnEALw_wcB"><strong>Elasticsearch</strong></a> is a distributed, open source search and analytics engine for all types of data, including textual, numerical, geospatial, structured, and unstructured. Raw data from Enterprises flows into Elasticsearch from a variety of sources, including logs, system metrics, and web applications. Elasticsearch is built on top of Lucene and it excels at full-text search. Elasticsearch is fast and excels at delivering the most relevant responses to users.</p><p>In addition to full-text search, Elasticsearch also supports vector-search.</p><p><strong>Vector search</strong> leverages <a href="https://www.elastic.co/what-is/elasticsearch-machine-learning">ML</a> to capture the meaning and context of unstructured data, including text and images, transforming it into a numeric representation. Frequently used for <strong>semantic search,</strong> vector search finds similar data using approximate nearest neighbor (ANN) algorithms. Compared to traditional keyword search, vector search yields more relevant results and executes faster. Users can enhance the search experience by combining vector search with filtering and aggregations to optimize relevance by implementing a hybrid search and combining it with traditional scoring.</p><p>Elasticsearch provides an easy-to-use and performant API to enable integration with other services. These features make Elasticsearch a preferred choice for enterprises to store business data and improve search experience.</p><p>Elasticsearch allows for the seamless integration of domain-specific context from the organization's data, thereby enhancing the performance and value of generative AI for achieving desired business objectives.</p><h3>Why Amazon Bedrock?</h3><p><a href="https://aws.amazon.com/bedrock/">Amazon Bedrock</a> is a fully managed service that offers a choice of high-performing FMs from leading AI companies like AI21 Labs, Anthropic, Cohere, Meta, Stability AI, and Amazon with a single API, along with a broad set of capabilities you need to build generative AI applications, simplifying development while maintaining privacy and security. With the comprehensive capabilities of Amazon Bedrock, you can easily experiment with a variety of top FMs, privately customize them with your data using techniques such as fine-tuning and RAG, and create managed agents that execute complex business tasks—from booking travel and processing insurance claims to creating ad campaigns and managing inventory—all without writing any code. Since Amazon Bedrock is serverless, you don't have to manage any infrastructure, and you can securely integrate and deploy generative AI capabilities into your applications using the AWS services you are already familiar with.</p><p>Amazon Bedrock offers several capabilities to support security and privacy requirements and has achieved HIPAA eligibility and GDPR compliance. With Amazon Bedrock, content is not used to improve the base models and is not shared with third-party model providers. Data in Amazon Bedrock is always encrypted in transit and at rest, and can encrypt the data using your own keys. <a href="https://aws.amazon.com/privatelink/">AWS PrivateLink</a> can be used with Amazon Bedrock to establish private connectivity between FMs and your Amazon Virtual Private Cloud (Amazon VPC) without exposing your traffic to the Internet.</p><h3>Solution overview</h3><p>Here's how to use RAG to enable Generative AI capabilities on domain-specific business data using Elasticsearch and Cohere Generate Model -Command through Amazon Bedrock.</p><p>The below architecture diagram explains how to get domain-specific responses from <strong>Cohere Command Model</strong> through Amazon Bedrock using enterprise data hosted in Elastic Enterprise Search using a technique called <strong>RAG.</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltacd3183ea4f4f1dd/6a17e783e9ea87c9eda9c5cd/1d97936d86f6e24e67d208e3cb84518a2b6eac18-1440x748.png" alt="" /><p><em>Figure 1. RAG Architecture using Elasticsearch and Amazon Bedrock</em></p><h4>Step by step explanation</h4><p><strong>Offline Data Ingestion:</strong></p><p>i. The documents are ingested using web crawler or any other ingestion mechanism.</p><p>ii. The <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic Learned Sparse EncodeR (ELSER)</a> model will embed the text and store the resulting tokens in the Elasticsearch Index</p><p><strong>Real-time flow on user query:</strong></p><ol><li><p>The User provides a question via the RAG web application</p></li><li><p>The RAG application generates a retrieval request initialized from the vector store (Elasticsearch index). At query time, the text will be embedded using the ELSER model and the resulting tokens will be used to perform a text expansion query.</p></li><li><p>The Retriever component of RAG application fetches the relevant documents from Elasticsearch vector store.</p></li><li><p>The RAG application passes the retrieved documents (context) along with user question (prompt) to the Cohere Command Model through Amazon Bedrock</p></li><li><p>The Cohere Command Model through Amazon Bedrock generates a textual response and sends it back to RAG application</p></li><li><p>The RAG application performs any required post processing tasks. For example, it adds source to the response generated from Cohere Command Model. The User views the response in the web application.</p></li></ol><p>We used the following <strong>AWS and third-party services</strong>:</p><ol><li><p><a href="https://aws.amazon.com/bedrock/">Amazon Bedrock</a> for interacting with LLMs from Cohere.</p></li><li><p>Cohere Command Model for Text Generation.</p></li><li><p>Elasticsearch for storing embeddings of the enterprise knowledge corpus and doing similarity search with user questions.</p></li><li><p>Python, LangChain and Streamlit for building the RAG application</p></li><li><p>Amazon EC2 for hosting the Streamlit application</p></li><li><p><a href="https://aws.amazon.com/iam/">AWS Identity and Access Management</a> roles and policies for access management.</p></li></ol><p>Prerequisites:</p><ol><li><p><strong>Sign up</strong> for a free trial of Elasticsearch cluster with <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud</a></p></li><li><p><strong>Create a new deployment</strong> on AWS following the <a href="https://www.elastic.co/guide/en/cloud/current/ec-create-deployment.html">steps</a></p></li><li><p><strong>Reset and download the elastic user password</strong> following these <a href="https://www.elastic.co/guide/en/cloud/current/ec-password-reset.html">steps</a></p></li><li><p><strong>Copy the Cloud ID</strong> from the My Deployment page listed under Deployments</p></li><li><p>Deploy ELSER Model: In Kibana, navigate to Machine Learning&gt; Trained models. ELSER can be found in the list of trained models. Click the Download model button under Actions. After the download is finished, start the deployment by clicking the <strong>Start deployment</strong> button. Go to the Elastic <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a> page to find more details.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3232748eebefafaa/6a17e78563baff7b63741c3c/32a80e07f0f4e904bacc835f2f7e79c31a375f09-1433x780.png" alt="" /><ol><li><p>Install packages and import modules: Firstly, we need to install modules. Make sure <a href="https://www.python.org/downloads/release/python-381/">python</a> is installed with min version 3.8.1. Then we need to import modules.</p></li></ol>pip install -qU langchain langchain-elasticsearch boto3

from getpass import getpass
from urllib.request import urlopen
from langchain_elasticsearch import ElasticsearchStore
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings.bedrock import BedrockEmbeddings
from langchain.llms.bedrock import Bedrock
from langchain.chains import RetrievalQA
import boto3
import json

<ol><li><p>Initialize Amazon Bedrock client using the following code</p></li></ol>default_region = "us-east-1"
AWS_REGION = input(f"AWS Region [default: {default_region}]: ") or default_region

def get_bedrock_client(region):
    bedrock_client = boto3.client("bedrock-runtime", region_name=region)
    return bedrock_client

<ol><li><p>Connect to Elasticsearch using Elastic Cloud Id, Elastic username and Elastic password. Use <strong>ElasticsearchStore</strong> to connect to our elastic cloud deployment. As we’re using ELSER we use “SparseVectorRetrievalStrategy”. This strategy uses Elasticsearch’s sparse vector retrieval to retrieve the top-k results.</p></li></ol>CLOUD_ID = getpass("Elastic deployment Cloud ID: ")
CLOUD_USERNAME = "elastic"
CLOUD_PASSWORD = getpass("Elastic deployment Password: ")


vector_store = ElasticsearchStore(
   es_cloud_id=CLOUD_ID,
   es_user=CLOUD_USERNAME,
   es_password=CLOUD_PASSWORD,
   index_name= "workplace_index",
   strategy=ElasticsearchStore.SparseVectorRetrievalStrategy()
)
<ol><li><p>Download the dataset, deserialize the document and split the document into passages. We’ll chunk the documents into passages in order to improve the retrieval specificity and to ensure that we can provide multiple passages within the context window of the final question answering prompt. Here we are chunking into 800 tokens with an overlap of 400 tokens. Here, we are using a simple splitter but LangChain offers more advanced splitters to reduce the chance of context being lost.</p></li></ol>url = "https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/example-apps/workplace-search/data/data

response = urlopen(url)

workplace_docs = json.loads(response.read())

metadata = []
content = []

for doc in workplace_docs:
  content.append(doc["content"])
  metadata.append({
      "name": doc["name"],
      "summary": doc["summary"],
      "rolePermissions":doc["rolePermissions"]
})

text_splitter = CharacterTextSplitter(chunk_size=800, chunk_overlap=400)
docs = text_splitter.create_documents(content, metadatas=metadata)

<ol><li><p>Index data to Elasticsearch using <a href="https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.elasticsearch.ElasticsearchStore.html#langchain_community.vectorstores.elasticsearch.ElasticsearchStore.from_documents">ElasticsearchStore.from_documents</a>.</p></li></ol>documents = vector_store.from_documents(
    docs,
    es_cloud_id=CLOUD_ID,
    es_user=CLOUD_USERNAME,
    es_password=CLOUD_PASSWORD,
    index_name="workplace_index"
    strategy=ElasticsearchStore.SparseVectorRetrievalStrategy()
)
<ol><li><p>Initialize the Amazon Bedrock LLM. In the Amazon Bedrock instance, will pass bedrock_client and specific model_id. In this case model_id = <code>cohere.command-text-v14.</code></p></li></ol>default_model_id = "cohere.command-text-v14"
AWS_MODEL_ID = input(f"AWS model [default: {default._model_id}]: ") or default_model_id

def create_bedrock_llm(bedrock_client, model_version_id):
    bedrock_lIm=Bedrock(
        model_id=model_version_id,
        client=bedrock_client,
        model_kwargs={'temperature': 0}
        )
    return bedrock_lIm
<ol><li><p>Asking a question: Now that we have the passages stored in Elasticsearch and LLM is initialized, we can now ask a question to get the relevant passages.</p></li></ol>retriever = vector_store.as_retriever()

qa = RetrievalQA.from_llm(
     llm=llm,
     retriever=retriever,
     return_source_documents=True
)

questions = [
    'What is the nasa sales team?',
    'What is our work from home policy?',
    'Does the company own my personal project?',
    'What job openings do we have?',
    'How does compensation work?'
]
question = questions [1]
print(f"Question: {question}\n")

ans = qa({"query": question})

print("\033[92m ---- Answer ---- \033 [Om')
print(ans["result"] + "\n")
print("\033[94m ---- Sources ----\033 [0m')
for doc in ans["source_documents"]:
  print("Name: " + doc.metadata ["name"])
  print("Content: " + doc.page_content)
  print ("-------\n")

<ol><li><p>(Optional) You can also add a reranking step to the search pipeline which can further improve the ranking of the results returned in the search step. See the <a href="https://docs.cohere.com/docs/deploying-with-aws-sagemaker">Deploying with Amazon SageMaker</a> guide on using <a href="https://txt.cohere.com/rerank/">Rerank.</a></p></li></ol><h2>Conclusion</h2><p>In this post, we showed how to create a RAG web application using a combination of Elasticsearch, Amazon Bedrock, Cohere Command Model and open source python packages like LangChain and Streamlit.</p><p>We encourage you to learn more by exploring <a href="https://aws.amazon.com/sagemaker/jumpstart/?p=pm&amp;c=sm&amp;z=2">Amazon SageMaker Jumpstart</a>, <a href="https://aws.amazon.com/bedrock/">Amazon Bedrock</a>, <a href="https://cohere.com/">Cohere</a>, and <a href="https://www.elastic.co/?ultron=B-Stack-Trials-AMER-US-W&amp;gambit=Stack-Core-EXT&amp;blade=adwords-s&amp;hulk=paid&amp;Device=c&amp;thor=elasticsearch&amp;gclid=Cj0KCQjw1_SkBhDwARIsANbGpFuGD05uYzL230GZmrmDxpIIUdX5GpC0e_wwdUr8OAwseTx7dx3jjywaAuhEEALw_wcB">Elasticsearch</a> and building a solution using the sample implementation provided in this post and a dataset relevant to your business. If you have questions or suggestions, leave a comment.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/gen-ai-using-cohere-llm</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/gen-ai-using-cohere-llm</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Udayasimha Theepireddy,Meor Amer,Ayan Ray,James Yi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5cd8853e995046c9/6a17e7872f4a5c03b6fa88f6/52748b9e2f082718804ed9c0d8f4272f4e4f893a-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 23 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Domain specific generative AI: pre-training, fine-tuning, and RAG]]></title>
    <description><![CDATA[Explore strategies for integrating domain-specific knowledge into large language models (LLMs) through pre-training, fine-tuning, and RAG.]]></description>
    <content:encoded><![CDATA[<p>There are a number of strategies to add domain specific knowledge to large language models (LLMs), and more approaches are being investigated as part of <a href="https://acl2023-retrieval-lm.github.io/">an active research field</a>. Methods such as pre-training and fine-tuning on domain specific datasets allow the LLM to reason and generate domain specific language. However, using these LLMs as knowledge bases is still prone to hallucinations. If the domain language is similar to the LLM training data, using external information retrieval systems via Retrieval Augmented Generation (RAG) to provide contextual information to the LLM can improve factual responses. Ultimately, a combination of fine-tuning and RAG may provide the best result.</p><p>The blog attempts to describe some of the basic processes for storing and retrieving knowledge from LLMs. Followup blogs will describe different RAG strategies in more detail.</p><p></p><p>Pre-training</p><p>Fine-tuning</p><p>Retrieval Augmented Generation</p><p>Training duration</p><p>Days to weeks to months</p><p>Minutes to hours</p><p>Not required</p><p>Customisation</p><p>Requires large amount of domain training data  Can customise model architecture, size. tokenizer etc. Creates new “foundation” LLM model</p><p>Add domain-specific data  Tune for specific tasks.  Updates LLM model.</p><p>No model weights.  External information retrieval system can be tuned to align with LLM.  Prompt can be optimised for task performance.</p><p>Objective</p><p>Next-token prediction</p><p>Increase task performance</p><p>Increase task performance for specific set of domain documents</p><p>Expertise</p><p>High</p><p>Medium</p><p>Low</p><h2>Introduction to domain specific generative AI</h2><p>Generative AI technologies, built on large language models (LLMs), have substantially progressed our ability to develop tools for processing, comprehending, and generating text. Furthermore, these technologies have introduced an innovative information retrieval mechanism, wherein generative AI technologies directly respond to user queries using the stored (parametric) knowledge of the model.</p><p>However, it's important to note that the parametric knowledge of the model is a condensed representation of the entire training dataset. Thus, employing these technologies for a specific knowledge base or domain beyond the original training data does come with certain limitations, such as:</p><ul><li><p>The generative AI's responses might lack context or accuracy, as they won't have access to information that wasn't present in the training data.</p></li><li><p>There is potential for generating plausible-sounding but incorrect or misleading information (<a href="https://aclanthology.org/2021.findings-emnlp.320.pdf">hallucinations</a>).</p></li></ul><p>Different strategies exist to overcome these limitations, such as extending the original training data, fine-tuning the model, and integrating with an external source of domain-specific knowledge. These various approaches yield distinct behaviours and carry differing implementation costs.</p><h2>Strategies for integrating domain-specific knowledge into LLMs</h2><h3>Domain specific pre-training for LLMs</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt928972dfb98a9f9c/6a1711fc0e2e4915dc41a26a/85cabbc57c68146c67a4bf978e1dd5549a20923c-960x540.png" alt="" /><p>LLMs are pre-trained on huge corpora of data that represent a wide range of natural language use cases:</p><p>Model</p><p>Total dataset size</p><p>Data sources</p><p>Training cost</p><p>PaLM 540B</p><p>780 billion tokens</p><p>Social media conversations (multilingual) 50%; Filtered web pages (multilingual) 27%; Books (English) 13%; GitHub (code) 5%; Wikipedia (multilingual) 4%; News (English) 1%</p><p>8.4M TPU v2 hours</p><p>GPT-3</p><p>499 billion tokens</p><p>Common Crawl (filtered) 60%; WebText2 22%; Books1 8%; Books2 8%; Wikipedia 3%</p><p>0.8M GPU hours</p><p>LLaMA 2</p><p>2 trillion tokens</p><p>“mix of data from publicly available sources”</p><p>3.3M GPU hours </p><p>The costs of this pre-training step are substantial, and there’s a significant amount of work required to curate and prepare the datasets. Both of these tasks require a high level of technical expertise.</p><p>In addition, pre-training is only one step in creating the model. Typically, the models are then fine-tuned on a narrower dataset that is carefully curated and tailored for specific tasks. This process also typically involves human reviewers that rank and review possible model outputs to improve the model’s performance and safety. This adds further complexity and cost to the process.</p><p>Examples of this approach applied to specific domains include:</p><ul><li><p><a href="https://www.biorxiv.org/content/10.1101/2022.07.20.500902v1.full.pdf">ESMFold</a>, <a href="https://arxiv.org/pdf/2206.13517.pdf">ProGen2</a> and others - LLM for protein sequences: protein sequences can be represented using language-like sequences but are not covered by natural language models</p></li><li><p><a href="https://arxiv.org/pdf/2211.09085.pdf">Galactica</a> - LLM for science: trained exclusively on a large collection of scientific datasets, and includes special processing to handle scientific notations</p></li><li><p><a href="https://arxiv.org/pdf/2303.17564.pdf">BloombergGPT</a> - LLM for finance: trained on 51% financial data, 49% public datasets</p></li><li><p><a href="https://arxiv.org/pdf/2305.06161.pdf">StarCoder</a> - LLM for code: trained on 6.4TB of permissively licensed source code in 384 programming languages, and included 54 GB of GitHub issues and repository-level metadata</p></li></ul><p>The domain-specific models generally outperform generalist models within their respective domains, with the most significant improvements observed in domains that differ significantly from natural language (such as protein sequences and code). However, for knowledge-intensive tasks, these domain-specific models suffer from the same limitations due to their reliance on parametric knowledge. Therefore, while these models can understand the relationships and structure of the domain more effectively, they are still prone to inaccuracies and hallucinations.</p><h3>Domain specific fine-tuning for LLMs</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6649b2195ef435cb/6a1711fde8fbce8a4e39fd73/f9c42e2b3fae929312f17d429fea1c11e44357f9-960x540.png" alt="" /><p>Fine-tuning for LLMs involves training a pre-trained model on a specific task or domain to enhance its performance in that area. It adapts the model's knowledge to a narrower context by updating its parameters using task-specific data, while retaining its general language understanding gained during pre-training. This approach optimises the model for specific tasks, saving significant time compared to training from scratch.</p><h4>Examples</h4><ul><li><p><a href="https://crfm.stanford.edu/2023/03/13/alpaca.html">Alpaca</a> - fine-tuned LLaMA-7B model that behaves qualitatively similarly to OpenAI’s GPT-3.5</p></li><li><p><a href="https://www.stochastic.ai/blog/xfinance-vs-bloomberg-gpt">xFinance</a> - fine-tuned LLaMA-13B model for financial-specific tasks. Reportedly outperforms BloombergGPT</p></li><li><p><a href="https://arxiv.org/pdf/2303.14070.pdf">ChatDoctor</a> - fine-tuned LLaMA-7B model for medical chat.</p></li><li><p><a href="https://huggingface.co/jinaai/falcon-40b-code-alpaca">falcon-40b-code-alpaca</a> - fine-tuned falcon-40b model for code generation from natural language</p></li></ul><h4>Costs: fine-tuning vs. pre-training LLMs</h4><p>Costs for fine-tuning are significantly smaller than for pre-training. In addition, novel methods such as parameter-efficient fine-tuning (<a href="https://github.com/huggingface/peft">PEFT</a>) methods (e.g. <a href="https://arxiv.org/pdf/2106.09685.pdf">LoRA</a>, adapters, prompt tuning, and in-context learning as described above) enable very efficient adaptation of pre-trained language models (PLMs) to various downstream applications without fine-tuning all the model's parameters. For example,</p><p>Model</p><p>Fine-tuning method</p><p>Fine-tuning dataset</p><p>Cost</p><p>Alpaca </p><p>Self-Instruct</p><p>52K unique instructions and the corresponding outputs</p><p>3 hours on 8 80GB A100s:24 GPU hours</p><p>xFinance</p><p>Unsupervised fine-tuning and instruction fine-tuning using xTuring library</p><p>493M token text dataset; 82K instruction dataset</p><p>25 hours on 8 A100 80GB GPUs:200 GPU hours</p><p>ChatDoctor</p><p>Self-Instruct</p><p>110K patient-doctor interactions</p><p>3 hours on 6 A100 GPUS: 18 GPU hours</p><p>falcon-40b-code-alpaca</p><p>Self-Instruct</p><p>52K instruction dataset; 20K instruction-input-code triplets</p><p>4 hours on 4 A100 80GB GPUs: 16 GPU hours</p><p>Similar to domain-specific pre-trained models, these models typically exhibit better performance within their respective domains, yet they still face the limitations associated with parametric knowledge.</p><h3>Enhancing LLMs with Retrieval Augmented Generation (RAG)</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltabcd8f29eadaa390/6a1711ffb0367d9da172be36/1dde84a8973bbf045dc9024ee5619ee5b7459a9f-960x540.png" alt="" /><p>LLMs store factual knowledge in their parameters, and their ability to access and precisely manipulate this knowledge is still limited. This can lead to LLMs providing non-factual but seemingly plausible predictions (hallucinations) - particularly for unpopular questions. Additionally, providing references for their decisions and updating their knowledge efficiently remain open research problems.</p><p>A general purpose recipe to address these limitations is RAG, where the LLM's parametric knowledge is grounded with external or non-parametric knowledge from an information retrieval system. This knowledge is passed as additional context in the prompt to the LLM and specific instructions are given to the LLM on how to use this contextual information. This keeps it more inline with the discussion so far about parametric knowledge. The advantages of this approach are:</p><ul><li><p>Unlike fine-tuning and pre-training, LLM parameters do not change and so there are no training costs</p></li><li><p>Expertise required to simple implementation is low (although more advanced strategies exist)</p></li><li><p>Response can be tightly constrained to context returned from the information retrieval system, limiting hallucinations</p></li><li><p>Smaller task specific LLMs can be used - as the LLM is being used for a specific task rather than a knowledge base.</p></li><li><p>Knowledge base is easily updatable as it requires no changes to the LLM</p></li><li><p>Responses can cite sources for human verification and link outs</p></li></ul><p>Strategies to combine this non-parametric knowledge (i.e. retrieved text) with an LLM’s parametric knowledge is an <a href="https://acl2023-retrieval-lm.github.io/slides/3-architecture.pdf">active area of research</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04a78b28c5eab515/6a171201cdacbf9af27d2b12/beec836f570eaf9a6a5945ee869e1d37e89e3190-1014x463.png" alt="" /><p>Some of these approaches involve modifying the LLM in conjunction with the retrieval strategy and so can not be classified as distinctly as the definitions in this blog. We will dive into more details in further blogs.</p><h2>Example</h2><p>In a simple example, we utilised a fine-tuned LLaMA2 13B model, which was based on the information from this <a href="https://towardsdatascience.com/leveraging-qlora-for-fine-tuning-of-task-fine-tuned-models-without-catastrophic-forgetting-d9bcd594cff4">blog</a>. This model underwent fine-tuning using AWS blog posts published after the LLaMA2 pre-training and fine-tuning data cutoff date, specifically those from July 23rd, 2023. We also ingested these documents into a Elasticsearch and established a simple RAG pipeline. In this pipeline, model responses are generated based on the retrieved documents serving as context. Red highlights indicate incorrect responses, and blue highlights correct responses.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04470fdb73579b8d/6a171203839dfac0f5dcfffd/55da35f1cce6f015d38b1e49f16c47d12da95f74-960x540.png" alt="" /><p>However, it's important to note that this is just a single example and does not constitute a comprehensive evaluation of fine-tuning versus RAG, but provides an example of fine-tuning before <a href="https://www.anyscale.com/blog/fine-tuning-is-for-form-not-facts">useful for form, not facts</a>.. We plan to conduct more thorough comparisons in upcoming blogs.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/domain-specific-generative-ai-pre-training-fine-tuning-rag</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/domain-specific-generative-ai-pre-training-fine-tuning-rag</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Steve Dodson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab7c62bf91cd741e/6a1712042b835f6332f4b32f/ab358b8ccb26b9fe4ee4f909fd7cd308e0e182f9-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 22 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Demystifying ChatGPT & LLMs: Different methods for building AI search]]></title>
    <description><![CDATA[Explore the inner workings of ChatGPT and LLMs, and discover three effective approaches for building generative AI search experiences for specific domains.]]></description>
    <content:encoded><![CDATA[<h2>What is ChatGPT?</h2><p>First things first, <a href="https://chat.openai.com/">ChatGPT</a> is awesome! It can help you work more efficiently — from summarizing a 10,000-word document to providing a list of differentiations between competing products, as well as many other tasks.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta90b8f4b4113a42c/6a17120ca929cf7f2dae0afd/52ad8d748c52d89f59b012cb1903af1d6fbdef14-1440x698.png" alt="differences" /><p>ChatGPT is the best known <a href="https://www.elastic.co/what-is/large-language-models">large language model</a> (LLM) based on the transformer architecture. But there are other LLMs that you may have heard of, including BERT (Bidirectional Encoder Representation from Transformer), Bard (Language Model for Dialogue Applications), or LLaMA (LLM Meta AI). LLMs have multiple layers of neural networks that work together to analyze text and predict outputs. They’re trained with a left-to-right or bidirectional transformer that maximizes the probability of following and preceding words in context to figure out what might come next in a sentence. LLMs also have an attention mechanism that allows them to focus selectively on parts of text in order to identify the most relevant sections. For example, <em>Rex is adorable and he is a cat</em>. “He”, in this sentence, refers to “Cat” and “Rex.”</p><h2>Understanding Large Language Models (LLMs)</h2><p>Large Language Models are generally compared by the number of parameters — and bigger is better. The number of parameters is a measure of the size and the complexity of the model. The more parameters a model has, the more data it can process, learn from, and generate. However, having more parameters also means having more computational and memory resource demands. Parameters are learned or updated during the training process by using an optimization algorithm that tries to minimize the error or the loss between the predicted outputs and the actual outputs. By adjusting the parameters, the model can improve its performance and accuracy on a given task or domain.</p><h3>LLMs are expensive to train</h3><p>Modern LLMs have billions of parameters that are trained on trillions of tokens and cost millions of dollars. Training an LLM includes identifying a data set, making sure the data set is large enough for it to perform functions like a human, determining the network layer configurations, using supervised learning to learn the information in the data set, and finally, fine-tuning. Needless to say, retraining LLMs on domain specific data is also very expensive.</p><h2>How does a GPT model work?</h2><p>A Generative pre-trained transformer (GPT) model is a type of neural network that uses the transformer architecture to learn from large amounts of text data. The model has two main components: an encoder and a decoder. The encoder processes the input text and converts it into a sequence of vectors called embeddings that represent the meaning and context of each word/subword in numerics. However, the decoder generates the output text by predicting the next word in the sequence based on the embeddings and the previous words.</p><p>The GPT model uses a technique called “attention” to focus on the most relevant parts of the input and output texts and to capture long-range dependencies and relationships between words. The model is trained by using a large corpus of texts as both the input and the output and by minimizing the difference between the predicted words and the actual words. It can then be fine-tuned or adapted to specific tasks or domains by using smaller and more specialized data sets.</p><h3>Tokens</h3><p>Tokens are the basic units of text or code that an LLM uses to process and generate language. Tokens can be characters, words, subwords, or other segments of text or code, depending on the chosen tokenization method or scheme. They are assigned numerical values or identifiers and are arranged in sequences or vectors, then are fed into or outputted from the model. Tokenization is the process of splitting the input and output texts into smaller units that can be processed by the LLM models.</p><p>For example, the sentence “A quick brown fox jumps over a lazy dog” can be tokenized into the following tokens: “a,” “quick,” “brown,” “fox,” “jumps,” “over,” “a,” “lazy,” and “dog.”</p><h3>Embedding</h3><p><a href="https://www.elastic.co/blog/how-to-deploy-nlp-text-embeddings-and-vector-search">Embeddings</a> are vectors or arrays of numbers that represent the meaning and the context of the tokens that the model processes and generates. They are derived from the parameters of the model and are used to encode and decode the input and output texts. Embeddings help the model to understand the semantic and syntactic relationships between the tokens and to generate more relevant and coherent texts. They are essential components of the transformer architecture that GPT-based models use. They can also vary in size and dimension, depending on the model and the task.</p><p>At a minimum, pre-trained LLMs contain embedding for tens of thousands of words, tokens, and terms. For example, ChatGPT-3 has a vocabulary of 14,735,746 words and a dimension of 1,536. The following is from a small model called distilbert-based-uncased. Despite the fact that this is a small model, it is still 100s mb in size.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt88b7f78278b84915/6a17120dcf4f25b42ab2d281/55c5de07087f92b1a90de551934607458c02c318-740x778.png" alt="token embedding" /><h3>Transformer</h3><p>A transformer model is a neural network that learns context or meaning by tracking relationships in sequential data like the words in this sentence. In its simplest form, a transformer will take an input and predict an output. Within the transformer, there is an encoder stack and a decoder stack.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58397107dde44443/6a17120f66c4f91cfaf8c14f/7d7fcacf75bb5ffa805974f6b100ba4bbc347614-840x800.png" alt="input output" /><p>Let’s dig into the encoder block and the decoder block. In the encoder block, there are two important components: the self-attention neural network and the feed-forward neural network.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4760e4d0298d2dbe/6a171210839dfa61e2dd0001/5a14e5ede835455ead80ec491b70149488bfb662-1060x500.png" alt="neural networks" /><p>The self-attention layer is crucial as it builds in the “understanding” of the current token from the previous words that are relevant to the current one. For example, “it” refers to the chicken in “the chicken crossed the road because it wants to know what the jokes are all about.”</p><p>The other important layer in an encoder is the feed-forward neural network (FFNN). FFNN predicts what word comes after the current token.</p><p>Moving on to the decoder side, the encoder-decoder attention layer stands out. The encoder-decoder layer focuses on relevant parts of the input sentence, taking into account the layer below it and the output of the encoder stack.</p><p>Putting it all together:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt714e963310a2bf78/6a171212a929cf8c05ae0b01/b8ee600d6538ba2bcd2a0ebe3f43df3d6f45e557-1440x792.png" alt="An abstract look at the transformer model" /><p>We will take an input, tokenize the input, and obtain token IDs of the tokens before converting them into embeddings for each token. From there, we will pass the embeddings into a transformer block. At the end of the process, the transformer will predict a series of output tokens. The following image provides a detailed look at the transformer model.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ead6cabb4690bb0/6a17121367045bdda145c309/176cc78a831751e61fd809dbde671b4d1ef30f0b-789x421.png" alt="second transformer model" /><p>The decoder stack outputs a vector of floats. The linear layer projects the vector of floats produced by the stack of decoders into a larger vector called a logits vector. If the model has a 10,000-word vocabulary, then the linear layer maps the decoder output onto a 10,000 cell vector. The softmax layer turns those scores from the logit vector into probabilities — all positive — and adding up to 100%. The cell with the highest probability is chosen, and the word associated with it is produced as the output for this step.</p><h2>The challenges with ChatGPT and LLMs</h2><ul><li><p>They are trained on data that has no domain knowledge and could be out of date. For example, hallucinations are incorrect answers given as if they are correct and are common with LLMs.</p></li><li><p>The models on their own do not have a natural ability to apply or extract filters from the input. Examples include time, date, and geographical filters.</p></li><li><p>There is no access control on what document users can see.</p></li><li><p>There are serious privacy and sensitive data control concerns.</p></li><li><p>It is slow and very expensive to train on your own data and keep it up to date.</p></li><li><p>Response from ChatGPT or other LLMs can be slow. Usually, Elasticsearch would have millisecond query responses. With LLMs, it can take up to seconds to get a response. But this is expected as LLMs are performing complex tasks. Also, ChatGPT charges by the number of token processed. If you have a high velocity workload like Black Friday merchandise search for an ecommerce site, it can get very expensive very quickly. Not to mention, it probably won’t meet &lt;10ms query SLA.</p></li><li><p>It is difficult, if not impossible, to interpret how ChatGPT or other LLMs arrived at query results. Besides hallucinations, ChatGPT and otherLLMs may produce irrelevant responses that are difficult to determine how the model produced the erroneous answer.</p></li></ul><h2>Integration of Elasticsearch with LLMs</h2><p>Elasticsearch supports a bag of words and BM25 information retrieval approach, in addition to vector search through kNN and aNN natively (kNN is the exact nearest neighbor distance of all documents and aNN is the approximation). For aNN, Elasticsearch uses the HNSW (hierarchical navigable small world) algorithm for calculating approximate nearest neighbor distance. Elastic can mitigate many of the problems with LLMs while letting our users take advantage of all the good things ChatGPT and other LLMs can provide.</p><p>Elasticsearch can be used as a <a href="https://www.elastic.co/elasticsearch/vector-database">vector database</a>, and to perform hybrid retrieval across text and vector data. There are three patterns where Elasticsearch can provide clear benefits when used with LLMs:</p><ol><li><p>Provide context to your data and integrate with ChatGPT or other LLMs</p></li><li><p>Enable you to bring your own model (any 3rd party model)</p></li><li><p>Use the built-in Elastic Learned Sparse Encoder model</p></li></ol><h3>Method 1: Provide context to your data and integrate with ChatGPT or other LLMs</h3><p>The following depicts how to separate LLMs from your data while integrating with generative AI.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5f80418923645f5/6a171215961e698110c4d027/f345f66991086bca713c737fba75e183bd6ac83e-1412x752.png" alt="Using Elasticsearch as a vector store and integrate with LLM" /><p>A customer can bring their own embedding generated by a LLM and ingest their data along with the embedding into Elasticsearch. Then, the customer can take the similarity search results from their own data stored in Elasticsearch (context to the user’s question) to ChatGPT or another LLM to construct natural-language based answers to their users.</p><p>Also, the newly released reciprocal rank fusion (RRF) allows users to perform hybrid search, which can combine and rank search results. For instance, the BM25 method can filter for the relevant documents along with vector search to provide the best documents. With RRF, customers can achieve best search results natively through Elasticsearch instead of through their own applications, which greatly reduces complexity and maintenance of their applications.</p><h3>Method 2: Bring your own model</h3><p>The recently announced <a href="https://www.elastic.co/enterprise-search/generative-ai">Elasticsearch Relevance Engine</a></p><p><a href="https://www.elastic.co/enterprise-search/generative-ai">TM</a><a href="https://www.elastic.co/enterprise-search/generative-ai"> (ESRE</a>TM<a href="https://www.elastic.co/enterprise-search/generative-ai">)</a> provides the capability to bring your own LLMs. This capability has been available for a while through machine learning. The Elasticsearch machine learning team has been scaffolding infrastructure for integrating transformer-based models. Starting with the <a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-8-0">8.8 release</a>, you can ingest and query just like you would normally do in Elasticsearch through the search APIs. On top of that, you can use the hybrid search method with RRF, which provides even better relevance. As the models are managed and integrated into Elasticsearch, it reduces operation complexity while achieving the most relevant search results.</p><p></p><p>This approach would require the users to know what model would work well for their use case and a commercial relationship with Elastic.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab30afdfbc480563/6a171216839dfa1873dd0005/78676dcf352fcb63238c82e031cb7e7c33deedad-962x473.png" alt="method 2" /><h3>Method 3: Use the built-in sparse encoder model</h3><p>Elastic Learned Sparse Encoder is the Elastic out-of-the-box language model that outperforms SPLADE (SParse Lexical AnD Expansion Model), which itself is a state-of-the-art model. Elastic Learned Sparse Encoder solves the vocabulary mismatch problem where a document may be relevant to a query but does not contain any terms that appear in the query. An example of the mismatch may be if we ask <em>“how have American corporations have assisted with Covid-19 efforts”</em>, then manufacturers of ventilators may not appear in the query results.</p><p>Elastic Learned Sparse Encoder is accessible just like other search endpoints via the text_expansion query. Elastic Learned Sparse Encoder enables our user to begin the state-of-the-art generative AI search with a click and yield immediate results. Elastic Learned Sparse Encoder is also an Elastic commercial feature.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltebfe651a5fccbddb/6a17121847d49c4a2d2d8b2e/47b63a2a851bc3b72fefd204397e99f1e102c762-1040x400.png" alt="method 3" /><p>Here are some benchmark results using the BEIR benchmark. We used several standardized data sets (horizontal axis) and applied different retrieval metaphors (vertical axis). As you can see, a combination of BM25 and our Learned Sparse Encoder using RRF, returns the best relevance scores. These scores with RRF beat the SPLADE model and our Learned Sparse Encoder model when considered by itself. We published more details on <a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">our blog here</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b33813737f12d7a/6a17121947d49c27462d8b32/152565c3ba0986e8e81ca9e473da66e37e6a3be3-1440x549.png" alt="Elastic Learned Sparse Encoder compared to other popular retrieval methods (source Elasticsearch)" /><h2>Terms and definitions</h2><h3>Neural Network (NN)</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd94a58379948c9ea/6a17121b839dfac075dd0009/d78d42ffba46112457ecf72bdbedb93618c0fbd4-351x349.png" alt="neural networks chart" /><p>Each node is a neuron. Think of each individual node as its own linear regression model, composed of input data, weights, a bias (or threshold), and an output. The math representation may look like:</p><p>∑wixi + bias = w1x1 + w2x2 + w3x3 + bias</p><p>output = f(x) = 1 if ∑w1x1 + b&gt;= 0; 0 if ∑w1x1 + b &lt; 0</p><p>Once an input layer is determined, weights (w) are assigned. These weights help determine the importance of any given variable with larger ones contributing more significantly to the output compared to other inputs. All inputs are then multiplied by their respective weights and then summed. Afterward, the output is passed through an activation function, which determines the output. If that output exceeds a given threshold, it activates the node and passes data to the next layer in the network. This results in the output of one node becoming the input of the next node. This process of passing data from one layer to the next layer is a feed-forward network. This is just one type of NNs.</p><h3>LLM parameters</h3><p><em>Weights</em> are numerical values that define the strength of connections between neurons across different layers in the model. <em>Biases</em> are additional numerical values that are added to the weighted sum of inputs before being passed through an activation function.</p><h3>SPLADE</h3><p><a href="https://arxiv.org/abs/2107.05720">SPLADE</a> is a late interaction model. The idea behind SPLADE models is that using a pre-trained language model like BERT can identify connections between words and use the knowledge to enhance sparse vector embedding. You would use this when you have a document that covers a wide range of topics, such as a Wikipedia article about a WWII movie — it contains the plot, the actors, the history, and the studio that released the film.</p><p>With embedding retrieval techniques alone, the relevance of the document to queries becomes an issue because the document can be projected onto a large number of dimensions and render it close to none of the queries. SPLADE solves the problem by combining all token-level probability distributions into a single distribution that tells us the relevance of every token in the vocabulary to our input sentence, similar to the BM25 method. Elastic Learned Sparse Encoder is the Elastic version of the SPLADE model.</p><h3>RRF</h3><p>RRF is a hybrid search query that normalizes and combines multiple search result sets with different relevant indicators into a single result set. Based on our own testing, combining RRF (BM25 + Elastic Learned Sparse Encoder) produces the best search relevance.</p><h2>Wrap up</h2><p>By combining the creative capabilities of technologies, such as ChatGPT, and the business context of proprietary data, we can truly transform how customers, employees, and organizations search.</p><p>Retrieval augmented generation (RAG) bridges the gap between large language models that power generative AI and private data sources. Well-known limitations of large language models can be addressed with context-based retrieval, enabling you to build deeply engaging search.</p> <ul><li><p><a href="https://elastic.co/elasticsearch/vector-database">Use Elasticsearch as your vector database</a></p></li><li><p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT and Elasticsearch: OpenAI meets private data</a></p></li><li><p><a href="https://www.elastic.co/enterprise-search/generative-ai">Generative AI search tools for developers</a></p></li></ul><p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p><p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/demystifying-chatgpt-methods-building-ai-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/demystifying-chatgpt-methods-building-ai-search</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Sherry Ger]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt537d387cb7738d7f/6a17121dcdacbf5b4b7d2b1a/2356f6d43bbd976157addf019223b1a424cd6093-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 28 Jul 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Generative AI using Elastic and Amazon SageMaker JumpStart]]></title>
    <description><![CDATA[Learn how to build a generative artificial intelligence (GAI) solution with Amazon SageMaker JumpStart, Elastic, and Hugging Face open source LLMs using the sample implementation provided in this post and a data set relevant to your business.]]></description>
    <content:encoded><![CDATA[<p>In the rapidly advancing world of artificial intelligence, one of the most intriguing and transformative developments is <a href="https://www.elastic.co/what-is/generative-ai">generative artificial intelligence (GAI)</a>. GAI represents a significant leap forward in AI capabilities, enabling machines to generate original and creative content across various domains including conversations, stories, images, videos, and music. Enterprises seek not only top-performing infrastructure, but also a secure platform to harness the power of GAI without compromising their sensitive data and intellectual property. <a href="https://www.elastic.co/what-is/large-language-models">Large language models (LLMs)</a> strive to understand and produce text that resembles human language, utilizing the structure, meaning, and context of natural language.</p><p>Elastic and Amazon Web Services (AWS) understand this pressing need and have taken the lead in offering cutting-edge solutions to meet these demands. Using Amazon SageMaker JumpStart combined with Elasticsearch’s capabilities, businesses can now confidently explore and adopt the most suitable AI models for their specific use cases while maintaining cost-effectiveness, security, and <a href="https://www.elastic.co/blog/privacy-first-ai-search-langchain-elasticsearch">privacy</a>.</p><p>Elasticsearch’s integration with advanced AI models further enhances its capabilities. By leveraging Elasticsearch’s <a href="https://www.elastic.co/enterprise-search/generative-ai">retrieval prowess</a>, LLMs can access the most relevant documents to provide accurate and up-to-date responses. This synergy between Elasticsearch and LLMs ensures that users receive contextually relevant and factual answers to their queries, setting a new standard for information retrieval and AI-powered assistance.</p><p>Elasticsearch is a scalable data store and vector database that offers a range of features to ensure exceptional search performance. It supports traditional keyword and text-based search using the BM25 algorithm, as well as AI-ready <a href="https://www.elastic.co/elasticsearch/vector-database">vector search</a> with exact match and approximate kNN (k-Nearest Neighbor) search capabilities. These advanced features allow Elasticsearch to retrieve highly relevant results for queries expressed in natural language. By combining traditional, vector, or hybrid search approaches, Elasticsearch delivers precise results, making it effortless for users to find the information customers need.</p><p>The Elasticsearch platform seamlessly incorporates robust machine learning and artificial intelligence capabilities directly into its solutions, empowering you to create highly sought-after applications and accomplish tasks with remarkable efficiency. By leveraging these advanced technologies, you can harness the full potential of Elasticsearch to deliver exceptional user experiences and expedite your workflow.</p><h2>Implementing RAG using Elasticsearch and open source LLM available in Amazon SageMaker JumpStart</h2><p>The solution below explains how to use Retrieval Augmented Generation (RAG) to enable GAI capabilities on domain-specific business data using Elasticsearch, Amazon SageMaker JumpStart, and your choice of open source LLMs.</p><h3>Solution overview</h3><p>We will start by reviewing the architecture diagram below. It explains how to get domain-specific responses from an LLM hosted in Amazon SageMaker JumpStart using enterprise data hosted in Elasticsearch using RAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt03d8da21895b6412/6a17f5455772623f5c1bcd54/f4cd684ae24b76ee5d4395394f30e0d22ce5a20a-1440x745.png" alt="Figure 1. RAG Architecture using Elasticsearch and Amazon SageMaker" /><p>We used the following AWS and third-party services:</p><ol><li><p><a href="https://aws.amazon.com/pm/sagemaker">Amazon SageMaker</a> and <a href="https://aws.amazon.com/sagemaker/jumpstart/">Amazon SageMaker JumpStart</a> for hosting the open source LLMs from Hugging Face</p></li><li><p>Falcon 40B Instruct and Flan-T5 XL LLM from Hugging Face</p></li><li><p>Elasticsearch for storing embeddings of the enterprise knowledge corpus and doing similarity search with user questions</p></li><li><p>Python, <a href="https://python.langchain.com/v0.1/docs/get_started/introduction/">LangChain</a>, and <a href="https://streamlit.io/">Streamlit</a> for building the RAG application</p></li><li><p>Amazon EC2 for hosting the Streamlit application</p></li><li><p><a href="https://aws.amazon.com/iam/">AWS Identity and Access Management</a> roles and policies for access management</p></li></ol><h3>Step-by-step explanation</h3><p><strong>Offline data ingestion:</strong></p><p>We ingest data from an enterprise knowledge corpus – for example this could be internal web pages, documents describing a company’s process, or corporate financial data.</p><ol><li><p>The documents are ingested using a web crawler or any other ingestion mechanism.</p></li><li><p>The textual content is converted into vectors and stored in a dense_vector field by a sentence transformer type ML model.</p></li></ol><p><strong>Real-time flow on user query:</strong></p><ol><li><p>The user provides a question via the Retrieval Augmented Generation (RAG) web application.</p></li><li><p>The RAG application generates a hybrid search request for Elasticsearch based on the user's question and sends it to Elasticsearch. The hybrid search request does a BM25 match on the text field and kNN search on the dense_vector field.</p></li><li><p>Elasticsearch returns the document body and source URL (if applicable) to the RAG application. The RAG application accepts only the top scored document.</p></li><li><p>The RAG application passes the top scored document body (context) along with user question (prompt) to the LLM hosted as Amazon SageMaker endpoint.</p></li><li><p>The Amazon SageMaker endpoint generates a textual response and sends it back to the RAG application.</p></li><li><p>The RAG application performs any required post processing tasks. For example, it adds a source url to the response generated from the LLM. The user views the response in the web application.</p></li></ol><p>Let’s now look at a few setup steps and a few implementation steps to create a working search solution:</p><p><strong>Setup steps:</strong></p><ol><li><p><strong>Sign up</strong> for a free trial of an Elasticsearch cluster with <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud</a>.</p></li><li><p><strong>Create a new deployment</strong> on AWS following <a href="https://www.elastic.co/guide/en/cloud/current/ec-create-deployment.html">these steps</a>.</p></li><li><p><strong>Add a new machine learning node</strong> following the steps below. This will enable you to run machine learning models in your deployment.</p></li><li><p>Click on <strong>Edit</strong> under Deployment Name in the left navigation bar.</p></li><li><p>Scroll down to the Machine Learning instances box.</p></li><li><p>Click <strong>+Add Capacity</strong>.</p></li><li><p>Under Size per zone, click and select <strong>2GB RAM</strong>.</p></li><li><p>Click on <strong>Save</strong> and then <strong>Confirm</strong>.</p></li><li><p><strong>Reset and download the elastic user password</strong> following these <a href="https://www.elastic.co/guide/en/cloud/current/ec-password-reset.html">steps</a>.</p></li><li><p><strong>Copy the deployment ID</strong> from the Overview page under Deployment name.</p></li><li><p><strong>Load an embedding model into Elasticsearch.</strong> Here, we have used<a href="https://huggingface.co/sentence-transformers/all-distilroberta-v1"><strong>all-distilroberta-v1</strong></a> model hosted in the Hugging Face model hub. You can choose other sentence transformer types based on your case. Import this Python <strong>notebook</strong> <a href="https://github.com/Udayel/RAGElastic-LLM">here</a> in Amazon SageMaker and run it. Provide the <strong>Cloud Id</strong> , <strong>Elasticsearch username</strong> , and <strong>Elasticsearch password</strong> when prompted. This will download the model from Hugging Face, chunk it up, load it into Elasticsearch, and deploy the model onto the machine learning node of the Elasticsearch cluster.</p></li><li><p><strong>Create an Elasticsearch index</strong> by opening Kibana from the Elastic Cloud console and navigating to Enterprise Search - Overview. Click on <strong>Create an Elasticsearch Index</strong>. Choose <strong>Web Crawler</strong> as the Ingestion method. Enter a suitable Index name and click <strong>Create Index</strong>.</p></li><li><p><strong>Add an Inference Pipeline</strong> by clicking on <strong>Pipelines tab &gt; Copy</strong> and customizing it in the Ingest Pipeline Box. Click <strong>Add Inference Pipeline</strong> in the Machine Learning Inference Pipelines box. Enter the Name for the new pipeline. Select the trained Model loaded in step 6 and Select title as source field. Click <strong>Continue</strong> in two subsequent screens and click <strong>Create Pipeline</strong> at the Review stage.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99f3fcd7de79819d/6a17f5471d1b834f2e93e59c/0aa8612be25d206f962fe81c9bff08769cad6b9f-1440x779.png" alt="Figure 2. Adding Inference Pipeline in Elasticsearch" /><ol><li><p><strong>Update the mapping for dense vector</strong> by clicking on <strong>Dev Tools</strong> and running the following code. This will enable you to run kNN search on the title field vectors. From Elasticsearch version 8.8+, this step will be handled automatically.</p></li></ol>POST search-elastic-docs/_mapping
{
 "properties": {
   "title-vector": {
     "type": "dense_vector",
     "dims": 768,
     "index": true,
     "similarity": "dot_product"
   }
 }
}
<ol><li><p><strong>Configure web crawler</strong> to crawl Elastic Docs (you can replace this with your Enterprise Domain corpus). Click on the relevant index under Available indices. Click on the <strong>Manage Domains</strong> tab. Click <strong>Add domain</strong>. Enter <a href="https://www.elastic.co/guide/en"><strong>https://www.elastic.co/guide/en</strong></a> and click <strong>Validate Domain</strong>. Click <strong>Add domain</strong> and then <strong>Add Crawl rules</strong>. Add the following rules. Click <strong>Crawl</strong> and then <strong>Crawl all domains on this index</strong>. This will start Elasticsearch’s web crawler and it will crawl the targeted documents, generate vectors for the title field, and index the document and vector.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbdf843267a3291d/6a17f548a2929920f6d02dd7/51d6e71a3562a1f1e07d64ab40e192e9c0342a09-1440x834.png" alt="Figure 3. Adding Web Crawling rules in Elasticsearch" /><p>The <strong>implementation steps</strong> for instantiating the solution presented in this post are as follows:</p><ol><li><p><strong>Choose your LLM.</strong> Amazon SageMaker JumpStart offers a wide selection of proprietary and publicly available foundation models from various model providers. Log in to Amazon SageMaker Studio, open Amazon SageMaker JumpStart, and search for your preferred Foundation model. Please find the list of models available for each task <a href="https://aws.amazon.com/sagemaker/jumpstart/?sagemaker-data-wrangler-whats-new.sort-by=item.additionalFields.postDateTime&amp;sagemaker-data-wrangler-whats-new.sort-order=desc">here</a>.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt453df8cf0664bb04/6a17f54afbc5f8222a491c1f/86ed5cadd37078d41237502fc1cc3438446ada6c-1440x624.png" alt="Figure 4. LLMs in Amazon SageMaker JumpStart" /><ol><li><p><strong>Deploy your LLM.</strong> Amazon SageMaker JumpStart studio also provides a no-code interface to deploy the model. You can easily deploy a model with few clicks. After the deployment is successful, copy the Endpoint Name.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c2b995cb1db4810/6a17f54b6df731cb540a1073/d163b7593a40d7cff7170dd4aba3082227aea73b-1440x627.png" alt="Figure 5. Deploying LLMs using Amazon SageMaker JumpStart Console" /><ol><li><p><strong>Download and set up the RAG Application.</strong> Launch an EC2 instance and clone the code from this <a href="https://github.com/Udayel/RAGElastic-LLM">GitHub link</a>. Set up a virtual environment following <a href="https://docs.python.org/3/library/venv.html">these steps</a>. Install the required Python libraries by running the command pip install -r requirements.txt. Update the config.sh file with the following:</p></li><li><p>ES_CLOUD_ID: Elastic Cloud Deployment ID</p></li><li><p>ES_USERNAME: Elasticsearch Cluster User</p></li><li><p>ES_PASSWORD: Elasticsearch User password</p></li><li><p>FLAN_T5_ENDPOINT: Amazon SageMaker Endpoint Name pointing to Flan T5</p></li><li><p>FALCON_40B_ENDPOINT: Amazon SageMaker Endpoint Name pointing to Falcon 40B</p></li><li><p>AWS_REGION: AWS Region</p></li><li><p><strong>Run the application</strong> using the command streamlit run rag_elastic_aws.py. This will start a web browser and the url will be printed to the command line.</p></li><li><p><strong>Response of LLM without context.</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt645d15fec3945281/6a17f54daf47b60bf0cde0ff/633edd0c512231b96badd2586aafec5d9071fb3a-1440x627.png" alt="Figure 6. Sample response of LLM without context" /><ol><li><p><strong>Response of LLM with context</strong> derived from Elasticsearch.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee94315b36674a22/6a17f54ebe6086573b0048f9/6c326977f60b70c4711429abf0a5e82f36713402-1440x630.png" alt="Figure 7. Sample response of LLM with domain-specific context" /><h2>Conclusion</h2><p>In this post, we showed you how to create a Retrieval Augmented Generation-based search application using a combination of Elasticsearch, Amazon SageMaker JumpStart, open-source LLMs from Hugging Face, and open source Python packages like LangChain and Streamlit.</p><p>Learn more by exploring <a href="https://aws.amazon.com/sagemaker/jumpstart/">JumpStart</a>, <a href="https://aws.amazon.com/bedrock/titan/">Amazon Titan</a> models, <a href="https://aws.amazon.com/bedrock/">Amazon Bedrock</a>, and <a href="https://www.elastic.co/">Elastic</a> to build a solution using the sample implementation provided in this post and a data set relevant to your business.</p><p>Or, start your own <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k?trk=5fbc596b-6d2a-433a-8333-0bd1f28e84da&amp;sc_channel=el">7-day free trial</a> by signing up via <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k?trk=d54b31eb-671c-49ba-88bb-7a1106421dfa%E2%89%BBchannel=el">AWS Marketplace</a> and quickly spin up a deployment in minutes on any of the <a href="https://www.elastic.co/guide/en/cloud/current/ec-reference-regions.html#ec_amazon_web_services_aws_regions">Elastic Cloud regions on AWS</a> around the world. Your AWS Marketplace purchase of Elastic will be included in your monthly consolidated billing statement and will draw against your committed spend with AWS.</p><p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p><p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/generative-ai-using-elastic-amazon-sagemaker-jumpstart</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/generative-ai-using-elastic-amazon-sagemaker-jumpstart</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Udayasimha Theepireddy,Ayan Ray]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9f7119562f10d5a/6a17f5507f6f151debc09ca3/f10a706b66a83e9df451ece25326cbcd10134e3c-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 25 Jul 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ChatGPT and Elasticsearch: A plugin to use ChatGPT with your Elastic data]]></title>
    <description><![CDATA[Learn how to implement a plugin and enable ChatGPT users to extend ChatGPT with any content indexed in Elasticsearch, using the Elastic documentation.]]></description>
    <content:encoded><![CDATA[<p>Update: April 16th, 2024</p><p>OpenAI has discontinued the use of plugins in ChatGPT. You can read more about this <a href="https://help.openai.com/en/articles/8988022-winding-down-the-chatgpt-plugins-beta">here</a>. We recommend reading <a href="https://www.elastic.co/search-labs/tutorials/chatbot-tutorial/welcome">this</a> tutorial instead to learn how to build a large language model (LLM) chatbot that uses a pattern known as <a href="https://www.elastic.co/what-is/retrieval-augmented-generation">Retrieval-Augmented Generation</a>. You can also read <a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-creating-custom-gpts-with-elastic-data">this</a> blog to learn how to create custom GPTs with Elastic data.</p><p>You may have read this <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">previous blog post</a> about our journey to connect Elasticsearch’s relevance capabilities with OpenAI question-answering capabilities. The key idea in that post was to illustrate how to use Elastic with OpenAI’s GPT model to build a response and return context-relevant content to users.</p><p>The application that we built can expose a search endpoint and be called by any front-end service. The good news is that now OpenAI has released a private alpha of the future <a href="https://openai.com/blog/chatgpt-plugins">ChatGPT plugin framework</a>.</p><p>In this blog, you will learn how to implement the plugin and extend the use of ChatGPT to any content indexed in Elasticsearch, using the Elastic documentation.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c29f4ce816ee08d/6a1711cb66c4f932b5f8c143/67a68ec5eee1b81462e0adeef41d5963054ec65e-1440x1239.png" alt="summarize transaction sampling" /><h2>What is a ChatGPT plugin?</h2><p><a href="https://openai.com/blog/chatgpt-plugins">ChatGPT plugins</a> are extensions that are developed to assist the model in completing its knowledge or executing actions.</p><p>For example, we know that the cutover of ChatGPT from a knowledge perspective is September 2021, so any question on recent data won’t be answered. In addition, any question that relates to something too specific beyond the boundaries of what the model has been trained on won’t be answered.</p><p>Plugins can broaden the scope of possible applications and enhance the capabilities of the models, but reciprocally, the plugin's output is augmented by the model itself.</p><p>The official list of plugins currently supported by ChatGPT are listed below. You can expect this list to expand rapidly as more organizations experiment with ChatGPT:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c7f4f71fefa8bb/6a1711ccb339d5202c76a0ee/34e746016e23a8a8b8fded4ecfcf34b6fcaba039-1440x583.png" alt="chatgpt plugins list" /><p>As you scan through the list, you’ll notice that the use cases are slowly revealing themselves here. In the case of Expedia, for example, its plugin is extending ChatGPT to assist in planning travel, making ChatGPT a trip-planning assistant.</p><p>This blog aims to achieve similar objectives for Elastic — to allow ChatGPT to access Elastic’s current knowledge base and assist you with your Elastic projects.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda2a330ddbd80a0d/6a1711cea6c2b981bce7980e/226eacfcaa5c0f2e3d42f7381e360e81a1d52433-656x634.png" alt="plugin store" /><h2>Architecture</h2><p>We are going to bring a slight modification that has a positive cost impact in the sample code presented in <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">part 1</a> by my colleague <a href="https://www.elastic.co/blog/author/jeff-vestal">Jeff Vestal</a>.</p><p>We will remove the call to OpenAI API, as now ChatGPT will fulfill the role of taking the content from Elasticsearch and digesting it back to the user:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762330bba798a8b8/6a1711d0839dfa22c0dcfff5/fac71d9933fdd297308bc54ebc471108ef9a4b07-1440x900.png" alt="elastic chatgpt diagram" /><ol><li><p>ChatGPT makes a call to the <code>/search</code> endpoint of the plugin.</p></li></ol><ul><li><p>This decision is based on the plugin “rules” <code>description_for_human</code> (see plugin-manifest below).</p></li></ul><ol><li><p>The plugin code creates a search request that is sent to Elasticsearch.</p></li><li><p>Documentation body and original url are returned to Python.</p></li><li><p>The plugin returns the document body and url, in text form to ChatGPT.</p></li><li><p>ChatGPT uses the information from the plugin to craft its response.</p></li></ol><p>Again, this blog post assumes that you have set up your <a href="https://www.elastic.co/cloud">Elastic Cloud</a> account, <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data#eland">vectorized your content</a>, and have an Elasticsearch cluster filled with data ready to be used. If you haven’t set all that up, see <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">our previous post</a> for detailed steps to follow.</p><h2>Plugin code</h2><p>OpenAI built a fairly simple-to-handle plugin framework for ChatGPT. It deploys a service that exposes:</p><ul><li><p>The plugin manifest, explaining what the plugin provides to the users <em>and</em> to ChatGPT</p></li><li><p>The plugin openAPI definition, which is the functional description that enables ChatGPT to understand the available APIs The plugin code can be <a href="https://github.com/elastic/ElasticGPT_Plugin/">found here</a>.</p></li></ul><h3>Plugin file structure</h3><p>The screenshot below shows what the structure looks like:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae1c75244b991b74/6a1711d1ab7f0895addb9fb5/b01242a370046e6bf0bab96edb2366b2fa1f22bf-728x436.png" alt="elasticgpt doc plugin" /><ul><li><p>The plugin manifest is stored in the ai-plugin.json file under the .well-known directory as per OpenAI best practices.</p></li><li><p>The main service code is in app.py.</p></li><li><p>The Dockerfile will be later used to deploy the plugin to Google Cloud Compute.</p></li><li><p>The plugin’s logo (logo.ong) as displayed in the ChatGPT plugin store, here the Elastic logo.</p></li><li><p>The OpenAI description of the plugin.</p></li></ul><h3>Python code</h3><p>For the full code, refer to the <a href="https://github.com/elastic/ElasticGPT_Plugin/">GitHub repository</a>. We are going to look only at the main part of this code:</p>…
@app.get("/search")
…
@app.get("/logo.png")
…
@app.get("/.well-known/ai-plugin.json")
…
@app.get("/openapi.yaml")
…
<p>We took out all the details and kept the main parts here. There are two categories of APIs here:</p><ol><li><p>The one required by OpenAI to build a plugin:</p></li></ol><ul><li><p>/logo.png: retrieve the plugin logo</p></li><li><p>/.well-known/ai-plugin.json: fetches the plugin manifest</p></li><li><p>/openapi.yaml: fetches the plugin OpenAPI description</p></li></ul><ol><li><p>The plugin API:</p></li></ol><ul><li><p>/search is the only one here exposed to ChatGPT that runs the search in Elasticsearch</p></li></ul><h3>Plugin manifest</h3><p>The plugin manifest is what ChatGPT will use to validate the existence (reachable) of the plugin. The definition is the below:</p>{
   "schema_version": "v1",
   "name_for_human": "ElasticGPTDoc_Plugin",
   "name_for_model": "ElasticGPTDoc_Plugin",
   "description_for_human": "Elastic Assistant, you know, for knowledge",
   "description_for_model": "Get most recent elasticsearch docs post 2021 release, anything after release 7.15",
   "auth": {
     "type": "none"
   },
   "api": {
     "type": "openapi",
     "url": "PLUGIN_HOSTNAME/openapi.yaml",
     "is_user_authenticated": false
   },
   "logo_url": "PLUGIN_HOSTNAME/logo.png",
   "contact_email": "info@elastic.co",
   "legal_info_url": "http://www.example.com/legal"
 }
<p>There are a couple of things to point out here:</p><ol><li><p>There are two descriptions:</p></li></ol><ul><li><p>description_for_human - This is what the human sees when installing the plugin in the ChatGPT web UI.</p></li><li><p>description_for_model - Instructions for the model to understand when to use the plugin.</p></li></ul><ol><li><p>There are some placeholders such as PLUGIN_HOSTNAME that are replaced in the Python code.</p></li></ol><h3>OpenAPI definition</h3><p>Our code will only expose a single API endpoint to ChatGPT allowing it to search for Elastic documentation. Here is the description:</p>openapi: 3.0.1
info:
 title: ElasticDocGPT
 description: Retrieve information front the most recent Elastic documentation
 version: 'v1'
servers:
 - url: PLUGIN_HOSTNAME
paths:
 /search:
   get:
     operationId: search
     summary: retrieves the document matching the query
     parameters:
     - in: query
       name: query
       schema:
           type: string
       description: use to filter relevant part of the elasticsearch documentations
     responses:
       "200":
         description: OK


<p>For the definition file, the key points are:</p><ul><li><p>We take the ChatGPT prompt content and pass it as a query to our Elasticsearch cluster.</p></li><li><p>Some placeholders such as PLUGIN_HOSTNAME are replaced in the Python code.</p></li></ul><h2>Deploying the Elastic plugin in Google Cloud Platform (GCP)</h2><p>You have a choice in picking a deployment method to expose your plugin, as well as using a different cloud provider. We use GCP in this blog post — more specifically Google Cloud Run and Google Cloud Build. The first is to expose and run the service, and the second is for continuous integration.</p><h2>Setup</h2><p>This setup assumes your GCP user has the right permissions to:</p><ul><li><p>Build a container image with Google Cloud Build in the Google Container Registry</p></li><li><p>Deploy a container in Google Cloud Run</p></li></ul><p>If not, you will need to update permissions on the <a href="https://console.cloud.google.com/iam-admin/iam">GCP IAM page</a>.</p><p>We are going to use the gcloud CLI to set up our environment. You can find the installation instructions <a href="https://cloud.google.com/sdk/docs/install">here</a>.</p><p>Once installed, run the following command to authenticate:</p>  gcloud auth
<p>Then set the project identifier to your GCP project:</p>
  gcloud config set project PROJECT_ID

<p>You are now ready to build and deploy.</p><h3>Build and deploy</h3><p>The first step is to build the container image using Cloud Build and push it to the Google Container Registry:</p>  gcloud builds submit --tag gcr.io/PROJECT_ID/my-python-app
<p>Replace PROJECT_ID with your GCP project ID and my-python-app with the name you want to give to your container image.</p><p>Export the environment required by the Python code to create the Elasticsearch client:</p>
  export YOUR_CLOUD_ID=VALUE
  export YOUR_CLOUD_PASS=VALUE
  export YOUR_CLOUD_USER=VALUE

<p>Finally, deploy the container image to Cloud Run:</p>
  gcloud run deploy my-python-app \
  --image gcr.io/PROJECT_ID/my-python-app \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars  cloud_id=YOUR_CLOUD_ID,cloud_pass=YOUR_CLOUD_PASS,cloud_user=YOUR_CLOUD_USER

<p>You should see your service running in Cloud Run:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e48a26348ecc676/6a1711d3e8fbcefcfc39fd6d/b8c3ab3e7208e2e8f05ed101fc6fe9ba7582c649-654x424.png" alt="cloud run services" /><p>Note that you can also activate the continuous integration so that any commit in your GitHub repository will trigger a redeploy. On the service details page, click on <strong>Set up continuous deployment</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd5f75018e65a4bbe/6a1711d50e2e4920ca41a25c/954c1c27fc8fc7d5198f18dc727ab9df1a953a9d-538x102.png" alt="" /><h2>Installing the plugin in ChatGPT</h2><p>Once the plugin is deployed and has a publicly accessible endpoint, it can be installed in ChatGPT. In our case, since this is deployed in Google Cloud Run, you can get the URL here:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ea1f2fc9b4dba32/6a1711d6acf0880435be9c6b/db3b12bf18c8cf15435a32ffeaf731ef6082e3bf-1404x108.png" alt="elastic doc gpt" /><p>Then in <a href="https://chat.openai.com/chat">ChatGPT</a>, go in the plugin store:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec39261316cb081c/6a1711d8964cea07f808bcd9/3783c95bda4592b93f202ac5bdb498f9a3f04c6a-1440x361.png" alt="plugins alpha" /><p>Choose to do “Develop your own plugin”:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaa7f16957d702a99/6a1711d9a292997e25d01136/77d07ff08f573ac1f8c07468d135cc373a4b94a6-1440x607.png" alt="develop your own plugin" /><p>Paste the URL you copied from the Google Cloud Run page:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe72a85a4f8b3a30/6a1711db6234e09cd2db1b00/d872c6577df2463558d93a39ebe6ca6197934cf4-1072x604.png" alt="enter your website domain" /><p>Ensure the plugin is found and valid:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71c1ff30970d5310/6a1711dcd7c0227595de65ca/6ac18505045bedf44511b364aae4934fe80d33a7-1034x568.png" alt="found plugin" /><p>Follow the installation instructions until you see your plugin available in the list:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec91c3047e89994a/6a1711de4a531b2e2836aa93/7ce491c6d2b096fa917cb50ff8fe805d6d23431d-1252x398.png" alt="plugins alpha elastic" /><h2>Let’s test our plugin!</h2><p>OK, now for the best part! Do remember that ChatGPT decides to delegate when your prompt goes beyond its knowledge. To ensure that happens, just ask a question similar to this example:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32ecc342b659ffe8/6a1711e00c48570cd001abaa/5fbff0d0197f493e341658291f8cbc154a2dfb8a-1440x1292.png" alt="highlights of latest elastic release" /><p>With the steps provided in this blog, you can create your own plugin and deploy it on a cloud provider or your own hosts. This allows you to start exploring enhancing ChatGPT's knowledge and functionality, enhancing an already amazing tool with specialized and proprietary knowledge.</p><p>You can try all of the capabilities discussed in this blog today! Get started by signing up for a <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">free Elastic Cloud trial</a>.</p><p>Here are some other blogs you may find interesting:</p><ul><li><p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT and Elasticsearch: OpenAI meets private data</a></p></li><li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Monitor OpenAI API and GPT models with OpenTelemetry and Elastic</a></p></li><li><p><a href="https://www.elastic.co/security-labs/exploring-applications-of-chatgpt-to-improve-detection-response-and-understanding">Exploring the Future of Security with ChatGPT</a></p></li></ul><p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-plugin-elastic-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-plugin-elastic-data</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Baha Azarmi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltafa5e250e50af311/6a1711e10e2e49950841a262/b42ad0b8550fc9ee532c0d93d2587aecdaf5dd5a-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ChatGPT and Elasticsearch: OpenAI meets private data]]></title>
    <description><![CDATA[Integrate Elasticsearch's search relevance with ChatGPT's question-answering capability to enhance your domain-specific knowledge base.]]></description>
    <content:encoded><![CDATA[<p><strong>NOTE: This blog has been revisited with an update incorporating new features Elastic has released since this was first published. </strong><a href="https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-rag-enhancements"><strong>Please check out the new blog here!</strong></a></p><p>Combine Elasticsearch's search relevance with OpenAI's ChatGPT's question-answering capabilities to query your data. In this blog, you'll learn how to connect ChatGPT to proprietary data stores using Elasticsearch and build question/answer capabilities for your data.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt01bcddf80e3722d2/6a1711a3cdacbf135a7d2afc/ffbdc3b88620a1f53af18480929c6978d2fcaa44-1440x1187.png" alt="elasticdocs gpt list the steps free trial" /><h2>What is ChatGPT?</h2><p>In recent months, there has been a surge of excitement around ChatGPT, a groundbreaking AI model created by OpenAI. But what exactly is ChatGPT?</p><p>Based on the powerful GPT architecture, ChatGPT is designed to understand and generate human-like responses to text inputs. GPT stands for "Generative Pre-trained Transformer.” The Transformer is a cutting-edge model architecture that has revolutionized the field of natural language processing (NLP). These models are pre-trained on vast amounts of data and are capable of understanding context, generating relevant responses, and even carrying on a conversation. To learn more about the history of transformer models and some NLP basics in the Elastic Stack, be sure to check out the great <a href="https://www.youtube.com/watch?v=SvvbMCwyOnU">talk by Elastic ML Engineer Josh Devins</a>.</p><p>The primary goal of ChatGPT is to facilitate meaningful and engaging interactions between humans and machines. By leveraging the recent advancements in NLP, ChatGPT models can provide a wide range of applications, from chatbots and virtual assistants to content generation, code completion, and much more. These AI-powered tools have rapidly become an invaluable resource in countless industries, helping businesses streamline their processes and enhance their services.</p><h2>Limitations of ChatGPT &amp; how to minimize them</h2><p>Despite the incredible potential of ChatGPT, there are certain limitations that users should be aware of. One notable constraint is the knowledge cutoff date. Currently, ChatGPT is trained on data up to September 2021, meaning it is unaware of events, developments, or changes that have occurred since then. Consequently, users should keep this limitation in mind while relying on ChatGPT for up-to-date information. This can lead to outdated or incorrect responses when discussing rapidly changing areas of knowledge such as software enhancements and capabilities or even world events.</p><p>ChatGPT, while an impressive AI language model, can occasionally hallucinate in its responses, often exacerbated when it lacks access to relevant information. This overconfidence can result in incorrect answers or misleading information being provided to users. It is important to be aware of this limitation and approach the responses generated by ChatGPT with a degree of skepticism, cross-checking and verifying the information when necessary to ensure accuracy and reliability.</p><p>Another limitation of ChatGPT is its lack of knowledge about domain-specific content. While it can generate coherent and contextually relevant responses based on the information it has been trained on, it is unable to access domain-specific data or provide personalized answers that depend on a user's unique knowledge base. For instance, it may not be able to provide insights into an organization’s proprietary software or internal documentation. Users should, therefore, exercise caution when seeking advice or answers on such topics from ChatGPT directly.</p><p>One way to minimize these limitations is by providing ChatGPT access to specific documents relevant to your domain and questions, and enabling ChatGPT’s language understanding capabilities to generate tailored responses.</p><p>This can be accomplished by connecting ChatGPT to a search engine like Elasticsearch.</p><h2>Elasticsearch — you know, for search!</h2><p>Elasticsearch is a scalable data store and vector database designed to deliver relevant document retrieval, ensuring that users can access the information they need quickly and accurately. Elasticsearch’s primary focus is on delivering the most relevant results to users, streamlining the search process, and enhancing user experience.</p><p>Elasticsearch boasts a myriad of features to ensure top-notch search performance, including support for traditional keyword and text-based search (<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-similarity.html">BM25</a>) and an AI-ready vector search with exact match and approximate kNN (<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k-Nearest Neighbor</a>) search capabilities. These advanced features allow Elasticsearch to retrieve results that are not only relevant but also for queries that have been expressed using natural language. By leveraging traditional, vector, or hybrid search (BM25 + kNN), Elasticsearch can deliver results with unparalleled precision, helping users find the information they need with ease.</p><p>One of the key strengths of Elasticsearch is its robust API, which enables seamless integration with other services to extend and enhance its capabilities. By integrating Elasticsearch with various third-party tools and platforms, users can create powerful and customized search solutions tailored to their specific requirements. This flexibility and extensibility makes Elasticsearch an ideal choice for businesses looking to improve their search capabilities and stay ahead in the competitive digital landscape.</p><p>By working in tandem with advanced AI models like ChatGPT, Elasticsearch can provide the most relevant documents for ChatGPT to use in its response. This synergy between Elasticsearch and ChatGPT ensures that users receive factual, contextually relevant, and up-to-date answers to their queries. In essence, the combination of Elasticsearch's retrieval prowess and ChatGPT's natural language understanding capabilities offers an unparalleled user experience, setting a new standard for information retrieval and AI-powered assistance.</p><h2>How to use ChatGPT with Elasticsearch</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d63e45e8e71c526/6a1711a5961e696f34c4d013/4c3858ece4620036b838131efd4548b844a1c8ae-1440x951.png" alt="use chatgpt with elasticsearch" /><ol><li><p>Python interface accepts user questions.</p></li></ol><p>Generate a hybrid search request for Elasticsearch</p><ul><li><p>BM25 match on the title field</p></li><li><p>kNN search on the title-vector field</p></li><li><p>Boost kNN search results to align scores</p></li><li><p>Set size=1 to return only the top scored document</p></li></ul><ol><li><p>Search request is sent to Elasticsearch.</p></li><li><p>Documentation body and original url are returned to python.</p></li><li><p>API call is made to OpenAI ChatCompletion.</p></li></ol><ul><li><p>Prompt: "answer this question &lt;question&gt; using only this document &lt;body_content from top search result&gt;"</p></li></ul><ol><li><p>Generated response is returned to python.</p></li><li><p>Python adds on original documentation source url to generated response and prints it to the screen for the user.</p></li></ol><p>The ElasticDoc ChatGPT process utilizes a Python interface to accept user questions and generate a hybrid search request for Elasticsearch, combining BM25 and kNN search approaches to find the most relevant document from the Elasticsearch Docs site, now indexed in Elasticsearch. However, you do not have to use hybrid search or even vector search. Elasticsearch provides the flexibility to use whichever search pattern best fits your needs and provides the most relevant results for your specific data sets.</p><p>After retrieving the top result, the program crafts a prompt for OpenAI's ChatCompletion API, instructing it to answer the user's question using only the information from the selected document. This prompt is key to ensuring the ChatGPT model only uses information from the official documentation, lessening the chance of hallucinations.</p><p>Finally, the program presents the API-generated response and a link to the source documentation to the user, offering a seamless and user-friendly experience that integrates front-end interaction, Elasticsearch querying, and OpenAI API usage for efficient question-answering.</p><p>Note that while we are only returning the top-scored document for simplicity, the best practice would be to return multiple documents to provide more context to ChatGPT. The correct answer could be found in more than one documentation page, or if we were generating vectors for the full body text, those larger bodies of text may need to be chunked up and stored across multiple Elasticsearch documents. By leveraging Elasticsearch's ability to search across numerous vector fields in tandem with traditional search methods, you can significantly enhance your top document recall.</p><h2>Technical setup</h2><p>The technical requirements are fairly minimal, but it takes some steps to put all the pieces together. For this example, we will configure the <a href="https://www.elastic.co/web-crawler">Elasticsearch web crawler</a> to ingest the Elastic documentation and generate vectors for the title on ingest. You can follow along to replicate this setup or use your own data. To follow along we will need:</p><ul><li><p>Elasticsearch cluster</p></li><li><p>Eland Python library</p></li><li><p>OpenAI API account</p></li><li><p>Somewhere to run our python frontend and api backend</p></li></ul><h3>Elastic Cloud setup</h3><p>The steps in this section assume you don’t currently have an Elasticsearch cluster running in Elastic Cloud. If you do you, can skip to the next section.</p><p><strong>Sign up</strong> If you don’t already have an Elasticsearch cluster, you can sign up for a free trial with <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic Cloud</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3af069b6efc24a7b/6a1711a647d49c1eb52d8b0a/1e9fcc7281b87db1024bdd52d97050b680cf654d-920x1086.png" alt="start free trial" /><p><strong>Create deployment</strong> After you sign up, you will be prompted to create your first deployment.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b04d11d688b0e04/6a1711a8a292996a4cd01124/9bab0a32b62863103ac57843078b35bae6b3d939-1440x823.png" alt="create first deployment" /><ul><li><p>Create a name for your deployment.</p></li><li><p>You can accept the default cloud provider and region or click Edit Settings and choose another location.</p></li><li><p>Click Create deployment. Shortly a new deployment will be provisioned for you and you will be logged in to Kibana. <strong>Back to the Cloud</strong> We need to do a couple of things back in the Cloud Console before we move on: Click on the Navigation Icon in the upper left and select Manage this deployment.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte4d09a0d03af921d/6a1711a947d49c15562d8b0e/c4064762d0fe4858de9f92018084dd3d654f8a68-277x449.png" alt="manage this deployment" /><p>Add a machine learning node.</p><ul><li><p>Back in the Cloud Console, click on Edit under your Deployment’s name in the left navigation bar.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt570ab096155f4cd7/6a1711aa28671432b593e42e/cdbf2abe7b7f2efe95c083a5800ce5c8edbac5e0-330x252.png" alt="deployments edit monitoring" /><ul><li><p>Scroll down to the Machine Learning instances box and click +Add Capacity.</p></li><li><p>Under Size per zone, click and select 2 GB RAM.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt79a4423f5ab22324/6a1703aeb339d5901a769e85/e30e63a849b2ba1fdcc58c946a5a482db8ac88d0-1432x292.png" alt="machine learning instances" /><ul><li><p>Scroll down and click on Save.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt75bcca2c5fb12ec1/6a1711ac28671421f193e432/1d3efb1b02888e310c47948966aef3a8fd8879a2-556x176.png" alt="save equivalent api request" /><ul><li><p>In the pop-up summarizing the architecture changes, click Confirm.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ed7ef67cc298e2a/6a1711ae14b270b607e3c6eb/04e0da7b1378609ae810fabe2ac84f9917b5a69c-384x152.png" alt="cancel confirm" /><ul><li><p>In a few moments, your deployment will now have the ability to run machine learning models!</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt438d9d1a5474c693/6a1711afb0367dae8e72be2a/c5dcac69868ce404bf986ccf7a6e6429b4ad807c-1440x156.png" alt="change summary" /><p>Reset Elasticsearch Deployment User and password:</p><ul><li><p>Click on Security on the left navigation under your deployment’s name.</p></li><li><p>Click on Reset Password and confirm with Reset. (Note: as this is a new cluster nothing should be using this Elastic password.)</p></li><li><p>Download the newly created password for the “elastic” user. (We will use this to load our model from Hugging Face and in our python program.)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt76c75e99c823c09f/6a1711b17d8d67766970e85c/da1dd29d4b3d61ce79921b0bb1a15629b553e689-912x638.png" alt="save deployment credentials" /><p>Copy the Elasticsearch Deployment Cloud ID.</p><ul><li><p>Click on your Deployment name to go to the overview page.</p></li><li><p>On the right-hand side click the copy icon to copy your Cloud ID. (Save this for use later to connect to the Deployment.)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt352796cac3fbe52b/6a1711b37d8d6725a570e860/f5dcfe766eea84e4a116caf33d8e068119863bba-1440x159.png" alt="applications hardware profile" /><h3>Eland</h3><p>We next need to load an embedding model into Elasticsearch to generate vectors for our blog titles and later for our user’s search questions. We will be using the <a href="https://huggingface.co/sentence-transformers/all-distilroberta-v1">all-distilroberta-v1</a> model trained by SentenceTransformers and hosted on the Hugging Face model hub. This particular model isn’t required for this setup to work. It is good for general use as it was trained on very large data sets covering a wide range of topics. However, with vector search use cases, using a model fine-tuned to your particular data set will usually provide the best relevancy.</p><p>To do this, we will use the <a href="https://github.com/elastic/eland#readme">Eland python library</a> created by Elastic. The library provides a wide range of data science functions, but we will be using it as a bridge to load the model into Elasticsearch from the Hugging Face model hub so it can be deployed on machine learning nodes for inference use.</p><p>Eland can either be run as part of a python script or on the command line. The repo also provides a Docker container for users looking to go that route. Today we will run Eland in a <a href="https://github.com/jeffvestal/ElasticDocs_GPT/blob/main/load_embedding_model.ipynb">small python notebook</a>, which can run in Google’s Colab in the web browser for free.</p><p>Open the <a href="https://github.com/jeffvestal/ElasticDocs_GPT/blob/main/load_embedding_model.ipynb">program link</a> and click the “Open in Colab” button at the top to launch the notebook in colab.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt277c97e73b673f82/6a1711b460084b6b6a3c4680/5a1b9de1ba50f8e48dab6341070194c70b715b61-236x40.png" alt="open in colab" /><p>Set the variable hf_model_id to the model name. This model is set already in the example code but if you want to use a different model or just for future information:</p><ul><li><p>hf_model_id='sentence-transformers/all-distilroberta-v1'</p></li><li><p>Copy model name from Hugging Face. The easiest way to do this is to click the copy icon to the right of the model name.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbf5b198c21da493/6a1711b514b27074d6e3c6ef/d883b5342323b8bfcbf5a2a29014f48750717b25-1212x270.png" alt="hugging face" /><p>Run the cloud auth section, and you will be prompted to enter:</p><ul><li><p>Cloud ID (you can find this in the Elastic Cloud Console)</p></li><li><p>Elasticsearch Username (easiest will be to use the “Elastic” user created when the deployment was created)</p></li><li><p>Elasticsearch User Password</p></li></ul><p>Run the remaining steps.</p><ul><li><p>This will download the model from Hugging face, chunk it up, and load it into Elasticsearch.</p></li><li><p>Deploy (start) the model onto the machine learning node.</p></li></ul><h3>Elasticsearch index and web crawler</h3><p>Next up we will create a new Elasticsearch index to store our Elastic Documentation, configure the web crawler to automatically crawl and index those docs, as well as use an ingest pipeline to generate vectors for the doc titles.</p><strong>Note that you can use your proprietary data for this step, to create a question/answer experience tailored to your domain.</strong><ul><li><p>Open Kibana from the Cloud Console if you don’t already have it open.</p></li><li><p>In Kibana, Navigate to Enterprise Search -&gt; Overview. Click Create an Elasticsearch Index.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc51e9f198d426e7/6a1711b74a531b8d0936aa8d/57ae4a7024863162265b67da3f0419bcd7bd6f62-752x180.png" alt="create an elasticsearch index" /><ul><li><p>Using the Web Crawler as the ingestion method, enter elastic-docs as the index name. Then, click Create Index.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65d0058455a44207/6a1711b8b0367da8aa72be2e/51bd68374c03625dfa1e06cc3170e4173b5fb7b6-1440x474.png" alt="select an ingestion method" /><ul><li><p>Click on the “Pipelines” tab.</p></li><li><p>Click Copy and customize in the Ingest Pipeline Box.</p></li><li><p>Click Add Inference Pipeline in the Machine Learning Inference Pipelines box.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltabf90aa59e3e850f/6a1711ba0c4857d1a001ab9c/e3d85ab9e4b6688dc5ea8614f6b2248394177485-1186x436.png" alt="machine learning inference pipelines" /><ul><li><p>Enter the name elastic-docs_title-vector for the New pipeline.</p></li><li><p>Select the trained ML model you loaded in the Eland step above.</p></li><li><p>Select title as the Source field.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc443155a95f5187b/6a1711bb964cea4f6108bcd5/555170b190ea940418f2bf8ba7d45c41d2589a75-1440x818.png" alt="configure add a new pipeline" /><ul><li><p>Click Continue, then click Continue again at the Test stage.</p></li><li><p>Click Create Pipeline at the Review stage.</p></li></ul><p>Update mapping for dense_vector field. (Note: with Elasticsearch version 8.8+, this step should be automatic.)</p><ul><li><p>In the navigation menu, click on Dev Tools. You may have to click Dismiss on the flyout with documentation if this is your first time opening Dev Tools.</p></li><li><p>In Dev Tools in the Console tab, update the mapping for our dense vector target field with the following code. You simply paste it in the code box and click the little arrow to the right of line 1.</p></li></ul>POST search-elastic-docs/_mapping
{
  "properties": {
    "title-vector": {
      "type": "dense_vector",
      "dims": 768,
      "index": true,
      "similarity": "dot_product"
    }
  }
}
<ul><li><p>You should see the following response on the right half of the screen:</p></li></ul>{
  "acknowledged": true
}
<ul><li><p>This will allow us to run kNN search on the title field vectors later on.</p></li></ul><p>Configure web crawler to crawl Elastic Docs site:</p><ul><li><p>Click on the navigation menu one more time and click on Enterprise Search -&gt; Overview.</p></li><li><p>Under Content, click on Indices.</p></li><li><p>Click on search-elastic-docs under Available indices.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39f78c6403e11420/6a1711bd47d49c67992d8b1a/7b05934f8775f4f602eb258c7bb8c3c1b82bd285-1440x177.png" alt="available indices" /><ul><li><p>Click on the Manage Domains tab.</p></li><li><p>Click “Add domain.”</p></li><li><p>Enter <a href="https://www.elastic.co/guide/en">https://www.elastic.co/guide/en</a>, then click Validate Domain.</p></li><li><p>After the checks run, click Add domain. Then click Crawl rules.</p></li><li><p>Add the following crawl rules one at a time. Start with the bottom and work up. Rules are evaluated according to first match.</p></li></ul><p></p><p></p><p></p><p>Disallow</p><p>Contains</p><p>release-notes</p><p>Allow</p><p>Regex</p><p>/guide/en/.*/current/.*</p><p>Disallow</p><p>Regex</p><p>.*</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72127923b67d3b5a/6a1711bed7c022c73ade65c4/efd9052ba0038084855988b4fa6888a2a114a974-1440x410.png" alt="crawl rules" /><ul><li><p>With all the rules in place, click Crawl at the top of the page. Then, click Crawl all domains on this index.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcd9093ca85f8da56/6a1711c0a929cf9114ae0ae1/a50db802ba5cadb3863ffa314a8deee9b64c8dd7-638x380.png" alt="search engines crawl" /><p>Elasticsearch’s web crawler will now start crawling the documentation site, generating vectors for the title field, and indexing the documents and vectors.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteedc9efa600488e7/6a1711c2c1e8a5aea3f883d9/41eac123618338f127abffe9c957300f4e61fd0a-338x128.png" alt="crawling" /><p>The first crawl will take some time to complete. In the meantime, we can set up the OpenAI API credentials and the Python backend.</p><h2>Connecting with OpenAI API</h2><p>To send documents and questions to ChatGPT, we need an OpenAI API account and key. If you don’t already have an account, you can create a free account and you will be given an initial amount of free credits.</p><ul><li><p>Go to <a href="https://platform.openai.com">https://platform.openai.com</a> and click on Signup. You can go through the process to use an email address and password or login with Google or Microsoft.</p></li></ul><p>Once your account is created, you will need to create an API key:</p><ul><li><p>Click on <a href="https://platform.openai.com/account/api-keys">API Keys</a>.</p></li><li><p>Click Create new secret key.</p></li><li><p>Copy the new key and save it someplace safe as you won’t be able to view the key again.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c4b4def8f822024/6a1711c3a292995b95d0112e/b94891d6199c0c0c9f451eec9c8ac8d250882991-1114x586.png" alt="api key generated" /><h2>Python backend setup</h2><h3>Clone or download the python program</h3><p><a href="https://github.com/jeffvestal/ElasticDocs_GPT/blob/main/elasticdocs_gpt.py">Github Link to code</a></p><ol><li><p>Install required python libraries. We are running the example program in Replit, which has isolated environments. If you are running this on a laptop or VM, best practice is to <a href="https://docs.python.org/3/library/venv.html">set up a virtual ENV for python</a>.</p></li></ol><ul><li><p>Run pip install -r requirements.txt</p></li></ul><ol><li><p>Set authentication and connection environment variables (e.g., if running on the command line: export openai_api=”123456abcdefg789”)</p></li></ol><ul><li><p>openai_api - OpenAI API Key</p></li><li><p>cloud_id - Elastic Cloud Deployment ID</p></li><li><p>cloud_user - Elasticsearch Cluster User</p></li><li><p>cloud_pass - Elasticsearch User Password</p></li></ul><ol><li><p>Run the streamlit program. More info about <a href="https://docs.streamlit.io/library/get-started/installation">streamlit can be found in its docs</a>.</p></li></ol><ul><li><p>Streamlit has its own command to start: streamlit run elasticdocs_gpt.py</p></li></ul><ol><li><p>This will start a web browser and the url will be printed to the command line.</p></li></ol><h2>Sample chat responses</h2><p>With everything ingested and the front end up and running, you can start asking questions about the Elastic Documentations.</p><p>Asking “Show me the API call for an inference processor” now returns an example API call and some information about the configuration settings.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltffaa60616a42dc47/6a1711c50e2e49673b41a258/d767639258b64417da444346e191a158620cf134-1440x1448.png" alt="show api call" /><p>Asking for steps to add a new integration to Elastic Agent will return:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf0a7fe68f5fe11c5/6a1711c6a929cf650bae0ae5/5065af9bf9ae5ee5fd2a1c7636d2293746fb241d-1440x1272.png" alt="how add new integration" /><p>As mentioned earlier, one of the risks of allowing ChatGPT to answer questions based purely on data it has been trained on is its tendency to hallucinate incorrect answers. One of the goals of this project is to provide ChatGPT with the data containing the correct information and let it craft an answer.</p><p>So what happens when we give ChatGPT a document that does not contain the correct information? Say, asking it to tell you how to build a boat (which isn’t currently covered by Elastic’s documentation):</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba5ff5dddb7b8982/6a1711c8b339d58b8d76a0e8/43f80bfefab55261493751672669616cc6d0f54b-1440x548.png" alt="show build boat" /><p>When ChatGPT is unable to find an answer to the question in the document we provided, it falls back on our prompt instruction simply telling the user it is unable to answer the question.</p><h2>Elasticsearch’s robust retrieval + the power of ChatGPT</h2><p>In this example, we've demonstrated how integrating Elasticsearch's robust search retrieval capabilities with cutting-edge advancements in AI-generated responses from GPT models can elevate the user experience to a whole new level.</p><p>The individual components can be tailored to suit your specific requirements and adjusted to provide the best results. While we used the Elastic web crawler to ingest public data, you're not limited to this approach. Feel free to experiment with alternative embedding models, especially those fine-tuned for your domain-specific data.</p><p>You can try all of the capabilities discussed in this blog today! To build your own ElasticDocs GPT experience, sign up for an <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">Elastic trial account</a>, and then look at this <a href="https://github.com/jeffvestal/ElasticDocs_GPT">sample code repo</a> to get started.</p><p>If you would like ideas to experiment with search relevance, here are two to try out:</p><ul><li><p><a href="https://www.elastic.co/blog/how-to-deploy-nlp-text-embeddings-and-vector-search">[BLOG] Deploy NLP text embeddings and vector search using Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/blog/implement-image-similarity-search-elastic">[BLOG] Implement image similarity search with Elastic</a></p></li></ul><p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-openai-meets-private-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-openai-meets-private-data</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4478d2508f563479/6a1711c9a929cf0b9fae0ae9/1d616d244f05328ed677b008941db001d79c86b7-1440x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ChatGPT and Elasticsearch: enhance user experience with faceting, filtering, and more context]]></title>
    <description><![CDATA[By providing ChatGPT more context and using Elasticsearch's facets &amp; filters, you can refine the search and lower ChatGPT costs. Here's how.]]></description>
    <content:encoded><![CDATA[<p>In a recent blog post, we discussed how ChatGPT and Elasticsearch can <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">work together</a> to help manage proprietary data more effectively. By utilizing Elasticsearch's search capabilities and ChatGPT's contextual understanding, we demonstrated how the resulting outcomes can be improved.</p><p>In this post, we discuss how users’ experience can be further enhanced with the addition of facets, filtering, and additional context. By providing tools like ChatGPT additional context, you can increase the likelihood of obtaining more accurate results. See how Elasticsearch's faceting and filtering framework can allow users to refine their search and reduce the cost of engaging with ChatGPT.</p><h2>Comparing ChatGPT and Elasticsearch results</h2><p>To improve the user experience of our sample application, we've added a feature that displays the raw results alongside the ChatGPT-created response. This will help users better understand how ChatGPT works.</p><p>Since our source data set is only crawled, the structure in the documents makes it difficult to read for a human. To show this difference and therefore the value that ChatGPT can bring, we added the raw result next to the GPT created response.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c9d37f2456eefe3/6a17119ba929cf5105ae0ad9/0db9068dcd432aae0871c68bcdbf0227b7580e91-1440x939.png" alt="" /><p>Currently, this example application only returns a single result. And even though we have hybrid scoring with vector search and BM25, this result may not be perfect. If we take this not perfect result and pass it over to ChatGPT, there’s a good chance that the response we get won’t be great either, as the context was missing important information.</p><p>Ideally, we’d just pass more context into ChatGPT, but the current 3.5-turbo models are limited to 4,096 tokens (that’s including the response you expect to get, so the actual limit is much lower). Future models will likely have a much larger limit, but this also comes with a cost.</p><p>As of today, GPT-3.5-turbo costs $0.002 per 1K <a href="https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them">tokens</a>, while the up-and-coming GPT-4 with 32K context costs $0.06 per 1K tokens — that’s a factor of 30 more. Even with more powerful models coming in the next few years, there’s a chance that it’s not economically viable to do so for all user cases.</p><p>We will therefore not use GPT-4 right now and instead work around the max token limitation of GPT-3.5 by sending multiple concurrent requests and giving the user more flexibility in filtering the results.</p><h2>Leveraging aggregations, facets, and filtering in Elasticsearch to enhance ChatGPT</h2><p>To address this limitation, one of the biggest advantages of Elasticsearch is its robust faceting and filtering framework. When a user is searching for something, they may have additional preferences or context they can provide to dramatically increase the likelihood of obtaining the correct result. By leveraging Elasticsearch's faceting and filtering framework, we can allow users to refine their search based on various parameters such as date, location, or other relevant criteria.</p><p>It’s also important to note that many users have gotten used to having facet filtering options available when searching for something. Let us look at an example.</p><p>Searching for “How can I parse a message with Grok?” results in a document for ingest pipelines to be returned as the top result. This is not wrong, as ingest pipelines also support Grok expressions, but what if the user was interested in parsing his data using Logstash?</p><p>Using a simple terms aggregation as part of the request to fetch the hits, we can get a list of the top 10 product categories and offer these as a filtering option for a user.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt19e8ac27b6e24ca1/6a17119ccf4f25a6bcb2d267/39187b4da43ec7d9c75a4e7ff4ec6666b9f410b8-1440x816.png" alt="chatgpt options" /><p>If the user now selects “Logstash” on the left side, all results will be filtered for Logstash. It’s important to note that this all works while still using the same hybrid query model that we’ve talked about in the previous blog. We’re still using a combination of BM25 and kNN search to match our documents.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt150df5ca25922fc4/6a17119e8b73cb1bbe18a131/475f39166fd84e9728cb49d142b5f83e8af7b127-1440x814.png" alt="chatgpt grok filter plugin" /><h2>Loading multiple results in parallel</h2><p>We briefly mentioned the max token limit earlier. In short, the prompt that you send to the API and its response can’t be longer than 4,096 tokens. When searching your proprietary data, you would like to provide as much specific context as possible so the model can give you the best answer. However, the 4,096 tokens aren’t that much, especially when you include things like code snippets.</p><p>A very simple first step toward mitigating the limit is to just ask multiple times in parallel, giving a different context each time. Using our approach with Elasticsearch, instead of only fetching the top 1 result and sending that to OpenAI, we can change the application to load the top 10 hits instead and then ask the question with the respective context.</p><p>This gives us 10 unique answers to our question and greatly increases our chances of presenting a relevant answer to the end user. While we are increasing the burden of the user to look at the results, it still gives them more flexibility.</p><p>Think of it like this: if you try to debug a problem and search for an exception on Google, you quickly scan the list of the top four or five results that Google displays and click on the one that seems most fitting to your question. Showing the user multiple answers to their question is similar to this.</p><p>While having a single correct answer would be ideal, having more than one to choose from initially is a great starting point. And as mentioned before, it can be cheaper compared to using a more expensive model (such as GPT-4).</p><p>We can also get more creative with our prompt and ask ChatGPT to send us a specific response if it can’t answer the question using the provided context. This will allow us to remove the results from the UI later.</p><p>One prompt that worked well in our use case is:</p>prompt = f"Answer this question: {query}\n. Don’t give information not mentioned in the CONTEXT INFORMATION. If the CONTEXT INFORMATION contains code or API requests, your response should include code snippets. If the context does not contain relevant information, answer 'The provided page does not answer the question': \n {body}"
<h2>Working around the max token limit of ChatGPT: Answering a question from a set of answers</h2><p>Since we have more than a single answer to our question now, we can attempt to summarize them into a single response. For this, we will mostly follow the same approach as before, but instead of searching Elasticsearch for the context, we will just concatenate the individual answers we’ve received so far, excluding any where the model responded that it can’t answer it based on the provided context.</p><p>Note that the prompt for this run is a little different from the earlier prompt, so the model treats our context slightly differently. The provided prompt here is by no means perfect, and depending on the data, it should be adjusted and optimized further.</p>concatResult = ""
        for resultObject in results:
            if resultObject['choices'][0]["message"]["content"] != "The provided page does not answer the question.":
                concatResult += resultObject['choices'][0]["message"]["content"]
        if st.session_state['summarizeResults']['state']:
            results = [None] * 1
            tasks = []
            prompt = f"I will give you {numberOfResults} answers to this question.: \"{query}\"\n. They are ordered by their likelyhood to be correct. Come up with the best answer to the original question, using only the context I will provide you here. If the provided context contains code snippets or API requests, half of your response must be code snippets or API requests. \n {concatResult}"
            element = None
            with st.session_state['topResult']:
                with st.container():
                    st.markdown(f"**Summary of all results:**")
                    element = st.empty()

            with elasticapm.capture_span("top-result", "openai"):
                task = loop.create_task(achat_gpt(prompt, results, counter, element))
                tasks.append(task)
                loop.set_exception_handler(handle_exception)
                loop.run_until_complete(asyncio.wait(tasks))
	      loop.close()
<p>With this additional “reduce phase” in place, our app will now:</p><ul><li><p>Search Elasticsearch for the top 10 hits</p></li><li><p>10x in parallel ask OpenAI to answer the question, providing a different context each time</p></li><li><p>Concatenate responses from OpenAI and ask OpenAI once again to answer the question</p></li></ul><p>With this setup, we can use close to 40,000 tokens of context, while only paying for the considerably cheaper GPT-3.5 model. In another blog post, we will explore the cost in more detail and use Elastic APM for tracking our spend, alongside other metrics.</p><p>It should be noted that GPT-4 may still perform much better than the approach above, so use whatever works best for you and the amount of traffic you expect.</p><h2>Citations for your ChatGPT results</h2><p>One downside of large language models (LLMs) is their overconfidence and tendency to hallucinate. You ask a question, you get an answer. Whether the answer is actually correct is for you to decide. The model rarely admits that it does not know something. Providing the context and telling it to respond with a specific answer as we did above helps mitigates this to some extent.</p><p>But the provided context alongside getting the model to admit that it can’t answer a question also allows us to provide more accurate citations for the responses.</p><p>In the last section, we summarized our set of 10 answers into one global answer. In addition to just providing this global answer, we can also provide a list of all source documentation pages that we used to compile the result — basically any page where the model did not respond "The provided page does not answer the question."</p><p>In this screenshot, you can see the summary answer on a set of 10 results from Elasticsearch. Even though we inspected 10 results, we are only displaying the three links to the documentation that are actually relevant to answer the question. In this case, the other seven documents returned by Elasticsearch had something to do with documents or indices, but they didn’t specifically talk about how to index something.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c4bc2ffece60c7c/6a17119fe8fbce22a839fd5d/ca302c403cfe19dc86a69d9f38d400510908bf81-1440x952.png" alt="chatgpt to index a document" /><h2>Searching proprietary data</h2><p>We’ve mentioned in an <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">earlier blog post</a> that it’s great to use Elasticsearch and OpenAI to search proprietary data. However, we did use a web-crawler to crawl public documentation. That may seem a bit counterintuitive, and you’re right to think about it! OpenAI trains GPT models on web data, so we will assume it knows our documentation already. So why do we need Elasticsearch in addition to that data? Does this setup actually work on data that’s not public? It does — let’s prove it.</p><p>Using the existing setup, we will push a single super secret document about an internal project into our index.</p>PUT search-elastic-docs/_doc/1?pipeline=search-elastic-docs@ml-inference
{
  "title": "Project LfQg832p6Jx040809WZc",
  "product_name": "SuperSecret",
  "url": "https://www.example.com",
  "body_content": """What is Project LfQg832p6Jx040809WZc? Project LfQg832p6Jx040809WZc is an internal project that's not public information. This is the plan for the project: Step 1 is writing a blog post about OpenAi and Elasticsearch for private data. Step 2 is noticing that we didn't actually include any private data. Step 3 is including an example about private data

  We also have some super secret API requests as part of this project:
  PUT project/_doc/hello-world
  {
    "secret": "don't share this with anyone!"
  }

  """
}
<p>Next we’ll then head over to our app and search for “What are the steps for the internal project?”</p><p>In summary, we used faceting and filtering to, for certain use cases, reduce the number tokens of context required to engage with ChatGPT. By providing additional context at query time, we showed it is also possible to improve the accuracy of search results.</p><p><a href="https://www.elastic.co/blog/may-2023-launch-announcement"><strong>Learn more about the possibilities with Elasticsearch and AI</strong></a> <strong>.</strong></p><p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Costs referred to herein are based on the current OpenAI API pricing and how often we call it when loading our sample app.</em></p><p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-faceting-filtering-more-context</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-faceting-filtering-more-context</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fd006de03fddc5d/6a1711a1dc55de3cf7e00ef7/981b7b0cb9b9ca0561e9c1784f5ce51240199385-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ChatGPT and Elasticsearch: APM instrumentation, performance, and cost analysis]]></title>
    <description><![CDATA[Learn how to instrument a Python application that uses OpenAI, analyze its performance &amp; cost and integrate large language models (LLMs).]]></description>
    <content:encoded><![CDATA[<p>In a <a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">previous blog post</a>, we built a small Python application that queries Elasticsearch using a mix of vector search and BM25 to help find the most relevant results in a proprietary data set. The top hit is then passed to OpenAI, which answers the question for us.</p><p>In this blog, we will instrument a Python application that uses OpenAI and analyze its performance, as well as the cost to run the application. Using the data gathered from the application, we will also show how to integrate large language models (LLMs) into your application. As a bonus, we will try to answer the question: why does ChatGPT print its output word by word?</p><h2>Instrumenting the application with Elastic APM</h2><p>If you’ve had a chance to give our <a href="https://github.com/jeffvestal/ElasticDocs_GPT/blob/main/elasticdocs_gpt.py">sample application</a> a try, you might have noticed that the result does not load as quickly as you’d expect it to, from a search interface.</p><p>The now is if this is from our two-phased approach of running a query in Elasticsearch first or if the slow behavior is emerging from OpenAI, or if it’s a combination of the two.</p><p>Using Elastic APM, we can easily instrument this application to get a better look. All we need to do for the instrumentation is the following (we will show the full example at the end of the blog post and also in a GitHub repository):</p>import elasticapm
# the APM Agent is initialized
apmClient = elasticapm.Client(service_name="elasticdocs-gpt-v2-streaming")

# the default instrumentation is applied
# this will instrument the most common libraries, as well as outgoing http requests
elasticapm.instrument()
<p>Since our sample application is using Streamlit, we will also need to start at least one transaction and eventually end it again. In addition, we can also provide information about the outcome of the transaction to APM, so we can track failures properly.</p># start the APM transaction
apmClient.begin_transaction("user-query")

(...)



elasticapm.set_transaction_outcome("success")

# or "failure" for unsuccessful transactions
# elasticapm.set_transaction_outcome("success")

# end the APM transaction
apmClient.end_transaction("user-query")
<p>And that’s it — this would be enough to have full APM instrumentation for our application. That being said, we will be doing a little extra work here in order to get some more interesting data.</p><p>As a first step, we will add the user’s query to the APM metadata. This way we can inspect what the user was trying to search and can analyze some popular queries or reproduce errors.</p>elasticapm.label(query=query)
<p>In our async method, which talks to OpenAI, we will also add some more instrumentation so we can better visualize the tokens we receive, as well as to collect additional statistics.</p>async with elasticapm.async_capture_span('openaiChatCompletion', span_type='openai'):
        async for chunk in await openai.ChatCompletion.acreate(engine=engine, messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": truncated_prompt}],stream=True,):
            content = chunk["choices"][0].get("delta", {}).get("content")
            # since we have the stream=True option, we can get the output as it comes in
            # one iteration is one token
	  # we start a new span here for each token. These spans will be aggregated
            # into a compressed span automatically
            with elasticapm.capture_span("token", leaf=True, span_type="http"):
                if content is not None:
                    # concatenate the output to the previous one, so have the full response at the end
                    output += content
                    # with every token we get, we update the element
                    element.markdown(output)
<p>And finally, toward the very end of our application, we will also add the number of tokens and approximate cost to our APM transaction. This will enable us to visualize these metrics later and correlate them to the application performance.</p><p>If you do not use streaming, then the OpenAI response will contain a “total_tokens” field, which is the sum of the context you sent and the response returned. If you are using the “stream=True” option, then it’s your responsibility to calculate the number of tokens or approximate them. A common recommendation is to use “(len(prompt) + len(response)) / 4” for english text, but especially code snippets can throw off this approximation. If you need more exact numbers, you can use libraries like <a href="https://github.com/openai/tiktoken">tiktoken</a> to calculate the number of tokens.</p># add the number of tokens as a metadata label
elasticapm.label(openai_tokens = st.session_state['openai_current_tokens'])
# add the approximate cost as a metadata label
# currently the cost is $0.002 / 1000 tokens
elasticapm.label(openai_cost = st.session_state['openai_current_tokens'] / 1000 * 0.002)

<h2>Analyzing the APM data — Elasticsearch vs. OpenAI performance</h2><p>After instrumenting the application, a quick look at the “Dependencies” gives us a better understanding of what’s going on. It looks like our requests to Elasticsearch return within 125ms on average, while OpenAI takes 8,500ms to complete a request. (This screenshot was taken on a version of the application that does not use streaming. If you use streaming, the default instrumentation only considers the initial POST request in the dependency response time and not the time it takes to stream the full response.)</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa68cd926558eb2/6a17117f47d49c04ba2d8b02/78cb423a576c698d97b3d47a13ec896e371008a5-1440x494.png" alt="chatgpt dependencies" /><p>If you’ve used ChatGPT yourself already, you might have been wondering why the UI is printing every word individually, instead of just returning the full response immediately.</p><p>As it turns out, this is not actually to entice you to pay money if you’re using the free version! It’s more of a limitation of the inference model. In simple terms, in order to compute the next token, <a href="https://lilianweng.github.io/posts/2023-01-10-inference-optimization/">the model</a> will need to take into consideration the last token as well. So there’s not much room for parallelization. And since every token is processed individually, this token can also be sent to the client, while the computation for the next token is running.</p><p>In order to improve the UX, it can be helpful to therefore use a streaming approach when using the ChatCompletion functionality. This way a user can start to consume the first results while the full response is being generated. You can see this behavior in the GIF below. Even though all three responses are still loading, the user can scroll down and inspect what’s there already.</p><p>As mentioned previously, we added a bit more custom instrumentation than just the bare minimum. This allows us to get detailed information on where our time is spent. Let’s take a look at a full trace and see this streaming in action.</p><p>Our application is configured to fetch the top three hits from Elasticsearch, and then run one ChatCompletion request against OpenAI in parallel.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6234efc85bc5c69b/6a171180ab7f080136db9f9f/c930cf97ddc51049f839bba4aa41b3d1901f4c67-1440x679.png" alt="elastic openai in parallel" /><p>As we can see in the screenshot, loading the individual results takes about 15s. We can also see that requests to OpenAI that return a larger response take longer to return. But this is only a single request. Does this behavior happen for all requests? Is there a clear correlation between response time and number of tokens to back up our claims from earlier?</p><h2>Analyzing cost and response time</h2><p>Instead of visualizing the data using Elastic APM, we can also use custom dashboards and create visualizations from our APM data. Two interesting charts that we can build show the relationship between the number of tokens in a response and the duration of the request.</p><p>We can see that the more tokens get returned (x-axis in the first chart), the higher the duration (y-axis in the first chart). In the chart to the right, we can also see that the duration per 100 tokens returned stays almost flat at around 4s, no matter the number of tokens returned in total (x-axis).</p><p>If you want to improve the responsiveness of your application that uses OpenAI models, it might be a good idea to tell the model to keep the response short.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ccfdcdb869cd30e/6a1711820c4857151a01ab8c/6bbadaa8995e8c947b593451732dad3135806871-1440x549.png" alt="chatgpt response time vs tokens" /><p>In addition to this, we can also track our total spend and the average cost per page load, as well as other statistics.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf567d2300b1599d2/6a171183dc55dee5e2e00ee9/d1f6a54c30109a7aea12b7cbcbd183f516f96e51-1440x820.png" alt="chatgpt total cost" /><p>With our sample application, the cost for a single search is around 1.1¢. This number does not sound high, but it’s likely not something that you will have on your public website as a search alternative anytime soon. For company internal data and a search interface that’s only used occasionally, this cost is negligible.</p><p>In our testing, we’ve also hit frequent errors when using the OpenAI API in Azure, which eventually made us add a retry loop to the sample app with an exponential backoff. We can also capture these errors using Elastic APM.</p>while tries &lt; 5:
    try:
        print("request to openai for task number: " + str(index) + " attempt: " + str(tries))
        async with elasticapm.async_capture_span('openaiChatCompletion', span_type='openai'):
            async for chunk in await openai.ChatCompletion.acreate(engine=engine, messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": truncated_prompt}],stream=True,):
                content = chunk["choices"][0].get("delta", {}).get("content")
                counter += 1
                with elasticapm.capture_span("token", leaf=True, span_type="http"):
                    if content is not None:
                        output += content
                        element.markdown(output)
        break
    except Exception as e:
        client = elasticapm.get_client()
        # capture the exception using Elastic APM and send it to the apm server
        client.capture_exception()
        tries += 1
        time.sleep(tries * tries / 2)
        if tries == 5:
            element.error("Error: " + str(e))
        else:
            print("retrying...")
<p>Any captured errors are then visible in the waterfall charts as part of the span where the failure happened.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf84f271da871a62e/6a1711854a531b456736aa6f/49939b8e2b24eabb366799917681ebd66a8e53fa-1440x871.png" alt="timeline user query" /><p>In addition, Elastic APM also provides an overview of all the errors. In the screenshot below, you can see the occasional RateLimitError and APIConnectionError that we’ve encountered. Using our crude exponential retry mechanism, we can mitigate most of these problems.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d7f046dcd014818/6a171186a6c2b9203be797ec/203e60003a3f1da6dff6d5d21870bbe32da8c847-1440x764.png" alt="elasticdocs gpt v2 streaming" /><h2>Latency and failed transaction correlation</h2><p>With all the built-in metadata that the Elastic APM agent capture, as well as the custom labels we added, we can easily analyze if there’s any correlation between the performance and any of the metadata (like services version, user query, etc.)</p><p>As we can see below, there’s a small correlation between the query “How can I mount and index on a frozen node?” and a slower response time.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba078944b177b48f/6a171188d7c0228ae7de65a6/3839ef67669dcee18515618997d9e591d9445f63-1440x592.png" alt="latency distribution correlations" /><p>Similar analysis can be done on any transaction that resulted in an error. In this example, the two queries “How do I create an ingest pipeline” and “How can I create an ingest pipeline” fail more often than other queries, causing them to bubble up in this correlation analysis.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d4c16b1beb5ebfa/6a17118947d49cf9452d8b06/a4094ed5ba442a74e87b7fa73c18c110939452ff-1440x639.png" alt="failed transactions latency distribution" />import elasticapm
# the APM Agent is initialized
apmClient = elasticapm.Client(service_name="elasticdocs-gpt-v2-streaming")

# the default instrumentation is applied
# this will instrument the most common libraries, as well as outgoing http requests
elasticapm.instrument()

# if a user clicks the "Search" button in the UI
if submit_button:
	# start the APM transaction
apmClient.begin_transaction("user-query")
# add custom labels to the transaction, so we can see the users question in the API UI
elasticapm.label(query=query)



    async with elasticapm.async_capture_span('openaiChatCompletion', span_type='openai'):
        async for chunk in await openai.ChatCompletion.acreate(engine=engine, messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": truncated_prompt}],stream=True,):
            content = chunk["choices"][0].get("delta", {}).get("content")
            # since we have the stream=True option, we can get the output as it comes in
            # one iteration is one token
            with elasticapm.capture_span("token", leaf=True, span_type="http"):
                if content is not None:
                    # concatenate the output to the previous one, so have the full response at the end
                    output += content
                    # with every token we get, we update the element
                    element.markdown(output)
async def achat_gpt(prompt, result, index, element, model="gpt-3.5-turbo", max_tokens=1024, max_context_tokens=4000, safety_margin=1000):
    output = ""
    # we create on overall Span here to track the total process of doing the completion
    async with elasticapm.async_capture_span('openaiChatCompletion', span_type='openai'):
        async for chunk in await openai.ChatCompletion.acreate(engine=engine, messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": truncated_prompt}],stream=True,):
            content = chunk["choices"][0].get("delta", {}).get("content")
            # since we have the stream=True option, we can get the output as it comes in
            # one iteration is one token, so we create one small span for each
            with elasticapm.capture_span("token", leaf=True, span_type="http"):
                if content is not None:
                    # concatenate the output to the previous one, so have the full response at the end
                    output += content
                    # with every token we get, we update the element
                    element.markdown(output)
<p>In this blog, we instrumented an app written in Python to use OpenAI and analyze its performance. We looked at response latency and failed transactions, and we assessed the costs of running the application. We hope this how-to was useful for you!</p><p><a href="https://www.elastic.co/what-is/elasticsearch-machine-learning"><strong>Learn more about the possibilities with Elasticsearch and AI</strong></a> <strong>.</strong></p><p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p><p><em>Costs referred to herein are based on the current OpenAI API pricing and how often we call it when loading our sample app.</em></p><p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-apm-instrumentation-performance-cost-analysis</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-elasticsearch-apm-instrumentation-performance-cost-analysis</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762b38f91bd71c8e/6a170db8b339d547bb76a048/368db71c500e72d20fe225fe44c2c40231e29765-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Aggregate data faster with new the random_sampler aggregation]]></title>
    <description><![CDATA[Aggregate billions of documents in milliseconds instead of minutes with Elastic. Learn more about how the new random_sampler aggregation gives you statistically robust results at a lower cost.]]></description>
    <content:encoded><![CDATA[<p>With 8.2, the Elastic Stack gives users the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html"><code>random_sampler</code></a> aggregation. It adds the capability to randomly sample documents in a statistically robust manner. Randomly sampling documents in aggregations allows you to balance speed and accuracy at query time. You can aggregate billions of documents with high accuracy at a fraction of the latency. This allows you to achieve faster results with fewer resources and comparable accuracy — all with a simple aggregation.</p><p>Let's run through some basic details, best practices, and how it works, so you can try it out in the Elasticsearch Service today.</p><h2>Delivering speed and accuracy</h2><p>Random sampling in Elasticsearch has never been easier or faster. If your query has many aggregations, you can quickly obtain results by using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html"><code>random_sampler</code></a> aggregation.</p>POST _search?size=0&amp;track_total_hits=false
{
  "aggs": {
    "sampled": {
      "random_sampler": {
        "probability": 0.001,
        "seed": 42
      },
      "aggs": {
        ...
      }
    }
  }
}
<p>All the above aggregations nested under <code>random_sampler</code> will return sampled results. Each agg is roughly seeing only 0.1% of the documents (or 1 in every 1000th document). Where computational cost correlates with the number of documents, the aggregation speed increases. You may have also noticed the “<code>seed</code>” parameter. You can provide a <code>seed</code>to get consistent results on the same shards. Without a seed, a new random subset of documents is considered and you may get slightly different aggregated results.</p><p>How much faster is the <code>random_sampler</code>? The speed improves according to the provided probability as fewer documents are aggregated. The improvements relative to probability will eventually flatten out. Each aggregation has its own computational overhead regardless of the number of documents. An example of this overhead cost is comparing multi-bucket to single metric aggregations. Multi-bucket aggregations have a higher overhead due to their bucket handling logic. While speed is improved for multi-bucket aggregations, the rate of that speed increase will flatten out sooner than single metric.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1427bb522f9a2e33/6a17113be8fbce617c39fd55/e6a33afc9b30709dd5606bfb23726005f40bb803-800x600.png" alt="expected speedup" /><p>Figure 1. The speedup expected for aggregations of different constant overhead.</p><p>Here are some results on expected speed and error rate over an APM data set of 64 million documents.</p><p>The calculations are from: 300 query and aggregation combinations, 5 seeds, and 9 sampling probabilities. In total, 13,500 separate experiments generated the following graphs for median speedup and median relative error as a function of the downsample factor which is 1 / sample probability.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd264a08140ef2bed/6a17113dacf0883d26be9c4d/77697af30ebd43c64216df0a9e99951191dd612d-800x600.png" alt="median speedup" /><p>Figure 2. Median speedup as a function of the downsample factor (or 1 / probability provided for the sampler).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f3876b646dd7b14/6a17113e1949f77a4ae7ab20/047bb5dd28bedaee14998168d71ac152e3ea4392-800x600.png" alt="median error" /><p>Figure 3. Median relative error as a function of the downsample factor (or 1 / probability provided for the sampler).</p><p>With a probability of 0.001, for half of the scenarios tested, there was an 80x speed improvement or better with a 4% relative error or less. These tests involved a little over 64 million documents but spread across many shards. More compact shards and larger data can expect better results.</p><p>But, you may ask, do the visualizations look the same?</p><p>Below are two visualizations showing document counts for every 5 minutes over 100+ million documents. The total set loads in seconds and is sampled in milliseconds. This is with almost no discernible visual difference.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e84bd402ad8ec58/6a171140b339d56a6976a0c2/925ab6b529edb5065ec7d0f1886f7bc3eaa7da62-800x158.png" alt="sampled vs unsampled count" /><p>Figure 4. Sampled vs unsampled document count visualizations.</p><p>Here is another example. This time the average transaction by hour is calculated and visualized. While visually these are not exactly the same, the overall trends are still evident. For a quick overview of the data to catch trends, sampling works marvelously.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt583f18fbd2c02001/6a171141acf0885acabe9c51/1998189c574de515e1bff2dca886bd923a2af584-800x250.png" alt="sampled vs unsampled average" /><p>Figure 5. Sampled vs. unsampled average transaction time by hour visualization.</p><h2>Best practices for using sampling aggregation</h2><p>Sampling shines when you have a large data set. In these cases you might ask, should I sample before the data is indexed in Elasticsearch? Sampling at query time and before ingestion are complimentary. Each has its distinct advantages.</p><p>When sampling at ingest time, it can save disk and indexing costs. However, if your data has multiple facets, you have to stratify sampling over facets when sampling before ingestion, unless you know exactly how it will be queried. This suffers from the <a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality">curse of dimensionality</a> and you could end up with underrepresented sets of facets. Furthermore, you have to cater for the worst case when sampling before ingestion. For example, if you want to compute percentiles for two queries, one which matches 50% of the documents and one which matches 1% of documents in an index, you can get away with 7X more downsampling for the first query and achieve the same accuracy.</p><p>Here is a summary of what to expect from sampling with the <code>random_sampler</code> at query time.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt908ecf7a4a90ba82/6a1711436234e00ddcdb1ada/3a1cc19429ee33b95e6a0ab6eed2413665a5e0bd-640x480.png" alt="relative error" /><p>Figure 6. Relative error for different aggregations.</p><p>Sampling accuracy varies across aggregations (see Figure 5 for some examples). Here is a list of some aggregations in order of descending accuracy: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-percentile-aggregation.html">percentiles</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html">counts</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html">means</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html">sums</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-extendedstats-aggregation.html">variance</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html">minimum</a>, and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html">maximum</a>. Metric aggregation accuracy will also be affected by the underlying data variation: the lower the variation in the values, the fewer samples you need to get accurate aggregate values. The minimum and maximum will not be reliable with outliers, since there is always a reasonable chance that the sampled set misses the one very large (or small) value in the data set. If you are using terms aggregations (or some partitioning such as date histogram), aggregate values for terms (or buckets) with few values will be less accurate or missed altogether.</p><p>Aggregations also have fixed overheads (see Figure 1 for an example). This means as the sample size decreases, the performance improvement will eventually level out. Aggregations which have many buckets have higher overheads and so the speedup you will gain from sampling is smaller. For example, a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">terms aggregation</a> for a high cardinality field will show less performance benefit.</p><p>If in doubt, some simple experiments will often suffice to determine good settings for your data set. For example, suppose you want to speed up a dashboard; try reducing the sample probability while the visualizations look similar enough. Chances are your data characteristics will be stable and so this setting will remain reliable.</p><h2>Uncovering how sampling works</h2><p>Sampling considers the entire document set within a shard. Once it creates the sampled document set, sampling applies any provided user filter. The documents that match the filter and are within the sampled set are then aggregated (see Figure 7).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfed879f2c11f87c2/6a171144ab7f08018ddb9f8f/9145ccea9747229b2d08a8badf6120c20bd0e271-800x227.png" alt="request data flow" /><p>Figure 7. Typical request and data flow for the random_sampler aggregation.</p><p>The key to the sampling is generating this random subset of the shard efficiently and without statistical biases. Taking <a href="https://en.wikipedia.org/wiki/Geometric_distribution">geometrically distributed random</a> steps through the document set is equivalent to uniform random sampling, meaning each document in the set has an equally likely chance of being selected into the sample set. The advantage of this approach is that the sampling cost scales with p (where p is the probability configured in the aggregation). This means no matter how small p is, the relative latency of performing the sampling adds will remain fixed.</p><h2>Ensuring performance reliability and accuracy</h2><p>To achieve the highest performance, accuracy, and robustness, we evaluated a range of realistic scenarios.</p><p>In the case of <code>random_sampler,</code> the evaluation process is complicated by two factors:</p><ol><li><p>It cuts right across the aggregation framework and so it needs to be evaluated with many different combinations of query and aggregation,</p></li><li><p>The results are random numbers, so rather than running just once, you need to run multiple times and test the statistical properties of the result set.</p></li></ol><p>We began with a proof of concept that showed that the overall strategy worked and the performance characteristics were remarkable. However, there are multiple factors which can affect implementation performance and accuracy. For example, we found the off-the-shelf sampling code for the geometric distribution was not fast enough. We decided to roll our own using some tricks to extract more random samples per random bit along with a very fast quantized version of the log function. You also need to be careful that you are generating statistically independent samples for different shards. In summary, as is often the case, the devil is in the details.</p><p>Undaunted, we wrote a test harness using the <a href="https://elasticsearch-py.readthedocs.io/en/stable/">Elastic Python client</a> to programmatically generate aggregations and queries, and perform statistical tests of quality.</p><p>We wanted the approximations we produce to be unbiased. This means if you run a sampled aggregation repeatedly and averaged the results it would converge towards the true value. Standard machinery allows you to test if there is statistically significant evidence of bias. We used a <a href="https://en.wikipedia.org/wiki/Student%27s_t-test">t-test</a> for the difference between the statistic and true value for each aggregation. In over 300 different experiments, the minimum p-value was around 0.0003 which — given we ran 300 experiments — has about a 9% odds of occurring by chance. This is a little low, but not enough to worry about; furthermore the median p-value was 0.38.</p><p>We also tested whether various index properties affect the statistical properties. For example, we wanted to see if we could measure a statistically significant difference between the distribution of results with and without index sorting. A <a href="https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test">K-S test</a> can be used to check if samples come from the same distribution. In our 300 experiments the smallest p-value was around 0.002 which occurs with odds of about 45% by chance.</p><h2>Get started today</h2><p>We're not done with this feature yet. Once you have the ability to generate fast approximate results, a key question is: how accurate are those results? We're planning to integrate a confidence interval calculation directly into the aggregation framework to answer this efficiently in a future release. Learn more about random_sampler_aggregation in this documentation. You can explore this feature and more with a <a href="https://cloud.elastic.co/registration?elektra=whats-new-elastic-8-1-0-blog">free 14-day trial of Elastic Cloud</a>.</p><p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/aggregate-data-faster-with-new-the-random-sampler-aggregation</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/aggregate-data-faster-with-new-the-random-sampler-aggregation</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Benjamin Trent,Thomas Veasey]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte47734cb679b1cc8/6a171146a929cf44d5ae0ac5/bc75e4b6f15f183c75c931db011791301523d0cb-1217x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 20 Apr 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>