前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 LangChain 和 Elasticsearch 实现隐私优先的人工智能搜索

使用 LangChain 和 Elasticsearch 实现隐私优先的人工智能搜索

原创
作者头像
点火三周
发布2023-07-25 17:40:38
2.6K0
发布2023-07-25 17:40:38
举报
文章被收录于专栏:Elastic Stack专栏

By Dave Erickson

过去几个周末,我一直沉浸在“即时工程”的迷人世界中,学习Elasticsearch® 等向量数据库如何通过充当长期记忆和语义知识存储来增强 ChatGPT 等大型语言模型 (LLM)。然而,困扰我和许多其他经验丰富的数据架构师的一件事是,许多教程和演示完全依赖于向大型网络公司和基于云的人工智能公司发送您的私人数据。 

私人数据有多种形式,并且受到保护的原因不止一个。对于初创公司和企业公司来说,他们都知道他们的私人数据有时是他们的竞争优势。内部数据和客户数据通常包含个人身份信息,如果不加以保护,这些信息将产生法律和现实世界的严重后果。在可观察性和安全领域,对第三方服务的不谨慎使用可能是数据泄露的根源。我们甚至听说过与人工智能聊天工具的使用有关的网络安全漏洞的指控。

没有任何设计是无风险或完全私有的,即使与像 Elastic 这样对隐私和安全做出坚定承诺的公司合作或在真正的air gap环境中进行部署也是如此。然而,我已经处理了足够多的敏感数据用例,知道通过隐私优先的方法实现人工智能搜索具有非常实际的价值。我很喜欢我的同事Jeff Vestal关于将 OpenAI 工具与 Elasticsearch 结合使用的精彩演示,但本文将采用不同的方法。

我对该项目的方法有两个目标:

  • 私人——当我说私人时,我是认真的。虽然我将使用云托管的 Elasticsearch,但如果使用情况要求,我希望它完全脱网运行。让我们证明我们可以在不向第三方发送私密信息的情况下实现人工智能搜索的功能。
  • 乐趣——另外,让我们在做的过程中享受一些乐趣。我们将使用 Wookieepedia(一个在数据科学练习中很受欢迎的星球大战社区维基)的一部分,并制作一个私人 AI 问答助手。

跟随并亲自尝试的最简单方法是在 腾讯云 Elasticsearch Service 上启动 Elasticsearch 实例并运行 提供的 Python Notebook,这将小规模地实现该项目。如果您想运行完整的 Wookieepedia 抓取 180K 段落的星球大战知识并创建熟练的星球大战知识搜索,您可以按照此处 GitHub 存储库 中的代码进行操作。

全部完成后,它应该看起来像这样:

本着开放的精神,让我们引入两项开源技术来帮助 Elasticsearch:Hugging Face transformer 库和名为LangChain的新的、有趣的 Python 库,这将加快 Elasticsearch 作为向量数据库的使用速度。一旦设置好,LangChain将使我们的LLMs在程序上可以互换,这样我们就可以自由地尝试各种模型。

它将如何运作

什么是 LangChain ?LangChain 是一个 Python 和 JavaScript 框架,用于开发由大型语言模型支持的应用程序。LangChain 将与 OpenAI 的 API 配合使用,但它也擅长抽象数据库和 AI 工具之间的差异。

就其本身而言,ChatGPT 在星球大战智力问答方面的表现并不差。然而,它的训练数据集已经有几年的历史了,我们想要有关星球大战宇宙中最新电视节目和事件的答案。另请记住,我们假装这些数据过于私密,无法与云中的大型LLM共享。我们可以使用更新的数据自行调整大型语言模型,但是有一种更简单的方法可以完成此任务,该方法还允许我们始终使用可用的最新数据。

今天,让我们介绍一个规模较小且易于自行托管的LLM。我使用 Google 的 flan-t5-large 模型得到了很好的结果,它具有从注入的上下文中解析出答案的良好能力,弥补了训练的不足。我们将使用语义搜索来检索我们的私人知识,然后将带有问题的上下文注入到我们的私人LLM中。

  1. 从 Wookieepedia 中抓取所有经典文章,将数据放入暂存的 Python Pickle 文件中。

2A. 使用LangChain 的内置 Vectorstore 库将这些文章的每个段落加载到 Elasticsearch 中。

