在本教程中,我将引导您使用 Elasticsearch、OpenAI、LangChain 和 FastAPI 构建语义搜索服务。
LangChain 是这个领域的新酷孩子。 它是一个旨在帮助你与大型语言模型 (LLM) 交互的库。 LangChain 简化了与 LLMs 相关的许多日常任务,例如从文档中提取文本或在向量数据库中对它们建立索引。 如果你现在正在与 LLMs 一起工作,LangChain 可以节省你的工作时间。
然而,它的一个缺点是,尽管它的文档很广泛,但可能比较分散,对于新手来说很难理解。 此外,大多数在线内容都集中在最新一代的向量数据库上。 由于许多组织仍在使用 Elasticsearch 这样经过实战考验的技术,我决定使用它编写一个教程。
我将 LangChain 和 Elasticsearch 结合到了最常见的 LLM 应用之一:语义搜索。 在本教程中,我将引导你使用 Elasticsearch、OpenAI、LangChain 和 FastAPI 构建语义搜索服务。 你将创建一个应用程序,让用户可以提出有关马可·奥勒留《沉思录》的问题,并通过从书中提取最相关的内容为他们提供简洁的答案。
让我们深入了解吧!
你应该熟悉这些主题才能充分利用本教程:
此外,你必须安装 Docker 并在 OpenAI 上创建一个帐户。
你将构建一个包含三个组件的服务:
这是该架构的示意图:
接下来,你将设置本地环境。
请按照以下步骤设置您的本地环境:
1)安装 Python 3.10。
2)安装 Poetry。 它是可选的,但强烈推荐。
sudo pip install poetry
3) 克隆项目的存储库:
git clone https://github.com/liu-xiao-guo/semantic-search-elasticsearch-openai-langchain
4)从项目的根文件夹中,安装依赖项:
- poetry config virtualenvs.in-project true
- poetry install
- python3.10 -m venv .venv && source .venv/bin/activate
- pip install -r requirements.txt
5)打开 src/.env-example,添加你的 OpenAI 密钥,并将文件另存为 .env。
- (.venv) $ pwd
- /Users/liuxg/python/semantic-search-elasticsearch-openai-langchain/src
- (.venv) $ ls -al
- total 32
- drwxr-xr-x 7 liuxg staff 224 Sep 17 17:27 .
- drwxr-xr-x 13 liuxg staff 416 Sep 17 21:23 ..
- -rw-r--r-- 1 liuxg staff 41 Sep 17 17:27 .env-example
- -rw-r--r-- 1 liuxg staff 870 Sep 17 17:27 app.py
- -rw-r--r-- 1 liuxg staff 384 Sep 17 17:27 config.py
- drwxr-xr-x 3 liuxg staff 96 Sep 17 17:27 data
- -rw-r--r-- 1 liuxg staff 840 Sep 17 17:27 indexer.py
- (.venv) $ mv .env-example .env
- (.venv) $ vi .env
到目前为止,你将设置一个包含所需库和存储库的本地副本的虚拟环境。 你的项目结构应该如下所示:
- .
- ├── LICENSE
- ├── README.md
- ├── docker-compose.yml
- ├── .env
- ├── poetry.lock
- ├── pyproject.toml
- ├── requirements.txt
- └── src
- ├── app.py
- ├── config.py
- ├── .env
- ├── .env-example
- ├── data
- │ └── Marcus_Aurelius_Antoninus_-_His_Meditations_concerning_himselfe
- └── indexer.py
请注意:在上面的文件结构中,有两个 .env 文件。根目录下的 .env 文件是为 docker-compose.yml 文件所使用,而 src 目录里的文件是为应用所示使用。我们可以在根目录里的 .env 文件中定义想要的 Elastic Stack 版本号。
这些是项目中最相关的文件和目录:
全做完了! 我们继续向下进行吧。
在我们进入代码之前,你应该启动一个本地 Elasticsearch 集群。 打开一个新终端,导航到项目的根文件夹,然后运行:
docker-compose up
在上面的部署中,出于方便,我们使用了没有带安全的 Elastic Stack 的安装以方便进行开发。具体的安装步骤,请参阅另外一篇文章 “Elasticsearch:如何在 Docker 上运行 Elasticsearch 8.x 进行本地开发”。如果一切顺利,我们可以使用如下的命令来进行查看:
docker ps
- $ docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- a2866c0356a2 kibana:8.9.2 "/bin/tini -- /usr/l…" 4 minutes ago Up 4 minutes 0.0.0.0:5601->5601/tcp kibana
- b504079c59ea elasticsearch:8.9.2 "/bin/tini -- /usr/l…" 4 minutes ago Up 4 minutes 0.0.0.0:9200->9200/tcp, 9300/tcp elasticsearch
我们可以在浏览器中针对 Elasticsearch 进行访问:
我们还可以在 localhost:5601 上访问 Kibana:
在此步骤中,你将执行两件事:
看一下 src/indexer.py:
- from langchain.document_loaders import BSHTMLLoader
- from langchain.embeddings.openai import OpenAIEmbeddings
- from langchain.text_splitter import RecursiveCharacterTextSplitter
- from langchain.vectorstores import ElasticVectorSearch
-
- from config import Paths, openai_api_key
-
-
- def main():
- loader = BSHTMLLoader(str(Paths.book))
- data = loader.load()
-
- text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
- chunk_size=1000, chunk_overlap=0
- )
- documents = text_splitter.split_documents(data)
-
- embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key)
- db = ElasticVectorSearch.from_documents(
- documents,
- embeddings,
- elasticsearch_url="http://localhost:9200",
- index_name="elastic-index",
- )
- print(db.client.info())
-
-
- if __name__ == "__main__":
- main()
此代码采用 Meditations(书),将其拆分为 1,000 个 token 的文本块,然后在 Elasticsearch 集群中为这些块建立索引。 以下是详细的细分:
要运行此脚本,请打开终端,激活虚拟环境,然后从项目的 src 文件夹中运行以下命令:
- # ../src/
- export export OPENAI_API_KEY=your_open_ai_token
- python indexer.py
注意:你如果使用 OpenAI 来进行矢量化,那么你需要在你的账号中有充分的钱来支付这种费用,否则你可能得到如下的错误信息:
Retrying langchain.embeddings.openai.embed_with_retry.
._embed_with_retry in 4.0 seconds as it raised RateLimitError: You exceeded your current quota, please check your plan and billing details..
如果一切顺利,你应该得到与此类似的输出:
{'name': '0e1113eb2915', 'cluster_name': 'docker-cluster', 'cluster_uuid': 'og6mFMqwQtaJiv_3E_q2YQ', 'version': {'number': '8.9.2', 'build_flavor': 'default', 'build_type': 'docker', 'build_hash': '09520b59b6bc1057340b55750186466ea715e30e', 'build_date': '2023-03-27T16:31:09.816451435Z', 'build_snapshot': False, 'lucene_version': '9.5.0', 'minimum_wire_compatibility_version': '7.17.0', 'minimum_index_compatibility_version': '7.0.0'}, 'tagline': 'You Know, for Search'}
接下来,让我们创建一个简单的 FastAPI 应用程序,以与你的集群进行交互。
在此步骤中,你将创建一个简单的应用程序来与 Meditations 交互。 你将连接到 Elasticsearch 集群,始化检索提问/应答 Chain,并创建一个 /ask 端点以允许用户与应用程序交互。
看一下 src/app.py 的代码:
- from fastapi import FastAPI
- from langchain.chains import RetrievalQA
- from langchain.chat_models import ChatOpenAI
- from langchain.embeddings.openai import OpenAIEmbeddings
- from langchain.vectorstores import ElasticVectorSearch
-
- from config import openai_api_key
-
- embedding = OpenAIEmbeddings(openai_api_key=openai_api_key)
-
- db = ElasticVectorSearch(
- elasticsearch_url="http://localhost:9200",
- index_name="elastic-index",
- embedding=embedding,
- )
- qa = RetrievalQA.from_chain_type(
- llm=ChatOpenAI(temperature=0),
- chain_type="stuff",
- retriever=db.as_retriever(),
- )
-
- app = FastAPI()
-
-
- @app.get("/")
- def index():
- return {
- "message": "Make a post request to /ask to ask questions about Meditations by Marcus Aurelius"
- }
-
-
- @app.post("/ask")
- def ask(query: str):
- response = qa.run(query)
- return {
- "response": response,
- }
此代码允许用户提出有关马库斯·奥勒留《沉思录》的问题,并向用户提供答案。 让我向你展示它是如何工作的:
最后,你可以从终端运行该应用程序(使用你的虚拟环境):
uvicorn app:app --reload
然后,访问 http://127.0.0.1:8000/docs,并通过询问有关这本书的问题来测试 /ask:
如果一切顺利,你应该得到这样的结果:
就是这样! 您现在已经启动并运行了自己的基于 Elasticsearch、OpenAI、Langchain 和 FastAPI 的语义搜索服务。
干得好! 在本教程中,你学习了如何使用 Elasticsearch、OpenAI 和 Langchain 构建语义搜索引擎。
特别是,你已经了解到:
希望您觉得本教程有用。 如果你有任何疑问,请参入讨论!