作者:来自 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)导入软件包和凭证
安装所需的软件包
css
`pip install -q -U elasticsearch langchain transformers huggingface_hub torch` AI写代码
导入所有依赖项
python
`
1. import json
2. import os
3. from getpass import getpass
4. from urllib.request import urlopen
6. from elasticsearch import Elasticsearch, helpers
7. from langchain.text_splitter import CharacterTextSplitter
8. from langchain.vectorstores import ElasticsearchStore
9. from langchain import HuggingFacePipeline
10. from langchain.chains import RetrievalQA
11. from langchain.prompts import ChatPromptTemplate
12. from langchain.schema.output_parser import StrOutputParser
13. from langchain.schema.runnable import RunnablePassthrough
14. from huggingface_hub import login
15. from transformers import AutoTokenizer, AutoModelForCausalLM
16. from transformers import AutoTokenizer, pipeline
`AI写代码
我们将针对不同用途使用 LangChain 的不同模块。
获取凭证
ini
`
1. ELASTIC_API_KEY = getpass("Elastic API Key :")
2. ELASTIC_CLOUD_ID = getpass("Elastic Cloud ID :")
3. elastic_index_name = "gemma-rag"
`AI写代码
这将从用户输入中接收 ELASTIC_API_KEY 和 ELASTIC_CLOUD_ID。所有数据将存储在 gemma-rag 索引中。
2)准备文档
下载示例数据集并反序列化文档
ini
`
1. url = "https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/datasets/workplace-documents.json"
2. response = urlopen(url)
3. workplace_docs = json.loads(response.read())
`AI写代码
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 的字符数量来衡量分块长度。
scss
`
1. metadata = []
2. content = []
4. for doc in workplace_docs:
5. content.append(doc["content"])
6. metadata.append(
7. {
8. "name": doc["name"],
9. "summary": doc["summary"],
10. "rolePermissions": doc["rolePermissions"],
11. }
12. )
14. text_splitter = CharacterTextSplitter(chunk_size=50, chunk_overlap=0)
15. docs = text_splitter.create_documents(content, metadatas=metadata)
`AI写代码
这里我们将为 content 字段创建分块。
3)索引文档
假设你已经下载并部署了 ELSERv2 模型。ELSER(Elastic Learned Sparse EncodeR)是由 Elastic 开发的检索模型。它使用户能够执行语义搜索,并通过考虑上下文含义和用户意图来提升搜索结果的相关性,而不是仅仅依赖精确的关键词匹配。
我们将使用 ElasticsearchStore 库进行文档索引,它是 langChain 向量存储功能中的一个重要组件。
ini
`
1. es = ElasticsearchStore.from_documents(
2. docs,
3. es_cloud_id=ELASTIC_CLOUD_ID,
4. es_api_key=ELASTIC_API_KEY,
5. index_name=elastic_index_name,
6. strategy=ElasticsearchStore.SparseVectorRetrievalStrategy(
7. model_id=".elser_model_2"
8. ),
9. )
11. es
`AI写代码
让我们验证文档是否已正确插入。登录 Kibana,然后进入菜单 ☰ > 管理 > 开发工具。在 gemma-rag 索引上执行下面的查询。
bash
`GET gemma-rag/_search` AI写代码
响应
python
`
1. {
2. "_index": "gemma-rag",
3. "_id": "f0cb9857-6500-41de-89e6-c29ebede31ab",
4. "_score": 1,
5. "_ignored": [
6. "metadata.summary.keyword"
7. ],
8. "_source": {
9. "metadata": {
10. "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",
11. "rolePermissions": [
12. "demo",
13. "manager"
14. ],
15. "name": "Work From Home Policy"
16. },
17. "vector": {
18. "tokens": {
19. "19": 1.1510628,
20. "2019": 0.83059055,
21. "laptop": 0.2694121,
22. "rent": 0.17121923,
23. "conducting": 0.118694015,
24. "freelance": 0.6926271,
25. "broad": 0.2849962,
26. "guidelines": 1.0599052,
27. .
28. .
29. .
30. .
31. .
32. .
33. .
34. .
35. .
36. .
37. .
38. .
39. .
40. "supporting": 0.16413163,
41. "ensuring": 0.48137796,
42. "mask": 0.074894086,
43. "delivery": 0.18148012,
44. "hours": 0.05213894,
45. "comply": 0.20511511,
46. "continuity": 0.87717825,
47. "mobile": 0.6216534,
48. "time": 0.85393053,
49. "threat": 0.066342406,
50. "pm": 0.19746083
51. },
52. "model_id": ".elser_model_2"
53. },
54. "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.
55. Scope"""
56. }
57. }
`AI写代码收起代码块
- text- 该字段保存分块后的数据。
- vectors.tokens- 包含由 ELSER 模型生成的所有 token。语义搜索将在此字段上执行。
4)使用 Hugging Face 在本地加载 Gemma 模型
为什么使用 Hugging Face?
Hugging Face 是一个协作平台,我们可以在其中托管和协作开发无限量的免费开放模型。你可以找到不同类型的开放模型、数据集和演示应用程序。它们都是公开可用的开源资源。你可以使用 Hugging Face 在本地机器上运行所有模型。
Gemma 是一个最先进的开放模型,托管在 Hugging Face 上。你可以直接在本地机器上运行它。
Hugging Face 登录
要开始使用 Gemma,你需要在执行 notebook_login() 时传入 Hugging Face 访问令牌。
markdown
`
1. from huggingface_hub import notebook_login
3. notebook_login()
`AI写代码

