Skip to content

Commit ebcf147

Browse files
authored
fix: fix ragas types + add py.typed (#2000)
1 parent 616fb5a commit ebcf147

5 files changed

Lines changed: 38 additions & 55 deletions

File tree

.github/workflows/ragas.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,9 @@ jobs:
5050
- name: Install Hatch
5151
run: pip install --upgrade hatch
5252

53-
# TODO: Once this integration is properly typed, use hatch run test:types
54-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1771
5553
- name: Lint
5654
if: matrix.python-version == '3.9' && runner.os == 'Linux'
57-
run: hatch run fmt-check && hatch run lint:typing
55+
run: hatch run fmt-check && hatch run test:types
5856

5957
- name: Generate docs
6058
if: matrix.python-version == '3.9' && runner.os == 'Linux'

integrations/ragas/pyproject.toml

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,18 @@ integration = 'pytest -m "integration" {args:tests}'
6666
all = 'pytest {args:tests}'
6767
cov-retry = 'all --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x'
6868

69-
types = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
69+
types = "mypy -p haystack_integrations.components.evaluators.ragas {args}"
7070

71-
# TODO: remove lint environment once this integration is properly typed
72-
# test environment should be used instead
73-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1771
74-
[tool.hatch.envs.lint]
75-
installer = "uv"
76-
detached = true
77-
dependencies = ["pip", "mypy>=1.0.0", "ruff>=0.0.243"]
78-
79-
[tool.hatch.envs.lint.scripts]
80-
typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
71+
[tool.mypy]
72+
install_types = true
73+
non_interactive = true
74+
check_untyped_defs = true
75+
disallow_incomplete_defs = true
8176

77+
[[tool.mypy.overrides]]
78+
# ragas does not provide types
79+
module = ["ragas.*"]
80+
ignore_missing_imports = true
8281

8382
[tool.ruff]
8483
target-version = "py38"
@@ -158,16 +157,3 @@ parallel = false
158157
omit = ["*/tests/*", "*/__init__.py"]
159158
show_missing = true
160159
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
161-
162-
163-
[[tool.mypy.overrides]]
164-
module = [
165-
"haystack.*",
166-
"pytest.*",
167-
"ragas.*",
168-
"datasets.*",
169-
"numpy",
170-
"grpc",
171-
"haystack_integrations.*",
172-
]
173-
ignore_missing_imports = true

integrations/ragas/src/haystack_integrations/components/evaluators/py.typed

Whitespace-only changes.

integrations/ragas/src/haystack_integrations/components/evaluators/ragas/evaluator.py

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import re
2-
from typing import Any, Dict, List, Optional, Union, get_args, get_origin
2+
from typing import Any, Dict, List, Optional, Union, cast, get_args, get_origin
33

44
from haystack import Document, component
55
from haystack.dataclasses import ChatMessage
6-
from pydantic import ValidationError # type: ignore
6+
from pydantic import ValidationError
77

8-
from ragas import evaluate # type: ignore
8+
from ragas import evaluate
99
from ragas.dataset_schema import (
1010
EvaluationDataset,
1111
EvaluationResult,
@@ -135,7 +135,7 @@ def run(
135135
)
136136

137137
except (ValueError, ValidationError) as e:
138-
raise self._handle_conversion_error(e) from None
138+
self._handle_conversion_error(e)
139139

140140
dataset = EvaluationDataset([sample])
141141

@@ -147,7 +147,7 @@ def run(
147147
embeddings=self.embedding,
148148
)
149149
except (ValueError, ValidationError) as e:
150-
raise self._handle_evaluation_error(e) from None
150+
self._handle_evaluation_error(e)
151151

152152
return {"result": result}
153153

@@ -157,22 +157,19 @@ def _process_documents(self, documents: Union[List[Union[Document, str]], None])
157157
:param documents: List of Documents or strings to process
158158
:return: List of document contents as strings or None
159159
"""
160-
if documents:
161-
first_type = type(documents[0])
162-
if first_type is Document:
163-
if not all(isinstance(doc, Document) for doc in documents):
164-
error_message = "All elements in documents list must be of type Document."
165-
raise ValueError(error_message)
166-
return [doc.content for doc in documents] # type: ignore[union-attr]
167-
168-
if first_type is str:
169-
if not all(isinstance(doc, str) for doc in documents):
170-
error_message = "All elements in documents list must be strings."
171-
raise ValueError(error_message)
172-
return documents
173-
error_message = "Unsupported type in documents list."
174-
raise ValueError(error_message)
175-
return documents
160+
if documents is None:
161+
return None
162+
163+
if isinstance(documents, list) and all(isinstance(doc, str) for doc in documents):
164+
# we need to check types again in the list comprehension to make mypy happy
165+
return [doc for doc in documents if isinstance(doc, str)]
166+
167+
if isinstance(documents, list) and all(isinstance(doc, Document) for doc in documents):
168+
# we need to check types again in the list comprehension to make mypy happy
169+
return [doc.content for doc in documents if isinstance(doc, Document) and doc.content]
170+
171+
error_message = "'documents' must be a list of either Documents or strings."
172+
raise ValueError(error_message)
176173

177174
def _process_response(self, response: Optional[Union[List[ChatMessage], str]]) -> Union[str, None]:
178175
"""Process response into expected format.
@@ -181,14 +178,14 @@ def _process_response(self, response: Optional[Union[List[ChatMessage], str]]) -
181178
:return: None or Processed response string
182179
"""
183180
if isinstance(response, list): # Check if response is a list
184-
if all(isinstance(item, ChatMessage) for item in response):
185-
return response[0]._content[0].text
181+
if all(isinstance(item, ChatMessage) and item.text for item in response):
182+
return response[0].text
186183
return None
187184
elif isinstance(response, str):
188185
return response
189186
return response
190187

191-
def _handle_conversion_error(self, error: Exception):
188+
def _handle_conversion_error(self, error: Exception) -> None:
192189
"""Handle evaluation errors with improved messages.
193190
194191
:params error: Original error
@@ -199,7 +196,9 @@ def _handle_conversion_error(self, error: Exception):
199196
"retrieved_contexts": "documents",
200197
}
201198
for err in error.errors():
202-
field = err["loc"][0]
199+
# loc is a tuple of strings and ints but according to pydantic docs, the first element is a string
200+
# https://docs.pydantic.dev/latest/errors/errors/
201+
field = cast(str, err["loc"][0])
203202
haystack_field = field_mapping.get(field, field)
204203
expected_type = self.run.__annotations__.get(haystack_field)
205204
type_desc = self._get_expected_type_description(expected_type)
@@ -213,7 +212,7 @@ def _handle_conversion_error(self, error: Exception):
213212
)
214213
raise ValueError(error_message)
215214

