From 219e746d340c5899142c1cec5e852679f32c9cba Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Sat, 18 Oct 2025 13:39:49 +0200 Subject: [PATCH 1/7] feat: `SentenceWindowRetriever` now supports `run_async` --- .../retrievers/sentence_window_retriever.py | 75 ++++++ .../test_sentence_window_retriever_async.py | 233 ++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 test/components/retrievers/test_sentence_window_retriever_async.py diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py index 6568f5795f4..48cad8f34ee 100644 --- a/haystack/components/retrievers/sentence_window_retriever.py +++ b/haystack/components/retrievers/sentence_window_retriever.py @@ -255,3 +255,78 @@ def run(self, retrieved_documents: list[Document], window_size: Optional[int] = context_documents.extend(context_docs_sorted) return {"context_windows": context_text, "context_documents": context_documents} + + @component.output_types(context_windows=list[str], context_documents=list[Document]) + async def run_async(self, retrieved_documents: list[Document], window_size: Optional[int] = None): + """ + Based on the `source_id` and on the `doc.meta['split_id']` get surrounding documents from the document store. + + Implements the logic behind the sentence-window technique, retrieving the surrounding documents of a given + document from the document store. + + :param retrieved_documents: List of retrieved documents from the previous retriever. + :param window_size: The number of documents to retrieve before and after the relevant one. This will overwrite + the `window_size` parameter set in the constructor. + :returns: + A dictionary with the following keys: + - `context_windows`: A list of strings, where each string represents the concatenated text from the + context window of the corresponding document in `retrieved_documents`. + - `context_documents`: A list `Document` objects, containing the retrieved documents plus the context + document surrounding them. The documents are sorted by the `split_idx_start` + meta field. + + """ + window_size = window_size or self.window_size + + if window_size < 1: + raise ValueError("The window_size parameter must be greater than 0.") + + if ( + not all(self.split_id_meta_field in doc.meta for doc in retrieved_documents) + and self.raise_on_missing_meta_fields + ): + raise ValueError(f"The retrieved documents must have '{self.split_id_meta_field}' in their metadata.") + + if ( + not all(field in doc.meta for doc in retrieved_documents for field in self._source_id_meta_fields) + and self.raise_on_missing_meta_fields + ): + raise ValueError(f"The retrieved documents must have '{self.source_id_meta_field}' in their metadata.") + + context_text = [] + context_documents = [] + for doc in retrieved_documents: + source_ids = [doc.meta.get(field) for field in self._source_id_meta_fields] + split_id = doc.meta.get(self.split_id_meta_field) + + if any(source_id is None for source_id in source_ids) or split_id is None: + logger.warning( + "Document {doc_id} is missing required metadata fields to be used with " + "SentenceWindowRetriever: {source_id} or {split_id}. Skipping context retrieval for this document.", + doc_id=doc.id, + source_id=self._source_id_meta_fields, + split_id=self.split_id_meta_field, + ) + context_text.append(doc.content or "") + context_documents.append(doc) + continue + + min_before = split_id - window_size + max_after = split_id + window_size + source_id_filters = [ + {"field": f"meta.{source_id_meta_field}", "operator": "==", "value": source_id} + for source_id_meta_field, source_id in zip(self._source_id_meta_fields, source_ids) + ] + conditions = [ + {"field": f"meta.{self.split_id_meta_field}", "operator": ">=", "value": min_before}, + {"field": f"meta.{self.split_id_meta_field}", "operator": "<=", "value": max_after}, + *source_id_filters, + ] + context_docs = await self.document_store.filter_documents_async( + {"operator": "AND", "conditions": conditions} + ) + context_text.append(self.merge_documents_text(context_docs)) + context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) + context_documents.extend(context_docs_sorted) + + return {"context_windows": context_text, "context_documents": context_documents} diff --git a/test/components/retrievers/test_sentence_window_retriever_async.py b/test/components/retrievers/test_sentence_window_retriever_async.py new file mode 100644 index 00000000000..724495e80fb --- /dev/null +++ b/test/components/retrievers/test_sentence_window_retriever_async.py @@ -0,0 +1,233 @@ +# SPDX-FileCopyrightText: 2022-present deepset GmbH +# +# SPDX-License-Identifier: Apache-2.0 + +import random +import re + +import pytest + +from haystack import Document, Pipeline +from haystack.components.preprocessors import DocumentSplitter +from haystack.components.retrievers import InMemoryBM25Retriever +from haystack.components.retrievers.sentence_window_retriever import SentenceWindowRetriever +from haystack.core.pipeline.async_pipeline import AsyncPipeline +from haystack.document_stores.in_memory import InMemoryDocumentStore + + +class TestSentenceWindowRetrieverAsync: + async def test_document_without_split_id(self): + docs = [ + Document(content="This is a text with some words. There is a ", meta={"id": "doc_0"}), + Document(content="some words. There is a second sentence. And there is ", meta={"id": "doc_1"}), + ] + with pytest.raises(ValueError, match="The retrieved documents must have 'split_id_test' in their metadata."): + retriever = SentenceWindowRetriever( + document_store=InMemoryDocumentStore(), window_size=3, split_id_meta_field="split_id_test" + ) + await retriever.run_async(retrieved_documents=docs) + + @pytest.mark.asyncio + async def test_document_without_source_id(self): + docs = [ + Document(content="This is a text with some words. There is a ", meta={"id": "doc_0", "split_id": 0}), + Document( + content="some words. There is a second sentence. And there is ", + meta={"id": "doc_1", "split_id": 1, "source_id_test": "source1"}, + ), + ] + with pytest.raises(ValueError, match="The retrieved documents must have 'source_id_test' in their metadata."): + retriever = SentenceWindowRetriever( + document_store=InMemoryDocumentStore(), window_size=3, source_id_meta_field="source_id_test" + ) + await retriever.run_async(retrieved_documents=docs) + + @pytest.mark.asyncio + async def test_document_without_all_source_ids(self): + docs = [ + Document( + content="These are words from the first section", + meta={"id": "doc_1", "split_id": 0, "section_id": "section1"}, + ), + Document( + content="These are words from the second section, but missing section_id", + meta={"id": "doc_0", "split_id": 0}, + ), + ] + with pytest.raises( + ValueError, match=re.escape("The retrieved documents must have '['id', 'section_id']' in their metadata.") + ): + retriever = SentenceWindowRetriever( + document_store=InMemoryDocumentStore(), window_size=3, source_id_meta_field=["id", "section_id"] + ) + await retriever.run_async(retrieved_documents=docs) + + @pytest.mark.asyncio + async def test_run_async_invalid_window_size(self): + docs = [Document(content="This is a text with some words. There is a ", meta={"id": "doc_0", "split_id": 0})] + with pytest.raises(ValueError): + retriever = SentenceWindowRetriever(document_store=InMemoryDocumentStore(), window_size=0) + await retriever.run_async(retrieved_documents=docs) + + @pytest.mark.asyncio + async def test_constructor_parameter_does_not_change(self): + retriever = SentenceWindowRetriever(InMemoryDocumentStore(), window_size=5) + assert retriever.window_size == 5 + + doc = { + "id": "doc_0", + "content": "This is a text with some words. There is a ", + "source_id": "c5d7c632affc486d0cfe7b3c0f4dc1d3896ea720da2b538d6d10b104a3df5f99", + "page_number": 1, + "split_id": 0, + "split_idx_start": 0, + "_split_overlap": [{"doc_id": "doc_1", "range": (0, 23)}], + } + + await retriever.run_async(retrieved_documents=[Document.from_dict(doc)], window_size=1) + assert retriever.window_size == 5 + + @pytest.mark.asyncio + async def test_context_documents_returned_are_ordered_by_split_idx_start(self): + docs = [] + accumulated_length = 0 + for sent in range(10): + content = f"Sentence {sent}." + docs.append( + Document( + content=content, + meta={ + "id": f"doc_{sent}", + "split_idx_start": accumulated_length, + "source_id": "source1", + "split_id": sent, + }, + ) + ) + accumulated_length += len(content) + + random.shuffle(docs) + + doc_store = InMemoryDocumentStore() + doc_store.write_documents(docs) + retriever = SentenceWindowRetriever(document_store=doc_store, window_size=3) + + # run the retriever with a document whose content = "Sentence 4." + result = await retriever.run_async(retrieved_documents=[doc for doc in docs if doc.content == "Sentence 4."]) + + # assert that the context documents are in the correct order + assert len(result["context_documents"]) == 7 + assert [doc.meta["split_idx_start"] for doc in result["context_documents"]] == [11, 22, 33, 44, 55, 66, 77] + + @pytest.mark.asyncio + async def test_run_async_custom_fields(self): + docs = [] + accumulated_length = 0 + for sent in range(10): + content = f"Sentence {sent}." + docs.append( + Document( + content=content, + meta={ + "id": f"doc_{sent}", + # Missing split_idx_start + "source_id_test": "source1", + "split_id_test": sent, + }, + ) + ) + accumulated_length += len(content) + + random.shuffle(docs) + + doc_store = InMemoryDocumentStore() + doc_store.write_documents(docs) + retriever = SentenceWindowRetriever( + document_store=doc_store, + window_size=3, + source_id_meta_field="source_id_test", + split_id_meta_field="split_id_test", + ) + + # run the retriever with a document whose content = "Sentence 4." + result = await retriever.run_async(retrieved_documents=[doc for doc in docs if doc.content == "Sentence 4."]) + assert len(result["context_documents"]) == 7 + + @pytest.mark.asyncio + async def test_run_async_with_multiple_source_ids(self): + docs = [ + Document(content="This is the first chunk.", meta={"section": "1", "split_id": 0, "source_id": "source1"}), + Document(content="This is the second chunk.", meta={"section": "1", "split_id": 1, "source_id": "source1"}), + Document(content="This is the third chunk.", meta={"section": "1", "split_id": 2, "source_id": "source1"}), + Document( + content="This is a chunk from section 2.", meta={"section": "2", "split_id": 3, "source_id": "source1"} + ), + ] + doc_store = InMemoryDocumentStore() + doc_store.write_documents(docs) + + retriever = SentenceWindowRetriever( + document_store=doc_store, window_size=5, source_id_meta_field=["section", "source_id"] + ) + result = await retriever.run_async( + retrieved_documents=[ + Document( + content="This is the second chunk.", meta={"section": "1", "split_id": 1, "source_id": "source1"} + ) + ] + ) + + assert len(result["context_windows"]) == 1 + assert len(result["context_documents"]) == 3 + assert all(doc.meta["section"] == "1" for doc in result["context_documents"]) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_run_async_with_pipeline(self): + splitter = DocumentSplitter(split_length=1, split_overlap=0, split_by="period") + text = ( + "This is a text with some words. There is a second sentence. And there is also a third sentence. " + "It also contains a fourth sentence. And a fifth sentence. And a sixth sentence. And a seventh sentence" + ) + doc = Document(content=text) + docs = splitter.run([doc]) + doc_store = InMemoryDocumentStore() + doc_store.write_documents(docs["documents"]) + + pipe = AsyncPipeline() + pipe.add_component("bm25_retriever", InMemoryBM25Retriever(doc_store, top_k=1)) + pipe.add_component( + "sentence_window_retriever", SentenceWindowRetriever(document_store=doc_store, window_size=2) + ) + pipe.connect("bm25_retriever", "sentence_window_retriever") + result = await pipe.run_async({"bm25_retriever": {"query": "third"}}) + + assert result["sentence_window_retriever"]["context_windows"] == [ + "This is a text with some words. There is a second sentence. And there is also a third sentence. " + "It also contains a fourth sentence. And a fifth sentence." + ] + assert len(result["sentence_window_retriever"]["context_documents"]) == 5 + + result = await pipe.run_async( + {"bm25_retriever": {"query": "third"}, "sentence_window_retriever": {"window_size": 1}} + ) + assert result["sentence_window_retriever"]["context_windows"] == [ + " There is a second sentence. And there is also a third sentence. It also contains a fourth sentence." + ] + assert len(result["sentence_window_retriever"]["context_documents"]) == 3 + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_serialization_deserialization_in_pipeline(self): + doc_store = InMemoryDocumentStore() + pipe = AsyncPipeline() + pipe.add_component("bm25_retriever", InMemoryBM25Retriever(doc_store, top_k=1)) + pipe.add_component( + "sentence_window_retriever", SentenceWindowRetriever(document_store=doc_store, window_size=2) + ) + pipe.connect("bm25_retriever", "sentence_window_retriever") + + serialized = pipe.to_dict() + deserialized = AsyncPipeline.from_dict(serialized) + + assert deserialized == pipe From 7085da94be4dd8da32d4515a4de288274cf49c9a Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Mon, 20 Oct 2025 10:41:24 +0200 Subject: [PATCH 2/7] refactor: Reduce duplicated code --- .../retrievers/sentence_window_retriever.py | 157 +++++++++--------- 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py index 48cad8f34ee..4f0243a98df 100644 --- a/haystack/components/retrievers/sentence_window_retriever.py +++ b/haystack/components/retrievers/sentence_window_retriever.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Optional, Union +from typing import Any, Callable, Optional, Union from haystack import Document, component, default_from_dict, default_to_dict, logging from haystack.document_stores.types import DocumentStore @@ -204,55 +204,15 @@ def run(self, retrieved_documents: list[Document], window_size: Optional[int] = """ window_size = window_size or self.window_size - - if window_size < 1: - raise ValueError("The window_size parameter must be greater than 0.") - - if ( - not all(self.split_id_meta_field in doc.meta for doc in retrieved_documents) - and self.raise_on_missing_meta_fields - ): - raise ValueError(f"The retrieved documents must have '{self.split_id_meta_field}' in their metadata.") - - if ( - not all(field in doc.meta for doc in retrieved_documents for field in self._source_id_meta_fields) - and self.raise_on_missing_meta_fields - ): - raise ValueError(f"The retrieved documents must have '{self.source_id_meta_field}' in their metadata.") + self._raise_if_windows_size_is_negative(window_size) + self._raise_if_documents_do_not_have_expected_metadata(retrieved_documents) context_text = [] context_documents = [] for doc in retrieved_documents: - source_ids = [doc.meta.get(field) for field in self._source_id_meta_fields] - split_id = doc.meta.get(self.split_id_meta_field) - - if any(source_id is None for source_id in source_ids) or split_id is None: - logger.warning( - "Document {doc_id} is missing required metadata fields to be used with " - "SentenceWindowRetriever: {source_id} or {split_id}. Skipping context retrieval for this document.", - doc_id=doc.id, - source_id=self._source_id_meta_fields, - split_id=self.split_id_meta_field, - ) - context_text.append(doc.content or "") - context_documents.append(doc) - continue - - min_before = split_id - window_size - max_after = split_id + window_size - source_id_filters = [ - {"field": f"meta.{source_id_meta_field}", "operator": "==", "value": source_id} - for source_id_meta_field, source_id in zip(self._source_id_meta_fields, source_ids) - ] - conditions = [ - {"field": f"meta.{self.split_id_meta_field}", "operator": ">=", "value": min_before}, - {"field": f"meta.{self.split_id_meta_field}", "operator": "<=", "value": max_after}, - *source_id_filters, - ] - context_docs = self.document_store.filter_documents({"operator": "AND", "conditions": conditions}) - context_text.append(self.merge_documents_text(context_docs)) - context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) - context_documents.extend(context_docs_sorted) + text, docs = self._retrieve_context_for_document(doc, window_size) + context_text.append(text) + context_documents.extend(docs) return {"context_windows": context_text, "context_documents": context_documents} @@ -277,10 +237,23 @@ async def run_async(self, retrieved_documents: list[Document], window_size: Opti """ window_size = window_size or self.window_size + self._raise_if_windows_size_is_negative(window_size) + self._raise_if_documents_do_not_have_expected_metadata(retrieved_documents) + + context_text = [] + context_documents = [] + for doc in retrieved_documents: + text, docs = await self._retrieve_context_for_document_async(doc, window_size) + context_text.append(text) + context_documents.extend(docs) + + return {"context_windows": context_text, "context_documents": context_documents} + def _raise_if_windows_size_is_negative(self, window_size: int) -> None: if window_size < 1: raise ValueError("The window_size parameter must be greater than 0.") + def _raise_if_documents_do_not_have_expected_metadata(self, retrieved_documents: list[Document]) -> None: if ( not all(self.split_id_meta_field in doc.meta for doc in retrieved_documents) and self.raise_on_missing_meta_fields @@ -293,40 +266,60 @@ async def run_async(self, retrieved_documents: list[Document], window_size: Opti ): raise ValueError(f"The retrieved documents must have '{self.source_id_meta_field}' in their metadata.") - context_text = [] - context_documents = [] - for doc in retrieved_documents: - source_ids = [doc.meta.get(field) for field in self._source_id_meta_fields] - split_id = doc.meta.get(self.split_id_meta_field) - - if any(source_id is None for source_id in source_ids) or split_id is None: - logger.warning( - "Document {doc_id} is missing required metadata fields to be used with " - "SentenceWindowRetriever: {source_id} or {split_id}. Skipping context retrieval for this document.", - doc_id=doc.id, - source_id=self._source_id_meta_fields, - split_id=self.split_id_meta_field, - ) - context_text.append(doc.content or "") - context_documents.append(doc) - continue - - min_before = split_id - window_size - max_after = split_id + window_size - source_id_filters = [ - {"field": f"meta.{source_id_meta_field}", "operator": "==", "value": source_id} - for source_id_meta_field, source_id in zip(self._source_id_meta_fields, source_ids) - ] - conditions = [ - {"field": f"meta.{self.split_id_meta_field}", "operator": ">=", "value": min_before}, - {"field": f"meta.{self.split_id_meta_field}", "operator": "<=", "value": max_after}, - *source_id_filters, - ] - context_docs = await self.document_store.filter_documents_async( - {"operator": "AND", "conditions": conditions} + def _retrieve_context_for_document(self, doc: Document, window_size: int) -> tuple[str, list[Document]]: + source_ids = [doc.meta.get(field) for field in self._source_id_meta_fields] + split_id = doc.meta.get(self.split_id_meta_field) + + if any(source_id is None for source_id in source_ids) or split_id is None: + logger.warning( + "Document {doc_id} is missing required metadata fields to be used with " + "SentenceWindowRetriever: {source_id} or {split_id}. Skipping context retrieval for this document.", + doc_id=doc.id, + source_id=self._source_id_meta_fields, + split_id=self.split_id_meta_field, ) - context_text.append(self.merge_documents_text(context_docs)) - context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) - context_documents.extend(context_docs_sorted) - - return {"context_windows": context_text, "context_documents": context_documents} + return (doc.content or "", [doc]) + + assert split_id is not None + filter_conditions = self._build_filter_conditions(split_id, window_size, source_ids) + context_docs = self.document_store.filter_documents(filter_conditions) + context_text = self.merge_documents_text(context_docs) + context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) + + return context_text, context_docs_sorted + + async def _retrieve_context_for_document_async(self, doc: Document, window_size: int) -> tuple[str, list[Document]]: + source_ids = [doc.meta.get(field) for field in self._source_id_meta_fields] + split_id = doc.meta.get(self.split_id_meta_field) + + if any(source_id is None for source_id in source_ids) or split_id is None: + logger.warning( + "Document {doc_id} is missing required metadata fields to be used with " + "SentenceWindowRetriever: {source_id} or {split_id}. Skipping context retrieval for this document.", + doc_id=doc.id, + source_id=self._source_id_meta_fields, + split_id=self.split_id_meta_field, + ) + return (doc.content or "", [doc]) + + assert split_id is not None + filter_conditions = self._build_filter_conditions(split_id, window_size, source_ids) + context_docs = await self.document_store.filter_documents_async(filter_conditions) + context_text = self.merge_documents_text(context_docs) + context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) + + return context_text, context_docs_sorted + + def _build_filter_conditions(self, split_id: int, window_size: int, source_ids: list[Any]) -> dict[str, Any]: + min_before = split_id - window_size + max_after = split_id + window_size + source_id_filters = [ + {"field": f"meta.{source_id_meta_field}", "operator": "==", "value": source_id} + for source_id_meta_field, source_id in zip(self._source_id_meta_fields, source_ids) + ] + conditions = [ + {"field": f"meta.{self.split_id_meta_field}", "operator": ">=", "value": min_before}, + {"field": f"meta.{self.split_id_meta_field}", "operator": "<=", "value": max_after}, + *source_id_filters, + ] + return {"operator": "AND", "conditions": conditions} From 6f6c6e5433456ffc5def9fe895163a6751e60679 Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Mon, 20 Oct 2025 10:43:19 +0200 Subject: [PATCH 3/7] refactor: Remove unused import --- haystack/components/retrievers/sentence_window_retriever.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py index 4f0243a98df..7fb5cf835ea 100644 --- a/haystack/components/retrievers/sentence_window_retriever.py +++ b/haystack/components/retrievers/sentence_window_retriever.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from typing import Any, Callable, Optional, Union +from typing import Any, Optional, Union from haystack import Document, component, default_from_dict, default_to_dict, logging from haystack.document_stores.types import DocumentStore From 999bea9edd7a413b3244c81bfe13a9a58b8f5b06 Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Mon, 20 Oct 2025 10:53:25 +0200 Subject: [PATCH 4/7] docs: Add release notes --- ...-async-to-sentence-window-retriever-06165eb1fbf9a76f.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/add-run-async-to-sentence-window-retriever-06165eb1fbf9a76f.yaml diff --git a/releasenotes/notes/add-run-async-to-sentence-window-retriever-06165eb1fbf9a76f.yaml b/releasenotes/notes/add-run-async-to-sentence-window-retriever-06165eb1fbf9a76f.yaml new file mode 100644 index 00000000000..5ce43729781 --- /dev/null +++ b/releasenotes/notes/add-run-async-to-sentence-window-retriever-06165eb1fbf9a76f.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added async support to `SentenceWindowRetriever` with a new `run_async()` method, allowing the retriever to be used in + async pipelines and workflows. From 6767595366fa89bef06e5ea8ac7f2adf978c60ef Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Mon, 20 Oct 2025 11:04:34 +0200 Subject: [PATCH 5/7] style: ignore type error from missing `run_async` in `DocumentStore` protocol --- haystack/components/retrievers/sentence_window_retriever.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py index 7fb5cf835ea..36999e70267 100644 --- a/haystack/components/retrievers/sentence_window_retriever.py +++ b/haystack/components/retrievers/sentence_window_retriever.py @@ -304,7 +304,7 @@ async def _retrieve_context_for_document_async(self, doc: Document, window_size: assert split_id is not None filter_conditions = self._build_filter_conditions(split_id, window_size, source_ids) - context_docs = await self.document_store.filter_documents_async(filter_conditions) + context_docs = await self.document_store.filter_documents_async(filter_conditions) # type: ignore context_text = self.merge_documents_text(context_docs) context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) From 4c66803fda9e88c9dbb78f898b071687ce56046c Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Mon, 20 Oct 2025 11:47:14 +0200 Subject: [PATCH 6/7] docs: Precise the ignored mypy error and add a comment explaining why --- haystack/components/retrievers/sentence_window_retriever.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py index 36999e70267..21f9a1512dc 100644 --- a/haystack/components/retrievers/sentence_window_retriever.py +++ b/haystack/components/retrievers/sentence_window_retriever.py @@ -304,7 +304,8 @@ async def _retrieve_context_for_document_async(self, doc: Document, window_size: assert split_id is not None filter_conditions = self._build_filter_conditions(split_id, window_size, source_ids) - context_docs = await self.document_store.filter_documents_async(filter_conditions) # type: ignore + # Ignoring type error because DocumentStore protocol doesn't define filter_documents_async + context_docs = await self.document_store.filter_documents_async(filter_conditions) # type: ignore[misc] context_text = self.merge_documents_text(context_docs) context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field]) From 669a704f7c9d2b81be53c4fa7db6e0d706054a9c Mon Sep 17 00:00:00 2001 From: Charles-Meldhine Madi Mnemoi Date: Tue, 21 Oct 2025 11:26:37 +0200 Subject: [PATCH 7/7] style: update mypy ignore type for filter_documents_async in SentenceWindowRetriever --- haystack/components/retrievers/sentence_window_retriever.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py index 21f9a1512dc..74449d83674 100644 --- a/haystack/components/retrievers/sentence_window_retriever.py +++ b/haystack/components/retrievers/sentence_window_retriever.py @@ -305,7 +305,7 @@ async def _retrieve_context_for_document_async(self, doc: Document, window_size: assert split_id is not None filter_conditions = self._build_filter_conditions(split_id, window_size, source_ids) # Ignoring type error because DocumentStore protocol doesn't define filter_documents_async - context_docs = await self.document_store.filter_documents_async(filter_conditions) # type: ignore[misc] + context_docs = await self.document_store.filter_documents_async(filter_conditions) # type: ignore[attr-defined] context_text = self.merge_documents_text(context_docs) context_docs_sorted = sorted(context_docs, key=lambda doc: doc.meta[self.split_id_meta_field])