| from __future__ import annotations |
|
|
| import hashlib |
| import math |
|
|
|
|
| class EmbeddingService: |
| """Deterministic local embeddings so Chroma can persist vectors without a remote embedding API.""" |
|
|
| def __init__(self, dimensions: int = 256) -> None: |
| self.dimensions = dimensions |
|
|
| def embed_documents(self, texts: list[str]) -> list[list[float]]: |
| return [self.embed_query(text) for text in texts] |
|
|
| def embed_query(self, text: str) -> list[float]: |
| vector = [0.0] * self.dimensions |
| tokens = self._tokenize(text) |
| if not tokens: |
| return vector |
|
|
| for token in tokens: |
| digest = hashlib.sha256(token.encode("utf-8")).digest() |
| bucket = int.from_bytes(digest[:4], "big") % self.dimensions |
| sign = 1.0 if digest[4] % 2 == 0 else -1.0 |
| vector[bucket] += sign |
|
|
| norm = math.sqrt(sum(value * value for value in vector)) or 1.0 |
| return [value / norm for value in vector] |
|
|
| @staticmethod |
| def _tokenize(text: str) -> list[str]: |
| return [token.strip().lower() for token in text.replace("\n", " ").split() if token.strip()] |
|
|