2B. 或者,我们可以使用在Elasticsearch中托管的PyTorch transformers。我们将把文本嵌入模型部署到 Elasticsearch,以利用分布式计算并加快流程。

  1. 当我们进行提问时,将使用 Elasticsearch 的向量搜索找到与该问题在语义上最相似的段落。然后,我们将把该段落添加到一个小型本地LLM的提示中,作为问题的背景,然后将其留给生成式人工智能的魔力,以获得们的智力问答问题的简短答案。

设置Python和Elasticsearch环境

确保您的计算机上安装有 Python 3.9 或类似版本。我使用 3.9 是为了更轻松地实现库与 GPU 加速的兼容性,但这对于该项目来说不是必需的。任何最新的 3.X 版本的 Python 都可以工作。

代码语言:shell
复制
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install beautifulsoup4 eland elasticsearch huggingface-hub langchain tqdm torch requests sentence_transformers

如果您已经下载了示例代码,则可以使用以下 pip install 命令提取我所使用的代码的确切版本。

代码语言:shell
复制
pip install -r requirements.txt

您可以按照此处的说明设置 Elasticsearch 集群。使用云上的资源是最简单的方式。

在该文件夹中创建一个 .env 文件并加载 Elasticsearch 的连接详细信息。

代码语言:shell
复制
export ES_SERVER="YOURDESSERVERNAME.es.us-central1.gcp.cloud.es.io"
export ES_USERNAME="YOUR READ WRITE AND INDEX CREATING USER"
export ES_PASSWORD="YOUR PASSWORD"

步骤 1. 抓取数据

在上面下载的代码仓库中有一个小的数据集位于Dataset/starwars_small_sample_data.pickle。如果您可以在这个小数据集上继续,则可以跳过此步骤。

抓取代码改编自Dennis Bakhuis 的优秀数据科学博客项目- 请查看!他只提取每篇文章的第一段,而我更改了代码以提取全部内容。他可能需要将数据保持在适合主内存的大小,但我们不存在这个问题,因为我们有 Elasticsearch,这将使数据能够很好地扩展到 PB 级别。

您还可以在这里轻松插入您自己的私有数据源。LangChain 有一些优秀的实用程序库,可以将文本数据分割成更短的块。

抓取不是本文的重点,因此如果您想自己小规模运行它,请查看 Python Notebook,或者下载源代码并按如下方式运行:

代码语言:javascript
复制
source .env
python3 step-1A-scrape-urls.py
python3 step-1B-scrape-content.py

完成后,您应该能够像这样浏览保存的 Pickle 文件以确保它有效。

代码语言:javascript
复制
from pathlib import Path
import pickle


bookFilePath = "starwars_*_data*.pickle"
files = sorted(Path('./Dataset').glob(bookFilePath))
for fn in files:
   with open(fn,'rb') as f:
       part = pickle.load(f)
       for key, value in part.items():
           title = value['title'].strip()
           print(title)

如果您跳过了网页抓取,只需将bookFilePath更改为“ starwars_small_sample_data.pickle ”即可使用我在 GitHub 存储库中包含的示例。

步骤 2-a, 在 Elasticsearch 中加载嵌入

完整的代码显示了我如何仅使用 LangChain 来完成此操作。代码的关键部分是像上面的示例一样循环遍历保存的 Pickle 文件,并提取出作为段落的字符串列表,然后将它们传递给LangChain Vectorstore的from_texts()函数。

代码语言:javascript
复制
from langchain.vectorstores import ElasticVectorSearch
from langchain.embeddings import HuggingFaceEmbeddings
from pathlib import Path
import pickle
import os
from tqdm import tqdm


model_name = "sentence-transformers/all-mpnet-base-v2"
hf = HuggingFaceEmbeddings(model_name=model_name)


index_name = "book_wookieepedia_mpnet"
endpoint = os.getenv('ES_SERVER', 'ERROR')
username = os.getenv('ES_USERNAME', 'ERROR')
password = os.getenv('ES_PASSWORD', 'ERROR')
url = f"https://{username}:{password}@{endpoint}:443"
db = ElasticVectorSearch(embedding=hf, elasticsearch_url=url, index_name=index_name)


batchtext = []
bookFilePath = "starwars_*_data*.pickle"
files = sorted(Path('./Dataset').glob(bookFilePath))
for fn in files:
    with open(fn,'rb') as f:
       part = pickle.load(f)
       for ix, (key, value) in tqdm(enumerate(part.items()), total=len(part)):
           paragraphs = value['paragraph']
           for p in paragraphs:
               batchtext.append(p)
       db.from_texts(batchtext,
                     embedding=hf,
                     elasticsearch_url=url,
                     index_name=index_name)

