-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathembed.py
More file actions
143 lines (111 loc) · 4.57 KB
/
Copy pathembed.py
File metadata and controls
143 lines (111 loc) · 4.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""Optional embedding helpers for VelesDB.
A thin, dependency-free :class:`Embedder` protocol plus adapters around
common providers. Each adapter lazy-imports its backend so plain
``import velesdb`` stays zero-dep beyond NumPy.
Install the matching extra to enable an adapter::
pip install velesdb[embed-openai]
pip install velesdb[embed-sentence-transformers]
pip install velesdb[embed] # both
Example::
from velesdb import Database
from velesdb.embed import SentenceTransformerEmbedder
embedder = SentenceTransformerEmbedder("all-MiniLM-L6-v2")
db = Database("./data")
db.create_collection("docs", dimension=embedder.dimension)
vectors = embedder.embed(["hello world", "vector search rocks"])
"""
from __future__ import annotations
from typing import Any, Protocol, Sequence, runtime_checkable
try: # optional backend, gated by `pip install velesdb[embed-openai]`
import openai as _openai
except ImportError: # pragma: no cover - tested via no-extras install
_openai = None # type: ignore[assignment]
try: # optional backend, gated by `pip install velesdb[embed-sentence-transformers]`
import sentence_transformers as _sentence_transformers
except ImportError: # pragma: no cover - tested via no-extras install
_sentence_transformers = None # type: ignore[assignment]
_OPENAI_MISSING_HINT = (
"OpenAIEmbedder requires the 'openai' package. "
"Install with: pip install velesdb[embed-openai]"
)
_SENTENCE_TRANSFORMERS_MISSING_HINT = (
"SentenceTransformerEmbedder requires 'sentence-transformers'. "
"Install with: pip install velesdb[embed-sentence-transformers]"
)
@runtime_checkable
class Embedder(Protocol):
"""Minimal interface a VelesDB embedding adapter must satisfy.
``dimension`` is ``0`` until it can be inferred — either by passing it
explicitly to the adapter constructor or by calling :meth:`embed` once.
"""
dimension: int
def embed(self, texts: Sequence[str]) -> list[list[float]]: # pragma: no cover - protocol
...
class OpenAIEmbedder:
"""OpenAI / Azure-OpenAI compatible embedding adapter.
Requires ``pip install velesdb[embed-openai]``. The ``base_url`` argument
lets you point the same client at Azure OpenAI, vLLM, or any other
OpenAI-compatible endpoint.
"""
def __init__(
self,
model: str = "text-embedding-3-small",
*,
api_key: str | None = None,
base_url: str | None = None,
dimensions: int | None = None,
) -> None:
openai_module = _load_openai()
self._client = openai_module.OpenAI(api_key=api_key, base_url=base_url)
self.model = model
self.dimension: int = 0 if dimensions is None else dimensions
def embed(self, texts: Sequence[str]) -> list[list[float]]:
if not texts:
return []
kwargs: dict[str, Any] = {"model": self.model, "input": list(texts)}
if self.dimension > 0:
kwargs["dimensions"] = self.dimension
response = self._client.embeddings.create(**kwargs)
vectors = [list(item.embedding) for item in response.data]
if self.dimension == 0 and vectors:
self.dimension = len(vectors[0])
return vectors
class SentenceTransformerEmbedder:
"""Local SentenceTransformers adapter — no API key, runs on-device.
Requires ``pip install velesdb[embed-sentence-transformers]``.
"""
def __init__(
self,
model: str = "all-MiniLM-L6-v2",
*,
device: str | None = None,
normalize: bool = True,
) -> None:
sentence_transformers_module = _load_sentence_transformers()
self._model = sentence_transformers_module.SentenceTransformer(model, device=device)
self._normalize = normalize
dim = self._model.get_sentence_embedding_dimension()
self.dimension: int = int(dim) if dim is not None else 0
def embed(self, texts: Sequence[str]) -> list[list[float]]:
if not texts:
return []
vectors = self._model.encode(
list(texts),
convert_to_numpy=True,
show_progress_bar=False,
normalize_embeddings=self._normalize,
)
return vectors.tolist()
def _load_openai() -> Any:
if _openai is None:
raise ImportError(_OPENAI_MISSING_HINT)
return _openai
def _load_sentence_transformers() -> Any:
if _sentence_transformers is None:
raise ImportError(_SENTENCE_TRANSFORMERS_MISSING_HINT)
return _sentence_transformers
__all__ = [
"Embedder",
"OpenAIEmbedder",
"SentenceTransformerEmbedder",
]