Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EvaluationSetClient for deepset cloud to fetch evaluation sets and la… #2345

Merged
merged 37 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d0106db
EvaluationSetClient for deepset cloud to fetch evaluation sets and la…
FHardow Mar 22, 2022
382c2fb
make DeepsetCloudDocumentStore able to fetch uploaded evaluation set …
FHardow Mar 22, 2022
45fcf05
fix missing renaming of get_evaluation_set_names in DeepsetCloudDocum…
FHardow Mar 22, 2022
1534666
update documentation for evaluation set functionality in deepset clou…
FHardow Mar 22, 2022
3f559f0
DeepsetCloudDocumentStore tests for evaluation set functionality
FHardow Mar 22, 2022
21de609
rename index to evaluation_set_name for DeepsetCloudDocumentStore eva…
FHardow Mar 23, 2022
7168807
raise DeepsetCloudError when no labels were found for evaluation set
FHardow Mar 23, 2022
8e68bff
make use of .get_with_auto_paging in EvaluationSetClient
FHardow Mar 23, 2022
dc16792
Return result of get_with_auto_paging() as it parses the response alr…
FHardow Mar 25, 2022
2d5219b
Make schema import source more specific
FHardow Mar 25, 2022
9229110
fetch all evaluation sets for a workspace in deepset Cloud
FHardow Mar 25, 2022
164b8ec
Rename evaluation_set_name to label_index
FHardow Mar 25, 2022
ab22631
make use of generator functionality for fetching labels
FHardow Mar 25, 2022
68d7fbb
Update Documentation & Code Style
github-actions[bot] Mar 25, 2022
fdefaa5
Adjust function input for DeepsetCloudDocumentStore.get_all_labels, a…
FHardow Mar 25, 2022
4487b53
Merge branch 'feature/fetch-evaluation-set-from-dc' of github.com:dee…
FHardow Mar 25, 2022
56bdb6c
Match error message with pytest.raises
FHardow Mar 25, 2022
23bee8d
Update Documentation & Code Style
github-actions[bot] Mar 25, 2022
e2154b2
DeepsetCloudDocumentStore.get_labels_count raises DeepsetCloudError w…
FHardow Mar 25, 2022
ab138a3
Merge branch 'feature/fetch-evaluation-set-from-dc' of github.com:dee…
FHardow Mar 25, 2022
6b6b8bf
remove unneeded import in tests
FHardow Mar 25, 2022
9604991
DeepsetCloudDocumentStore tests, make reponse bodies a string through…
FHardow Mar 25, 2022
86ee3f2
Merge branch 'master' of github.com:deepset-ai/haystack into feature/…
FHardow Mar 29, 2022
f3b03bc
DeepsetcloudDocumentStore.get_label_count - move raise to return
FHardow Mar 29, 2022
2e7059c
stringify uuid before json.dump as uuid is not serilizable
FHardow Mar 29, 2022
c75d5e6
DeepsetcloudDocumentStore - adjust response mocking in tests
FHardow Mar 29, 2022
5f3e4bb
DeepsetcloudDocumentStore - json dump response body in test
FHardow Mar 29, 2022
32b901a
DeepsetCloudDocumentStore introduce label_index, EvaluationSetClient …
FHardow Mar 30, 2022
2553129
Update Documentation & Code Style
github-actions[bot] Mar 30, 2022
eb1054d
DeepsetCloudDocumentStore rename evaluation_set to evaluation_set_res…
FHardow Mar 30, 2022
805b9ca
Merge branch 'feature/fetch-evaluation-set-from-dc' of github.com:dee…
FHardow Mar 30, 2022
f539c7c
DeepsetCloudDocumentStore - rename missed variable in test
FHardow Mar 30, 2022
a8b33aa
DeepsetCloudDocumentStore - rename missed label_index to index in doc…
FHardow Mar 30, 2022
a93e7f3
Update Documentation & Code Style
github-actions[bot] Mar 30, 2022
10eff9c
DeepsetCloudDocumentStore - update docstrings for EvaluationSetClient
FHardow Mar 30, 2022
6638585
Merge branch 'feature/fetch-evaluation-set-from-dc' of github.com:dee…
FHardow Mar 30, 2022
709ac21
DeepsetCloudDocumentStore - fix typo in doc string
FHardow Mar 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions haystack/document_stores/deepsetcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(
api_endpoint: Optional[str] = None,
similarity: str = "dot_product",
return_embedding: bool = False,
evaluation_set_name: str = "default",
):
"""
A DocumentStore facade enabling you to interact with the documents stored in Deepset Cloud.
Expand Down Expand Up @@ -65,6 +66,10 @@ def __init__(
f"{indexing_info['pending_file_count']} files are pending to be indexed. Indexing status: {indexing_info['status']}"
)

self.evaluation_set_client = DeepsetCloud.get_evaluation_set_client(
api_key=api_key, api_endpoint=api_endpoint, workspace=workspace, evaluation_set_name=evaluation_set_name
)

super().__init__()

def get_all_documents(
Expand Down Expand Up @@ -441,16 +446,45 @@ def write_documents(
"""
raise NotImplementedError("DeepsetCloudDocumentStore currently does not support writing documents.")

