c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
121 lines
5.3 KiB
Python
121 lines
5.3 KiB
Python
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import math
|
|
|
|
import pytest
|
|
|
|
from haystack import Pipeline
|
|
from haystack.components.embedders import MockTextEmbedder
|
|
from haystack.components.embedders.mock_utils import _l2_normalize
|
|
|
|
|
|
def _ones(text: str) -> list[float]:
|
|
"""Module-level embedding function used to test `embedding_fn` and its serialization."""
|
|
return [1.0, 1.0, 1.0]
|
|
|
|
|
|
def test_l2_normalize_handles_zero_vector():
|
|
# defensive guard in the shared deterministic-embedding helper: a zero vector is returned unchanged
|
|
assert _l2_normalize([0.0, 0.0]) == [0.0, 0.0]
|
|
|
|
|
|
class TestMockTextEmbedder:
|
|
@pytest.mark.parametrize(
|
|
("args", "kwargs", "exception", "match"),
|
|
[
|
|
(([0.1, 0.2],), {"embedding_fn": _ones}, ValueError, "either 'embedding' or 'embedding_fn'"),
|
|
((), {"dimension": 0}, ValueError, "must be a positive integer"),
|
|
(([],), {}, ValueError, "must not be empty"),
|
|
((["not", "numbers"],), {}, TypeError, "must be a sequence of numbers"),
|
|
],
|
|
)
|
|
def test_init_rejects_invalid_config(self, args, kwargs, exception, match):
|
|
with pytest.raises(exception, match=match):
|
|
MockTextEmbedder(*args, **kwargs)
|
|
|
|
def test_deterministic_embedding(self):
|
|
embedding = MockTextEmbedder(dimension=16).run("hello")["embedding"]
|
|
assert len(embedding) == 16
|
|
assert all(isinstance(value, float) for value in embedding)
|
|
# embeddings are L2-normalized, like real ones
|
|
assert math.isclose(math.sqrt(sum(value * value for value in embedding)), 1.0, abs_tol=1e-9)
|
|
|
|
def test_deterministic_distinguishes_texts(self):
|
|
embedder = MockTextEmbedder(dimension=8)
|
|
assert embedder.run("pizza")["embedding"] == embedder.run("pizza")["embedding"]
|
|
assert embedder.run("pizza")["embedding"] != embedder.run("pasta")["embedding"]
|
|
# determinism holds across instances and processes (stable hash, not the salted built-in hash)
|
|
assert (
|
|
MockTextEmbedder(dimension=8).run("x")["embedding"] == MockTextEmbedder(dimension=8).run("x")["embedding"]
|
|
)
|
|
|
|
def test_fixed_embedding(self):
|
|
embedder = MockTextEmbedder([0.1, 0.2, 0.3])
|
|
assert embedder.run("anything")["embedding"] == [0.1, 0.2, 0.3]
|
|
assert embedder.run("something else")["embedding"] == [0.1, 0.2, 0.3]
|
|
|
|
def test_embedding_fn(self):
|
|
assert MockTextEmbedder(embedding_fn=_ones).run("hello")["embedding"] == [1.0, 1.0, 1.0]
|
|
|
|
def test_embedding_fn_invalid_return_raises(self):
|
|
# embedding_fn deliberately returns a non-vector to exercise the runtime type check
|
|
embedder = MockTextEmbedder(embedding_fn=lambda text: "not a vector") # type: ignore[arg-type, return-value]
|
|
with pytest.raises(TypeError, match="must be a sequence of numbers"):
|
|
embedder.run("hello")
|
|
|
|
def test_prefix_suffix_affect_embedding(self):
|
|
plain = MockTextEmbedder(dimension=8).run("hello")["embedding"]
|
|
prefixed = MockTextEmbedder(dimension=8, prefix="search: ").run("hello")["embedding"]
|
|
assert plain != prefixed
|
|
|
|
def test_meta(self):
|
|
meta = MockTextEmbedder(dimension=4).run("a b c")["meta"]
|
|
assert meta["model"] == "mock-model"
|
|
assert meta["usage"] == {"prompt_tokens": 3, "total_tokens": 3}
|
|
# init model and meta are reflected and merged
|
|
custom = MockTextEmbedder(dimension=4, model="custom", meta={"extra": "value"}).run("hi")["meta"]
|
|
assert custom["model"] == "custom"
|
|
assert custom["extra"] == "value"
|
|
|
|
def test_run_rejects_non_string(self):
|
|
# a non-string input is passed on purpose to exercise the runtime type check
|
|
with pytest.raises(TypeError, match="expects a string"):
|
|
MockTextEmbedder().run(["not", "a", "string"]) # type: ignore[arg-type]
|
|
|
|
async def test_run_async(self):
|
|
embedder = MockTextEmbedder(dimension=8)
|
|
assert (await embedder.run_async("hello"))["embedding"] == embedder.run("hello")["embedding"]
|
|
|
|
def test_warm_up_sets_flag_and_run_auto_warms(self):
|
|
embedder = MockTextEmbedder(dimension=8)
|
|
assert embedder._is_warmed_up is False
|
|
embedder.warm_up()
|
|
assert embedder._is_warmed_up is True
|
|
# run auto-warms a fresh instance
|
|
fresh = MockTextEmbedder(dimension=8)
|
|
assert len(fresh.run("hello")["embedding"]) == 8
|
|
assert fresh._is_warmed_up is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"embedder",
|
|
[
|
|
MockTextEmbedder(dimension=8, model="m", meta={"k": "v"}, prefix="p", suffix="s"),
|
|
MockTextEmbedder(embedding_fn=_ones),
|
|
MockTextEmbedder([0.1, 0.2, 0.3]),
|
|
],
|
|
ids=["deterministic", "embedding_fn", "fixed"],
|
|
)
|
|
def test_serialization_roundtrip(self, embedder):
|
|
restored = MockTextEmbedder.from_dict(embedder.to_dict())
|
|
assert isinstance(restored, MockTextEmbedder)
|
|
assert restored.run("hello")["embedding"] == embedder.run("hello")["embedding"]
|
|
|
|
def test_in_pipeline(self):
|
|
pipeline = Pipeline()
|
|
pipeline.add_component("embedder", MockTextEmbedder(dimension=8))
|
|
restored = Pipeline.from_dict(pipeline.to_dict())
|
|
result = restored.run({"embedder": {"text": "hello"}})
|
|
assert len(result["embedder"]["embedding"]) == 8
|