partners/voyageai: enable setting output dimension (#28740)

Voyage has introduced voyage-3-large and voyage-code-3, which feature
different output dimensions by leveraging a technique called "Matryoshka
Embeddings" (see blog -
https://blog.voyageai.com/2024/12/04/voyage-code-3/).
These two models are available in various sizes: [256, 512, 1024, 2048]
(https://docs.voyageai.com/docs/embeddings#model-choices).

This PR adds the option to set the required output dimension.
This commit is contained in:
Omri Eliyahu Levy
2024-12-17 17:02:00 +02:00
committed by GitHub
parent 0afc284920
commit f8883a1321
6 changed files with 1096 additions and 668 deletions

View File

@@ -1,5 +1,5 @@
import logging import logging
from typing import Any, Iterable, List, Optional from typing import Any, Iterable, List, Literal, Optional, cast
import voyageai # type: ignore import voyageai # type: ignore
from langchain_core.embeddings import Embeddings from langchain_core.embeddings import Embeddings
@@ -37,8 +37,10 @@ class VoyageAIEmbeddings(BaseModel, Embeddings):
_aclient: voyageai.client_async.AsyncClient = PrivateAttr() _aclient: voyageai.client_async.AsyncClient = PrivateAttr()
model: str model: str
batch_size: int batch_size: int
output_dimension: Optional[Literal[256, 512, 1024, 2048]] = None
show_progress_bar: bool = False show_progress_bar: bool = False
truncation: Optional[bool] = None truncation: bool = True
voyage_api_key: SecretStr = Field( voyage_api_key: SecretStr = Field(
alias="api_key", alias="api_key",
default_factory=secret_from_env( default_factory=secret_from_env(
@@ -105,22 +107,26 @@ class VoyageAIEmbeddings(BaseModel, Embeddings):
_iter = self._get_batch_iterator(texts) _iter = self._get_batch_iterator(texts)
for i in _iter: for i in _iter:
embeddings.extend( r = self._client.embed(
self._client.embed(
texts[i : i + self.batch_size], texts[i : i + self.batch_size],
model=self.model, model=self.model,
input_type="document", input_type="document",
truncation=self.truncation, truncation=self.truncation,
output_dimension=self.output_dimension,
).embeddings ).embeddings
) embeddings.extend(cast(Iterable[List[float]], r))
return embeddings return embeddings
def embed_query(self, text: str) -> List[float]: def embed_query(self, text: str) -> List[float]:
"""Embed query text.""" """Embed query text."""
return self._client.embed( r = self._client.embed(
[text], model=self.model, input_type="query", truncation=self.truncation [text],
model=self.model,
input_type="query",
truncation=self.truncation,
output_dimension=self.output_dimension,
).embeddings[0] ).embeddings[0]
return cast(List[float], r)
async def aembed_documents(self, texts: List[str]) -> List[List[float]]: async def aembed_documents(self, texts: List[str]) -> List[List[float]]:
embeddings: List[List[float]] = [] embeddings: List[List[float]] = []
@@ -132,8 +138,9 @@ class VoyageAIEmbeddings(BaseModel, Embeddings):
model=self.model, model=self.model,
input_type="document", input_type="document",
truncation=self.truncation, truncation=self.truncation,
output_dimension=self.output_dimension,
) )
embeddings.extend(r.embeddings) embeddings.extend(cast(Iterable[List[float]], r.embeddings))
return embeddings return embeddings
@@ -143,5 +150,6 @@ class VoyageAIEmbeddings(BaseModel, Embeddings):
model=self.model, model=self.model,
input_type="query", input_type="query",
truncation=self.truncation, truncation=self.truncation,
output_dimension=self.output_dimension,
) )
return r.embeddings[0] return cast(List[float], r.embeddings[0])

View File

@@ -16,8 +16,8 @@ from voyageai.object import RerankingObject # type: ignore
class VoyageAIRerank(BaseDocumentCompressor): class VoyageAIRerank(BaseDocumentCompressor):
"""Document compressor that uses `VoyageAI Rerank API`.""" """Document compressor that uses `VoyageAI Rerank API`."""
client: voyageai.Client = None client: voyageai.Client = None # type: ignore
aclient: voyageai.AsyncClient = None aclient: voyageai.AsyncClient = None # type: ignore
"""VoyageAI clients to use for compressing documents.""" """VoyageAI clients to use for compressing documents."""
voyage_api_key: Optional[SecretStr] = None voyage_api_key: Optional[SecretStr] = None
"""VoyageAI API key. Must be specified directly or via environment variable """VoyageAI API key. Must be specified directly or via environment variable

File diff suppressed because it is too large Load Diff

View File

@@ -19,9 +19,9 @@ disallow_untyped_defs = "True"
"Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22langchain-voyageai%3D%3D0%22&expanded=true" "Release Notes" = "https://github.com/langchain-ai/langchain/releases?q=tag%3A%22langchain-voyageai%3D%3D0%22&expanded=true"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<4.0" python = ">=3.9,<3.13"
langchain-core = "^0.3.15" langchain-core = "^0.3.15"
voyageai = ">=0.2.1,<1" voyageai = ">=0.3.2,<1"
pydantic = ">=2,<3" pydantic = ">=2,<3"
[tool.ruff.lint] [tool.ruff.lint]

View File

@@ -51,3 +51,12 @@ async def test_langchain_voyageai_async_embedding_query() -> None:
embedding = VoyageAIEmbeddings(model=MODEL) # type: ignore[call-arg] embedding = VoyageAIEmbeddings(model=MODEL) # type: ignore[call-arg]
output = await embedding.aembed_query(document) output = await embedding.aembed_query(document)
assert len(output) == 1024 assert len(output) == 1024
def test_langchain_voyageai_embedding_documents_with_output_dimension() -> None:
"""Test voyage embeddings."""
documents = ["foo bar"]
embedding = VoyageAIEmbeddings(model="voyage-3-large", output_dimension=256) # type: ignore[call-arg]
output = embedding.embed_documents(documents)
assert len(output) == 1
assert len(output[0]) == 256

View File

@@ -47,3 +47,15 @@ def test_initialization_voyage_1_batch_size() -> None:
assert emb.batch_size == 15 assert emb.batch_size == 15
assert emb.model == "voyage-01" assert emb.model == "voyage-01"
assert emb._client is not None assert emb._client is not None
def test_initialization_with_output_dimension() -> None:
emb = VoyageAIEmbeddings(
api_key="NOT_A_VALID_KEY", # type: ignore
model="voyage-3-large",
output_dimension=256,
batch_size=10,
)
assert isinstance(emb, Embeddings)
assert emb.model == "voyage-3-large"
assert emb.output_dimension == 256