def get_evaluation_set_names(self) -> List[str]:
"""
Returns a list of names for uploaded evaluation sets to deepset cloud.

:return: list of evaluation set names
"""
return self.evaluation_set_client.get_evaluation_set_names()

def get_all_labels(
self,
index: Optional[str] = None,
evaluation_set_name: Optional[str] = None,
filters: Optional[Dict[str, Union[Dict, List, str, int, float, bool]]] = None,
headers: Optional[Dict[str, str]] = None,
) -> List[Label]:
raise NotImplementedError("DeepsetCloudDocumentStore currently does not support labels.")
"""
Returns a list of labels for the given index name.

def get_label_count(self, index: Optional[str] = None, headers: Optional[Dict[str, str]] = None) -> int:
raise NotImplementedError("DeepsetCloudDocumentStore currently does not support labels.")
:param evaluation_set_name: Optional name of evaluation set for which labels should be searched.
If None, the DocumentStore's default evaluation_set_name (self.evaluation_set_name) will be used.
:filters: Not supported.
:param headers: Not supported.

:return: list of Labels.
"""
return self.evaluation_set_client.get_labels(evaluation_set_name=evaluation_set_name)

def get_label_count(
self, evaluation_set_name: Optional[str] = None, headers: Optional[Dict[str, str]] = None
) -> int:
"""
Counts the number of labels for the given index and returns the value.

:param evaluation_set_name: Optional evaluation set name for which the labels should be counted.
If None, the DocumentStore's default index (self.index) will be used.
:param headers: Not supported.

:return: number of labels for the given index
"""
raise self.evaluation_set_client.get_labels_count(evaluation_set_name=evaluation_set_name)

