高级检索:从查询中提取元数据以改进检索
使用 LLM 从查询中提取元数据,用作改进 RAG 应用程序中检索的过滤器。
2024 年 5 月 13 日这是 **高级用例** 系列的第一部分
1️⃣ 从查询中提取元数据以改进检索
2️⃣ 查询扩展
3️⃣ 查询分解
4️⃣ 自动化元数据丰富
在检索增强生成 (RAG) 应用程序中,检索步骤(为大型语言模型 (LLM) 提供相关上下文)对于生成高质量的响应至关重要。有多种方法可以改进检索,而元数据过滤是最简单的方法之一。元数据过滤,即根据一些具体元数据限制搜索空间的方法,可以真正提高检索文档的质量。以下是使用元数据过滤的一些优点:
- 相关性:元数据过滤会缩小检索的信息范围。这可确保生成的响应与特定查询或主题保持一致。
- 准确性:基于域、源、日期或主题等元数据进行过滤,可确保用于生成的信息准确且值得信赖。这对于准确性至关重要的应用程序尤其重要。例如,如果您需要特定年份的信息,使用年份作为元数据过滤器将仅检索相关数据。
- 效率:消除不相关或低质量的信息可提高 RAG 应用程序的效率,减少所需的处理量,并加快检索响应时间。
您有两种应用元数据过滤器的方法:可以在运行管道时直接指定,也可以从查询本身提取。在本文中,我们将重点介绍如何从查询中提取过滤器,以提高 RAG 应用程序中生成响应的质量。让我们开始吧。
元数据过滤器简介
首先,什么是元数据?元数据(或元标签)实际上是关于您的数据的数据,用于根据各种属性(如日期、主题、源或您发现相关的任何其他信息)对信息进行分类、排序和过滤。将元信息合并到数据中后,您可以对与检索器一起使用的查询应用过滤器,以根据此元数据限制搜索范围,并确保您的答案来自您数据的特定切片。
想象一下,您的文档存储中有以下文档
documents = [
Document(
content="Some text about revenue increase",
meta={"year": 2022, "company": "Nvidia", "name":"A"}),
Document(
content="Some text about revenue increase",
meta={"year": 2023, "company": "Nvidia", "name":"B"}),
Document(
content="Some text about revenue increase",
meta={"year": 2022, "company": "BMW", "name":"C"}),
Document(
content="Some text about revenue increase",
meta={"year": 2023, "company": "BMW", "name":"D"}),
Document(
content="Some text about revenue increase",
meta={"year": 2022, "company": "Mercedes", "name":"E"}),
Document(
content="Some text about revenue increase",
meta={"year": 2023, "company": "Mercedes", "name":"F"}),
]
当查询为“*收入增长的原因*”时,检索器会返回所有文档,因为它们都包含有关收入的一些信息。但是,下面的元数据过滤器可确保检索器返回的任何文档在 year 元数据字段中具有值 2022 ,并且在 company 元数据字段中具有值BMW 或Mercedes 。因此,仅检索名称为“C”和“E”的文档。
pipeline.run(
data={
"retriever":{
"query": "Causes of the revenue increase",
"filters": {
"operators": "AND",
"conditions": [
{"field": "meta.year", "operator": "==", "value": "2022"},
{"field": "meta.company", "operator": "in", "value": ["BMW", "Mercedes"]}
]
}
}
}
)
在此示例中,我们显式传递过滤器,但有时,查询本身可能包含在查询过程中用作元数据过滤器的信息。在这种情况下,我们需要在将查询与检索器一起使用之前预处理查询以提取过滤器。
从查询中提取元数据过滤器
在基于 LLM 的应用程序中,查询使用自然语言编写。不时地,它们包含可用于改进检索的元数据过滤器的宝贵提示。我们可以提取这些提示,将它们整合成元数据过滤器,并与检索器一起使用。例如,当查询为“*2022 年 Nvidia 的收入是多少?*”时,我们可以将2022提取为years,将Nvidia提取为companies。基于此信息,与检索器一起使用的元数据过滤器应如下所示:
"filters": {
"operators": "AND",
"conditions": [
{"field": "meta.years", "operator": "==", "value": "2022"},
{"field": "meta.companies", "operator": "==", "value": "Nvidia"}
]
}
幸运的是,LLM 能够从非结构化文本中提取结构化信息。让我们逐步了解如何实现一个自定义组件,该组件使用 LLM 从查询中提取关键字、短语或实体,并生成元数据过滤器。
实现 QueryMetadataExtractor
🧑🍳 您可以在我们的 cookbook 从查询中提取元数据过滤器 中找到并运行所有代码
我们首先创建一个 自定义组件QueryMetadataExtractor,它接受 query 和 metadata_fields 作为输入并输出 filters。此组件封装了一个生成管道,该管道由 PromptBuilder 和 OpenAIGenerator 组成。该管道指示 LLM 从给定查询中提取关键字、短语或实体,然后可将其用作元数据过滤器。在提示中,我们包含说明以确保输出格式为 JSON,并提供 metadata_fields 以及 query 以确保从查询中提取正确的实体。
管道在组件的 init 方法中初始化后,我们会在 run 方法中对 LLM 输出进行后处理。此步骤可确保提取的元数据格式正确,以便用作元数据过滤器。
import json
from typing import Dict, List
from haystack import Pipeline, component
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGenerator
@component()
class QueryMetadataExtractor:
def __init__(self):
prompt = """
You are part of an information system that processes users queries.
Given a user query you extract information from it that matches a given list of metadata fields.
The information to be extracted from the query must match the semantics associated with the given metadata fields.
The information that you extracted from the query will then be used as filters to narrow down the search space
when querying an index.
Just include the value of the extracted metadata without including the name of the metadata field.
The extracted information in 'Extracted metadata' must be returned as a valid JSON structure.
###
Example 1:
Query: "What was the revenue of Nvidia in 2022?"
Metadata fields: {"company", "year"}
Extracted metadata fields: {"company": "nvidia", "year": 2022}
###
Example 2:
Query: "What were the most influential publications in 2023 regarding Alzheimer's disease?"
Metadata fields: {"disease", "year"}
Extracted metadata fields: {"disease": "Alzheimer", "year": 2023}
###
Example 3:
Query: "{{query}}"
Metadata fields: "{{metadata_fields}}"
Extracted metadata fields:
"""
self.pipeline = Pipeline()
self.pipeline.add_component(name="builder", instance=PromptBuilder(prompt))
self.pipeline.add_component(name="llm", instance=OpenAIGenerator(model="gpt-3.5-turbo"))
self.pipeline.connect("builder", "llm")
@component.output_types(filters=Dict[str, str])
def run(self, query: str, metadata_fields: List[str]):
result = self.pipeline.run({'builder': {'query': query, 'metadata_fields': metadata_fields}})
metadata = json.loads(result['llm']['replies'][0])
# this can be done with specific data structures and in a more sophisticated way
filters = []
for key, value in metadata.items():
field = f"meta.{key}"
filters.append({f"field": field, "operator": "==", "value": value})
return {"filters": {"operator": "AND", "conditions": filters}}
首先,让我们单独测试 QueryMetadataExtractor,传入一个查询和一组元数据字段。
extractor = QueryMetadataExtractor()
query = "What were the most influential publications in 2022 regarding Parkinson's disease?"
metadata_fields = {"disease", "year"}
result = extractor.run(query, metadata_fields)
print(result)
结果应如下所示
{'filters': {'operator': 'AND',
'conditions': [
{'field': 'meta.disease', 'operator': '==', 'value': 'Alzheimers'},
{'field': 'meta.year', 'operator': '==', 'value': 2023}
]}
}
请注意,QueryMetadataExtractor 已从查询中提取了元数据字段,并以可直接传递给 Retriever 的过滤器的格式返回。默认情况下,QueryMetadataExtractor 将使用所有元数据字段作为条件,并结合 AND 运算符。
在管道中使用 QueryMetadataExtractor
现在,让我们将 QueryMetadataExtractor 插入到与 DocumentStore 连接的 Retriever 的 Pipeline 中,以了解其在实践中的工作原理。
我们首先创建一个 InMemoryDocumentStore 并向其中添加一些文档。我们在每个文档的“meta”字段中包含有关“year”和“disease”的信息。
from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.document_stores.types import DuplicatePolicy
documents = [
Document(
content="some publication about Alzheimer prevention research done over 2023 patients study",
meta={"year": 2022, "disease": "Alzheimer", "author": "Michael Butter"}),
Document(
content="some text about investigation and treatment of Alzheimer disease",
meta={"year": 2023, "disease": "Alzheimer", "author": "John Bread"}),
Document(
content="A study on the effectiveness of new therapies for Parkinson's disease",
meta={"year": 2022, "disease": "Parkinson", "author": "Alice Smith"}
),
Document(
content="An overview of the latest research on the genetics of Parkinson's disease and its implications for treatment",
meta={"year": 2023, "disease": "Parkinson", "author": "David Jones"}
)
]
document_store = InMemoryDocumentStore(bm25_algorithm="BM25Plus")
document_store.write_documents(documents=documents, policy=DuplicatePolicy.OVERWRITE)
然后,我们创建一个由 QueryMetadataExtractor 和连接到上述 InMemoryDocumentStore 的 InMemoryBM25Retriever 组成的管道。
在 文档:创建管道 中了解有关连接组件和创建管道的信息。
from haystack import Pipeline, Document
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
retrieval_pipeline = Pipeline()
metadata_extractor = QueryMetadataExtractor()
retriever = InMemoryBM25Retriever(document_store=document_store)
retrieval_pipeline.add_component(instance=metadata_extractor, name="metadata_extractor")
retrieval_pipeline.add_component(instance=retriever, name="retriever")
retrieval_pipeline.connect("metadata_extractor.filters", "retriever.filters")
现在定义一个查询和元数据字段,并将它们传递给管道
query = "publications 2023 Alzheimer's disease"
metadata_fields = {"year", "author", "disease"}
retrieval_pipeline.run(data={"metadata_extractor": {"query": query, "metadata_fields": metadata_fields}, "retriever":{"query": query}})
这将仅返回元数据字段 year = 2023 且 disease = Alzheimer 的文档
{'documents':
[Document(
id=e3b0bfd497a9f83397945583e77b293429eb5bdead5680cc8f58dd4337372aa3,
content: 'some text about investigation and treatment of Alzheimer disease',
meta: {'year': 2023, 'disease': 'Alzheimer', 'author': 'John Bread'},
score: 2.772588722239781)]
}
结论
元数据过滤技术在提高检索文档的相关性和准确性方面表现出色,从而能够生成高质量的 RAG 应用程序响应。通过我们实现的自定义组件 QueryMetadataExtractor,我们可以从用户查询中提取过滤器,并直接与检索器一起使用。
本文是高级用例系列的第一部分。如果您想了解最新的 Haystack 开发动态,可以 订阅我们的 newsletter 或 加入我们的 Discord 社区 💙
