Skip to content

windlass.providers.embeddings.hash

hash

A deterministic, dependency-free embedding model.

HashEmbedder projects text into a fixed-dimensional space using hashed character n-grams — the classic "hashing trick". It is not a semantic model and will never beat a trained encoder, but it has three properties that make it the right default for a framework:

  • Zero dependencies and zero latency. The full RAG pipeline runs on a bare pip install windlass.
  • Deterministic. The same text always yields the same vector, so tests can assert on retrieval order.
  • Genuinely useful for lexical overlap. Documents sharing character n-grams score higher, which makes the smoke tests and tutorials return sensible results instead of noise.

Switch to huggingface or openai the moment you care about semantics.

Example

emb = HashEmbedder(dimensions=64) a, b, c = emb.embed(["machine learning", "machine learning models", "sailing"]) from windlass.core.vectors import cosine_similarity cosine_similarity(a, b) > cosine_similarity(a, c) True

HashEmbedder

HashEmbedder(
    *, dimensions: int = 384, ngram: int = 3, use_words: bool = True, **config: Any
)

Bases: Embedder

Hashed character n-gram embeddings.

Parameters:

Name Type Description Default
dimensions int

Output dimensionality. Higher values reduce hash collisions; 256 is a reasonable floor for anything beyond a smoke test.

384
ngram int

Character n-gram length used for the sub-word signal.

3
use_words bool

Also hash whole words, which sharpens exact-term matching.

True
**config Any

Forwarded to :class:~windlass.interfaces.embedding.Embedder.

{}
Example

len(HashEmbedder(dimensions=32).embed_one("hello")) 32

Source code in src\windlass\providers\embeddings\hash.py
def __init__(
    self,
    *,
    dimensions: int = 384,
    ngram: int = 3,
    use_words: bool = True,
    **config: Any,
) -> None:
    if dimensions < 8:
        raise ValueError("dimensions must be at least 8")
    if ngram < 1:
        raise ValueError("ngram must be at least 1")
    config.setdefault("model", "hash-ngram")
    super().__init__(dimensions=dimensions, **config)
    self.ngram = ngram
    self.use_words = use_words

default_model classmethod

default_model() -> str

Return "hash-ngram".

Source code in src\windlass\providers\embeddings\hash.py
@classmethod
def default_model(cls) -> str:
    """Return ``"hash-ngram"``."""
    return "hash-ngram"

dimension

dimension() -> int

Return the configured dimensionality without probing.

Source code in src\windlass\providers\embeddings\hash.py
def dimension(self) -> int:
    """Return the configured dimensionality without probing."""
    return int(self._dimensions or 384)

aembed_texts async

aembed_texts(texts: list[str], *, kind: str = 'document') -> list[list[float]]

Embed a batch of texts.

Parameters:

Name Type Description Default
texts list[str]

The texts to embed.

required
kind str

Ignored — this model treats queries and documents identically.

'document'

Returns:

Type Description
list[list[float]]

One vector per input.

Source code in src\windlass\providers\embeddings\hash.py
async def aembed_texts(self, texts: list[str], *, kind: str = "document") -> list[list[float]]:
    """Embed a batch of texts.

    Args:
        texts: The texts to embed.
        kind: Ignored — this model treats queries and documents identically.

    Returns:
        One vector per input.
    """
    return [self._vectorize(text) for text in texts]