跳过内容

RAG 测试集生成

在 RAG 应用中,当用户通过您的应用与一组文档进行交互时,系统可能会遇到不同模式的查询。让我们首先了解 RAG 应用中可能遇到的不同类型的查询。

RAG 中的查询类型

graph TD
    A[Queries] --> B[Single-Hop Query]
    A --> C[Multi-Hop Query]

    B --> D1[Specific Query]

    B --> E1[Abstract Query]

    C --> F1[Specific Query]

    C --> G1[Abstract Query]

单跳查询

单跳查询是一个直接的问题,需要从单个文档或来源检索信息以提供相关答案。它只需要一步即可得到答案。

示例 (具体查询)

  • “阿尔伯特·爱因斯坦在哪一年发表了相对论?”

这是一个具体的、基于事实的问题,可以通过从包含该信息的文档中单次检索来回答。

示例 (抽象查询)

  • “爱因斯坦的理论如何改变了我们对时间和空间的理解?”

虽然此查询仍然涉及单个概念(相对论),但它需要从源材料中提供更抽象或解释性的说明。

多跳查询

多跳查询涉及多个推理步骤,需要来自两个或多个来源的信息。系统必须从各种文档中检索信息并连接起来以生成准确的答案。

示例 (具体查询)

  • “哪位科学家影响了爱因斯坦的相对论研究,他们提出了什么理论?”

这要求系统检索影响爱因斯坦的科学家以及特定理论的信息,这些信息可能来自两个不同的来源。

示例 (抽象查询)

  • “自爱因斯坦最初发表相对论以来,科学理论是如何演变的?”

这个抽象查询需要检索不同来源随时间推移的多条信息,以形成对该理论演变的广泛、解释性响应。

RAG 中的具体查询与抽象查询

  • 具体查询:侧重于清晰的、基于事实的检索。RAG 的目标是从一个或多个直接解决具体问题的文档中检索高度相关的信息。

  • 抽象查询:需要更广泛、更具解释性的响应。在 RAG 中,抽象查询挑战检索系统,使其从包含更高级推理、解释或观点的文档中提取信息,而不是简单的事实。

在单跳和多跳两种情况下,具体查询与抽象查询之间的区分通过决定侧重于精度(具体)还是综合更广泛的想法(抽象)来塑造检索和生成过程。

不同类型的查询需要合成不同的上下文。为了解决这个问题,Ragas 使用基于知识图谱的方法进行测试集生成。

知识图谱创建

鉴于我们想从给定文档集中生成不同类型的查询,我们的主要挑战是确定合适的块或文档集,以便 LLM 能够创建查询。为了解决这个问题,Ragas 使用基于知识图谱的方法进行测试集生成。

knowledge graph creation
知识图谱创建

知识图谱通过使用以下组件创建

文档分割器

文档被分块以形成层次节点。分块可以使用不同的分割器完成。例如,在财务文档的情况下,可以使用基于章节(如损益表、资产负债表、现金流量表等)分割文档的分割器进行分块。您可以编写自己的自定义分割器,根据与您的领域相关的章节来分割文档。

示例

from ragas.testset.graph import Node

sample_nodes = [Node(
    properties={"page_content": "Einstein's theory of relativity revolutionized our understanding of space and time. It introduced the concept that time is not absolute but can change depending on the observer's frame of reference."}
),Node(
    properties={"page_content": "Time dilation occurs when an object moves close to the speed of light, causing time to pass slower relative to a stationary observer. This phenomenon is a key prediction of Einstein's special theory of relativity."}
)]
sample_nodes
输出
[Node(id: 4f6b94, type: , properties: ['page_content']),
 Node(id: 952361, type: , properties: ['page_content'])]

graph TD
    A[Node: 4f6b94] -.-> |Properties| A1[page_content]

    B[Node: 952361] -.-> |Properties| B1[page_content]

提取器

使用不同的提取器从每个节点中提取信息,这些信息可用于建立节点之间的关系。例如,在财务文档的情况下,可以使用实体提取器来提取公司名称等实体,使用关键词提取器来提取每个节点中的重要关键词等。您可以编写自己的自定义提取器来提取与您的领域相关的信息。

提取器可以是基于 LLM 的(继承自 LLMBasedExtractor),也可以是基于规则的(继承自 Extractor)。

示例

假设我们有一个来自知识图谱的样本节点。我们可以使用 NERExtractor 从该节点中提取命名实体。

from ragas.testset.transforms.extractors import NERExtractor

extractor = NERExtractor()
output = [await extractor.extract(node) for node in sample_nodes]
output[0]
返回一个包含提取器类型和提取信息的元组。

('entities',
 {'ORG': [],
  'LOC': [],
  'PER': ['Einstein'],
  'MISC': ['theory of relativity',
   'space',
   'time',
   "observer's frame of reference"]})

让我们将提取的信息添加到节点。

_ = [node.properties.update({key:val}) for (key,val), node in zip(output, sample_nodes)]
sample_nodes[0].properties

输出

{'page_content': "Einstein's theory of relativity revolutionized our understanding of space and time. It introduced the concept that time is not absolute but can change depending on the observer's frame of reference.",
 'entities': {'ORG': [],
  'LOC': [],
  'PER': ['Einstein'],
  'MISC': ['theory of relativity',
   'space',
   'time',
   "observer's frame of reference"]}}

