使用 Elasticsearch 和 OpenAI 构建生成式 AI 应用程序

本笔记本演示了如何:

  • 将 OpenAI Wikipedia 向量数据集索引到 Elasticsearch 中
  • 使用 Streamlit 构建一个简单的 Gen AI 应用程序,该应用程序使用 Elasticsearch 检索上下文并使用 OpenAI 制定答案

安装

安装 Elasticsearch 及 Kibana

如果你还没有安装好自己的 Elasticsearch 及 Kibana,那么请参考一下的文章来进行安装:

在安装的时候,请选择 Elastic Stack 8.x 进行安装。在安装的时候,我们可以看到如下的安装信息:

环境变量

在启动 Jupyter 之前,我们设置如下的环境变量:

ini 复制代码
1.  export ES_USER="elastic"
2.  export ES_PASSWORD="xnLj56lTrH98Lf_6n76y"
3.  export ES_ENDPOINT="localhost"
4.  export OPENAI_API_KEY="YourOpenAiKey"

请在上面修改相应的变量的值。这个需要在启动 jupyter 之前运行。

拷贝 Elasticsearch 证书

我们把 Elasticsearch 的证书拷贝到当前的目录下:

bash 复制代码
1.  $ pwd
2.  /Users/liuxg/python/elser
3.  $ cp ~/elastic/elasticsearch-8.12.0/config/certs/http_ca.crt .
4.  $ ls http_ca.crt 
5.  http_ca.crt

安装 Python 依赖包

ini 复制代码
python3 -m pip install -qU openai pandas==1.5.3 wget elasticsearch streamlit tqdm load_dotenv

准备数据

我们可以使用如下的命令来下载数据:

bash 复制代码
wget https://cdn.openai.com/API/examples/data/vector_database_wikipedia_articles_embedded.zip
markdown 复制代码
1.  $ pwd
2.  /Users/liuxg/python/elser
3.  $ wget https://cdn.openai.com/API/examples/data/vector_database_wikipedia_articles_embedded.zip
4.  --2024-02-09 12:06:36--  https://cdn.openai.com/API/examples/data/vector_database_wikipedia_articles_embedded.zip
5.  Resolving cdn.openai.com (cdn.openai.com)... 13.107.213.69
6.  Connecting to cdn.openai.com (cdn.openai.com)|13.107.213.69|:443... connected.
7.  HTTP request sent, awaiting response... 200 OK
8.  Length: 698933052 (667M) [application/zip]
9.  Saving to: 'vector_database_wikipedia_articles_embedded.zip'

11.  vector_database_wikipedi 100%[==================================>] 666.55M  1.73MB/s    in 3m 2s   

13.  2024-02-09 12:09:40 (3.66 MB/s) - 'vector_database_wikipedia_articles_embedded.zip' saved [698933052/698933052]

创建应用并展示

我们在当前的目录下打入如下的命令来创建 notebook:

markdown 复制代码
1.  $ pwd
2.  /Users/liuxg/python/elser
3.  $ jupyter notebook

导入包及连接到 Elasticsearch

python 复制代码
1.  import os
2.  from getpass import getpass
3.  from elasticsearch import Elasticsearch, helpers
4.  import wget, zipfile, pandas as pd, json, openai
5.  import streamlit as st
6.  from tqdm.notebook import tqdm
7.  from dotenv import load_dotenv

9.  load_dotenv()

11.  openai_api_key=os.getenv('OPENAI_API_KEY')
12.  elastic_user=os.getenv('ES_USER')
13.  elastic_password=os.getenv('ES_PASSWORD')
14.  elastic_endpoint=os.getenv("ES_ENDPOINT")

16.  url = f"https://{elastic_user}:{elastic_password}@{elastic_endpoint}:9200"
17.  client = Elasticsearch(url, ca_certs = "./http_ca.crt", verify_certs = True)

19.  print(client.info())

配置 OpenAI 连接

我们的示例将使用 OpenAI 来制定答案,因此请在此处提供有效的 OpenAI Api 密钥。

你可以按照本指南检索你的 API 密钥。然后测试与OpenAI的连接,检查该笔记本使用的型号是否可用。