步骤2-b 通过托管训练模型节省时间和金钱

我发现在我的旧版Intel MacBook上,创建嵌入式向量需要花费很多小时的处理时间。我说话客气些 - 看起来可能需要好几天。我认为我可以使用Elastic托管服务的动态可扩展的机器学习(ML)节点更快地完成,而且成本更低。

最终结果:这种方法在Elastic Cloud上运行的节点上花费了40分钟,每小时成本为5美元,比我本地运行要快得多,并且与处理嵌入式向量的OpenAI当前的 token 费用相当。高效地完成这一步是一个更大的主题,但我对于在Elastic Cloud中能够快速获得并行推理流程而无需学习新技能或将我的数据交给非私有API感到印象深刻。

对于这一步,我们将把嵌入生成交给 Elasticsearch 集群,该集群可以托管嵌入模型并以分布式方式嵌入文本段落。为此,我们必须加载数据并使用摄取管道确保最终形式与 LangChain 使用的索引映射匹配。在 Kibana 的开发工具中运行以下 REST 命令:

代码语言:javascript
复制
PUT /book_wookieepedia_mpnet
{
 "settings": {
   "number_of_shards": 4
 },
 "mappings": {
   "properties": {
     "metadata": {
       "type": "object"
     },
     "text": {
       "type": "text"
     },
     "vector": {
       "type": "dense_vector",
       "dims": 768
     }
   }
 }
}

接下来,我们将使用 eland Python 库将嵌入模型上传到 Elasticsearch。

代码语言:shell
复制
source .env
python3 step-3A-upload-model.py

接下来,我们进入 Elastic Cloud 控制台,将 ML 层扩展到 64 个 vCPU(我当前笔记本电脑性能的 8 倍)。

现在,我们将在 Kibana 中部署经过训练的 ML 模型。在规模上,性能测试表明,用户应该从每个模型allocation 1 个 thread 开始,并增加 allocation 数量以提高吞吐量。可以在此处找到文档和指南。我进行了实验,对于这个较小的集合,我在 32 个 allocation(每个 allocation 2 个 thread)的配置上得到了最佳结果。要进行设置,请转至 Stack Management > Machine Learning 。使用 Synchronize saved objects 功能使 Kibana 看到我们使用 Python 代码推送到 Elasticsearch 的模型。然后在单击时出现的菜单中部署模型。 

现在,让我们再次使用开发工具创建一个新的索引和摄取管道,用于处理文档中的文本段落,将结果放入名为“vector”的密集向量字段中,并将该段落复制到预期的“text”字段。

代码语言:json
复制
PUT /book_wookieepedia_mpnet
{
 "settings": {
   "number_of_shards": 4
 },
 "mappings": {
   "properties": {
     "metadata": {
       "type": "object"
     },
     "text": {
       "type": "text"
     },
     "vector": {
       "type": "dense_vector",
       "dims": 768
     }
   }
 }
}


PUT _ingest/pipeline/sw-embeddings
{
 "description": "Text embedding pipeline",
 "processors": [
   {
     "inference": {
       "model_id": "sentence-transformers__all-mpnet-base-v2",
       "target_field": "text_embedding",
       "field_map": {
         "text": "text_field"
       }
     }
   },
   {
     "set":{
       "field": "vector",
       "copy_from": "text_embedding.predicted_value"
     }
   },
   {
     "remove": {
       "field": "text_embedding"
     }
   }
 ],
 "on_failure": [
   {
     "set": {
       "description": "Index document to 'failed-<index>'",
       "field": "_index",
       "value": "failed-{{{_index}}}"
     }
   },
   {
     "set": {
       "description": "Set error message",
       "field": "ingest.failure",
       "value": "{{_ingest.on_failure_message}}"
     }
   }
 ]
}

测试管道以确保其正常工作。

代码语言:json
复制
POST _ingest/pipeline/sw-embeddings/_simulate
{
 "docs": [
   {
     "_source": {
       "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
       "metadata": {
         "a": "b"
       }
     }
   }
 ]
}

现在我们准备使用 Elasticsearch 的普通 Python 库批量加载数据,以我们的摄取管道为目标,正确创建向量嵌入并转换我们的数据以符合 LangChain 的期望。

代码语言:shell
复制
source .env
python3 step-3B-batch-hosted-vectorize.py

成功!以 OpenAI 计算,该数据约为 1300 万个 token,因此在非私有和云中处理该数据的成本约为 5.40 美元。使用 Elastic Cloud,需要再每小时花费 5 美元的机器上运行 40 分钟。

