문서에서 커스텀 멀티 홉(multi-hop) 쿼리 만들기

문서에서 커스텀 멀티 홉(multi-hop) 쿼리 만들기

이 튜토리얼에서는 문서에서 커스텀 멀티 홉 쿼리를 만드는 방법을 배워요. 이건 표준 쿼리 유형으로는 만들 수 없는 쿼리를 생성하게 해 주는 아주 강력한 기능이에요. 여러분의 사용 사례에 더 특화된 쿼리를 만드는 데도 도움이 돼요.

출처: 문서

본문

샘플 문서 로드

GitLab handbook의 샘플 문서를 사용하고 있어요. 아래 명령으로 다운로드할 수 있어요.

! git clone https://huggingface.co/datasets/vibrantlabsai/Sample_Docs_Markdown
from langchain_community.document_loaders import DirectoryLoader, TextLoader

path = "Sample_Docs_Markdown/"
loader = DirectoryLoader(path, glob="**/*.md")
docs = loader.load()

KG 생성

문서로 기본 지식 그래프(knowledge graph)를 만들어요.

from ragas.testset.graph import KnowledgeGraph
from ragas.testset.graph import Node, NodeType


kg = KnowledgeGraph()
for doc in docs:
    kg.nodes.append(
        Node(
            type=NodeType.DOCUMENT,
            properties={
                "page_content": doc.page_content,
                "document_metadata": doc.metadata,
            },
        )
    )

LLM과 Embedding 모델 설정

원하는 모델을 써도 되는데, 여기서는 open-ai 모델을 사용할게요.

from openai import OpenAI
from ragas.llms import llm_factory
from ragas.embeddings import OpenAIEmbeddings

openai_client = OpenAI()
llm = llm_factory("gpt-4o-mini", client=openai_client)
embedding = OpenAIEmbeddings(client=openai_client)

추출기와 관계 빌더 설정

멀티 홉 쿼리를 만들려면 어떤 문서 집합이 그 쿼리에 쓰일 수 있는지 파악해야 해요. Ragas는 문서/노드 사이의 관계를 사용해 멀티 홉 쿼리 생성에 적합한 노드를 판별해요. 예를 들어, 노드 A와 노드 B가 어떤 관계(엔티티나 keyphrase 중복 같은)로 연결되어 있다면 그 둘 사이에 멀티 홉 쿼리를 만들 수 있어요.

여기서는 추출기 2개와 관계 빌더 1개를 사용해요.

  • Headline extractor: 문서에서 헤드라인을 추출해요
  • Keyphrase extractor: 문서에서 핵심어(keyphrase)를 추출해요
  • Headline splitter: 헤드라인을 기준으로 문서를 노드로 나눠요
  • OverlapScore Builder: keyphrase 중복을 기준으로 노드 사이 관계를 만들어요
from ragas.testset.transforms import Parallel, apply_transforms
from ragas.testset.transforms import (
    HeadlinesExtractor,
    HeadlineSplitter,
    KeyphrasesExtractor,
    OverlapScoreBuilder,
)


headline_extractor = HeadlinesExtractor(llm=llm)
headline_splitter = HeadlineSplitter(min_tokens=300, max_tokens=1000)
keyphrase_extractor = KeyphrasesExtractor(
    llm=llm, property_name="keyphrases", max_num=10
)
relation_builder = OverlapScoreBuilder(
    property_name="keyphrases",
    new_property_name="overlap_score",
    threshold=0.01,
    distance_threshold=0.9,
)

transforms = [
    headline_extractor,
    headline_splitter,
    keyphrase_extractor,
    relation_builder,
]

apply_transforms(kg, transforms=transforms)
Applying KeyphrasesExtractor:   6%|██████▏                                                                                                         | 2/36 [00:01<00:17,  1.94it/s]Property 'keyphrases' already exists in node 'a2f389'. Skipping!
Applying KeyphrasesExtractor:  17%|██████████████████▋                                                                                             | 6/36 [00:01<00:04,  6.37it/s]Property 'keyphrases' already exists in node '3068c0'. Skipping!
Applying KeyphrasesExtractor:  53%|██████████████████████████████████████████████████████████▌                                                    | 19/36 [00:02<00:01,  8.88it/s]Property 'keyphrases' already exists in node '854bf7'. Skipping!
Applying KeyphrasesExtractor:  78%|██████████████████████████████████████████████████████████████████████████████████████▎                        | 28/36 [00:03<00:00,  9.73it/s]Property 'keyphrases' already exists in node '2eeb07'. Skipping!
Property 'keyphrases' already exists in node 'd68f83'. Skipping!
Applying KeyphrasesExtractor:  83%|████████████████████████████████████████████████████████████████████████████████████████████▌                  | 30/36 [00:03<00:00,  9.35it/s]Property 'keyphrases' already exists in node '8fdbea'. Skipping!
Applying KeyphrasesExtractor:  89%|██████████████████████████████████████████████████████████████████████████████████████████████████▋            | 32/36 [00:04<00:00,  7.76it/s]Property 'keyphrases' already exists in node 'ef6ae0'. Skipping!

