문서 질의응답

문서 질의응답 (Extractive QA 파이프라인)

질문의 답을 '추출형(extractive)' 방식으로 찾는 파이프라인을 만들어 볼게요. 추출형 모델은 텍스트에서 정답을 그대로 뽑아 주고, 정답이 문서의 어느 위치에 있는지까지 알려줘요. 정확성이 중요한 상황, 그리고 답이 정확히 어디서 나왔는지 알아야 할 때 유용한 방식이에요.

출처: 공식문서

개요

이 튜토리얼에서는 문서를 먼저 색인하는 파이프라인을 만든 뒤, 그 위에 추출형 질의응답 파이프라인을 얹어요. 사용하는 컴포넌트는 TransformersExtractiveReader, InMemoryDocumentStore, InMemoryEmbeddingRetriever, DocumentWriter, 그리고 문서·질문 임베더 두 종류예요.

설치

pip install haystack-ai transformers-haystack sentence-transformers-haystack "datasets>=2.6.1"

문서 스토어에 데이터 로드

데이터로는 고대 세계 7대 불가사의 위키피디아 데이터셋을 써요. 이미 정리된 데이터라 바로 Haystack Document로 바꿀 수 있어요. 색인 파이프라인은 임베더(문서를 벡터로) → DocumentWriter(문서 저장) 순으로 연결돼요.

from datasets import load_dataset
from haystack import Document
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack_integrations.components.readers.transformers import TransformersExtractiveReader
from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentEmbedder
from haystack.components.writers import DocumentWriter

dataset = load_dataset("bilgeyucel/seven-wonders", split="train")

documents = [Document(content=doc["content"], meta=doc["meta"]) for doc in dataset]

model = "sentence-transformers/multi-qa-mpnet-base-dot-v1"

document_store = InMemoryDocumentStore()

indexing_pipeline = Pipeline()

indexing_pipeline.add_component(instance=SentenceTransformersDocumentEmbedder(model=model), name="embedder")
indexing_pipeline.add_component(instance=DocumentWriter(document_store=document_store), name="writer")
indexing_pipeline.connect("embedder.documents", "writer.documents")

indexing_pipeline.run({"documents": documents})

여기서 쓴 임베딩 모델은 마음대로 바꿔도 되지만, 추출형 파이프라인에서도 같은 모델을 써야 한다는 점만 기억하세요.

추출형 QA 파이프라인 만들기

QA 파이프라인은 임베더·리트리버·리더 세 컴포넌트로 구성돼요.

  • SentenceTransformersTextEmbedder: 질문을 벡터로 바꿔요.
  • InMemoryEmbeddingRetriever: 벡터 검색으로 관련 문서를 가져와요. 문서 스토어와 밀접하게 결합되므로 InMemoryDocumentStore와 짝을 이뤄요.
  • TransformersExtractiveReader: 질문에 대한 답과 답의 위치, 신뢰도 점수를 돌려줘요.
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack_integrations.components.readers.transformers import TransformersExtractiveReader
from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersTextEmbedder


retriever = InMemoryEmbeddingRetriever(document_store=document_store)
reader = TransformersExtractiveReader()
reader.warm_up()

extractive_qa_pipeline = Pipeline()

extractive_qa_pipeline.add_component(instance=SentenceTransformersTextEmbedder(model=model), name="embedder")
extractive_qa_pipeline.add_component(instance=retriever, name="retriever")
extractive_qa_pipeline.add_component(instance=reader, name="reader")

extractive_qa_pipeline.connect("embedder.embedding", "retriever.query_embedding")
extractive_qa_pipeline.connect("retriever.documents", "reader.documents")

답 추출해보기

query = "Who was Pliny the Elder?"
extractive_qa_pipeline.run(
    data={"embedder": {"text": query}, "retriever": {"top_k": 3}, "reader": {"query": query, "top_k": 2}}
)

실행하면 ExtractedAnswer 객체가 나와요. 예를 들면 이런 모양이에요.

ExtractedAnswer(query='Who was Pliny the Elder?', score=0.8306006193161011, data='Roman writer', document=Document(...))

신뢰도 점수(score)는 0부터 1 사이예요. 높을수록 모델이 답을 더 확신한다는 뜻이에요. 리더는 확률 점수 순으로 답을 정렬하고, 선택 가능한 top_k로 반환 개수를 제한할 수 있어요.

리더는 기본적으로 no_answer=True를 써서, 답이 없다고 판단될 확률도 함께 돌려줘요. 만약 답만 딱 받고 싶다면 TransformersExtractiveReader를 초기화할 때 no_answerFalse로 설정하면 돼요.

더 알아보기