feat(voyage): rebrand to VoyageAI by MongoDB, refresh model list, support both contextual input shapes

This commit is contained in:
fzowl 2026-09-08 18:45:36 +02:00
parent 9f3ac8bc27
commit 0e11abb15d
9 changed files with 128 additions and 6 deletions

View file

@ -370,7 +370,7 @@ curl -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
| [Vercel AI Gateway (`vercel_ai_gateway`)](https://docs.litellm.ai/docs/providers/vercel_ai_gateway) | ✅ | ✅ | ✅ | | | | | | | |
| [VLLM (`vllm`)](https://docs.litellm.ai/docs/providers/vllm) | ✅ | ✅ | ✅ | | | | | | | |
| [Volcengine (`volcengine`)](https://docs.litellm.ai/docs/providers/volcano) | ✅ | ✅ | ✅ | | | | | | | |
| [Voyage AI (`voyage`)](https://docs.litellm.ai/docs/providers/voyage) | | | | ✅ | | | | | | |
| [VoyageAI by MongoDB (`voyage`)](https://docs.litellm.ai/docs/providers/voyage) | | | | ✅ | | | | | | |
| [WandB Inference (`wandb`)](https://docs.litellm.ai/docs/providers/wandb_inference) | ✅ | ✅ | ✅ | | | | | | | |
| [Watsonx Text (`watsonx_text`)](https://docs.litellm.ai/docs/providers/watsonx) | ✅ | ✅ | ✅ | | | | | | | |
| [xAI (`xai`)](https://docs.litellm.ai/docs/providers/xai) | ✅ | ✅ | ✅ | | | | | | | |

View file

@ -105,11 +105,27 @@ class VoyageContextualEmbeddingConfig(BaseEmbeddingConfig):
headers: dict,
) -> dict:
return {
"inputs": input,
"inputs": self._normalize_contextual_inputs(input),
"model": model,
**optional_params,
}
@staticmethod
def _normalize_contextual_inputs(
input: AllEmbeddingInputValues | list[list[str]],
) -> AllEmbeddingInputValues | list[list[str]]:
"""
Voyage's contextualized embeddings API accepts ``inputs`` as either a
flat ``list[str]`` (one document's chunks) or a nested ``list[list[str]]``
(multiple documents). Both are sent through unchanged; a bare ``str`` is
wrapped into a single-element list so the payload always matches the spec.
Reference: https://docs.voyageai.com/docs/contextualized-chunk-embeddings
"""
if isinstance(input, str):
return [input]
return input
def transform_embedding_response(
self,
model: str,

View file

@ -56413,6 +56413,26 @@
"output_vector_size": 1024,
"source": "https://docs.voyageai.com/docs/pricing"
},
"voyage/voyage-4-nano": {
"input_cost_per_token": 0.0,
"litellm_provider": "voyage",
"max_input_tokens": 32000,
"max_tokens": 32000,
"mode": "embedding",
"output_cost_per_token": 0.0,
"output_vector_size": 1024,
"source": "https://docs.voyageai.com/docs/embeddings"
},
"voyage/voyage-multilingual-2": {
"input_cost_per_token": 1.2e-07,
"litellm_provider": "voyage",
"max_input_tokens": 32000,
"max_tokens": 32000,
"mode": "embedding",
"output_cost_per_token": 0.0,
"output_vector_size": 1024,
"source": "https://docs.voyageai.com/docs/pricing"
},
"voyage/voyage-context-4": {
"input_cost_per_token": 1.2e-07,
"litellm_provider": "voyage",

View file

@ -2283,7 +2283,7 @@
}
},
"voyage": {
"display_name": "Voyage AI (`voyage`)",
"display_name": "VoyageAI by MongoDB (`voyage`)",
"url": "https://docs.litellm.ai/docs/providers/voyage",
"endpoints": {
"chat_completions": false,

View file

@ -3228,7 +3228,7 @@
},
{
"provider": "Voyage",
"provider_display_name": "Voyage AI",
"provider_display_name": "VoyageAI by MongoDB",
"litellm_provider": "voyage",
"credential_fields": [
{

View file

@ -56413,6 +56413,26 @@
"output_vector_size": 1024,
"source": "https://docs.voyageai.com/docs/pricing"
},
"voyage/voyage-4-nano": {
"input_cost_per_token": 0.0,
"litellm_provider": "voyage",
"max_input_tokens": 32000,
"max_tokens": 32000,
"mode": "embedding",
"output_cost_per_token": 0.0,
"output_vector_size": 1024,
"source": "https://docs.voyageai.com/docs/embeddings"
},
"voyage/voyage-multilingual-2": {
"input_cost_per_token": 1.2e-07,
"litellm_provider": "voyage",
"max_input_tokens": 32000,
"max_tokens": 32000,
"mode": "embedding",
"output_cost_per_token": 0.0,
"output_vector_size": 1024,
"source": "https://docs.voyageai.com/docs/pricing"
},
"voyage/voyage-context-4": {
"input_cost_per_token": 1.2e-07,
"litellm_provider": "voyage",

View file

@ -2594,7 +2594,7 @@
}
},
"voyage": {
"display_name": "Voyage AI (`voyage`)",
"display_name": "VoyageAI by MongoDB (`voyage`)",
"url": "https://docs.litellm.ai/docs/providers/voyage",
"endpoints": {
"chat_completions": false,

View file

@ -195,6 +195,51 @@ class TestVoyageContextualEmbeddings:
assert transformed["model"] == "voyage-context-3"
assert transformed["encoding_format"] == "float"
def test_contextual_embedding_flat_list_input(self):
"""A flat list[str] is passed through unchanged as inputs (spec allows list[str])"""
from litellm.llms.voyage.embedding.transformation_contextual import (
VoyageContextualEmbeddingConfig,
)
config = VoyageContextualEmbeddingConfig()
flat_input = ["chunk one", "chunk two"]
transformed = config.transform_embedding_request(
"voyage-context-4", flat_input, {}, {}
)
assert transformed["inputs"] == flat_input
assert transformed["model"] == "voyage-context-4"
def test_contextual_embedding_nested_list_input(self):
"""A nested list[list[str]] is passed through unchanged as inputs"""
from litellm.llms.voyage.embedding.transformation_contextual import (
VoyageContextualEmbeddingConfig,
)
config = VoyageContextualEmbeddingConfig()
nested_input = [["doc a chunk 1", "doc a chunk 2"], ["doc b chunk 1"]]
transformed = config.transform_embedding_request(
"voyage-context-4", nested_input, {}, {}
)
assert transformed["inputs"] == nested_input
def test_contextual_embedding_str_input_wrapped(self):
"""A bare str is wrapped into a single-element list so inputs is always a list"""
from litellm.llms.voyage.embedding.transformation_contextual import (
VoyageContextualEmbeddingConfig,
)
config = VoyageContextualEmbeddingConfig()
transformed = config.transform_embedding_request(
"voyage-context-4", "just one chunk", {}, {}
)
assert transformed["inputs"] == ["just one chunk"]
def test_contextual_embedding_response_transformation(self):
"""Test response transformation for contextual embeddings"""
from litellm.llms.voyage.embedding.transformation_contextual import (
@ -428,3 +473,24 @@ class TestVoyageContextualEmbeddings:
except Exception as e:
pytest.fail(f"Error occurred: {e}")
def test_voyage_current_models_registered():
"""The models currently listed on docs.voyageai.com resolve with voyage pricing/context"""
from litellm import get_model_info
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
litellm.model_cost = litellm.get_model_cost_map(url="")
expected = {
"voyage/voyage-4-nano": {"max_input_tokens": 32000, "input_cost_per_token": 0.0},
"voyage/voyage-multilingual-2": {"max_input_tokens": 32000, "input_cost_per_token": 1.2e-07},
"voyage/voyage-context-4": {"max_input_tokens": 120000, "input_cost_per_token": 1.2e-07},
}
for model, fields in expected.items():
info = get_model_info(model)
assert info["litellm_provider"] == "voyage", f"{model} wrong provider"
assert info["mode"] == "embedding", f"{model} wrong mode"
assert info["max_input_tokens"] == fields["max_input_tokens"], f"{model} wrong context"
assert info["input_cost_per_token"] == fields["input_cost_per_token"], f"{model} wrong price"

View file

@ -171,7 +171,7 @@ export enum Providers {
VERTEX_AI_BETA = "Vertex Ai Beta",
VLLM = "Local vLLM",
VolcEngine = "VolcEngine",
Voyage = "Voyage AI",
Voyage = "VoyageAI by MongoDB",
WANDB = "Wandb",
WATSONX = "Watsonx",
WATSONX_TEXT = "Watsonx Text",