Persona 구성

자동 persona 생성기를 사용해 이 단계를 자동화할 수도 있어요.

from ragas.testset.persona import Persona

person1 = Persona(
    name="gitlab employee",
    role_description="A junior gitlab employee curious on workings on gitlab",
)
persona2 = Persona(
    name="Hiring manager at gitlab",
    role_description="A hiring manager at gitlab trying to underestand hiring policies in gitlab",
)
persona_list = [person1, persona2]

멀티 홉 쿼리 만들기

MultiHopQuerySynthesizer 를 상속받아 쿼리 생성을 위한 시나리오를 만드는 함수를 수정해요.

단계:

  • 노드 사이의 관계를 기준으로 (nodeA, relationship, nodeB)의 적합 집합을 찾아요
  • 각 적합 집합에 대해
    • keyphrase를 하나 이상의 persona와 매칭해요
    • (Nodes, Persona, Query Style, Query Length)의 모든 가능한 조합을 만들어요
    • 조합에서 필요한 수만큼 쿼리를 샘플링해요
from dataclasses import dataclass
import typing as t
from ragas.testset.synthesizers.multi_hop.base import (
    MultiHopQuerySynthesizer,
    MultiHopScenario,
)
from ragas.testset.synthesizers.prompts import (
    ThemesPersonasInput,
    ThemesPersonasMatchingPrompt,
)


@dataclass
class MyMultiHopQuery(MultiHopQuerySynthesizer):

    theme_persona_matching_prompt = ThemesPersonasMatchingPrompt()

    async def _generate_scenarios(
        self,
        n: int,
        knowledge_graph,
        persona_list,
        callbacks,
    ) -> t.List[MultiHopScenario]:

        # query and get (node_a, rel, node_b) to create multi-hop queries
        results = kg.find_two_nodes_single_rel(
            relationship_condition=lambda rel: (
                True if rel.type == "keyphrases_overlap" else False
            )
        )

        num_sample_per_triplet = max(1, n // len(results))

        scenarios = []
        for triplet in results:
            if len(scenarios) < n:
                node_a, node_b = triplet[0], triplet[-1]
                overlapped_keywords = triplet[1].properties["overlapped_items"]
                if overlapped_keywords:

                    # match the keyword with a persona for query creation
                    themes = list(dict(overlapped_keywords).keys())
                    prompt_input = ThemesPersonasInput(
                        themes=themes, personas=persona_list
                    )
                    persona_concepts = (
                        await self.theme_persona_matching_prompt.generate(
                            data=prompt_input, llm=self.llm, callbacks=callbacks
                        )
                    )

                    overlapped_keywords = [list(item) for item in overlapped_keywords]

                    # prepare and sample possible combinations
                    base_scenarios = self.prepare_combinations(
                        [node_a, node_b],
                        overlapped_keywords,
                        personas=persona_list,
                        persona_item_mapping=persona_concepts.mapping,
                        property_name="keyphrases",
                    )

                    # get number of required samples from this triplet
                    base_scenarios = self.sample_diverse_combinations(
                        base_scenarios, num_sample_per_triplet
                    )

                    scenarios.extend(base_scenarios)

        return scenarios

query = MyMultiHopQuery(llm=llm)
scenarios = await query.generate_scenarios(
    n=10, knowledge_graph=kg, persona_list=persona_list
)

scenarios[4]
MultiHopScenario(
nodes=2
combinations=['Diversity Inclusion & Belonging', 'Diversity, Inclusion & Belonging Goals']
style=Web search like queries
length=short
persona=name='Hiring manager at gitlab' role_description='A hiring manager at gitlab trying to underestand hiring policies in gitlab')

멀티 홉 쿼리 실행

result = await query.generate_sample(scenario=scenarios[-1])
result.user_input
Output

'How does GitLab ensure that its DIB roundtables are effective in promoting diversity and inclusion?'

야! 멀티 홉 쿼리를 만들었어요. 이제 문서 사이의 관계를 만들고 탐색하면서 그런 쿼리들을 얼마든지 만들 수 있어요.

더 알아보기 (Learn more)