test: mock all Summarizer tests and move a few into e2e (#4299)

* stub e2e folders

* simplify pipeline test

* mocking

* unit tests fixed

* clean up e2e

* pipeline tests work

* pylint

* leftover

* small fix from #2994 and additional tests

* review feedback

* change summaries

* black

* revert models and summaries
This commit is contained in:
ZanSara
2023-03-01 17:30:55 +01:00
committed by GitHub
parent 583d2d8244
commit ae04ce3c6a
6 changed files with 191 additions and 106 deletions
+10
View File
@@ -1,8 +1,12 @@
import os
import uuid
from contextlib import contextmanager
import random
from pathlib import Path
import torch
import numpy as np
import pytest
from haystack.schema import Document
@@ -20,6 +24,12 @@ from haystack.document_stores import (
SAMPLES_PATH = Path(__file__).parent.parent / "test" / "samples"
# Fix all random seeds that come to mind
torch.manual_seed(0)
np.random.seed(0)
random.seed(0)
@pytest.fixture
def docs_all_formats():
return [
+48
View File
@@ -0,0 +1,48 @@
from haystack.schema import Document
from haystack.nodes import TransformersSummarizer
DOCS = [
Document(
content="""
PG&E stated it scheduled the blackouts in response to forecasts for high winds amid dry conditions.
The aim is to reduce the risk of wildfires. Nearly 800 thousand customers were scheduled to be affected
by the shutoffs which were expected to last through at least midday tomorrow.
"""
),
Document(
content="""
The tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, and the tallest
structure in Paris. Its base is square, measuring 125 metres (410 ft) on each side. During its construction,
the Eiffel Tower surpassed the Washington Monument to become the tallest man-made structure in the world, a
title it held for 41 years until the Chrysler Building in New York City was finished in 1930. It was the first
structure to reach a height of 300 metres. Due to the addition of a broadcasting aerial at the top of the tower
in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Excluding transmitters, the Eiffel
Tower is the second tallest free-standing structure in France after the Millau Viaduct.
"""
),
]
EXPECTED_SUMMARIES = [
"Hundreds of thousands of customers in California have been left without power after a power cut.",
"The Eiffel Tower in Paris is the world's tallest man-made structure.",
]
def test_summarization():
summarizer = TransformersSummarizer(model_name_or_path="sshleifer/distilbart-xsum-12-6", use_gpu=False)
summarized_docs = summarizer.predict(documents=DOCS)
assert len(summarized_docs) == len(DOCS)
for expected_summary, summary in zip(EXPECTED_SUMMARIES, summarized_docs):
assert expected_summary.strip() == summary.meta["summary"].strip()
def test_summarization_batch():
summarizer = TransformersSummarizer(model_name_or_path="sshleifer/distilbart-xsum-12-6", use_gpu=False)
summarized_docs = summarizer.predict_batch(documents=[DOCS, DOCS])
assert len(summarized_docs) == 2 # Number of document lists
assert len(summarized_docs[0]) == len(DOCS)
assert len(summarized_docs[1]) == len(DOCS)
for expected_summary, summary_1, summary_2 in zip(EXPECTED_SUMMARIES, summarized_docs[0], summarized_docs[1]):
assert expected_summary.strip() == summary_1.meta["summary"].strip()
assert expected_summary.strip() == summary_2.meta["summary"].strip()
@@ -0,0 +1,72 @@
from haystack import Document
from haystack.document_stores import InMemoryDocumentStore
from haystack.pipelines import SearchSummarizationPipeline
from haystack.nodes import BM25Retriever, TransformersSummarizer
def test_summarization_pipeline():
docs = [
Document(
content="""
PG&E stated it scheduled the blackouts in response to forecasts for high winds amid dry conditions.
The aim is to reduce the risk of wildfires. Nearly 800 thousand customers were scheduled to be affected
by the shutoffs which were expected to last through at least midday tomorrow.
"""
),
Document(
content="""
The Eiffel Tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, and the tallest
structure in Paris. Its base is square, measuring 125 metres (410 ft) on each side. During its construction,
the Eiffel Tower surpassed the Washington Monument to become the tallest man-made structure in the world, a
title it held for 41 years until the Chrysler Building in New York City was finished in 1930. It was the first
structure to reach a height of 300 metres. Due to the addition of a broadcasting aerial at the top of the tower
in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Excluding transmitters, the Eiffel
Tower is the second tallest free-standing structure in France after the Millau Viaduct.
"""
),
]
summarizer = TransformersSummarizer(model_name_or_path="sshleifer/distilbart-xsum-12-6", use_gpu=False)
ds = InMemoryDocumentStore(use_bm25=True)
retriever = BM25Retriever(document_store=ds)
ds.write_documents(docs)
query = "Eiffel Tower"
pipeline = SearchSummarizationPipeline(retriever=retriever, summarizer=summarizer, return_in_answer_format=True)
output = pipeline.run(query=query, params={"Retriever": {"top_k": 1}})
answers = output["answers"]
assert len(answers) == 1
assert "The Eiffel Tower is one of the world's tallest structures" == answers[0]["answer"].strip()
def test_summarization_pipeline_one_summary():
split_docs = [
Document(
content="""
The tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, and the tallest structure in Paris.
Its base is square, measuring 125 metres (410 ft) on each side. During its construction, the Eiffel Tower surpassed the
Washington Monument to become the tallest man-made structure in the world, a title it held for 41 years until the Chrysler
Building in New York City was finished in 1930.
"""
),
Document(
content="""
It was the first structure to reach a height of 300 metres. Due to the addition of a broadcasting aerial at the
top of the tower in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Excluding transmitters,
the Eiffel Tower is the second tallest free-standing structure in France after the Millau Viaduct.
"""
),
]
ds = InMemoryDocumentStore(use_bm25=True)
retriever = BM25Retriever(document_store=ds)
ds.write_documents(split_docs)
summarizer = TransformersSummarizer(model_name_or_path="sshleifer/distilbart-xsum-12-6", use_gpu=False)
query = "Eiffel Tower"
pipeline = SearchSummarizationPipeline(
retriever=retriever, summarizer=summarizer, generate_single_summary=True, return_in_answer_format=True
)
output = pipeline.run(query=query, params={"Retriever": {"top_k": 2}})
answers = output["answers"]
assert len(answers) == 1
assert answers[0]["answer"].strip() == "The Eiffel Tower was built in 1924 in Paris, France."
+4 -4
View File
@@ -128,10 +128,10 @@ class TransformersSummarizer(BaseSummarizer):
:return: List of Documents, where Document.meta["summary"] contains the summarization
"""
if self.min_length > self.max_length:
raise AttributeError("min_length cannot be greater than max_length")
raise ValueError("min_length cannot be greater than max_length")
if len(documents) == 0:
raise AttributeError("Summarizer needs at least one document to produce a summary.")
raise ValueError("Summarizer needs at least one document to produce a summary.")
contexts: List[str] = [doc.content for doc in documents]
@@ -178,12 +178,12 @@ class TransformersSummarizer(BaseSummarizer):
:param batch_size: Number of Documents to process at a time.
"""
if self.min_length > self.max_length:
raise AttributeError("min_length cannot be greater than max_length")
raise ValueError("min_length cannot be greater than max_length")
if len(documents) == 0 or (
isinstance(documents[0], list) and all(len(docs) == 0 for docs in documents if isinstance(docs, list))
):
raise AttributeError("Summarizer needs at least one document to produce a summary.")
raise ValueError("Summarizer needs at least one document to produce a summary.")
if batch_size is None:
batch_size = self.batch_size
-5
View File
@@ -549,11 +549,6 @@ def lfqa_generator(request):
return Seq2SeqGenerator(model_name_or_path=request.param, min_length=100, max_length=200)
@pytest.fixture
def summarizer():
return TransformersSummarizer(model_name_or_path="sshleifer/distilbart-xsum-12-6", use_gpu=False)
@pytest.fixture
def reader_without_normalized_scores():
return FARMReader(
+57 -97
View File
@@ -1,58 +1,74 @@
from ensurepip import version
import pytest
import haystack
from haystack.utils.torch_utils import ListDataset
from haystack.schema import Document
from haystack.pipelines import SearchSummarizationPipeline
from haystack.nodes import DensePassageRetriever, EmbeddingRetriever, TransformersSummarizer
from haystack.nodes.other.document_merger import DocumentMerger
from haystack.nodes import TransformersSummarizer
pytestmark = pytest.mark.skip("Tests are too heavy for Github runners, skipping for now")
DOCS = [
Document(
content="""PG&E stated it scheduled the blackouts in response to forecasts for high winds amid dry conditions. The aim is to reduce the risk of wildfires. Nearly 800 thousand customers were scheduled to be affected by the shutoffs which were expected to last through at least midday tomorrow."""
),
Document(
content="""The tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, and the tallest structure in Paris. Its base is square, measuring 125 metres (410 ft) on each side. During its construction, the Eiffel Tower surpassed the Washington Monument to become the tallest man-made structure in the world, a title it held for 41 years until the Chrysler Building in New York City was finished in 1930. It was the first structure to reach a height of 300 metres. Due to the addition of a broadcasting aerial at the top of the tower in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Excluding transmitters, the Eiffel Tower is the second tallest free-standing structure in France after the Millau Viaduct."""
),
]
EXPECTED_SUMMARIES = [
"California's largest electricity provider, PG&E, has shut down power supplies to thousands of customers.",
" The Eiffel Tower in Paris has officially opened its doors to the public.",
]
SPLIT_DOCS = [
Document(
content="""The tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, and the tallest structure in Paris. Its base is square, measuring 125 metres (410 ft) on each side. During its construction, the Eiffel Tower surpassed the Washington Monument to become the tallest man-made structure in the world, a title it held for 41 years until the Chrysler Building in New York City was finished in 1930."""
),
Document(
content="""It was the first structure to reach a height of 300 metres. Due to the addition of a broadcasting aerial at the top of the tower in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Excluding transmitters, the Eiffel Tower is the second tallest free-standing structure in France after the Millau Viaduct."""
),
]
# Documents order is very important to produce summary.
# Different order of same documents produce different summary.
EXPECTED_ONE_SUMMARIES = [
" The Eiffel Tower in Paris has officially opened its doors to the public.",
" The Eiffel Tower in Paris has become the tallest man-made structure in the world.",
DOCS = [Document(content=doc) for doc in ["First test doc", "Second test doc"]]
EXPECTED_SUMMARIES = ["First summary", "Second summary"]
SUMMARIZED_DOCS = [
Document(content=doc.content, meta={"summary": summary}) for doc, summary in zip(DOCS, EXPECTED_SUMMARIES)
]
@pytest.mark.integration
@pytest.mark.summarizer
def test_summarization(summarizer):
class MockHFPipeline:
def __init__(self, *a, **k):
pass
def __call__(self, docs, *a, **k):
summaries = [{"summary_text": summary} for summary in EXPECTED_SUMMARIES]
if isinstance(docs, ListDataset):
return [summaries for _ in docs]
return summaries
def tokenizer(self, *a, **k):
return {"input_ids": []}
@pytest.fixture
def mock_models(monkeypatch):
monkeypatch.setattr(haystack.nodes.summarizer.transformers, "pipeline", MockHFPipeline)
@pytest.fixture
def summarizer(mock_models) -> TransformersSummarizer:
return TransformersSummarizer(model_name_or_path="irrelevant/anyway", use_gpu=False)
@pytest.mark.unit
def test_summarization_no_docs(summarizer):
with pytest.raises(ValueError, match="at least one document"):
summarizer.predict(documents=[])
with pytest.raises(ValueError, match="at least one document"):
summarizer.predict_batch(documents=[])
@pytest.mark.unit
def test_summarization_no_docs(summarizer):
summarizer.min_length = 10
summarizer.max_length = 1
with pytest.raises(ValueError, match="min_length cannot be greater than max_length"):
summarizer.predict(documents=DOCS)
@pytest.mark.unit
def test_summarization_one_doc(summarizer):
summarized_docs = summarizer.predict(documents=[DOCS[0]])
assert len(summarized_docs) == 1
assert EXPECTED_SUMMARIES[0] == summarized_docs[0].meta["summary"]
@pytest.mark.unit
def test_summarization_more_docs(summarizer):
summarized_docs = summarizer.predict(documents=DOCS)
assert len(summarized_docs) == len(DOCS)
for expected_summary, summary in zip(EXPECTED_SUMMARIES, summarized_docs):
assert expected_summary == summary.meta["summary"]
@pytest.mark.integration
@pytest.mark.summarizer
@pytest.mark.unit
def test_summarization_batch_single_doc_list(summarizer):
summarized_docs = summarizer.predict_batch(documents=DOCS)
assert len(summarized_docs) == len(DOCS)
@@ -60,66 +76,10 @@ def test_summarization_batch_single_doc_list(summarizer):
assert expected_summary == summary.meta["summary"]
@pytest.mark.integration
@pytest.mark.summarizer
@pytest.mark.unit
def test_summarization_batch_multiple_doc_lists(summarizer):
summarized_docs = summarizer.predict_batch(documents=[DOCS, DOCS])
assert len(summarized_docs) == 2 # Number of document lists
assert len(summarized_docs[0]) == len(DOCS)
for expected_summary, summary in zip(EXPECTED_SUMMARIES, summarized_docs[0]):
assert expected_summary == summary.meta["summary"]
@pytest.mark.integration
@pytest.mark.summarizer
@pytest.mark.parametrize(
"retriever,document_store", [("embedding", "memory"), ("bm25", "elasticsearch")], indirect=True
)
def test_summarization_pipeline(document_store, retriever, summarizer):
document_store.write_documents(DOCS)
if isinstance(retriever, EmbeddingRetriever) or isinstance(retriever, DensePassageRetriever):
document_store.update_embeddings(retriever=retriever)
query = "Where is Eiffel Tower?"
pipeline = SearchSummarizationPipeline(retriever=retriever, summarizer=summarizer, return_in_answer_format=True)
output = pipeline.run(query=query, params={"Retriever": {"top_k": 1}})
answers = output["answers"]
assert len(answers) == 1
assert " The Eiffel Tower in Paris has officially opened its doors to the public." == answers[0]["answer"]
#
# Document Merger + Summarizer tests
#
@pytest.mark.integration
@pytest.mark.summarizer
def test_summarization_one_summary(summarizer):
dm = DocumentMerger()
merged_document = dm.merge(documents=SPLIT_DOCS)
summarized_docs = summarizer.predict(documents=merged_document)
assert len(summarized_docs) == 1
assert EXPECTED_ONE_SUMMARIES[0] == summarized_docs[0].meta["summary"]
@pytest.mark.integration
@pytest.mark.summarizer
@pytest.mark.parametrize(
"retriever,document_store", [("embedding", "memory"), ("bm25", "elasticsearch")], indirect=True
)
def test_summarization_pipeline_one_summary(document_store, retriever, summarizer):
document_store.write_documents(SPLIT_DOCS)
if isinstance(retriever, EmbeddingRetriever) or isinstance(retriever, DensePassageRetriever):
document_store.update_embeddings(retriever=retriever)
query = "Where is Eiffel Tower?"
pipeline = SearchSummarizationPipeline(
retriever=retriever, summarizer=summarizer, generate_single_summary=True, return_in_answer_format=True
)
output = pipeline.run(query=query, params={"Retriever": {"top_k": 2}})
answers = output["answers"]
assert len(answers) == 1
assert answers[0]["answer"] in EXPECTED_ONE_SUMMARIES