输入 Hugging Face 访问令牌,然后点击登录按钮。
使用模型(google/gemma-2b-it)初始化 tokenizer。
ini
`
1. model = AutoModelForCausalLM.from_pretrained("google/gemma-2b-it")
2. tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b-it")
`AI写代码
AutoTokenizer 用于将用户输入转换为 token 流,这些 token 流可以由 Gemma 模型进行处理。
使用方式
- GPU 使用:要在 GPU 上运行模型,请在 from_pretrained 方法中传入 device_map="auto" 参数。
ini
`tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b-it", device_map="auto")`AI写代码
- CPU 使用:只需移除参数 device_map,模型即可在 CPU 上运行。
你可以探索更多使用方式和优化方法,并根据你的需求使用它们。
创建文本生成 pipeline 并使用 LLM 进行初始化
这里我们将使用 transformer 的 pipeline。它是所有其他 pipeline 的抽象层,并提供了一种简单的方式来使用模型进行推理。
ini
`
1. pipe = pipeline(
2. "text-generation",
3. model=model,
4. tokenizer=tokenizer,
5. max_new_tokens=1024,
6. )
8. llm = HuggingFacePipeline(
9. pipeline=pipe,
10. model_kwargs={"temperature": 0.7},
11. )
`AI写代码
- text-generation:它将返回 TextGenerationPipeline。该 pipeline 会预测指定文本提示词后面将出现的单词。
- model 和 tokenizer:传入我们在上一步中初始化的 model 和 tokenizer。
- max_new_tokens:生成的最大 token 数量,不考虑提示词中的 token 数量。
- device="cuda":这将使用 CUDA 在 GPU 上执行所有计算。
5)使用提示模板创建链
现在我们将使用 retrievers 执行语义搜索。它将使用 ELSERv2 模型执行搜索。
css
`
1. def format_docs(docs):
2. return "\n\n".join(doc.page_content for doc in docs)
4. retriever = es.as_retriever(search_kwargs={"k": 5})
`AI写代码
这里 "k": 5 表示应该返回的最大文档数量。所有文档都会传递给 format_docs 方法,以连接成一个单独的上下文窗口。
我们将使用一个静态模板,其中 context 和 question 将作为占位符。两者都会根据我们提出的问题动态替换。你可以使用自己的提示词或模板。
ini
`
1. template = """Answer the question based only on the following context:\n
3. {context}
5. Question: {question}
6. """
7. prompt = ChatPromptTemplate.from_template(template)
9. chain = (
10. {"context": retriever | format_docs, "question": RunnablePassthrough()}
11. | prompt
12. | llm
13. | StrOutputParser()
14. )
`AI写代码
6)提出问题
arduino
`chain.invoke("What is the pet policy in the office?")` AI写代码

结论
在这篇博客中,我们探索了如何使用 Elasticsearch 进行语义搜索和文档检索,将 Gemma 集成到 RAG 系统中。Gemma 模型提供了更多调优选项。由于它们相对较小的规模,可以将其部署在任何环境中,例如笔记本电脑、台式机、私有服务器等。通过遵循上述步骤,并使用 Python 的 LangChain 框架,开发人员可以将 Gemma 无缝集成到他们的项目中,并释放其在生成任务中的全部潜力。或者,你也可以选择其他编程语言,在不依赖 LangChain 的情况下编写完整的流程(RAG)。展示上述所有实现的完整 Python notebook 可以在 elasticsearch-labs 仓库中找到。
原文:Build a RAG system with Gemma, Hugging Face & Elasticsearch | Elasticsearch Labs