<?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[Baha Azarmi - 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[Baha Azarmi - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/author/baha-azarmi</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/baha-azarmi</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/baha-azarmi.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Fri, 25 Sep 2026 13:09:22 GMT</lastBuildDate>
  <item>
    <title><![CDATA[An Elasticsearch Query Language (ES|QL) analysis: Millionaire odds vs. hit by a bus]]></title>
    <description><![CDATA[Use Elasticsearch Query Language (ES|QL) to run statistical analysis on demographic data index in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch Query Language (ES|QL) is designed for fast, efficient querying of large datasets. It has a straightforward syntax which will allow you to write complex queries easily, with a pipe based language, reducing the learning curve. We're going to use ES|QL to run statistical analysis and compare different odds.</p><p>If you are reading this, you probably want to know how rich you can get before actually reaching the same odds of being hit by a bus. I can't blame you, I want to know too. Let's work out the odds so that we can make sure we win the lottery rather than get in an accident!</p><p>What we are going to see in this blog is figuring out the probability of being hit by a bus and the probability of achieving wealth. We'll then compare both and understand until what point your chances of getting rich are higher, and when you should consider getting life insurance.</p><p>So how are we going to do that? This is going to be a mix of magic numbers pulled from different articles online, some synthetics data and the power of ES|QL, the new Elasticsearch Query Language. Let's get started.</p><h2>Data for the ES|QL analysis</h2><h3>The magic number</h3><p>The challenge starts here as the dataset is going to be somewhat challenging to find. We are then going to assume for the sake of the example that ChatGPT is always right. Let’s see what we get for the following question:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b0ac87235183705/6a17d75e3e03d768544f2ac1/e53cf27540f30f58af23fc26d3d1b93cc7fd5497-1440x229.png" alt="bus-odds" /><p>Cough Cough… That sounds about right, this is going to be our magic number.</p><h3>Generating the wealth data</h3><h4>Prerequisites</h4><p>Before running any of the scripts below, make sure to install the following packages:</p>
elasticsearch==8.14.0
matplotlib
numpy
panda
scipy

<p>Now, there is one more thing we need, a representative dataset with wealth distribution to compute wealth probability. There is definitely some portion of it here and there, but again, for the example we are going to generate a 500K line dataset with the below python script. I am using python 3.11.5 in this example:</p>
import pandas as pd
import numpy as np
import getpass
from elasticsearch import Elasticsearch, helpers

# Input the Elasticsearch host
hosts = input('Enter your Elasticsearch host address : ')

# Securely input the Elasticsearch API key
api_key = getpass.getpass(prompt='Enter your Elasticsearch API Key: ')

# Initialize Elasticsearch client
client = Elasticsearch(
    hosts=hosts,
    api_key=api_key,
)

# Generate synthetic data with a highly skewed distribution
num_records = 500000
np.random.seed(42)  # Ensure reproducibility

# Generate net worth using a highly skewed distribution
ages = np.random.randint(20, 80, num_records)  # Random ages between 20 and 80
incomes = np.random.exponential(scale=10000, size=num_records)  # Exponential distribution for income
# Use a more skewed distribution for net worth with a much larger range
net_worths = np.random.exponential(scale=100000000, size=num_records)  # Extremely skewed net worth

# Scale up the net worths to reach up to $100 billion
net_worths = np.clip(net_worths, 0, 100000000000)

# Create DataFrame
df = pd.DataFrame({
    'id': range(1, num_records + 1),
    'age': ages,
    'income': incomes,
    'net_worth': net_worths,
    'counter': range(1, num_records + 1)  # Add a counter field for pagination
})

# Index the data into Elasticsearch
index_name = 'raw_wealth_data_large'
try:
    if client.indices.exists(index=index_name):
        client.indices.delete(index=index_name)
except exceptions.NotFoundError:
    pass
client.indices.create(index=index_name)


def generator(df):
    for index, row in df.iterrows():
        yield {
            "_index": index_name,
            "_source": row.to_dict()
        }

helpers.bulk(client, generator(df))

print("Data indexed successfully.")
<p>It should take some time to run depending on your configuration since we are injecting 500K documents here!</p><p>FYI, after playing with a couple of versions of the script above and the ESQL query on the synthetic data, it was obvious that the net worth generated across the population was not really representative of the real world. So I decided to use a log-normal distribution (np.random.lognormal) for income to reflect a more realistic spread where most people have lower incomes, and fewer people have very high incomes.</p><p>Net Worth Calculation: Used a combination of random multipliers (np.random.uniform(0.5, 5)) and additional noise (np.random.normal(0, 10000)) to calculate net worth. Added a check to ensure no negative net worth values by using np.maximum(0, net_worths).</p><p>Not only have we generated 500K documents, but we also used the Elasticsearch python client to bulk ingest all these documents in our deployment. Please note that you will find the endpoint to pass in as hosts Cloud ID in the code above.</p><p>For the deployment API key, open Kibana, and generate the key in Stack Management / API Keys:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb38e3a3a44c66b9e/6a17d7606864a40557b685e2/544c32086c18ca3b5e65fa0e5bbf2d60490a7f66-1440x864.png" alt="api-key" /><p>The good news is that if you have a real data set, all you will need to do is to change the above code to read your dataset and write documents with the same data mapping.</p><p>Ok we're getting there! The next step is pouring our wealth distribution.</p><h2>ES|QL wealth analysis</h2><h3>Introducing ES|QL: A powerful tool for data analysis</h3><p>The arrival of Elasticsearch Query Language (ES|QL) is very exciting news for our users. It largely simplifies querying, analyzing, and visualizing data stored in Elasticsearch, making it a powerful tool for all data-driven use cases.</p><p>ES|QL comes with a variety of functions and operators, to perform aggregations, statistical analyses, and data transformations. We won’t address them all in this blog post, however <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">our documentation</a> is very detailed and will help you familiarize with the language and the possibilities.</p><p>To get started with ES|QL today and run the blog post queries, simply <a href="https://www.elastic.co/getting-started?utm_source=github&amp;utm_content=elasticsearch-labs-notebook">start a trial on Elastic Cloud</a>, load the data and run your first ES|QL query.</p><h3>Understanding the wealth distribution with our first query</h3><p>To get familiar with the dataset, head to Discover in Kibana and switch to ES|QL in the dropdown on the left hand side:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5aba101d232dbb13/6a17d762e8fbce48b53a174c/357703b5c0b543daa61fe98354de29e57182469d-1440x585.png" alt="discover" /><p>Let’s fire our first request:</p>from raw_wealth_data_large | keep age, id, income, net_worth | limit 10
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd9597115bb01b8c3/6a17d7643e03d7488f4f2ac5/898970fd63e9b2811bf24badd72ef57a45912564-1312x1930.png" alt="result set" /><p>As you could expect from our indexing script earlier, we are finding the documents we bulk ingested, notice the simplicity of pulling data from a given dataset with ES|QL where every query starts with the From clause, then your index.</p><p>In the query above given we have 500K lines, we limited the amount of returned documents to 10. To do this, we are passing the output of the first segment of the query via a pipe to the limit command to only get 10 results. Pretty intuitive, right?</p><p>Alright, what would be more interesting is to understand the wealth distribution in our dataset, for this we will leverage one of the 30 functions ES|QL provides, namely percentile.</p><p>This will allow us to understand the relative position of each data point within the distribution of net worth. By calculating the median percentile (50th percentile), we can gauge where an individual’s net worth stands compared to others.</p>
FROM raw_wealth_data_large
| stats p50 = percentile(net_worth, 50) 

<p>Like our first query, we are passing the output of our index to another function, Stats, which combined with the percentile function will output the median net worth:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35023abe49d381f3/6a17d7654b055d09c5432048/57f65e58941783994d67dee7ee763a4934bd8ca9-1440x640.png" alt="result set" /><p>The median is about 54K, which unfortunately is probably optimistic compared to the real world, but we are not going to solve this here. If we go a little further, we can look at the distribution in more granularity by computing more percentiles:</p>
FROM raw_wealth_data_large
| STATS  p25 = percentile(net_worth, 25)
       , p50 = percentile(net_worth, 50)
       , p75 = percentile(net_worth, 75)
       , p90 = percentile(net_worth, 90)
       , p95 = percentile(net_worth, 95)
       , p96 = percentile(net_worth, 96)
       , p98 = percentile(net_worth, 98)
       , p97 = percentile(net_worth, 97)
       , p99 = percentile(net_worth, 99)
| keep p25, p25, p50, p75, p90, p95, p96, p97, p98, p99

<p>With the below output:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48a66c799c539a68/6a17d767414c644eaa944fd6/59f1c99d52887e275422aea7d4e0547c8f3ca886-1440x458.png" alt="Percentile result" /><p>The data reveals a significant disparity in wealth distribution, with the majority of wealth being concentrated among the richest individuals. Specifically, the top 5% (95th percentile) possess a disproportionately large portion of the total wealth, with a net worth starting at $852,988.26 and increasing dramatically in the higher percentiles.</p><p>The 99th percentile individuals hold a net worth exceeding $2 million, highlighting the skewed nature of wealth distribution. This indicates that a substantial portion of the population has modest net worth, which is probably what we want for this example.</p><p>Another way to look at this is to augment the previous query and grouping by age to see if there is, (in our synthetic dataset), a relation between wealth and age:</p>
FROM raw_wealth_data_large
| STATS  p25 = percentile(net_worth, 25)
      , p50 = percentile(net_worth, 50)
      , p75 = percentile(net_worth, 75)
      , p90 = percentile(net_worth, 90)
      , p95 = percentile(net_worth, 95)
      , p96 = percentile(net_worth, 96)
      , p98 = percentile(net_worth, 98)
      , p97 = percentile(net_worth, 97)
      , p99 = percentile(net_worth, 99) by age
| keep p25, p25, p50, p75, p90, p95, p96, p97, p98, p99, age
<p>This could be visualized in a Kibana dashboard. Simply:</p><ul><li><p>Navigate to Dashboard</p></li><li><p>Add a new ES|QL visualization</p></li><li><p>Copy and paste our query</p></li><li><p>Move the age field to the horizontal axis in the visualization configuration</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e72df525e4fe575/6a17d769b1e113339979f0d3/f47a62215862c5981d2642128c2e41ac707a7476-1066x1864.png" alt="Create ESQL visualization" /><p>Which will output:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2de89c1c00eb23be/6a17d76afbc5f807ff491908/59e17103d0233fa9d79dd65e510b776b96041489-1440x854.png" alt="Visualization output" /><p>The above suggests that the data generator randomized wealth uniformly across the population age, there is no specific trend pattern we can really see.</p><h4>Median Absolute Deviation (MAD)</h4><p>We calculate the median absolute deviation (MAD) to measure the variability of net worth in a robust manner, less influenced by outliers.</p>
FROM raw_wealth_data_large
| stats median_net_worth = MEDIAN(net_worth), mad_net_worth = MEDIAN_ABSOLUTE_DEVIATION(net_worth)
| keep median_net_worth, mad_net_worth

<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1488254b38e203e/6a17d76c2f4a5c2a81fa8766/535103bff9200ff4c2566418fedf8654de30fc13-1440x335.png" alt="Visualization output" /><p>With a median net worth of 44,205.44, we can infer the typical range of Net Worth: Most individuals’ net worth falls within a range of 9,581.78 to $97,992.66.</p><h3>The statistical showdown between Net Worth and Bus Collision</h3><p>Alright, this is the moment to understand how rich we can get, based on our dataset, before getting hit by a bus. To do that, we are going to leverage ES|QL to pull our entire dataset in chunks and load it into a pandas dataframe to build a net worth probability distribution. Finally, we will determine where the ends meet between the net worth and bus collision probabilities.</p><p>The entire Python <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/esql-millionaire/millionaire.ipynb">notebook is available here</a>. I also recommend you read <a href="https://www.elastic.co/search-labs/blog/esql-pandas-dataframes-python">this blog post</a> which walks you through using ES|QL with pandas dataframes.</p><h4>Helper functions</h4><p>As you can see in the previously referred blog post, we introduced support for ES|QL since version 8.12 of the Elasticsearch python client. Thus our notebook first defines the below functions:</p>
from io import StringIO

# Function to execute ESQL query and fetch data in chunks
def execute_esql_query(query):
    response = client.esql.query(query=query, format="csv")
    return pd.read_csv(StringIO(response.body))

# Function to fetch paginated data using the counter field
def fetch_paginated_data(index, num_records, size=10000):
    all_data = pd.DataFrame()
    for start in range(1, num_records + 1, size):
        end = start + size - 1
        query = f"""
        FROM {index}
        | WHERE counter &gt;= {start} AND counter &lt;= {end}
        | limit {size}
        """
        data_chunk = execute_esql_query(query)
        all_data = pd.concat([all_data, data_chunk], ignore_index=True)
    return all_data

<p>The first function is straightforward and executes an ES|QL query, the second is fetching the entire dataset from our index. Notice the trick in there that I am using a counter built-in to a field in my index to paginate through the data. This is workaround I am using while our engineering team is working on <a href="https://github.com/elastic/elasticsearch/issues/100000">the support for pagination in ES|QL</a>.</p><p>Next, knowing that we have 500K documents in our index, we simply call these function to load the data in a data frame:</p>
# Fetch all data using pagination and ES|QL
num_records = 500000
all_data_df = fetch_paginated_data(index_name, num_records)
print(f"Total Data Retrieved: {len(all_data_df)} records")

<h4>Fit Pareto distribution</h4><p>Next, we fit our data to a Pareto distribution, which is often used to model wealth distribution because it reflects the reality that a small percentage of the population controls most of the wealth. By fitting our data to this distribution, we can more accurately represent the probabilities of different net worth levels.</p>from scipy.stats import pareto



# Fit a Pareto distribution to the data
shape, loc, scale = pareto.fit(all_data_df['net_worth'], floc=0)

# Calculate the probability density for each net worth
all_data_df['net_worth_probability'] = pareto.pdf(all_data_df['net_worth'], shape, loc=loc, scale=scale)

# Normalize the probabilities to sum to 1
all_data_df['net_worth_probability'] /= all_data_df['net_worth_probability'].sum()

print("Data with Net Worth Probability:")
print(all_data_df.head())

<p>We can visualize the pareto distribution with the code below: ``</p>
import matplotlib.pyplot as plt
from scipy.stats import pareto

# Assuming all_data_df contains the fetched net worth data from Elasticsearch
# Fit a Pareto distribution to the data
shape, loc, scale = pareto.fit(all_data_df['net_worth'], floc=0)

# Plot the Net Worth Probability Distribution
plt.figure(figsize=(10, 6))

# Plot histogram of empirical net worth data
plt.hist(all_data_df['net_worth'], bins=100, density=True, alpha=0.6, color='g', label='Empirical Data')

# Plot fitted Pareto distribution
xmin, xmax = plt.xlim()
x = np.linspace(xmin, xmax, 100)
p = pareto.pdf(x, shape, loc=loc, scale=scale)
plt.plot(x, p, 'k', linewidth=2, label='Fitted Pareto Distribution')

# Show the plot
plt.xlabel('Net Worth')
plt.y bnblabel('Probability')
plt.title('Net Worth Probability Distribution')
plt.legend()
plt.grid(True)
plt.show()

<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c446c9de49ec808/6a17d76d2f4a5c686bfa876a/31a715f9d4881c6662c67fe72178635b5836a033-1440x942.png" alt="Pareto" /><h4>Breaking point</h4><p>Finally, with the calculated probability, we determine the target net worth corresponding to the bus hit probability and visualize it. Remember, we use the magic number ChatGPT gave us for the probability of getting hit by a bus:</p>
# Find the Net Worth Corresponding to the Bus Hit Probability
target_probability = 0.0000181
cumulative_probability = all_data_df['net_worth_probability'].cumsum()
target_net_worth_df = all_data_df[cumulative_probability &gt;= target_probability].head(1)
target_net_worth = target_net_worth_df['net_worth'].iloc[0]
print(f"Net Worth with Probability &gt;= {target_probability}: {target_net_worth}")

# Plot the Net Worth Probability Distribution
plt.figure(figsize=(10, 6))
plt.hist(all_data_df['net_worth'], bins=100, density=True, alpha=0.6, color='g', label='Empirical Data')
xmin, xmax = plt.xlim()
x = np.linspace(xmin, xmax, 100)
p = pareto.pdf(x, shape, loc=loc, scale=scale)
plt.plot(x, p, 'k', linewidth=2, label='Fitted Pareto Distribution')
plt.axhline(y=target_probability, color='r', linestyle='--', label='Bus Hit Probability')
plt.axvline(x=target_net_worth, color='g', linestyle='--', label=f'Net Worth = {target_net_worth:.2f}')
plt.xlabel('Net Worth')
plt.ylabel('Probability')
plt.title('Net Worth Probability Distribution')
plt.legend()
plt.grid(True)
plt.show()

<h2>Conclusion</h2><p>Based on our synthetic dataset, this chart vividly illustrates that the probability of amassing a net worth of approximately $12.5 million is as rare as the chance of being hit by a bus. For the fun of it, let’s ask ChatGPT what the probability is:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt224d185dc880bf21/6a17d76f7f6f1581edc09989/077e13f5ebce5019d00b7374ad6fd22dbcf7fe0b-1440x925.png" alt="Probaility Distribution" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d0ab89f76b8b2e2/6a17d77063baff5bf1741ac3/4aaa32cd60d59770137ae5a3fb582675e606bbd8-1440x210.png" alt="Net worth" /><p>Okay… $439 million? I think ChatGPT might be hallucinating again.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-query-language-esql-statistical-analysis</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-query-language-esql-statistical-analysis</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Baha Azarmi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1acb1d84387a6310/6a17d7726df7314a250a0d48/274867ef7971390c5d1d4f535c76e50a9f4a8224-1206x1522.png" length="0" type="image/png"/>
    <pubDate>Tue, 20 Aug 2024 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>
  </channel>
</rss>