216-
def _handle_evaluation_error(self, error: Exception):
215+
def _handle_evaluation_error(self, error: Exception) -> None:
217216
error_message = str(error)
218217
columns_match = re.search(r"additional columns \[(.*?)\]", error_message)
219218
field_mapping = {
@@ -233,7 +232,7 @@ def _handle_evaluation_error(self, error: Exception):
233232
)
234233
raise ValueError(updated_error_message)
235234

236-
def _get_expected_type_description(self, expected_type) -> str:
235+
def _get_expected_type_description(self, expected_type: Any) -> str:
237236
"""Helper method to get a description of the expected type."""
238237
if get_origin(expected_type) is Union:
239238
expected_types = [getattr(t, "__name__", str(t)) for t in get_args(expected_type)]

integrations/ragas/tests/test_evaluator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def test_initializer_allows_optional_llm_and_embeddings():
8282
"invalid_input,field_name,error_message",
8383
[
8484
(["Invalid query type"], "query", "'query' field expected"),
85-
([123, ["Invalid document"]], "documents", "Unsupported type in documents list"),
85+
([123, ["Invalid document"]], "documents", "'documents' must be a list"),
8686
(["score_1"], "rubrics", "'rubrics' field expected"),
8787
],
8888
)

0 commit comments

Comments
 (0)