haystack/test/nodes/test_generator.py

177 lines
7.8 KiB
Python
Raw Normal View History

from unittest.mock import patch, create_autospec
[RAG] Integrate "Retrieval-Augmented Generation" with Haystack (#484) * Adding dummy generator implementation * Adding tutorial to try the model * Committing current non working code * Committing current update where we need to call generate function directly and need to convert embedding to tensor way * Addressing review comments. * Refactoring finder, and implementing rag_generator class. * Refined the implementation of RAGGenerator and now it is in clean shape * Renaming RAGGenerator to RAGenerator * Reverting change from finder.py and addressing review comments * Remove support for RagSequenceForGeneration * Utilizing embed_passage function from DensePassageRetriever * Adding sample test data to verify generator output * Updating testing script * Updating testing script * Fixing bug related to top_k * Updating latest farm dependency * Comment out farm dependency * Reverting changes from TransformersReader * Adding transformers dataset to compare transformers and haystack generator implementation * Using generator_encoder instead of question_encoder to generate context_input_ids * Adding workaround to install FARM dependency from master branch * Removing unnecessary changes * Fixing generator test * Removing transformers datasets * Fixing generator test * Some cleanup and updating TODO comments * Adding tutorial notebook * Updating tutorials with comments * Explicitly passing token model in RAG test * Addressing review comments * Fixing notebook * Refactoring tests to reduce memory footprint * Split generator tests in separate ci step and before running it reclaim memory by terminating containers * Moving tika dependent test to separate dir * Remove unwanted code * Brining reader under session scope * Farm is now session object hence restoring changes from default value * Updating assert for pdf converter * Dummy commit to trigger CI flow * REducing memory footprint required for generator tests * Fixing mypy issues * Marking test with tika and elasticsearch markers. Reverting changes in CI and pytest splits * reducing changes * Fixing CI * changing elastic search ci * Fixing test error * Disabling return of embedding * Marking generator test as well * Refactoring tutorials * Increasing ES memory to 750M * Trying another fix for ES CI * Reverting CI changes * Splitting tests in CI * Generator and non-generator markers split * Adding pytest.ini to add markers and enable strict-markers option * Reducing elastic search container memory * Simplifying generator test by using documents with embedding directly * Bump up farm to 0.5.0
2020-10-30 18:06:02 +01:00
import pytest
from haystack import Pipeline
from haystack.schema import Document, Answer
from haystack.nodes.answer_generator import OpenAIAnswerGenerator
from haystack.nodes import PromptTemplate
[RAG] Integrate "Retrieval-Augmented Generation" with Haystack (#484) * Adding dummy generator implementation * Adding tutorial to try the model * Committing current non working code * Committing current update where we need to call generate function directly and need to convert embedding to tensor way * Addressing review comments. * Refactoring finder, and implementing rag_generator class. * Refined the implementation of RAGGenerator and now it is in clean shape * Renaming RAGGenerator to RAGenerator * Reverting change from finder.py and addressing review comments * Remove support for RagSequenceForGeneration * Utilizing embed_passage function from DensePassageRetriever * Adding sample test data to verify generator output * Updating testing script * Updating testing script * Fixing bug related to top_k * Updating latest farm dependency * Comment out farm dependency * Reverting changes from TransformersReader * Adding transformers dataset to compare transformers and haystack generator implementation * Using generator_encoder instead of question_encoder to generate context_input_ids * Adding workaround to install FARM dependency from master branch * Removing unnecessary changes * Fixing generator test * Removing transformers datasets * Fixing generator test * Some cleanup and updating TODO comments * Adding tutorial notebook * Updating tutorials with comments * Explicitly passing token model in RAG test * Addressing review comments * Fixing notebook * Refactoring tests to reduce memory footprint * Split generator tests in separate ci step and before running it reclaim memory by terminating containers * Moving tika dependent test to separate dir * Remove unwanted code * Brining reader under session scope * Farm is now session object hence restoring changes from default value * Updating assert for pdf converter * Dummy commit to trigger CI flow * REducing memory footprint required for generator tests * Fixing mypy issues * Marking test with tika and elasticsearch markers. Reverting changes in CI and pytest splits * reducing changes * Fixing CI * changing elastic search ci * Fixing test error * Disabling return of embedding * Marking generator test as well * Refactoring tutorials * Increasing ES memory to 750M * Trying another fix for ES CI * Reverting CI changes * Splitting tests in CI * Generator and non-generator markers split * Adding pytest.ini to add markers and enable strict-markers option * Reducing elastic search container memory * Simplifying generator test by using documents with embedding directly * Bump up farm to 0.5.0
2020-10-30 18:06:02 +01:00
import logging
@pytest.mark.unit
@patch("haystack.nodes.answer_generator.openai.openai_request")
def test_openai_answer_generator_default_api_base(mock_request):
with patch("haystack.nodes.answer_generator.openai.load_openai_tokenizer"):
generator = OpenAIAnswerGenerator(api_key="fake_api_key")
assert generator.api_base == "https://api.openai.com/v1"
generator.predict(query="test query", documents=[Document(content="test document")])
assert mock_request.call_args.kwargs["url"] == "https://api.openai.com/v1/completions"
@pytest.mark.unit
@patch("haystack.nodes.answer_generator.openai.openai_request")
def test_openai_answer_generator_custom_api_base(mock_request):
with patch("haystack.nodes.answer_generator.openai.load_openai_tokenizer"):
generator = OpenAIAnswerGenerator(api_key="fake_api_key", api_base="https://fake_api_base.com")
assert generator.api_base == "https://fake_api_base.com"
generator.predict(query="test query", documents=[Document(content="test document")])
assert mock_request.call_args.kwargs["url"] == "https://fake_api_base.com/completions"
@pytest.mark.integration
@pytest.mark.parametrize("haystack_openai_config", ["openai", "azure"], indirect=True)
def test_openai_answer_generator(haystack_openai_config, docs):
if not haystack_openai_config:
pytest.skip("No API key found, skipping test")
openai_generator = OpenAIAnswerGenerator(
api_key=haystack_openai_config["api_key"],
azure_base_url=haystack_openai_config.get("azure_base_url", None),
azure_deployment_name=haystack_openai_config.get("azure_deployment_name", None),
model="text-babbage-001",
top_k=1,
)
prediction = openai_generator.predict(query="Who lives in Berlin?", documents=docs, top_k=1)
assert len(prediction["answers"]) == 1
assert "Carla" in prediction["answers"][0].answer
@pytest.mark.integration
@pytest.mark.parametrize("haystack_openai_config", ["openai", "azure"], indirect=True)
def test_openai_answer_generator_custom_template(haystack_openai_config, docs):
if not haystack_openai_config:
pytest.skip("No API key found, skipping test")
lfqa_prompt = PromptTemplate(
"""Synthesize a comprehensive answer from your knowledge and the following topk most relevant paragraphs and
the given question.\n===\Paragraphs: {context}\n===\n{query}"""
)
node = OpenAIAnswerGenerator(
api_key=haystack_openai_config["api_key"],
azure_base_url=haystack_openai_config.get("azure_base_url", None),
azure_deployment_name=haystack_openai_config.get("azure_deployment_name", None),
model="text-babbage-001",
top_k=1,
prompt_template=lfqa_prompt,
)
prediction = node.predict(query="Who lives in Berlin?", documents=docs, top_k=1)
assert len(prediction["answers"]) == 1
@pytest.mark.integration
@pytest.mark.parametrize("haystack_openai_config", ["openai", "azure"], indirect=True)
def test_openai_answer_generator_max_token(haystack_openai_config, docs, caplog):
if not haystack_openai_config:
pytest.skip("No API key found, skipping test")
openai_generator = OpenAIAnswerGenerator(
api_key=haystack_openai_config["api_key"],
azure_base_url=haystack_openai_config.get("azure_base_url", None),
azure_deployment_name=haystack_openai_config.get("azure_deployment_name", None),
model="text-babbage-001",
top_k=1,
)
openai_generator.MAX_TOKENS_LIMIT = 116
with caplog.at_level(logging.INFO):
prediction = openai_generator.predict(query="Who lives in Berlin?", documents=docs, top_k=1)
assert "Skipping all of the provided Documents" in caplog.text
assert len(prediction["answers"]) == 1
# Can't easily check content of answer since it is generative and can change between runs
# mock tokenizer that splits the string
class MockTokenizer:
def encode(self, *args, **kwargs):
return str.split(*args, **kwargs)
def tokenize(self, *args, **kwargs):
return str.split(*args, **kwargs)
@pytest.mark.unit
def test_build_prompt_within_max_length():
with patch("haystack.nodes.answer_generator.openai.load_openai_tokenizer") as mock_load_tokenizer:
mock_load_tokenizer.return_value = MockTokenizer()
generator = OpenAIAnswerGenerator(api_key="fake_key", max_tokens=50)
generator.MAX_TOKENS_LIMIT = 92
query = "query"
documents = [Document("most relevant document"), Document("less relevant document")]
prompt_str, prompt_docs = generator._build_prompt_within_max_length(query=query, documents=documents)
assert len(prompt_docs) == 1
assert prompt_docs[0] == documents[0]
@pytest.mark.unit
def test_openai_answer_generator_pipeline_max_tokens():
"""
tests that the max_tokens parameter is passed to the generator component in the pipeline
"""
question = "What is New York City like?"
mocked_response = "Forget NYC, I was generated by the mock method."
nyc_docs = [Document(content="New York is a cool and amazing city to live in the United States of America.")]
pipeline = Pipeline()
# mock load_openai_tokenizer to avoid accessing the internet to init tiktoken
with patch("haystack.nodes.answer_generator.openai.load_openai_tokenizer"):
openai_generator = OpenAIAnswerGenerator(api_key="fake_api_key", model="text-babbage-001", top_k=1)
pipeline.add_node(component=openai_generator, name="generator", inputs=["Query"])
openai_generator.run = create_autospec(openai_generator.run)
openai_generator.run.return_value = ({"answers": mocked_response}, "output_1")
result = pipeline.run(query=question, documents=nyc_docs, params={"generator": {"max_tokens": 3}})
assert result["answers"] == mocked_response
openai_generator.run.assert_called_with(query=question, documents=nyc_docs, max_tokens=3)
@pytest.mark.unit
@patch("haystack.nodes.answer_generator.openai.OpenAIAnswerGenerator.predict")
def test_openai_answer_generator_run_with_labels_and_isolated_node_eval(patched_predict, eval_labels):
label = eval_labels[0]
query = label.query
document = label.labels[0].document
patched_predict.return_value = {
"answers": [Answer(answer=label.labels[0].answer.answer, document_ids=[document.id])]
}
with patch("haystack.nodes.answer_generator.openai.load_openai_tokenizer"):
openai_generator = OpenAIAnswerGenerator(api_key="fake_api_key", model="text-babbage-001", top_k=1)
result, _ = openai_generator.run(query=query, documents=[document], labels=label, add_isolated_node_eval=True)
assert "answers_isolated" in result
@pytest.mark.unit
@patch("haystack.nodes.answer_generator.base.BaseGenerator.predict_batch")
def test_openai_answer_generator_run_batch_with_labels_and_isolated_node_eval(patched_predict_batch, eval_labels):
queries = [label.query for label in eval_labels]
documents = [[label.labels[0].document] for label in eval_labels]
patched_predict_batch.return_value = {
"queries": queries,
"answers": [
[Answer(answer=label.labels[0].answer.answer, document_ids=[label.labels[0].document.id])]
for label in eval_labels
],
}
with patch("haystack.nodes.answer_generator.openai.load_openai_tokenizer"):
openai_generator = OpenAIAnswerGenerator(api_key="fake_api_key", model="text-babbage-001", top_k=1)
result, _ = openai_generator.run_batch(
queries=queries, documents=documents, labels=eval_labels, add_isolated_node_eval=True
)
assert "answers_isolated" in result