graph TD
    A[Node: 4f6b94] -.-> |Properties| A1[page_content]
    A -.-> |Properties| A2[entities]

    B[Node: 952361] -.-> |Properties| B1[page_content]
    B -.-> |Properties| B2[entities]

关系构建器

提取的信息用于建立节点之间的关系。例如,在财务文档的情况下,可以根据节点中存在的实体在节点之间建立关系。您可以编写自己的自定义关系构建器,根据与您的领域相关的信息来建立节点之间的关系。

示例

from ragas.testset.graph import KnowledgeGraph
from ragas.testset.transforms.relationship_builders.traditional import JaccardSimilarityBuilder

kg = KnowledgeGraph(nodes=sample_nodes)
rel_builder = JaccardSimilarityBuilder(property_name="entities", key_name="PER", new_property_name="entity_jaccard_similarity")
relationships = await rel_builder.transform(kg)
relationships
输出
[Relationship(Node(id: 4f6b94) <-> Node(id: 952361), type: jaccard_similarity, properties: ['entity_jaccard_similarity'])]
由于两个节点都具有相同的实体“Einstein”,因此基于实体相似性在节点之间建立了关系。

graph TD
    A[Node: 4f6b94] -.-> |Properties| A1[page_content]
    A -.-> |Properties| A2[entities]

    B[Node: 952361] -.-> |Properties| B1[page_content]
    B -.-> |Properties| B2[entities]

    A ===|entity_jaccard_similarity| B

现在让我们了解如何使用上述组件以及一个 transform 来构建知识图谱,这将使您的工作更轻松。

转换

用于构建知识图谱的所有组件可以组合成一个单独的 transform,该 transform 可以应用于知识图谱来构建知识图谱。Transforms 由按顺序应用于知识图谱的一系列组件组成。它还可以处理组件的并行处理。apply_transforms 方法用于将 transforms 应用于知识图谱。

示例

让我们使用上述组件以及一个 transform 来构建上述知识图谱。

from ragas.testset.transforms import apply_transforms
transforms = [
    extractor,
    rel_builder
    ]

apply_transforms(kg,transforms)

要并行应用部分组件,您可以将它们包装在 Parallel 类中。

from ragas.testset.transforms import KeyphraseExtractor, NERExtractor
from ragas.testset.transforms import apply_transforms, Parallel

tranforms = [
    Parallel(
        KeyphraseExtractor(),
        NERExtractor()
    ),
    rel_builder
]

apply_transforms(kg,transforms)

知识图谱创建完成后,可以通过遍历图谱来生成不同类型的查询。例如,要生成查询“比较公司 X 和公司 Y 从 2020 财年到 2023 财年的收入增长”,可以遍历图谱以找到包含公司 X 和公司 Y 从 2020 财年到 2023 财年收入增长信息的节点。

场景生成

现在我们有了知识图谱,可以用来构建正确的上下文来生成任何类型的查询。当用户群体与 RAG 系统交互时,他们可能会根据其用户画像(例如,高级工程师、初级工程师等)、查询长度(短、长等)、查询风格(正式、非正式等)以各种方式提出查询。为了生成涵盖所有这些场景的查询,Ragas 使用基于场景的方法进行测试集生成。

测试集生成中的每个 Scenario 是以下参数的组合。

  • 节点:用于生成查询的节点
  • 查询长度:所需查询的长度,可以是短、中或长等。
  • 查询风格:查询的风格,可以是网络搜索、聊天等。
  • 用户画像:用户的用户画像,可以是高级工程师、初级工程师等。(即将推出)
Scenario in Test Generation
测试生成中的场景

查询合成器

QuerySynthesizer 负责为单一查询类型生成不同的场景。generate_scenarios 方法用于为单一查询类型生成场景。generate_sample 方法用于为单个场景生成查询和参考答案。让我们通过一个示例来理解这一点。

示例

在前面的示例中,我们创建了一个知识图谱,其中包含两个基于实体相似性相互关联的节点。现在想象一下,您的知识图谱中有 20 对这样的节点,它们基于实体相似性相互关联。

假设您的目标是创建 50 个不同的查询,每个查询都是关于比较两个实体的抽象问题。我们首先必须查询知识图谱以获取基于实体相似性相互关联的节点对。然后,我们必须为每对节点生成场景,直到获得 50 个不同的场景。此逻辑在 generate_scenarios 方法中实现。

from dataclasses import dataclass
from ragas.testset.synthesizers.base_query import QuerySynthesizer

@dataclass
class EntityQuerySynthesizer(QuerySynthesizer):

    async def _generate_scenarios( self, n, knowledge_graph, callbacks):
        """
        logic to query nodes with entity
        logic describing how to combine nodes,styles,length,persona to form n scenarios
        """

        return scenarios

    async def _generate_sample(
        self, scenario, callbacks
    ):

        """
        logic on how to use tranform each scenario to EvalSample (Query,Context,Reference)
        you may create singleturn or multiturn sample
        """

        return SingleTurnSample(user_input=query, reference_contexs=contexts, reference=reference)