📘 **TELUS Agriculture & Consumer Goods** 如何通过 **Haystack Agents** 转变促销交易

教程:嵌入元数据以改进检索


⚠️ 注意:本教程中展示的方法并非适用于所有类型的元数据。此方法在嵌入的元数据有意义时效果最佳。例如,我们在此展示了嵌入“title”元字段,这也可以为嵌入模型提供良好的上下文。

概述

在将文档索引到文档存储中时,我们有两种选择:对文档的文本进行嵌入,或者在嵌入文本的同时嵌入一些有意义的元数据。在某些情况下,在嵌入文档内容的同时嵌入有意义的元数据可能会在后续的检索中带来改进。

在本教程中,我们将了解如何嵌入文档的元数据以及文本。我们将从 Wikipedia 获取各种页面,并将它们索引到 InMemoryDocumentStore 中,其中包含标题和 URL 等元数据信息。接下来,我们将比较使用和不使用这些元数据的检索效果。

设置

准备 Colab 环境

安装 Haystack

使用 pip 安装 Haystack 和其他必需的包

%%bash

pip install haystack-ai wikipedia sentence-transformers

使用元数据索引文档

创建一个管道,将小型示例数据集存储在 InMemoryDocumentStore 中,并附带其嵌入。我们将使用 SentenceTransformersDocumentEmbedder 为您的文档生成嵌入,并使用 DocumentWriter 将它们写入文档存储。

将这些组件添加到管道后,连接它们并运行管道。

💡 InMemoryDocumentStore 是运行教程最简单的文档存储,无需额外要求。您可以将其更改为任何其他可用的文档存储,例如 Weaviate、AstraDB、Qdrant、Pinecone 等。查看 文档存储的完整列表以及运行它们的说明。

首先,我们将创建一个辅助函数来创建索引管道。我们可以选择性地为该函数提供 meta_fields_to_embed。如果提供了该参数,SentenceTransformersDocumentEmbedder 将使用嵌入元数据的设置进行初始化,以与文档内容一起进行嵌入。

例如,下面的嵌入器将嵌入“url”字段以及文档的内容。

from haystack.components.embedders import SentenceTransformersDocumentEmbedder

embedder = SentenceTransformersDocumentEmbedder(meta_fields_to_embed=["url"])
from haystack import Pipeline
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack.components.writers import DocumentWriter
from haystack.document_stores.types import DuplicatePolicy
from haystack.utils import ComponentDevice


def create_indexing_pipeline(document_store, metadata_fields_to_embed=None):
    document_cleaner = DocumentCleaner()
    document_splitter = DocumentSplitter(split_by="period", split_length=2)
    document_embedder = SentenceTransformersDocumentEmbedder(
        model="thenlper/gte-large", meta_fields_to_embed=metadata_fields_to_embed
    )
    document_writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.OVERWRITE)

    indexing_pipeline = Pipeline()
    indexing_pipeline.add_component("cleaner", document_cleaner)
    indexing_pipeline.add_component("splitter", document_splitter)
    indexing_pipeline.add_component("embedder", document_embedder)
    indexing_pipeline.add_component("writer", document_writer)

    indexing_pipeline.connect("cleaner", "splitter")
    indexing_pipeline.connect("splitter", "embedder")
    indexing_pipeline.connect("embedder", "writer")

    return indexing_pipeline

接下来,我们可以索引来自各种 Wikipedia 文章的文档。我们将创建 2 个索引管道。

  • indexing_pipeline:该管道仅索引文档的内容。我们将这些文档索引到 document_store 中。
  • indexing_with_metadata_pipeline:该管道将元字段与文档内容一起索引。我们将这些文档索引到 document_store_with_embedded_metadata 中。
import wikipedia
from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore

some_bands = """The Beatles,The Cure""".split(",")

raw_docs = []

for title in some_bands:
    page = wikipedia.page(title=title, auto_suggest=False)
    doc = Document(content=page.content, meta={"title": page.title, "url": page.url})
    raw_docs.append(doc)

document_store = InMemoryDocumentStore(embedding_similarity_function="cosine")
document_store_with_embedded_metadata = InMemoryDocumentStore(embedding_similarity_function="cosine")

indexing_pipeline = create_indexing_pipeline(document_store=document_store)
indexing_with_metadata_pipeline = create_indexing_pipeline(
    document_store=document_store_with_embedded_metadata, metadata_fields_to_embed=["title"]
)

indexing_pipeline.run({"cleaner": {"documents": raw_docs}})
indexing_with_metadata_pipeline.run({"cleaner": {"documents": raw_docs}})

比较有嵌入元数据和无嵌入元数据的检索结果

最后一步,我们将创建一个检索管道,其中包含 2 个检索器。

  • 第一个:从 document_store 进行检索,该存储未嵌入元数据。
  • 第二个:从 document_store_with_embedded_metadata 进行检索,该存储嵌入了元数据。

然后,我们将能够比较结果,看看嵌入元数据是否在此案例中帮助了检索。

💡 这里我们使用的是 InMemoryEmbeddingRetriever,因为我们上面使用了 InMemoryDocumentStore。如果您使用的是其他文档存储,请将其更改为使用您所用文档存储的配套嵌入检索器。查看 嵌入器文档以获取完整列表。

from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever

retrieval_pipeline = Pipeline()
retrieval_pipeline.add_component("text_embedder", SentenceTransformersTextEmbedder(model="thenlper/gte-large"))
retrieval_pipeline.add_component(
    "retriever", InMemoryEmbeddingRetriever(document_store=document_store, scale_score=False, top_k=3)
)
retrieval_pipeline.add_component(
    "retriever_with_embeddings",
    InMemoryEmbeddingRetriever(document_store=document_store_with_embedded_metadata, scale_score=False, top_k=3),
)

retrieval_pipeline.connect("text_embedder", "retriever")
retrieval_pipeline.connect("text_embedder", "retriever_with_embeddings")

让我们运行管道并比较来自 retrieverretirever_with_embeddings 的结果。下面您将看到每个检索器返回的 3 个文档,按相关性排序。

请注意,对于问题“The Beatles 是否去过 Bangor?”,第一个管道没有返回相关文档,但第二个管道返回了。这里,“title”meta 字段很有帮助,因为事实证明,包含有关 The Beatles 访问 Bangor 信息文档的标题中并未包含“The Beatles”的引用。但是,通过嵌入元数据,嵌入模型能够检索到正确的文档。

result = retrieval_pipeline.run({"text_embedder": {"text": "Have the Beatles ever been to Bangor?"}})

print("Retriever Results:\n")
for doc in result["retriever"]["documents"]:
    print(doc)

print("Retriever with Embeddings Results:\n")
for doc in result["retriever_with_embeddings"]["documents"]:
    print(doc)

下一步

🎉 恭喜!您已在索引时嵌入了元数据,以改进检索结果!

如果您喜欢本教程,还有更多关于 Haystack 的内容可以学习。

要及时了解最新的 Haystack 开发动态,您可以 注册我们的新闻通讯加入 Haystack Discord 社区

感谢阅读!