def write_labels(
self,
Expand Down
133 changes: 133 additions & 0 deletions haystack/utils/deepsetcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import time
from typing import Any, Dict, Generator, List, Optional, Tuple, Union

from haystack import Label, Document, Answer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this import introduces a cyclic dependency causing all tests to fail. Could you please try from haystack.schema import ...?


try:
from typing import Literal
except ImportError:
Expand Down Expand Up @@ -635,6 +637,115 @@ def _build_workspace_url(self, workspace: Optional[str] = None):
return self.client.build_workspace_url(workspace)


class EvaluationSetClient:
def __init__(
self, client: DeepsetCloudClient, workspace: Optional[str] = None, evaluation_set_name: Optional[str] = None
):
"""
A client to communicate with Deepset Cloud evaluation sets and labels.

:param client: Deepset Cloud client
:param workspace: workspace in Deepset Cloud
:param evaluation_set_name: name of the evaluation set

"""
self.client = client
self.workspace = workspace
self.evaluation_set_name = evaluation_set_name

def get_labels(self, evaluation_set_name: str, workspace: Optional[str] = None) -> List[Label]:
"""
Searches for labels for a given evaluation set in deepset cloud. Returns a list of all found labels.
If no labels were found, raises DeepsetCloudError.

:param evaluation_set_name: name of the evaluation set for which labels should be fetched
:param workspace: Optional workspace in Deepset Cloud
If None, the EvaluationSetClient's default workspace (self.workspace) will be used.

:return: list of Label
"""
try:
evaluation_set = self._get_evaluation_set(evaluation_set_name=evaluation_set_name, workspace=workspace)[0]
except IndexError:
raise DeepsetCloudError(f"No evaluation set found with the name {evaluation_set_name}")

labels = self._get_labels_from_evaluation_set(
workspace=workspace, evaluation_set_id=evaluation_set["evaluation_set_id"]
)

return [
Label(
query=label_dict["query"],
document=Document(content=label_dict["context"]),
is_correct_answer=True,
is_correct_document=True,
origin="user-feedback",
answer=Answer(label_dict["answer"]),
id=label_dict["label_id"],
no_answer=False if label_dict.get("answer", None) else True,
pipeline_id=None,
created_at=None,
updated_at=None,
meta=label_dict["meta"],
filters={},
)
for label_dict in labels
]

def get_labels_count(self, evaluation_set_name: Optional[str] = None, workspace: Optional[str] = None) -> int:
"""
Counts labels for a given evaluation set in deepset cloud.

:param evaluation_set_name: Optional index in Deepset Cloud
If None, the EvaluationSetClient's default index (self.index) will be used.
:param workspace: Optional workspace in Deepset Cloud
If None, the EvaluationSetClient's default workspace (self.workspace) will be used.

:return: Number of labels for the given (or defaulting) index
"""
evaluation_set = self._get_evaluation_set(evaluation_set_name=evaluation_set_name, workspace=workspace)
return evaluation_set[0]["total_labels"]

def get_evaluation_set_names(self, workspace: Optional[str] = None):
"""
Searches for all evaluation set names in the given workspace in Deepset Cloud.

:param workspace: Optional workspace in Deepset Cloud
If None, the EvaluationSetClient's default workspace (self.workspace) will be used.

:return: list of Label
"""
evaluation_sets_response = self._get_evaluation_set(evaluation_set_name=None, workspace=workspace)

return [eval_set["name"] for eval_set in evaluation_sets_response]

def _get_evaluation_set(self, evaluation_set_name: Optional[str], workspace: Optional[str] = None) -> List[dict]:
if not evaluation_set_name:
evaluation_set_name = self.evaluation_set_name

url = self._build_workspace_url(workspace=workspace)
evaluation_set_url = f"{url}/evaluation_sets"

for response in self.client.get_with_auto_paging(
url=evaluation_set_url, query_params={"name": evaluation_set_name}
):
return response.json().get("data", [])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't seem to fit together. get_with_auto_paging returns a generator of the objects within the "data" property.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh true, let me change that :)


def _get_labels_from_evaluation_set(
self, workspace: Optional[str] = None, evaluation_set_id: Optional[str] = None
) -> Generator[dict]:
url = f"{self._build_workspace_url(workspace=workspace)}/evaluation_sets/{evaluation_set_id}"
labels = self.client.get(url=url).json()

for label in labels:
yield label

def _build_workspace_url(self, workspace: Optional[str] = None):
if workspace is None:
workspace = self.workspace
return self.client.build_workspace_url(workspace)


class DeepsetCloud:
"""
A facade to communicate with Deepset Cloud.
Expand Down Expand Up @@ -683,3 +794,25 @@ def get_pipeline_client(
"""
client = DeepsetCloudClient(api_key=api_key, api_endpoint=api_endpoint)
return PipelineClient(client=client, workspace=workspace, pipeline_config_name=pipeline_config_name)

@classmethod
def get_evaluation_set_client(
cls,
api_key: Optional[str] = None,
api_endpoint: Optional[str] = None,
workspace: str = "default",
evaluation_set_name: str = "default",
) -> EvaluationSetClient:
"""
Creates a client to communicate with Deepset Cloud labels.

:param api_key: Secret value of the API key.
If not specified, will be read from DEEPSET_CLOUD_API_KEY environment variable.
:param api_endpoint: The URL of the Deepset Cloud API.
If not specified, will be read from DEEPSET_CLOUD_API_ENDPOINT environment variable.
:param workspace: workspace in Deepset Cloud
:param evaluation_set_name: name of the evaluation set in Deepset Cloud

"""
client = DeepsetCloudClient(api_key=api_key, api_endpoint=api_endpoint)
return EvaluationSetClient(client=client, workspace=workspace, evaluation_set_name=evaluation_set_name)
148 changes: 148 additions & 0 deletions test/test_document_store.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import List
from uuid import uuid4

import numpy as np
import pandas as pd
import pytest
Expand Down Expand Up @@ -1633,6 +1636,151 @@ def test_DeepsetCloudDocumentStore_query(deepset_cloud_document_store):
assert len(filtered_docs) < len(docs)


@pytest.mark.parametrize(
"body, expected_count",
[
(
{
"data": [
{
"evaluation_set_id": uuid4(),
"name": DC_TEST_INDEX,
"created_at": "2022-03-22T13:40:27.535Z",
"matched_labels": 2,
"total_labels": 10,
}
],
"has_more": False,
"total": 1,
},
10,
),
(
{
"data": [
{
"evaluation_set_id": uuid4(),
"name": DC_TEST_INDEX,
"created_at": "2022-03-22T13:40:27.535Z",
"matched_labels": 0,
"total_labels": 0,
}
],
"has_more": False,
"total": 1,
},
0,
),
],
)
@responses.activate
def test_DeepsetCloudDocumentStore_count_of_labels_for_evaluation_set(
deepset_cloud_document_store, body: dict, expected_count: int
):
if MOCK_DC:
responses.add(
method=responses.GET,
url=f"{DC_API_ENDPOINT}/workspaces/default/evaluation_sets",
status=200,
body=body,
query_params={"name": DC_TEST_INDEX},
)
else:
responses.add_passthru(DC_API_ENDPOINT)

count = deepset_cloud_document_store.get_labels_count()
assert count == expected_count


@responses.activate
def test_DeepsetCloudDocumentStore_lists_evaluation_set_names(deepset_cloud_document_store):
if MOCK_DC:
responses.add(
method=responses.GET,
url=f"{DC_API_ENDPOINT}/workspaces/default/evaluation_sets",
status=200,
body={
"data": [
{
"evaluation_set_id": uuid4(),
"name": DC_TEST_INDEX,
"created_at": "2022-03-22T13:40:27.535Z",
"matched_labels": 2,
"total_labels": 10,
}
],
"has_more": False,
"total": 1,
},
)
else:
responses.add_passthru(DC_API_ENDPOINT)

names = deepset_cloud_document_store.get_evaluation_set_names()
assert names == [DC_TEST_INDEX]


@responses.activate
def test_DeepsetCloudDocumentStore_fetches_lables_for_evaluation_set(deepset_cloud_document_store):
if MOCK_DC:
responses.add(
method=responses.GET,
url=f"{DC_API_ENDPOINT}/workspaces/default/evaluation_sets",
status=200,
body=[
{
"label_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"query": "What is berlin?",
"answer": "biggest city in germany",
"answer_start": 0,
"answer_end": 0,
"meta": {},
"context": "Berlin is the biggest city in germany.",
"external_file_name": "string",
"file_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"state": "Label matching status",
"candidates": "Candidates that were found in the label <-> file matching",
}
],
)
else:
responses.add_passthru(DC_API_ENDPOINT)

labels = deepset_cloud_document_store.get_all_labels(evaluation_set_name=DC_TEST_INDEX)
assert labels == [
Label(
query="What is berlin?",
document=Document(content="Berlin is the biggest city in germany."),
is_correct_answer=True,
is_correct_document=True,
origin="user-feedback",
answer=Answer("biggest city in germany"),
id="3fa85f64-5717-4562-b3fc-2c963f66afa6",
no_answer=False,
pipeline_id=None,
created_at=None,
updated_at=None,
meta={},
filters={},
)
]


@responses.activate
def test_DeepsetCloudDocumentStore_fetches_lables_for_evaluation_set_raises_deepsetclouderror_when_nothing_found(
deepset_cloud_document_store,
):
if MOCK_DC:
responses.add(
method=responses.GET, url=f"{DC_API_ENDPOINT}/workspaces/default/evaluation_sets", status=200, body=[]
)
else:
responses.add_passthru(DC_API_ENDPOINT)

with pytest.raises(DeepsetCloudError):
deepset_cloud_document_store.get_all_labels(evaluation_set_name=DC_TEST_INDEX)


@responses.activate
def test_DeepsetCloudDocumentStore_query_by_embedding(deepset_cloud_document_store):
query_emb = np.random.randn(768)
Expand Down