diff --git a/litellm/llms/voyage/common_utils.py b/litellm/llms/voyage/common_utils.py index 2f4f63a8a43..78a4ea6f9f7 100644 --- a/litellm/llms/voyage/common_utils.py +++ b/litellm/llms/voyage/common_utils.py @@ -33,3 +33,8 @@ def get_default_base_url(api_key: str | None = None) -> str: if resolved is not None and resolved.startswith(MONGODB_API_KEY_PREFIX): return MONGODB_API_BASE return VOYAGE_API_BASE + + +def get_voyage_api_base(api_base: str | None = None, api_key: str | None = None) -> str: + """Resolve the host to call, explicit value first, then ``VOYAGE_API_BASE``, then the key's issuer.""" + return api_base or get_secret_str("VOYAGE_API_BASE") or get_default_base_url(api_key) diff --git a/litellm/llms/voyage/embedding/transformation.py b/litellm/llms/voyage/embedding/transformation.py index 1ba66e5ce65..b57a31d0c41 100644 --- a/litellm/llms/voyage/embedding/transformation.py +++ b/litellm/llms/voyage/embedding/transformation.py @@ -8,7 +8,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from litellm.litellm_core_utils.url_utils import ensure_api_route from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig -from litellm.llms.voyage.common_utils import get_default_base_url, get_voyage_api_key +from litellm.llms.voyage.common_utils import get_voyage_api_base, get_voyage_api_key from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues from litellm.types.utils import EmbeddingResponse, Usage @@ -51,7 +51,7 @@ class VoyageEmbeddingConfig(BaseEmbeddingConfig): litellm_params: dict, stream: bool | None = None, ) -> str: - return ensure_api_route(api_base or get_default_base_url(api_key), "embeddings") + return ensure_api_route(get_voyage_api_base(api_base, api_key), "embeddings") def get_supported_openai_params(self, model: str) -> list: return [ diff --git a/litellm/llms/voyage/embedding/transformation_contextual.py b/litellm/llms/voyage/embedding/transformation_contextual.py index 62c03139fbe..4f0529033fb 100644 --- a/litellm/llms/voyage/embedding/transformation_contextual.py +++ b/litellm/llms/voyage/embedding/transformation_contextual.py @@ -13,7 +13,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from litellm.litellm_core_utils.url_utils import ensure_api_route from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig -from litellm.llms.voyage.common_utils import get_default_base_url, get_voyage_api_key +from litellm.llms.voyage.common_utils import get_voyage_api_base, get_voyage_api_key from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues from litellm.types.utils import EmbeddingResponse, Usage @@ -59,7 +59,7 @@ class VoyageContextualEmbeddingConfig(BaseEmbeddingConfig): litellm_params: dict, stream: bool | None = None, ) -> str: - return ensure_api_route(api_base or get_default_base_url(api_key), "contextualizedembeddings") + return ensure_api_route(get_voyage_api_base(api_base, api_key), "contextualizedembeddings") def get_supported_openai_params(self, model: str) -> list: # mutable-ok: base class signature return ["encoding_format", "dimensions"] diff --git a/litellm/llms/voyage/embedding/transformation_multimodal.py b/litellm/llms/voyage/embedding/transformation_multimodal.py index c04a2e1f26b..222a3fa5689 100644 --- a/litellm/llms/voyage/embedding/transformation_multimodal.py +++ b/litellm/llms/voyage/embedding/transformation_multimodal.py @@ -16,7 +16,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from litellm.litellm_core_utils.url_utils import ensure_api_route from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig -from litellm.llms.voyage.common_utils import get_default_base_url, get_voyage_api_key +from litellm.llms.voyage.common_utils import get_voyage_api_base, get_voyage_api_key from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues from litellm.types.utils import EmbeddingResponse, Usage @@ -60,7 +60,7 @@ class VoyageMultimodalEmbeddingConfig(BaseEmbeddingConfig): litellm_params: dict, stream: bool | None = None, ) -> str: - return ensure_api_route(api_base or get_default_base_url(api_key), "multimodalembeddings") + return ensure_api_route(get_voyage_api_base(api_base, api_key), "multimodalembeddings") def get_supported_openai_params(self, model: str) -> list: return ["dimensions"] diff --git a/tests/unit/llms/voyage/test_common_utils.py b/tests/unit/llms/voyage/test_common_utils.py index 51d6c3ed0af..da890d9ada9 100644 --- a/tests/unit/llms/voyage/test_common_utils.py +++ b/tests/unit/llms/voyage/test_common_utils.py @@ -4,6 +4,7 @@ from litellm.llms.voyage.common_utils import ( MONGODB_API_BASE, VOYAGE_API_BASE, get_default_base_url, + get_voyage_api_base, get_voyage_api_key, ) from litellm.llms.voyage.embedding.transformation import VoyageEmbeddingConfig @@ -20,7 +21,7 @@ VOYAGE_KEY_ENV_VARS = ("VOYAGE_API_KEY", "VOYAGE_AI_API_KEY", "VOYAGE_AI_TOKEN") @pytest.fixture(autouse=True) def clear_voyage_env(monkeypatch): - for env_var in VOYAGE_KEY_ENV_VARS: + for env_var in (*VOYAGE_KEY_ENV_VARS, "VOYAGE_API_BASE"): monkeypatch.delenv(env_var, raising=False) @@ -72,6 +73,50 @@ def test_explicit_api_base_overrides_key_routing(config, endpoint): assert url == f"https://gateway.internal/v1/{endpoint}" +@pytest.mark.parametrize( + "config, endpoint", + [ + (VoyageEmbeddingConfig(), "embeddings"), + (VoyageContextualEmbeddingConfig(), "contextualizedembeddings"), + (VoyageMultimodalEmbeddingConfig(), "multimodalembeddings"), + ], +) +@pytest.mark.parametrize("env_value", ["https://gateway.internal/v1", "https://gateway.internal/v1/"]) +def test_env_api_base_overrides_key_routing(monkeypatch, config, endpoint, env_value): + """A caller who only gets to pass a model and a key reaches a custom host through the env var""" + monkeypatch.setenv("VOYAGE_API_BASE", env_value) + + url = config.get_complete_url(None, "al-key", "voyage-3", {}, {}) + + assert url == f"https://gateway.internal/v1/{endpoint}" + + +@pytest.mark.parametrize( + "config, endpoint", + [ + (VoyageEmbeddingConfig(), "embeddings"), + (VoyageContextualEmbeddingConfig(), "contextualizedembeddings"), + (VoyageMultimodalEmbeddingConfig(), "multimodalembeddings"), + ], +) +def test_explicit_api_base_beats_env_api_base(monkeypatch, config, endpoint): + monkeypatch.setenv("VOYAGE_API_BASE", "https://from-env.internal/v1") + + url = config.get_complete_url("https://on-request.internal/v1", "pa-key", "voyage-3", {}, {}) + + assert url == f"https://on-request.internal/v1/{endpoint}" + + +def test_get_voyage_api_base_prefers_request_then_env_then_key_issuer(monkeypatch): + assert get_voyage_api_base("https://on-request.internal/v1", "al-key") == "https://on-request.internal/v1" + assert get_voyage_api_base(None, "al-key") == MONGODB_API_BASE + + monkeypatch.setenv("VOYAGE_API_BASE", "https://from-env.internal/v1") + + assert get_voyage_api_base(None, "al-key") == "https://from-env.internal/v1" + assert get_voyage_api_base("https://on-request.internal/v1", "al-key") == "https://on-request.internal/v1" + + @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()