使用 Gemma、Hugging Face 和 Elasticsearch 构建 RAG 系统 作者来自 Elastic Ashish Tiwari这篇博客将展示如何使用 Elasticsearch 和 Python 构建一个 RAG 系统以执行语义搜索并创建一个基于你的私有数据集运行的问题回答服务。你将获取最相关的文档作为上下文窗口并将它们连同一个需要回答的问题一起发送给 Gemma 模型。更多阅读Elasticsearch在本地使用 Gemma LLM 对私人数据进行问答背景Google 推出了最先进的开放模型 Gemma。它基于 Gemini 开发过程中使用的相同研究和技术构建。你可以根据需求使用你的私有数据对 Gemma 进行自定义微调用于提出摘要问题或者通过指定上下文窗口将其用于 RAG检索增强生成。它可以满足不同类型的使用场景。Gemma 已发布两个规模版本 —— Gemma2B 和 Gemma7B。两者都有预训练版本和指令调优版本。你可以选择其中任意一个gemma-2b、gemma-2b-it、gemma-7b、gemma-7b-it。这些都是仅支持英语语言的文本到文本text-to-text和仅解码器decoder-only模型。如何使用 Gemma、Hugging Face 和 Elasticsearch 构建 RAG 系统前提条件Elastic 凭证—— 创建一个 Elastic Cloud 部署以获取所有 Elastic 凭证ELASTIC_CLOUD_ID、ELASTIC_API_KEY。ELSER v2 —— 确保 Elastic ELSERv2 模型已在你的 Elastic 实例中下载并部署。Hugging Face 令牌 —— 要开始使用 Gemma 模型需要在 Hugging Face 上同意相关条款并创建具有写入角色的访问令牌。Gemma 模型 —— 我们将使用 gemma-2b-it不过 Google 也发布了 gemma-2b、gemma-7b、gemma-7b-it 模型。Python 3.10 或更高版本概述我们将逐步构建 RAG。我们将使用 LangChain 构建一个完整流程。LangChain 提供了开发由 LLM 驱动的应用程序的框架尽管你也可以编写自己的流程来开发 RAG。通过 ELSERv2 模型处理文档为存储到 Elasticsearch 中做准备。使用 Hugging Face 在本地运行 Gemma 模型。执行语义搜索并获取最相关的文档集合。通过传递包含上下文窗口的提示词向本地运行的 Gemma 模型提问。我们将使用 Python 构建一个完整流程。1导入软件包和凭证安装所需的软件包pip install -q -U elasticsearch langchain transformers huggingface_hub torch导入所有依赖项import json import os from getpass import getpass from urllib.request import urlopen from elasticsearch import Elasticsearch, helpers from langchain.text_splitter import CharacterTextSplitter from langchain.vectorstores import ElasticsearchStore from langchain import HuggingFacePipeline from langchain.chains import RetrievalQA from langchain.prompts import ChatPromptTemplate from langchain.schema.output_parser import StrOutputParser from langchain.schema.runnable import RunnablePassthrough from huggingface_hub import login from transformers import AutoTokenizer, AutoModelForCausalLM from transformers import AutoTokenizer, pipeline我们将针对不同用途使用 LangChain 的不同模块。获取凭证ELASTIC_API_KEY getpass(Elastic API Key :) ELASTIC_CLOUD_ID getpass(Elastic Cloud ID :) elastic_index_name gemma-rag这将从用户输入中接收 ELASTIC_API_KEY 和 ELASTIC_CLOUD_ID。所有数据将存储在 gemma-rag 索引中。2准备文档下载示例数据集并反序列化文档url https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/datasets/workplace-documents.json response urlopen(url) workplace_docs json.loads(response.read())JSON 包含工作场所数据例如休假政策、居家办公政策、解释薪酬如何运作、入职步骤等。假设这是我们的私有数据集Gemma 没有针对这些数据进行训练也无法访问这些数据。最终我们的系统将只从这个 JSON 数据中查找答案。文档分块RAG 中的文档分块指的是将大型文档拆分成更小的片段以便在问答任务期间进行更高效的处理和检索。为什么需要文档分块上下文窗口限制RAG检索增强生成中的上下文窗口指的是模型检索相关信息以生成答案时所使用的文本或文档部分。它有助于为生成准确且有意义的问题回答提供必要的上下文。上下文窗口的大小可能会根据具体实现或 LLM 的限制而有所不同。LLM 对上下文窗口有大小限制。如技术报告中所述Gemma 模型的上下文长度为8192 个 tokens。因此提供超过 8192 个 token 的上下文窗口没有意义。分块有助于保持你的上下文窗口限制。注意Token 是 LLM 处理数据的基本单位。在文本上下文中一个 token 可以是一个单词、一个单词的一部分子词甚至是一个字符 —— 这取决于分词过程。平衡上下文大小在使用大型语言模型LLM时考虑输入模型的上下文大小非常重要。LLM 一次能够处理的 token 数量有限。例如GPT-3.5-turbo 的 token 限制为 4096。此外随着上下文大小增加生成响应的质量可能会下降从而导致潜在的不准确或幻觉。处理更大的上下文还会导致更长的处理时间以及更高的 LLM 使用成本。这凸显了掌握检索技巧的重要性。在上下文分块和嵌入准确性之间取得正确平衡是关键。幻觉例如在你的 JSON 数据中有一个文档包含 3 个政策的数据即休假政策、居家办公政策和宠物政策。现在如果你搜索 “pet policy” 或 “work from home”它会返回同一个文档。即使搜索 “leave policy”结果也会相同。这种做法可能包含额外的不相关信息在这样的上下文窗口下LLM 可能会产生幻觉或者答案不会那么准确。而如果你将这个文档拆分成三个文档三个不同的政策它将选择准确的分块并将其作为上下文窗口。如何进行数据分块有不同的数据分块策略固定长度分块—— 将文档拆分成固定大小的片段例如若干字符、单词等。上下文感知分块—— 按逻辑和语义拆分文档。基于 NLP 的分块—— 使用 NLP自然语言处理更有效地对数据进行分块。将大型文本拆分成可管理的片段使我们能够分别总结每个部分从而得到更准确的整体摘要。你可以根据需求设计自己的数据分块逻辑。在这个示例中我们将使用固定长度分块。为此我们将使用 LangChain 的CharacterTextSplitter()。这个过程会根据字符对数据进行拆分例如换行符\n、句号.、逗号,等并通过分配给 chunk_size 的字符数量来衡量分块长度。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_size50, chunk_overlap0) docs text_splitter.create_documents(content, metadatasmetadata)这里我们将为 content 字段创建分块。3索引文档假设你已经下载并部署了 ELSERv2 模型。ELSERElastic Learned Sparse EncodeR是由 Elastic 开发的检索模型。它使用户能够执行语义搜索并通过考虑上下文含义和用户意图来提升搜索结果的相关性而不是仅仅依赖精确的关键词匹配。我们将使用 ElasticsearchStore 库进行文档索引它是 langChain 向量存储功能中的一个重要组件。es ElasticsearchStore.from_documents( docs, es_cloud_idELASTIC_CLOUD_ID, es_api_keyELASTIC_API_KEY, index_nameelastic_index_name, strategyElasticsearchStore.SparseVectorRetrievalStrategy( model_id.elser_model_2 ), ) es让我们验证文档是否已正确插入。登录 Kibana然后进入菜单 ☰ 管理 开发工具。在 gemma-rag 索引上执行下面的查询。GET gemma-rag/_search响应{ _index: gemma-rag, _id: f0cb9857-6500-41de-89e6-c29ebede31ab, _score: 1, _ignored: [ metadata.summary.keyword ], _source: { metadata: { summary: This policy outlines the guidelines for full-time remote work, including eligibility, equipment and resources, workspace requirements, communication expectations, performance expectations, time tracking and overtime, confidentiality and data security, health and well-being, and policy reviews and updates. Employees are encouraged to direct any questions or concerns, rolePermissions: [ demo, manager ], name: Work From Home Policy }, vector: { tokens: { 19: 1.1510628, 2019: 0.83059055, laptop: 0.2694121, rent: 0.17121923, conducting: 0.118694015, freelance: 0.6926271, broad: 0.2849962, guidelines: 1.0599052, . . . . . . . . . . . . . supporting: 0.16413163, ensuring: 0.48137796, mask: 0.074894086, delivery: 0.18148012, hours: 0.05213894, comply: 0.20511511, continuity: 0.87717825, mobile: 0.6216534, time: 0.85393053, threat: 0.066342406, pm: 0.19746083 }, model_id: .elser_model_2 }, text: The purpose of this full-time work-from-home policy is to provide guidelines and support for employees to conduct their work remotely, ensuring the continuity and productivity of business operations during the COVID-19 pandemic and beyond. Scope } }text- 该字段保存分块后的数据。vectors.tokens- 包含由 ELSER 模型生成的所有 token。语义搜索将在此字段上执行。4使用 Hugging Face 在本地加载 Gemma 模型为什么使用 Hugging FaceHugging Face 是一个协作平台我们可以在其中托管和协作开发无限量的免费开放模型。你可以找到不同类型的开放模型、数据集和演示应用程序。它们都是公开可用的开源资源。你可以使用 Hugging Face 在本地机器上运行所有模型。Gemma 是一个最先进的开放模型托管在 Hugging Face 上。你可以直接在本地机器上运行它。Hugging Face 登录要开始使用 Gemma你需要在执行 notebook_login() 时传入 Hugging Face 访问令牌。from huggingface_hub import notebook_login notebook_login()输入 Hugging Face 访问令牌然后点击登录按钮。使用模型google/gemma-2b-it初始化 tokenizer。model AutoModelForCausalLM.from_pretrained(google/gemma-2b-it) tokenizer AutoTokenizer.from_pretrained(google/gemma-2b-it)AutoTokenizer用于将用户输入转换为 token 流这些 token 流可以由 Gemma 模型进行处理。使用方式GPU 使用要在 GPU 上运行模型请在 from_pretrained 方法中传入 device_mapauto 参数。tokenizer AutoTokenizer.from_pretrained(google/gemma-2b-it, device_mapauto)CPU 使用只需移除参数 device_map模型即可在 CPU 上运行。你可以探索更多使用方式和优化方法并根据你的需求使用它们。创建文本生成 pipeline 并使用 LLM 进行初始化这里我们将使用 transformer 的 pipeline。它是所有其他 pipeline 的抽象层并提供了一种简单的方式来使用模型进行推理。pipe pipeline( text-generation, modelmodel, tokenizertokenizer, max_new_tokens1024, ) llm HuggingFacePipeline( pipelinepipe, model_kwargs{temperature: 0.7}, )text-generation它将返回 TextGenerationPipeline。该 pipeline 会预测指定文本提示词后面将出现的单词。model 和 tokenizer传入我们在上一步中初始化的 model 和 tokenizer。max_new_tokens生成的最大 token 数量不考虑提示词中的 token 数量。devicecuda这将使用 CUDA 在 GPU 上执行所有计算。5使用提示模板创建链现在我们将使用 retrievers 执行语义搜索。它将使用 ELSERv2 模型执行搜索。def format_docs(docs): return \n\n.join(doc.page_content for doc in docs) retriever es.as_retriever(search_kwargs{k: 5})这里 k: 5 表示应该返回的最大文档数量。所有文档都会传递给 format_docs 方法以连接成一个单独的上下文窗口。我们将使用一个静态模板其中 context 和 question 将作为占位符。两者都会根据我们提出的问题动态替换。你可以使用自己的提示词或模板。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() )6提出问题chain.invoke(What is the pet policy in the office?)结论在这篇博客中我们探索了如何使用 Elasticsearch 进行语义搜索和文档检索将 Gemma 集成到 RAG 系统中。Gemma 模型提供了更多调优选项。由于它们相对较小的规模可以将其部署在任何环境中例如笔记本电脑、台式机、私有服务器等。通过遵循上述步骤并使用 Python 的 LangChain 框架开发人员可以将 Gemma 无缝集成到他们的项目中并释放其在生成任务中的全部潜力。或者你也可以选择其他编程语言在不依赖 LangChain 的情况下编写完整的流程RAG。展示上述所有实现的完整 Python notebook 可以在 elasticsearch-labs 仓库中找到。原文Build a RAG system with Gemma, Hugging Face Elasticsearch | Elasticsearch Labs