javascript 复制代码
1.  from openai import OpenAI

3.  openai = OpenAI()
4.  openai.models.retrieve("text-embedding-ada-002")
markdown 复制代码
1.  $ pip3 list | grep openai
2.  langchain-openai                         0.0.5
3.  openai                                   1.12.0

下载数据集

csharp 复制代码
1.  with zipfile.ZipFile("vector_database_wikipedia_articles_embedded.zip",
2.  "r") as zip_ref:
3.      zip_ref.extractall("data")

运行上面的代码后,我们可以在如下地址找到解压缩的文件 vector_database_wikipedia_articles_embedded.csv:

bash 复制代码
1.  $ pwd
2.  /Users/liuxg/python/elser
3.  $ ls ./data
4.  __MACOSX                                        vector_database_wikipedia_articles_embedded.csv
5.  paul_graham

将 CSV 文件读入 Pandas DataFrame

接下来,我们使用 Pandas 库将解压的 CSV 文件读入 DataFrame。 此步骤可以更轻松地将数据批量索引到 Elasticsearch 中。

ini 复制代码
wikipedia_dataframe = pd.read_csv("data/vector_database_wikipedia_articles_embedded.csv")

使用映射创建索引

现在我们需要使用必要的映射创建一个 Elasticsearch 索引。 这将使我们能够将数据索引到 Elasticsearch 中。

我们对 title_vector 和 content_vector 字段使用密集向量字段类型。 这是一种特殊的字段类型,允许我们在 Elasticsearch 中存储密集向量。

稍后,我们需要以密集向量字段为目标进行 kNN 搜索。

lua 复制代码
1.  index_mapping= {
2.      "properties": {
3.        "title_vector": {
4.            "type": "dense_vector",
5.            "dims": 1536,
6.            "index": "true",
7.            "similarity": "cosine"
8.        },
9.        "content_vector": {
10.            "type": "dense_vector",
11.            "dims": 1536,
12.            "index": "true",
13.            "similarity": "cosine"
14.        },
15.        "text": {"type": "text"},
16.        "title": {"type": "text"},
17.        "url": { "type": "keyword"},
18.        "vector_id": {"type": "long"}

20.      }
21.  }
22.  client.indices.create(index="wikipedia_vector_index", mappings=index_mapping)

将数据索引到 Elasticsearch

以下函数生成所需的批量操作,这些操作可以传递到 Elasticsearch 的 bulk API,因此我们可以在单个请求中有效地索引多个文档。

对于 DataFrame 中的每一行,该函数都会生成一个字典,表示要索引的单个文档。

sql 复制代码
1.  def dataframe_to_bulk_actions(df):
2.      for index, row in df.iterrows():
3.          yield {
4.              "_index": 'wikipedia_vector_index',
5.              "_id": row['id'],
6.              "_source": {
7.                  'url' : row["url"],
8.                  'title' : row["title"],
9.                  'text' : row["text"],
10.                  'title_vector' : json.loads(row["title_vector"]),
11.                  'content_vector' : json.loads(row["content_vector"]),
12.                  'vector_id' : row["vector_id"]
13.              }
14.          }

由于数据帧很大,我们将以 100 个为一组对数据进行索引。我们使用 Python 客户端的 bulk API 帮助程序将数据索引到 Elasticsearch 中。

ini 复制代码
1.  total_documents = len(wikipedia_dataframe)

3.  progress_bar = tqdm(total=total_documents, unit="documents")
4.  success_count = 0

6.  for ok, info in helpers.streaming_bulk(client, actions=dataframe_to_bulk_actions(wikipedia_dataframe), raise_on_error=False, chunk_size=100):
7.    if ok:
8.      success_count += 1
9.    else:
10.      print(f"Unable to index {info['index']['_id']}: {info['index']['error']}")
11.    progress_bar.update(1)
12.    progress_bar.set_postfix(success=success_count)

等上面的代码运行完毕后,我们可以在 Kibana 中进行查看:

使用 Streamlit 构建应用程序

在下一节中, 你将使用 Streamlit 构建一个简单的界面。

