Testset Generation for RAG(面向 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]
Single-Hop Query(单跳查询)
单跳查询是一种直接的问题,需要从单个文档或来源检索信息以提供相关答案。它只涉及一步即可得出答案。
示例(Specific Query):
- “What year did Albert Einstein publish the theory of relativity?”
这是一个具体的、基于事实的问题,只需从包含该信息的文档中进行一次检索即可回答。
示例(Abstract Query):
- “How did Einstein’s theory change our understanding of time and space?”
虽然该查询仍指向单一概念(相对论),但它需要从来源材料中给出更抽象或解释性的说明。
Multi-Hop Query(多跳查询)
多跳查询涉及多个推理步骤,需要来自两个或更多来源的信息。系统必须从各种文档中检索信息并连接这些点,以生成准确答案。
示例(Specific Query):
- “Which scientist influenced Einstein’s work on relativity, and what theory did they propose?”
这要求系统检索关于影响爱因斯坦的科学家以及具体理论的信息,可能来自两个不同来源。
示例(Abstract Query):
- “How have scientific theories on relativity evolved since Einstein’s original publication?”
这种抽象查询需要随时间、跨不同来源检索多条信息,以形成关于该理论演变的宽泛、解释性回答。
RAG 中的 Specific vs. Abstract Queries
- Specific Query: 聚焦于清晰的、基于事实的检索。RAG 中的目标是从一个或多个直接处理该具体问题的文档中检索高度相关的信息。
- Abstract Query: 需要更宽泛、更解释性的回答。在 RAG 中,抽象查询挑战检索系统从包含更高层次推理、解释或观点(而非简单事实)的文档中拉取信息。
在单跳和多跳两种情况下,specific 与 abstract 查询的区分通过确定重点是精度(specific)还是综合更广泛的想法(abstract),塑造了检索和生成过程。
不同类型的查询需要合成不同的上下文。为解决这个问题,Ragas 使用基于知识图谱的方法进行测试集生成。
知识图谱创建
鉴于我们要从给定的文档集中制造不同类型的查询,我们的主要挑战是识别正确的 chunk 或文档集合,以使 LLM 能够创建查询。为解决这个问题,Ragas 使用基于知识图谱的方法进行测试集生成。
知识图谱创建
知识图谱通过以下组件创建:
Document Splitter(文档分割器)
文档被切块以形成层级节点。切块可以使用不同的 splitter 完成。例如,对于财务文档,切块可以使用基于 Income Statement、Balance Sheet、Cash Flow Statement 等章节分割文档的 splitter。你可以编写自己的自定义 splitter,基于与你领域相关的章节分割文档。
示例
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]
Extractors(提取器)
使用不同的 extractor 从每个节点提取信息,这些信息可用于建立节点之间的关系。例如,对于财务文档,可以使用 entity extractor 提取 Company Name 等实体,使用 keyphrase extractor 提取每个节点中存在的重要 keyphrase 等。你可以编写自己的自定义 extractor,提取与你领域相关的信息。
Extractor 可以是继承自 LLMBasedExtractor 的基于 LLM 的提取器,或继承自 Extractor 的基于规则的提取器。
示例
假设我们有知识图谱中的一个样本节点。我们可以使用 NERExtractor 从该节点提取命名实体。
from ragas.testset.transforms.extractors import NERExtractor
extractor = NERExtractor()
output = [await extractor.extract(node) for node in sample_nodes]
output[0]
返回提取器类型和提取信息的元组。
('entities', ['Einstein', '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': ['Einstein', 'theory of relativity', 'space', 'time', 'observer']}
graph TD
A[Node: 4f6b94] -.-> |Properties| A1[page_content]
A -.-> |Properties| A2[entities]
B[Node: 952361] -.-> |Properties| B1[page_content]
B -.-> |Properties| B2[entities]
Relationship builder(关系构建器)
提取的信息用于建立节点之间的关系。例如,对于财务文档,可以根据节点中存在的实体建立节点之间的关系。你可以编写自己的自定义 relationship builder,基于与你领域相关的信息建立节点之间的关系。
示例
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 构建知识图谱,这会让你的工作更容易。
Transforms
用于构建知识图谱的所有组件可以组合成单个 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)
知识图谱创建完成后,可以通过遍历图生成不同类型的查询。例如,要生成查询 “Compare the revenue growth of Company X and Company Y from FY2020 through FY2023”,可以遍历图以找到包含 Company X 和 Company Y 从 FY2020 到 FY2023 收入增长信息的节点。
Scenario Generation(场景生成)
现在我们有了可用于制造正确上下文以生成任何类型查询的知识图谱。当一群用户与 RAG 系统交互时,他们可能根据其 persona(例如 Senior Engineer、Junior Engineer 等)、查询长度(Short、Long 等)、查询风格(Formal、Informal 等)以各种方式表述查询。为生成覆盖所有这些场景的查询,Ragas 使用基于场景的方法进行测试集生成。
测试集生成中的每个 Scenario 是以下参数的组合。
- Nodes:用于生成查询的节点
- Query Length:期望查询的长度,可以是 short、medium 或 long 等。
- Query Style:查询的风格,可以是 web search、chat 等。
- Persona:用户的 persona,可以是 Senior Engineer、Junior Engineer 等。(即将推出)
测试生成中的 Scenario
Query Synthesizer(查询合成器)
QuerySynthesizer 负责为单一查询类型生成不同场景。generate_scenarios 方法用于为单一查询类型生成场景。generate_sample 方法用于为单一场景生成查询和 reference 答案。让我们通过一个示例来理解这一点。
示例
在上一个示例中,我们创建了一个知识图谱,其中包含两个基于实体相似度相关的节点。现在想象你的 KG 中有 20 对这样的节点,它们基于实体相似度彼此相关。
想象你的目标是创建 50 个不同的查询,每个查询都是关于比较两个实体的某种抽象问题。我们首先必须查询 KG,以获得基于实体相似度相关的节点对。然后我们必须为每对节点生成场景,直到得到 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)