Files
wehub-resource-sync c56bef871b
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

255 lines
12 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import os
from typing import Any
from unittest.mock import ANY, AsyncMock, Mock
import numpy as np
import pytest
from haystack import Document, component
from haystack.components.embedders import OpenAIDocumentEmbedder, OpenAITextEmbedder
from haystack.components.retrievers import InMemoryEmbeddingRetriever, TextEmbeddingRetriever
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.document_stores.types import DuplicatePolicy
@component
class MockTextEmbedder:
@component.output_types(embedding=list[float])
def run(self, text: str) -> dict[str, list[float]]:
return {"embedding": np.ones(384).tolist()}
class TestTextEmbeddingRetriever:
@pytest.fixture
def sample_documents(self):
return [
Document(content="Renewable energy is energy that is collected from renewable resources."),
Document(content="Solar energy is a type of green energy that is harnessed from the sun."),
Document(content="Wind energy is another type of green energy that is generated by wind turbines."),
Document(content="Geothermal energy is heat that comes from the sub-surface of the earth."),
Document(content="Fossil fuels, such as coal, oil, and natural gas, are non-renewable energy sources."),
]
@pytest.fixture
def document_store_with_embeddings(self, sample_documents):
"""Create a document store populated with embedded documents."""
document_store = InMemoryDocumentStore()
doc_embedder = OpenAIDocumentEmbedder()
doc_writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.SKIP)
embedded_docs = doc_embedder.run(sample_documents)["documents"]
doc_writer.run(documents=embedded_docs)
return document_store
def test_init(self):
embedding_retriever = InMemoryEmbeddingRetriever(document_store=InMemoryDocumentStore())
text_embedder = MockTextEmbedder()
retriever = TextEmbeddingRetriever(retriever=embedding_retriever, text_embedder=text_embedder)
assert retriever.retriever == embedding_retriever
assert retriever.text_embedder == text_embedder
def test_run_with_empty_document_store(self):
retriever = TextEmbeddingRetriever(
retriever=InMemoryEmbeddingRetriever(document_store=InMemoryDocumentStore()),
text_embedder=MockTextEmbedder(),
)
result = retriever.run(query="green energy")
assert "documents" in result
assert result["documents"] == []
def test_run_returns_documents_sorted_by_score(self):
doc_high = Document(content="Solar energy", id="doc1", score=0.9)
doc_low = Document(content="Fossil fuels", id="doc2", score=0.3)
doc_mid = Document(content="Wind energy", id="doc3", score=0.6)
@component
class MockRetriever:
@component.output_types(documents=list[Document])
def run(
self, query_embedding: list[float], filters: dict[str, Any] | None = None, top_k: int | None = None
) -> dict[str, list[Document]]:
return {"documents": [doc_low, doc_high, doc_mid]}
retriever = TextEmbeddingRetriever(retriever=MockRetriever(), text_embedder=MockTextEmbedder())
result = retriever.run(query="energy")
scores = [doc.score for doc in result["documents"]]
assert scores == sorted(scores, reverse=True)
def test_to_dict(self):
retriever = TextEmbeddingRetriever(
retriever=InMemoryEmbeddingRetriever(document_store=InMemoryDocumentStore()),
text_embedder=MockTextEmbedder(),
)
result = retriever.to_dict()
assert result == {
"type": "haystack.components.retrievers.text_embedding_retriever.TextEmbeddingRetriever",
"init_parameters": {
"retriever": {
"type": "haystack.components.retrievers.in_memory.embedding_retriever.InMemoryEmbeddingRetriever",
"init_parameters": {
"document_store": {
"type": "haystack.document_stores.in_memory.document_store.InMemoryDocumentStore",
"init_parameters": {
"bm25_tokenization_regex": "(?u)\\b\\w+\\b",
"bm25_algorithm": "BM25L",
"bm25_parameters": {},
"embedding_similarity_function": "dot_product",
"index": ANY,
"shared": True,
"return_embedding": True,
},
},
"filters": None,
"top_k": 10,
"scale_score": False,
"return_embedding": False,
"filter_policy": "replace",
},
},
"text_embedder": {
"type": "retrievers.test_text_embedding_retriever.MockTextEmbedder",
"init_parameters": {},
},
},
}
def test_from_dict(self, monkeypatch):
monkeypatch.setenv("OPENAI_API_KEY", "fake-api-key")
data = {
"type": "haystack.components.retrievers.text_embedding_retriever.TextEmbeddingRetriever",
"init_parameters": {
"retriever": {
"type": "haystack.components.retrievers.in_memory.embedding_retriever.InMemoryEmbeddingRetriever",
"init_parameters": {
"document_store": {
"type": "haystack.document_stores.in_memory.document_store.InMemoryDocumentStore",
"init_parameters": {
"bm25_tokenization_regex": "(?u)\\b\\w\\w+\\b",
"bm25_algorithm": "BM25L",
"bm25_parameters": {},
"embedding_similarity_function": "dot_product",
"index": "4bb5369d-779f-487b-9c16-3c40f503438b",
"shared": True,
"return_embedding": True,
},
},
"filters": None,
"top_k": 10,
"scale_score": False,
"return_embedding": False,
"filter_policy": "replace",
},
},
"text_embedder": {
"type": "haystack.components.embedders.openai_text_embedder.OpenAITextEmbedder",
"init_parameters": {
"api_key": {"env_vars": ["OPENAI_API_KEY"], "strict": True, "type": "env_var"},
"api_base_url": None,
"dimensions": None,
"model": "text-embedding-ada-002",
"organization": None,
"http_client_kwargs": None,
"prefix": "",
"suffix": "",
"timeout": None,
"max_retries": None,
},
},
},
}
result = TextEmbeddingRetriever.from_dict(data)
assert isinstance(result, TextEmbeddingRetriever)
assert isinstance(result.retriever, InMemoryEmbeddingRetriever)
assert isinstance(result.text_embedder, OpenAITextEmbedder)
@pytest.mark.skipif(os.environ.get("OPENAI_API_KEY", "") == "", reason="OPENAI_API_KEY is not set")
@pytest.mark.integration
def test_run_with_filters(self, document_store_with_embeddings):
retriever = TextEmbeddingRetriever(
retriever=InMemoryEmbeddingRetriever(document_store=document_store_with_embeddings),
text_embedder=OpenAITextEmbedder(),
)
result = retriever.run(query="energy", filters={"field": "meta.category", "operator": "==", "value": "solar"})
assert "documents" in result
assert all(doc.meta.get("category") == "solar" for doc in result["documents"])
@pytest.mark.skipif(os.environ.get("OPENAI_API_KEY", "") == "", reason="OPENAI_API_KEY is not set")
@pytest.mark.integration
def test_run_with_top_k(self, document_store_with_embeddings):
retriever = TextEmbeddingRetriever(
retriever=InMemoryEmbeddingRetriever(document_store=document_store_with_embeddings),
text_embedder=OpenAITextEmbedder(),
)
result = retriever.run(query="energy", top_k=2)
assert "documents" in result
assert len(result["documents"]) <= 2
class TestComponentLifecycle:
def test_warm_up_delegates_to_inner_components(self):
text_embedder = Mock(spec=["run", "warm_up"])
retriever = Mock(spec=["run", "warm_up"])
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
component.warm_up()
text_embedder.warm_up.assert_called_once()
retriever.warm_up.assert_called_once()
async def test_warm_up_async_delegates_to_inner_components(self):
text_embedder = Mock(spec=["run", "warm_up_async"])
text_embedder.warm_up_async = AsyncMock()
retriever = Mock(spec=["run", "warm_up_async"])
retriever.warm_up_async = AsyncMock()
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
await component.warm_up_async()
text_embedder.warm_up_async.assert_awaited_once()
retriever.warm_up_async.assert_awaited_once()
async def test_warm_up_async_falls_back_to_sync_warm_up(self):
text_embedder = Mock(spec=["run", "warm_up"])
retriever = Mock(spec=["run", "warm_up"])
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
await component.warm_up_async()
text_embedder.warm_up.assert_called_once()
retriever.warm_up.assert_called_once()
def test_close_delegates_to_inner_components(self):
text_embedder = Mock(spec=["run", "close"])
retriever = Mock(spec=["run", "close"])
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
component.close()
text_embedder.close.assert_called_once()
retriever.close.assert_called_once()
async def test_close_async_delegates_to_inner_components(self):
text_embedder = Mock(spec=["run", "close_async"])
text_embedder.close_async = AsyncMock()
retriever = Mock(spec=["run", "close_async"])
retriever.close_async = AsyncMock()
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
await component.close_async()
text_embedder.close_async.assert_awaited_once()
retriever.close_async.assert_awaited_once()
async def test_close_async_falls_back_to_sync_close(self):
text_embedder = Mock(spec=["run", "close"])
retriever = Mock(spec=["run", "close"])
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
await component.close_async()
text_embedder.close.assert_called_once()
retriever.close.assert_called_once()
async def test_lifecycle_is_safe_when_inner_components_lack_methods(self):
text_embedder = Mock(spec=["run"])
retriever = Mock(spec=["run"])
component = TextEmbeddingRetriever(retriever=retriever, text_embedder=text_embedder)
component.warm_up()
await component.warm_up_async()
component.close()
await component.close_async()