This commit is contained in:
Dor Amir 2026-09-15 20:26:32 -04:00 committed by GitHub
commit 60bd2a4166
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 574 additions and 0 deletions

View file

@ -657,6 +657,7 @@ azure_anthropic_models: Set = set()
azure_text_models: Set = set()
anyscale_models: Set = set()
cerebras_models: Set = set()
nadir_models: Set = set() # mutable-ok: provider registry, filled from model_cost at import like every sibling provider
galadriel_models: Set = set()
nvidia_nim_models: Set = set()
nvidia_riva_models: Set = set()
@ -889,6 +890,8 @@ def _populate_provider_model_sets(model_cost_map: Dict) -> None:
anyscale_models.add(key)
elif value.get("litellm_provider") == "cerebras":
cerebras_models.add(key)
elif value.get("litellm_provider") == "nadir":
nadir_models.add(key)
elif value.get("litellm_provider") == "galadriel":
galadriel_models.add(key)
elif value.get("litellm_provider") == "nvidia_nim":
@ -1077,6 +1080,7 @@ model_list = list(
| azure_anthropic_models
| anyscale_models
| cerebras_models
| nadir_models
| galadriel_models
| nvidia_nim_models
| nvidia_riva_models
@ -1183,6 +1187,7 @@ def _build_models_by_provider() -> dict:
"azure_text": azure_text_models,
"anyscale": anyscale_models,
"cerebras": cerebras_models,
"nadir": nadir_models,
"galadriel": galadriel_models,
"nvidia_nim": nvidia_nim_models,
"nvidia_riva": nvidia_riva_models,
@ -1956,6 +1961,7 @@ if TYPE_CHECKING:
FeatherlessAIConfig as FeatherlessAIConfig,
)
from .llms.cerebras.chat import CerebrasConfig as CerebrasConfig
from .llms.nadir.chat.transformation import NadirConfig as NadirConfig
from .llms.baseten.chat import BasetenConfig as BasetenConfig
from .llms.sambanova.chat import SambanovaConfig as SambanovaConfig
from .llms.sambanova.embedding.transformation import (

View file

@ -260,6 +260,7 @@ LLM_CONFIG_NAMES: Final = (
"NvidiaNimEmbeddingConfig",
"FeatherlessAIConfig",
"CerebrasConfig",
"NadirConfig",
"BasetenConfig",
"SambanovaConfig",
"SambaNovaEmbeddingConfig",
@ -1033,6 +1034,7 @@ _LLM_CONFIGS_IMPORT_MAP: Final = {
"FeatherlessAIConfig",
),
"CerebrasConfig": (".llms.cerebras.chat", "CerebrasConfig"),
"NadirConfig": (".llms.nadir.chat.transformation", "NadirConfig"),
"BasetenConfig": (".llms.baseten.chat", "BasetenConfig"),
"SambanovaConfig": (".llms.sambanova.chat", "SambanovaConfig"),
"SambaNovaEmbeddingConfig": (

View file

@ -667,6 +667,7 @@ LITELLM_CHAT_PROVIDERS: Final = [
"gigachat",
"nvidia_nim",
"cerebras",
"nadir",
"baseten",
"ai21_chat",
"volcengine",
@ -859,6 +860,7 @@ openai_compatible_endpoints: Final[list] = [
"codestral.mistral.ai/v1/fim/completions",
"api.groq.com/openai/v1",
"https://integrate.api.nvidia.com/v1",
"https://api.getnadir.com/v1",
"api.deepseek.com/v1",
"api.together.ai/v1",
"api.together.xyz/v1",

View file

@ -263,6 +263,9 @@ def get_llm_provider(
elif endpoint == "https://api.cerebras.ai/v1":
custom_llm_provider = "cerebras"
dynamic_api_key = get_secret_str("CEREBRAS_API_KEY")
elif endpoint == "https://api.getnadir.com/v1":
custom_llm_provider = "nadir" # rebind-ok: mirrors sibling endpoint branches
dynamic_api_key = get_secret_str("NADIR_API_KEY")
elif endpoint == "https://inference.baseten.co/v1":
custom_llm_provider = "baseten"
dynamic_api_key = get_secret_str("BASETEN_API_KEY")
@ -628,6 +631,18 @@ def _get_openai_compatible_provider_info(
elif custom_llm_provider == "cerebras":
api_base = api_base or get_secret("CEREBRAS_API_BASE") or "https://api.cerebras.ai/v1"
dynamic_api_key = api_key or get_secret_str("CEREBRAS_API_KEY")
elif custom_llm_provider == "nadir":
# Bind the server-side NADIR_API_KEY to the trusted Nadir endpoint. If a
# caller directs the request at a custom api_base, do NOT fall back to
# the env key: forwarding the server's key as a Bearer token to a
# caller-controlled host is a credential-exfiltration risk. Such
# overrides must supply their own key.
default_nadir_base: Final = get_secret_str("NADIR_API_BASE") or "https://api.getnadir.com/v1"
caller_base: Final = api_base
api_base = api_base or default_nadir_base # rebind-ok: mirrors sibling provider branches
trusted_base: Final = caller_base is None or caller_base.rstrip("/") == default_nadir_base.rstrip("/")
env_key: Final = get_secret_str("NADIR_API_KEY") if trusted_base else None
dynamic_api_key = api_key or env_key # rebind-ok: mirrors sibling provider branches
elif custom_llm_provider == "baseten":
# Use BasetenConfig to determine the appropriate API base URL
if api_base is None:

View file

@ -93,6 +93,8 @@ def get_supported_openai_params(
return litellm.nvidiaNimEmbeddingConfig.get_supported_openai_params()
elif custom_llm_provider == "cerebras":
return litellm.CerebrasConfig().get_supported_openai_params(model=model)
elif custom_llm_provider == "nadir":
return litellm.NadirConfig().get_supported_openai_params(model=model)
elif custom_llm_provider == "baseten":
return litellm.BasetenConfig().get_supported_openai_params(model=model)
elif custom_llm_provider == "xai":

View file

@ -0,0 +1,133 @@
"""
Nadir Chat Completions API
Nadir (https://getnadir.com) is an intelligent LLM router. A single virtual
model, ``nadir/auto``, classifies each request by complexity and routes it to
the cheapest model that clears the quality bar (e.g. Haiku for simple prompts,
Sonnet for mid, Opus for complex), then returns an OpenAI-compatible response.
The endpoint speaks the OpenAI ``/v1/chat/completions`` dialect, so no request
translation is required. Nadir accepts the key as a Bearer token, so the
standard OpenAI-compatible transport works unchanged.
Cost attribution: the routed model name belongs to the underlying vendor, so it
does not resolve against a ``nadir/*`` pricing entry. Nadir returns the
authoritative cost it computed for the call, and ``transform_response`` below
surfaces it the same way the OpenRouter provider does. This is also why Nadir
has its own dispatch branch in ``main.py`` instead of riding the generic
OpenAI-compatible path, which never calls ``transform_response``.
"""
from typing import Final
import httpx
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
from litellm.types.llms.openai import AllMessageValues
from litellm.types.utils import ModelResponse
# The OpenAI params Nadir's request schema actually accepts. Anything outside
# this set is dropped by the router rather than forwarded to the chosen model,
# so advertising more would be advertising a silent no-op. ``extra_headers``
# and ``max_retries`` are handled by the LiteLLM transport, not sent in the body.
_SUPPORTED_OPENAI_PARAMS: Final = (
"extra_headers",
"frequency_penalty",
"max_retries",
"max_tokens",
"presence_penalty",
"response_format",
"stream",
"temperature",
"top_p",
)
class NadirConfig(OpenAIGPTConfig):
"""
Reference: https://getnadir.com/docs
Nadir is OpenAI-compatible, so parameter mapping is inherited from
``OpenAIGPTConfig`` unchanged. ``model`` is a virtual router alias
(``auto``); the concrete model is chosen server-side per request.
"""
@classmethod
def get_config(cls) -> dict: # mutable-ok: return type fixed by the base interface
return super().get_config()
def get_supported_openai_params(self, model: str) -> list: # mutable-ok: return type fixed by the base interface
"""
Only the params Nadir's request schema actually accepts.
Nadir speaks the OpenAI dialect but validates into its own request
model, and anything outside that model is dropped rather than
forwarded. Inheriting the full OpenAI param set would therefore
advertise support that silently does nothing, so the list below is
restricted to what the endpoint honors. ``extra_headers`` and
``max_retries`` are handled by the LiteLLM transport rather than sent
in the body, so they stay.
Notably absent: ``tools`` / ``tool_choice`` / ``functions``. Function
calling is not part of the router's request schema today.
"""
return list(_SUPPORTED_OPENAI_PARAMS) # mutable-ok: the base interface returns a list
def _get_openai_compatible_provider_info(
self, api_base: "str | None", api_key: "str | None"
) -> "tuple[str | None, str | None]":
resolved_base: Final = api_base or "https://api.getnadir.com/v1"
return resolved_base, api_key
def transform_response(
self,
model: str,
raw_response: httpx.Response,
model_response: ModelResponse,
logging_obj: object,
request_data: dict, # mutable-ok: signature fixed by the base interface
messages: list[AllMessageValues], # mutable-ok: signature fixed by the base interface
optional_params: dict, # mutable-ok: signature fixed by the base interface
litellm_params: dict, # mutable-ok: signature fixed by the base interface
encoding: object,
api_key: str | None = None,
json_mode: bool | None = None,
) -> ModelResponse:
"""
Standard OpenAI response handling, plus Nadir's own cost.
The routed model (``claude-haiku-4-5``, say) belongs to the underlying
vendor, so it has no ``nadir/*`` pricing entry and the shared cost
calculator cannot price it. Nadir already computes the cost of the call
it actually made, so pass that through as the provider-reported cost
rather than mirroring every vendor's price list under this provider.
Same mechanism the OpenRouter provider uses.
"""
transformed: Final = super().transform_response(
model=model,
raw_response=raw_response,
model_response=model_response,
logging_obj=logging_obj,
request_data=request_data,
messages=messages,
optional_params=optional_params,
litellm_params=litellm_params,
encoding=encoding,
api_key=api_key,
json_mode=json_mode,
)
try:
cost: Final = raw_response.json()["nadir_metadata"]["cost"]["total_cost_usd"]
if cost is not None:
hidden: Final = transformed._hidden_params # pyright: ignore[reportPrivateUsage] # sole hidden-params channel
if "additional_headers" not in hidden:
hidden["additional_headers"] = {} # mutable-ok: the header bag the cost calculator reads
hidden["additional_headers"]["llm_provider-x-litellm-response-cost"] = float(cost)
except (ValueError, KeyError, TypeError):
# Best-effort: a body that is not JSON, or is missing the cost
# keys, is still a valid completion. Narrow rather than blind so a
# genuine bug in here is not swallowed.
pass
return transformed

View file

@ -3465,6 +3465,50 @@ def _complete_openrouter(ctx: _CompletionDispatchContext) -> _CompletionDispatch
return response
def _complete_nadir(ctx: _CompletionDispatchContext) -> _CompletionDispatchResult:
"""
Nadir has its own dispatch branch rather than riding the generic
OpenAI-compatible path, for the same reason OpenRouter does: the shared
OpenAI SDK handler never calls ``provider_config.transform_response``, and
Nadir needs it to report the cost of the model it actually routed to. The
routed model is a vendor name with no ``nadir/*`` pricing entry, so without
that hook every call records 0.0 spend.
``api_key`` deliberately does NOT fall back to the environment here.
``get_llm_provider`` already binds ``NADIR_API_KEY`` to the trusted Nadir
endpoint and withholds it when the caller points at their own ``api_base``;
re-reading the env var at this layer would undo that.
"""
api_base: Final = (
ctx.api_base or litellm.api_base or get_secret_str("NADIR_API_BASE") or "https://api.getnadir.com/v1"
)
api_key: Final = ctx.api_key or litellm.api_key
## COMPLETION CALL
response: Final = base_llm_http_handler.completion(
model=ctx.model,
stream=ctx.stream,
messages=ctx.messages,
acompletion=ctx.acompletion,
api_base=api_base,
model_response=ctx.model_response,
optional_params=ctx.optional_params,
litellm_params=ctx.litellm_params,
shared_session=ctx.shared_session,
custom_llm_provider="nadir",
timeout=ctx.timeout,
headers=ctx.headers or litellm.headers,
encoding=_get_encoding(),
api_key=api_key,
logging_obj=ctx.logging,
client=ctx.client,
)
## LOGGING
ctx.logging.post_call(input=ctx.messages, api_key=api_key, original_response=response)
return response
def _complete_vercel_ai_gateway(
ctx: _CompletionDispatchContext,
) -> _CompletionDispatchResult:
@ -5810,6 +5854,8 @@ def completion(
response = _complete_datarobot(_dispatch_ctx)
elif custom_llm_provider == "openrouter":
response = _complete_openrouter(_dispatch_ctx)
elif custom_llm_provider == "nadir":
response = _complete_nadir(_dispatch_ctx) # rebind-ok: mirrors sibling provider branches
elif custom_llm_provider == "vercel_ai_gateway":
response = _complete_vercel_ai_gateway(_dispatch_ctx)
elif custom_llm_provider == "palm":

View file

@ -2505,6 +2505,24 @@
],
"default_model_placeholder": "gpt-3.5-turbo"
},
{
"provider": "Nadir",
"provider_display_name": "Nadir",
"litellm_provider": "nadir",
"credential_fields": [
{
"key": "api_key",
"label": "API Key",
"placeholder": null,
"tooltip": null,
"required": true,
"field_type": "password",
"options": null,
"default_value": null
}
],
"default_model_placeholder": "nadir/auto"
},
{
"provider": "Oracle",
"provider_display_name": "Oracle Cloud Infrastructure (OCI)",

View file

@ -3949,6 +3949,7 @@ class LlmProviders(str, Enum):
NVIDIA_RIVA = "nvidia_riva"
SONIOX = "soniox"
CEREBRAS = "cerebras"
NADIR = "nadir"
AI21_CHAT = "ai21_chat"
VOLCENGINE = "volcengine"
CODESTRAL = "codestral"

View file

@ -4614,6 +4614,13 @@ def get_optional_params(
model=model,
drop_params=bool(drop_params),
)
elif custom_llm_provider == "nadir":
optional_params = litellm.NadirConfig().map_openai_params( # rebind-ok: same optional_params rebinding every sibling provider branch does
non_default_params=non_default_params,
optional_params=optional_params,
model=model,
drop_params=(drop_params if drop_params is not None and isinstance(drop_params, bool) else False),
)
elif custom_llm_provider == "xai":
optional_params = litellm.XAIChatConfig().map_openai_params(
model=model,
@ -6531,6 +6538,11 @@ def validate_environment(
keys_in_environment = True
else:
missing_keys.append("CEREBRAS_API_KEY")
elif custom_llm_provider == "nadir":
if "NADIR_API_KEY" in os.environ:
keys_in_environment = True # rebind-ok: same flag rebinding every sibling provider branch does
else:
missing_keys.append("NADIR_API_KEY")
elif custom_llm_provider == "baseten":
if "BASETEN_API_KEY" in os.environ:
keys_in_environment = True
@ -8175,6 +8187,7 @@ class ProviderConfigManager:
LlmProviders.HUGGINGFACE: (lambda: litellm.HuggingFaceChatConfig(), False),
LlmProviders.TOGETHER_AI: (lambda: litellm.TogetherAIChatConfig(), False),
LlmProviders.OPENROUTER: (lambda: litellm.OpenrouterConfig(), False),
LlmProviders.NADIR: (lambda: litellm.NadirConfig(), False),
LlmProviders.VERCEL_AI_GATEWAY: (
lambda: litellm.VercelAIGatewayConfig(),
False,

View file

@ -457,6 +457,24 @@
"interactions": true
}
},
"nadir": {
"display_name": "Nadir (`nadir`)",
"url": "https://docs.litellm.ai/docs/providers/nadir",
"endpoints": {
"chat_completions": true,
"messages": false,
"responses": false,
"embeddings": false,
"image_generations": false,
"audio_transcriptions": false,
"audio_speech": false,
"moderations": false,
"batches": false,
"rerank": false,
"a2a": false,
"interactions": false
}
},
"cerebras": {
"display_name": "Cerebras (`cerebras`)",
"url": "https://docs.litellm.ai/docs/providers/cerebras",

View file

@ -0,0 +1,318 @@
"""
Unit tests for the Nadir provider (https://getnadir.com).
Nadir is an OpenAI-compatible intelligent router: the virtual model
``nadir/auto`` is classified server-side and routed to the cheapest model that
clears the quality bar. These tests cover provider resolution, credential
handling, and config wiring without making a live API call.
"""
from unittest.mock import MagicMock, patch
import litellm
from litellm import get_llm_provider
from litellm.types.utils import LlmProviders
class TestNadirProviderResolution:
def test_model_prefix_resolves_to_nadir(self):
model, provider, dynamic_api_key, api_base = get_llm_provider(model="nadir/auto", api_key="sk-test")
assert provider == "nadir"
# The nadir/ prefix is stripped; the virtual router alias is sent upstream.
assert model == "auto"
def test_default_api_base(self):
_, _, _, api_base = get_llm_provider(model="nadir/auto", api_key="sk-test")
assert api_base == "https://api.getnadir.com/v1"
def test_api_base_override(self):
_, _, _, api_base = get_llm_provider(
model="nadir/auto",
api_key="sk-test",
api_base="https://gateway.internal/v1",
)
assert api_base == "https://gateway.internal/v1"
def test_api_key_from_env(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-live-env")
_, _, dynamic_api_key, _ = get_llm_provider(model="nadir/auto")
assert dynamic_api_key == "sk-live-env"
def test_endpoint_reverse_maps_to_nadir(self):
# A caller passing only the Nadir base_url (no nadir/ prefix) is still
# identified as the nadir provider.
_, provider, _, _ = get_llm_provider(
model="auto",
api_base="https://api.getnadir.com/v1",
api_key="sk-test",
)
assert provider == "nadir"
class TestNadirCredentialScoping:
"""The server NADIR_API_KEY must never be forwarded to a caller-supplied host."""
def test_env_key_used_for_default_endpoint(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-server-secret")
_, _, dynamic_api_key, _ = get_llm_provider(model="nadir/auto")
assert dynamic_api_key == "sk-server-secret"
def test_env_key_used_when_base_matches_default(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-server-secret")
_, _, dynamic_api_key, _ = get_llm_provider(model="nadir/auto", api_base="https://api.getnadir.com/v1/")
assert dynamic_api_key == "sk-server-secret"
def test_env_key_NOT_leaked_to_custom_base(self, monkeypatch):
# A caller-controlled api_base without a caller key must NOT receive
# the server's env credential.
monkeypatch.setenv("NADIR_API_KEY", "sk-server-secret")
_, _, dynamic_api_key, _ = get_llm_provider(model="nadir/auto", api_base="https://attacker.example/v1")
assert dynamic_api_key is None
def test_caller_key_used_for_custom_base(self, monkeypatch):
# A caller directing at a custom base may still supply their own key.
monkeypatch.setenv("NADIR_API_KEY", "sk-server-secret")
_, _, dynamic_api_key, _ = get_llm_provider(
model="nadir/auto",
api_base="https://self-hosted.internal/v1",
api_key="sk-caller-own",
)
assert dynamic_api_key == "sk-caller-own"
def test_env_key_used_for_operator_configured_base(self, monkeypatch):
# An operator-configured NADIR_API_BASE is trusted; passing that same
# base explicitly still uses the env key.
monkeypatch.setenv("NADIR_API_KEY", "sk-server-secret")
monkeypatch.setenv("NADIR_API_BASE", "https://nadir.mycorp.internal/v1")
_, _, dynamic_api_key, _ = get_llm_provider(model="nadir/auto", api_base="https://nadir.mycorp.internal/v1")
assert dynamic_api_key == "sk-server-secret"
class TestNadirRegistration:
def test_enum_member(self):
assert LlmProviders.NADIR.value == "nadir"
def test_config_loads(self):
assert litellm.NadirConfig().__class__.__name__ == "NadirConfig"
def test_supported_params_nonempty(self):
params = litellm.NadirConfig().get_supported_openai_params(model="auto")
assert isinstance(params, list) and len(params) > 0
# Streaming is advertised; real token-by-token requires the SSE-enabled
# Nadir backend, otherwise stream=False returns a single completion.
assert "stream" in params
def test_unsupported_params_are_not_advertised(self):
# Nadir validates into its own request schema and drops anything
# outside it, so the provider must not inherit OpenAI's full param
# list. Advertising these would silently no-op at request time.
params = litellm.NadirConfig().get_supported_openai_params(model="auto")
for unsupported in (
"tools",
"tool_choice",
"functions",
"function_call",
"parallel_tool_calls",
"stop",
"seed",
"n",
"logprobs",
"stream_options",
"user",
):
assert unsupported not in params, f"{unsupported} is not honored by Nadir"
class TestNadirParamMapping:
def test_get_optional_params_maps_nadir(self):
# Exercises the nadir branch in litellm.utils.get_optional_params.
params = litellm.get_optional_params(
model="auto",
custom_llm_provider="nadir",
temperature=0.5,
max_tokens=64,
)
assert params["temperature"] == 0.5
assert params["max_tokens"] == 64
def test_get_supported_openai_params_dispatcher(self):
# Exercises the nadir branch in the top-level get_supported_openai_params.
params = litellm.get_supported_openai_params(model="auto", custom_llm_provider="nadir")
assert isinstance(params, list) and "stream" in params
class TestNadirEnvValidation:
def test_validate_environment_detects_key(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-live-xyz")
result = litellm.validate_environment(model="nadir/auto")
assert result["keys_in_environment"] is True
def test_validate_environment_flags_missing_key(self, monkeypatch):
monkeypatch.delenv("NADIR_API_KEY", raising=False)
result = litellm.validate_environment(model="nadir/auto")
assert "NADIR_API_KEY" in result["missing_keys"]
class TestNadirDispatch:
"""Nadir must not ride the generic OpenAI-compatible path.
That path never calls ``provider_config.transform_response``, so the cost
Nadir reports would be dropped and every call would record 0.0 spend.
"""
def test_not_in_openai_compatible_providers(self):
assert "nadir" not in litellm.openai_compatible_providers
def test_provider_config_resolves(self):
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager
config = ProviderConfigManager.get_provider_chat_config(model="auto", provider=LlmProviders.NADIR)
assert config.__class__.__name__ == "NadirConfig"
class TestNadirCostAttribution:
"""The routed model is a vendor name with no nadir/* pricing entry, so the
cost Nadir reports is what must reach the cost calculator."""
def _transform(self, payload):
import httpx
from litellm.types.utils import ModelResponse
raw = httpx.Response(
200,
json=payload,
request=httpx.Request("POST", "https://api.getnadir.com/v1/chat/completions"),
)
return litellm.NadirConfig().transform_response(
model="auto",
raw_response=raw,
model_response=ModelResponse(),
logging_obj=MagicMock(),
request_data={},
messages=[],
optional_params={},
litellm_params={},
encoding=None,
)
def _payload(self, **extra):
base = {
"id": "req-1",
"object": "chat.completion",
"created": 0,
"model": "claude-haiku-4-5",
"choices": [{"index": 0, "message": {"role": "assistant", "content": "hi"}, "finish_reason": "stop"}],
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
}
base.update(extra)
return base
def test_reported_cost_reaches_the_cost_calculator(self):
from litellm.cost_calculator import get_response_cost_from_hidden_params
res = self._transform(self._payload(nadir_metadata={"cost": {"total_cost_usd": 0.00123}}))
assert get_response_cost_from_hidden_params(res._hidden_params) == 0.00123
def test_routed_model_is_preserved(self):
# Overwriting this with "auto" would misattribute every request.
res = self._transform(self._payload(nadir_metadata={"cost": {"total_cost_usd": 0.001}}))
assert res.model == "claude-haiku-4-5"
def test_missing_cost_does_not_fail_the_response(self):
res = self._transform(self._payload(nadir_metadata={}))
assert res.choices[0].message.content == "hi"
assert "llm_provider-x-litellm-response-cost" not in res._hidden_params.get("additional_headers", {})
class TestNadirCompletionDispatch:
"""`_complete_nadir` is the branch that routes Nadir through the httpx
handler instead of the OpenAI SDK path. These pin its contract without a
network call."""
def _call(self, **kwargs):
from litellm.types.utils import ModelResponse
captured = {}
def fake_completion(**call_kwargs):
captured.update(call_kwargs)
return ModelResponse()
with patch( # test-quality-ok: these tests assert the dispatch wiring itself (nadir must reach base_llm_http_handler, and which credentials it is handed); faking HTTP would not observe that
"litellm.main.base_llm_http_handler.completion", side_effect=fake_completion
):
litellm.completion(
model="nadir/auto",
messages=[{"role": "user", "content": "hi"}],
**kwargs,
)
return captured
def test_routes_through_the_http_handler_as_nadir(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-env")
captured = self._call()
assert captured["custom_llm_provider"] == "nadir"
assert captured["api_base"] == "https://api.getnadir.com/v1"
def test_env_key_is_used_for_the_default_endpoint(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-env")
assert self._call()["api_key"] == "sk-env"
def test_caller_key_wins(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-env")
assert self._call(api_key="sk-caller")["api_key"] == "sk-caller"
def test_env_key_is_not_forwarded_to_a_caller_supplied_host(self, monkeypatch):
# The scoping lives in get_llm_provider; _complete_nadir must not undo
# it by re-reading NADIR_API_KEY at dispatch time.
monkeypatch.setenv("NADIR_API_KEY", "sk-env")
captured = self._call(api_base="https://attacker.example/v1")
assert captured["api_key"] != "sk-env"
assert captured["api_base"] == "https://attacker.example/v1"
def test_custom_api_base_is_honoured(self, monkeypatch):
monkeypatch.setenv("NADIR_API_KEY", "sk-env")
captured = self._call(api_base="https://nadir.internal/v1", api_key="sk-own")
assert captured["api_base"] == "https://nadir.internal/v1"
assert captured["api_key"] == "sk-own"
class TestNadirConfigSurface:
def test_get_config_returns_a_mapping(self):
assert isinstance(litellm.NadirConfig.get_config(), dict)
def test_provider_info_defaults_the_base(self):
base, key = litellm.NadirConfig()._get_openai_compatible_provider_info(None, "sk-x")
assert base == "https://api.getnadir.com/v1"
assert key == "sk-x"
def test_provider_info_honours_an_explicit_base(self):
base, _ = litellm.NadirConfig()._get_openai_compatible_provider_info("https://nadir.internal/v1", "sk-x")
assert base == "https://nadir.internal/v1"
def test_non_json_body_does_not_fail_the_response(self):
# Exercises the narrow except: a body that is not JSON at all.
import httpx
from litellm.types.utils import ModelResponse
raw = httpx.Response(
200,
text="not json",
request=httpx.Request("POST", "https://api.getnadir.com/v1/chat/completions"),
)
with patch.object(litellm.NadirConfig.__bases__[0], "transform_response", return_value=ModelResponse()):
res = litellm.NadirConfig().transform_response(
model="auto",
raw_response=raw,
model_response=ModelResponse(),
logging_obj=MagicMock(),
request_data={},
messages=[],
optional_params={},
litellm_params={},
encoding=None,
)
assert "llm_provider-x-litellm-response-cost" not in res._hidden_params.get("additional_headers", {})