加载数据后,请记住使用云控制台将 Cloud ML 缩小到零或更合理的值。

第 3 步:赢得星球大战问答游戏

接下来我们就来玩转 LLM 和 LangChain 吧。我创建了一个库文件 lib_llm.py 来保存此代码。

代码语言:javascript
复制
from langchain import PromptTemplate, HuggingFaceHub, LLMChain
from langchain.llms import HuggingFacePipeline
from transformers import AutoTokenizer, pipeline, AutoModelForSeq2SeqLM
from langchain.vectorstores import ElasticVectorSearch
from langchain.embeddings import HuggingFaceEmbeddings
import os


cache_dir = "./cache"
def getFlanLarge():
  
   model_id = 'google/flan-t5-large'
   print(f">> Prep. Get {model_id} ready to go")
   tokenizer = AutoTokenizer.from_pretrained(model_id)
   model = AutoModelForSeq2SeqLM.from_pretrained(model_id, cache_dir=cache_dir)
  
   pipe = pipeline(
       "text2text-generation",
       model=model,
       tokenizer=tokenizer,
       max_length=100
   )
   llm = HuggingFacePipeline(pipeline=pipe)
   return llm


local_llm = getFlanLarge()


def make_the_llm():
   template_informed = """
   I am a helpful AI that answers questions.
   When I don't know the answer I say I don't know.
   I know context: {context}
   when asked: {question}
   my response using only information in the context is: """
   prompt_informed = PromptTemplate(
       template=template_informed,
       input_variables=["context", "question"])
   return LLMChain(prompt=prompt_informed, llm=local_llm)


## continued below

template_informed是其中关键但也易于理解的部分。我们所做的就是格式化一个提示模板,它将采用我们的两个参数:上下文和用户的问题。

继续上面的最终主要代码,它看起来如下所示:

代码语言:javascript
复制
## continued from above


topic = "Star Wars"
index_name = "book_wookieepedia_mpnet"


# Create the HuggingFace Transformer like before
model_name = "sentence-transformers/all-mpnet-base-v2"
hf = HuggingFaceEmbeddings(model_name=model_name)


## Elasticsearch as a vector db, just like before
endpoint = os.getenv('ES_SERVER', 'ERROR')
username = os.getenv('ES_USERNAME', 'ERROR')
password = os.getenv('ES_PASSWORD', 'ERROR')
url = f"https://{username}:{password}@{endpoint}:443"
db = ElasticVectorSearch(embedding=hf, elasticsearch_url=url, index_name=index_name)


## set up the conversational LLM
llm_chain_informed= make_the_llm()


def ask_a_question(question):
   ## get the relevant chunk from Elasticsearch for a question
   similar_docs = db.similarity_search(question)
   print(f'The most relevant passage: \n\t{similar_docs[0].page_content}')
   informed_context= similar_docs[0].page_content
   informed_response = llm_chain_informed.run(
       context=informed_context,
       question=question)
   return informed_response




# The conversational loop
print(f'I am a trivia chat bot, ask me any question about {topic}')
while True:
   command = input("User Question >> ")
   response= ask_a_question(command)
   print(f"\tAnswer  : {response}")

结论

通过一些数据处理,我们现在使用了人工智能,而不会将我们的数据暴露给第三方托管的 LLM(大型语言模型)。人工智能的世界正在迅速变化,但保护私人数据的安全性和控制性对于数据泄露的监管、财务和人类后果而言是我们都应该认真对待的事情。这种情况不太可能改变。我们与客户合作,帮助他们使用搜索来调查欺诈、保卫国家并改善弱势患者群体的结果。隐私很重要。

你是否和我一样对 LangChain 爱不释手?就像一位智慧的老绝地武士曾说过:“那很好。你已经迈出了进入更大世界的第一步。”从这里可以朝着许多方向发展。LangChain 帮助我们摆脱了与人工智能提示工程工作相关的复杂性。我知道 Elasticsearch 在作为生成式人工智能的长期记忆方面还有许多其他角色,因此我非常期待在这个快速变化的领域看到什么样的成果。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 它将如何运作
  • 设置Python和Elasticsearch环境
  • 步骤 1. 抓取数据
  • 步骤 2-a, 在 Elasticsearch 中加载嵌入
  • 步骤2-b 通过托管训练模型节省时间和金钱
  • 第 3 步:赢得星球大战问答游戏
  • 结论
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档