该应用程序将显示一个简单的搜索栏,用户可以在其中提出问题。 Elasticsearch 用于检索与问题匹配的相关文档(上下文),然后 OpenAI 使用上下文制定答案。

安装依赖项以在运行后访问应用程序。

diff 复制代码
!npm install localtunnel
ini 复制代码
1.  %%writefile app.py

3.  import os
4.  import streamlit as st
5.  import openai
6.  from elasticsearch import Elasticsearch
7.  from dotenv import load_dotenv

9.  from openai import OpenAI

11.  openai = OpenAI()

13.  load_dotenv()

15.  openai_api_key=os.getenv('OPENAI_API_KEY')
16.  elastic_user=os.getenv('ES_USER')
17.  elastic_password=os.getenv('ES_PASSWORD')
18.  elastic_endpoint=os.getenv("ES_ENDPOINT")

20.  url = f"https://{elastic_user}:{elastic_password}@{elastic_endpoint}:9200"
21.  client = Elasticsearch(url, ca_certs = "./http_ca.crt", verify_certs = True)

23.  # Define model
24.  EMBEDDING_MODEL = "text-embedding-ada-002"

27.  def openai_summarize(query, response):
28.      context = response['hits']['hits'][0]['_source']['text']
29.      summary = openai.chat.completions.create(
30.      model="gpt-3.5-turbo",
31.      messages=[
32.              {"role": "system", "content": "You are a helpful assistant."},
33.              {"role": "user", "content": "Answer the following question:" + query + "by using the following text: " + context},
34.          ]
35.      )

37.      print(summary)
38.      return summary.choices[0].message.content

41.  def search_es(query):
42.      # Create embedding
43.      question_embedding = openai.embeddings.create(input=query, model=EMBEDDING_MODEL)

45.      # Define Elasticsearch query
46.      response = client.search(
47.      index = "wikipedia_vector_index",
48.      knn={
49.          "field": "content_vector",
50.          "query_vector":  question_embedding.data[0].embedding,
51.          "k": 10,
52.          "num_candidates": 100
53.          }
54.      )
55.      return response

58.  def main():
59.      st.title("Gen AI Application")

61.      # Input for user search query
62.      user_query = st.text_input("Enter your question:")

64.      if st.button("Search"):
65.          if user_query:

67.              st.write(f"Searching for: {user_query}")
68.              result = search_es(user_query)

70.              # print(result)
71.              openai_summary = openai_summarize(user_query, result)
72.              st.write(f"OpenAI Summary: {openai_summary}")

74.              # Display search results
75.              if result['hits']['total']['value'] > 0:
76.                  st.write("Search Results:")
77.                  for hit in result['hits']['hits']:
78.                      st.write(hit['_source']['title'])
79.                      st.write(hit['_source']['text'])
80.              else:
81.                  st.write("No results found.")

83.  if __name__ == "__main__":
84.      main()

运行应用

运行应用程序并检查您的隧道 IP:

arduino 复制代码
!streamlit run app.py

整个 notebook 的源码可以在地址下载:github.com/liu-xiao-gu...

相关推荐
厚道4 小时前
Elasticsearch 的存储原理
后端·elasticsearch
朴拙数科6 小时前
在 macOS 上安装与自定义 Oh My Zsh:让终端美观又高效 [特殊字符]
大数据·elasticsearch·macos
张先shen10 小时前
Elasticsearch深度分页解决方案:search_after原理剖析
大数据·elasticsearch·搜索引擎
Fireworkitte13 小时前
ES 压缩包安装
大数据·elasticsearch
厚道1 天前
ES查询性能优化
elasticsearch
Fireworkitte2 天前
安装 Elasticsearch IK 分词器
大数据·elasticsearch
huisheng_qaq3 天前
【ElasticSearch实用篇-01】需求分析和数据制造
大数据·elasticsearch·制造
G皮T3 天前
【Elasticsearch】自定义评分检索
大数据·elasticsearch·搜索引擎·查询·检索·自定义评分·_score
feilieren3 天前
Docker 安装 Elasticsearch 9
运维·elasticsearch·docker·es
Java烘焙师4 天前
架构师必备:业务扩展模式选型
mysql·elasticsearch·架构·hbase·多维度查询