fix(voyage): route rerank on the key it authenticates with

get_complete_url picked the rerank host from the environment while the auth
header carried the request key, so an explicit MongoDB-issued key was posted
to api.voyageai.com. validate_environment now hands the resolved key to the
config instance and get_complete_url reads it back, so the host and the
credential always come from one key. The config is built per request, so
nothing carries over between them.
This commit is contained in:
fzowl 2026-09-18 19:21:43 +02:00
parent c53cf2c2b4
commit fcd9a894d9
2 changed files with 67 additions and 4 deletions

View file

@ -25,6 +25,16 @@ from ..embedding.transformation import VoyageError
class VoyageRerankConfig(BaseRerankConfig):
"""
``validate_environment`` stores the credential it authenticates with so ``get_complete_url``
can select the host that issued it. ``ProviderConfigManager.get_provider_rerank_config``
builds this config per request, so that key never reaches another one.
"""
def __init__(self) -> None:
super().__init__()
self._api_key: str | None = None
def get_supported_cohere_rerank_params(self, model: str) -> list:
return ["query", "documents", "top_n", "return_documents"]
@ -60,7 +70,7 @@ class VoyageRerankConfig(BaseRerankConfig):
optional_params: dict | None = None,
) -> str:
if api_base is None:
return f"{get_default_base_url()}/rerank"
return f"{get_default_base_url(self._api_key)}/rerank"
api_base = api_base.rstrip("/")
if not api_base.endswith("/v1/rerank"):
if api_base.endswith("/v1"):
@ -144,6 +154,7 @@ class VoyageRerankConfig(BaseRerankConfig):
resolved_api_key: Final = get_voyage_api_key(api_key)
if resolved_api_key is None:
raise ValueError("Voyage AI API key is required. Set via `api_key` parameter or `VOYAGE_API_KEY` env var.")
self._api_key = resolved_api_key
return {
"Authorization": f"Bearer {resolved_api_key}",
"content-type": "application/json",

View file

@ -72,10 +72,61 @@ def test_explicit_api_base_overrides_key_routing(config, endpoint):
assert url == f"https://gateway.internal/v1/{endpoint}"
@pytest.mark.parametrize("api_key, expected_host", [("al-key", MONGODB_API_BASE), ("pa-key", VOYAGE_API_BASE)])
def test_rerank_routes_by_request_key_prefix(api_key, expected_host):
config = VoyageRerankConfig()
config.validate_environment({}, "rerank-2.5", api_key=api_key)
assert config.get_complete_url(None, "rerank-2.5") == f"{expected_host}/rerank"
@pytest.mark.parametrize("api_key, expected_host", [("al-key", MONGODB_API_BASE), ("pa-key", VOYAGE_API_BASE)])
def test_rerank_routes_by_env_key_prefix(monkeypatch, api_key, expected_host):
monkeypatch.setenv("VOYAGE_API_KEY", api_key)
assert VoyageRerankConfig().get_complete_url(None, "rerank-2.5") == f"{expected_host}/rerank"
config = VoyageRerankConfig()
config.validate_environment({}, "rerank-2.5")
assert config.get_complete_url(None, "rerank-2.5") == f"{expected_host}/rerank"
def test_rerank_request_key_beats_env_key_for_routing(monkeypatch):
"""A MongoDB key on the request must not be posted to the Voyage host the env key names"""
monkeypatch.setenv("VOYAGE_API_KEY", "pa-from-env")
config = VoyageRerankConfig()
headers = config.validate_environment({}, "rerank-2.5", api_key="al-on-request")
assert headers["Authorization"] == "Bearer al-on-request"
assert config.get_complete_url(None, "rerank-2.5") == f"{MONGODB_API_BASE}/rerank"
def test_rerank_config_is_built_per_request_so_keys_cannot_leak(monkeypatch):
"""get_complete_url reads a key off the instance, so each request must get its own instance"""
import litellm
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager
monkeypatch.delenv("VOYAGE_API_KEY", raising=False)
first = ProviderConfigManager.get_provider_rerank_config(
model="rerank-2.5", provider=LlmProviders.VOYAGE, api_base=None, present_version_params=[]
)
second = ProviderConfigManager.get_provider_rerank_config(
model="rerank-2.5", provider=LlmProviders.VOYAGE, api_base=None, present_version_params=[]
)
assert isinstance(first, litellm.VoyageRerankConfig) and first is not second
first.validate_environment({}, "rerank-2.5", api_key="al-first-request")
second.validate_environment({}, "rerank-2.5", api_key="pa-second-request")
assert first.get_complete_url(None, "rerank-2.5") == f"{MONGODB_API_BASE}/rerank"
assert second.get_complete_url(None, "rerank-2.5") == f"{VOYAGE_API_BASE}/rerank"
def test_rerank_falls_back_to_env_when_validate_environment_did_not_run(monkeypatch):
"""A caller that skips validate_environment keeps the pre-existing env-only behaviour"""
monkeypatch.setenv("VOYAGE_API_KEY", "al-from-env")
assert VoyageRerankConfig().get_complete_url(None, "rerank-2.5") == f"{MONGODB_API_BASE}/rerank"
@pytest.mark.parametrize(
@ -95,11 +146,12 @@ def test_auth_header_uses_the_key_the_url_was_routed_on(monkeypatch, config):
def test_rerank_auth_header_uses_the_key_the_url_was_routed_on(monkeypatch):
monkeypatch.setenv("VOYAGE_AI_TOKEN", "al-from-env")
config = VoyageRerankConfig()
headers = VoyageRerankConfig().validate_environment({}, "rerank-2.5")
headers = config.validate_environment({}, "rerank-2.5")
assert headers["Authorization"] == "Bearer al-from-env"
assert VoyageRerankConfig().get_complete_url(None, "rerank-2.5").startswith(MONGODB_API_BASE)
assert config.get_complete_url(None, "rerank-2.5").startswith(MONGODB_API_BASE)
def test_get_voyage_api_key_prefers_env_vars_in_documented_order(monkeypatch):