haiku.rag/tests/test_embedder_config.py
Yiorgis Gozadinos 1e8e5e9f6f
Cover MCP, ingester and converter error paths
Add tests for the MCP tools' degradation contracts, malformed WebDAV
multistatus bodies, dry-run poller sweeps including the circuit-open and
discover-failure paths, FS source scheme and symlink handling, docling-serve
zip parsing, and the remaining embedding and reranker helpers. Parametrize
_strip_etag.

Drop the misplaced pragma on the analyze handler, which sat on the return and
left the except uncovered. Add one on the FS symlink OSError guard, which
resolve(strict=False) absorbs for every real link.
2026-07-26 19:14:26 +03:00

194 lines
6.1 KiB
Python

import pytest
from haiku.rag.config import (
AppConfig,
EmbeddingModelConfig,
EmbeddingsConfig,
OllamaConfig,
ProvidersConfig,
)
from haiku.rag.embeddings import get_embedder
def test_ollama_embedder_uses_config():
"""Test that Ollama embedder uses the config passed to get_embedder."""
custom_config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="ollama", name="custom-model", vector_dim=512
),
),
providers=ProvidersConfig(
ollama=OllamaConfig(base_url="http://custom-ollama:8080"),
),
)
embedder = get_embedder(custom_config)
assert embedder._vector_dim == 512
def test_openai_embedder_with_base_url():
"""Test that OpenAI embedder uses custom base_url for vLLM/LM Studio."""
custom_config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="openai",
name="some-local-model",
vector_dim=768,
base_url="http://localhost:8000/v1",
),
),
)
embedder = get_embedder(custom_config)
assert embedder._vector_dim == 768
def test_sentence_transformers_embedder_uses_config():
"""Test that SentenceTransformers embedder uses the config."""
custom_config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="sentence-transformers",
name="all-MiniLM-L6-v2",
vector_dim=384,
),
),
)
embedder = get_embedder(custom_config)
assert embedder._vector_dim == 384
def test_unsupported_provider_raises():
"""Test that unsupported provider raises ValueError."""
custom_config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="unsupported-provider", name="model", vector_dim=512
),
),
)
with pytest.raises(ValueError, match="Unsupported embedding provider"):
get_embedder(custom_config)
def test_ollama_embedder_appends_v1_when_missing():
"""Per-model base_url without /v1 should get it appended for Ollama."""
config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="ollama",
name="qwen3-embedding:4b",
vector_dim=2560,
base_url="http://my-ollama:11434",
),
),
)
embedder = get_embedder(config)
pa_model = embedder._embedder._model # type: ignore[union-attr] # ty: ignore[unresolved-attribute]
assert str(pa_model.base_url).rstrip("/").endswith("/v1") # type: ignore[union-attr] # ty: ignore[unresolved-attribute]
def test_ollama_embedder_does_not_double_append_v1():
"""If the user already includes /v1 we leave it alone."""
config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="ollama",
name="qwen3-embedding:4b",
vector_dim=2560,
base_url="http://my-ollama:11434/v1",
),
),
)
embedder = get_embedder(config)
pa_model = embedder._embedder._model # type: ignore[union-attr] # ty: ignore[unresolved-attribute]
url = str(pa_model.base_url).rstrip("/") # type: ignore[union-attr] # ty: ignore[unresolved-attribute]
assert url.endswith("/v1")
assert not url.endswith("/v1/v1")
def test_vllm_embedder_appends_v1_when_missing():
"""vLLM's chat-completions endpoint also lives under /v1. A user who
forgets the suffix would otherwise POST to <host>/embeddings and get
a 404 — match the Ollama behavior and append it."""
config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="vllm",
name="Qwen/Qwen3-VL-Embedding-8B",
vector_dim=4096,
base_url="http://my-vllm:8000",
),
),
)
embedder = get_embedder(config)
base_url = embedder._base_url # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
assert base_url.endswith("/v1")
def test_vllm_embedder_does_not_double_append_v1():
"""If the user already includes /v1 we leave it alone."""
config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider="vllm",
name="Qwen/Qwen3-VL-Embedding-8B",
vector_dim=4096,
base_url="http://my-vllm:8000/v1",
),
),
)
embedder = get_embedder(config)
base_url = embedder._base_url.rstrip("/") # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
assert base_url.endswith("/v1")
assert not base_url.endswith("/v1/v1")
def test_vector_dim_property_reports_configured_dimension():
from haiku.rag.embeddings import EmbedderWrapper
assert EmbedderWrapper(embedder=None, vector_dim=512).vector_dim == 512
@pytest.mark.parametrize(
"provider,env_var",
[("voyageai", "VOYAGE_API_KEY"), ("cohere", "CO_API_KEY")],
)
def test_saas_providers_build_offline(monkeypatch, provider, env_var):
"""Construction only wires the SDK; no request is made."""
monkeypatch.setenv(env_var, "test-key")
config = AppConfig(
embeddings=EmbeddingsConfig(
model=EmbeddingModelConfig(
provider=provider, name="some-model", vector_dim=1024
),
),
)
embedder = get_embedder(config)
assert embedder.vector_dim == 1024
def test_cohere_floats_rejects_missing_embeddings():
from types import SimpleNamespace
from haiku.rag.embeddings.cohere import _floats
result = SimpleNamespace(embeddings=SimpleNamespace(float_=None))
with pytest.raises(ValueError, match="no float embeddings"):
_floats(result)
def test_voyageai_to_pil_rejects_unsupported_type():
from haiku.rag.embeddings.voyageai import _to_pil
with pytest.raises(TypeError, match="Unsupported image type"):
_to_pil("not an image") # ty: ignore[invalid-argument-type]