Skip to content

feat(providers): add Nadir intelligent-router provider (nadir/auto) - #33227

Open
doramirdor wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
doramirdor:feat/nadir-provider
Open

feat(providers): add Nadir intelligent-router provider (nadir/auto)#33227
doramirdor wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
doramirdor:feat/nadir-provider

Conversation

@doramirdor

Copy link
Copy Markdown

What

Adds Nadir (https://getnadir.com) as a first-class provider. Nadir is an OpenAI-compatible intelligent router: instead of picking a model per request, you call a single virtual model, nadir/auto. Nadir classifies each request by complexity server-side and routes it to the cheapest model that clears the quality bar, then returns an OpenAI-compatible response.

from litellm import completion

response = completion(
    model="nadir/auto",
    messages=[{"role": "user", "content": "Hello!"}],
)
# response.model is the model Nadir routed to (e.g. "claude-haiku-4-5"),
# so LiteLLM cost tracking prices the real underlying model.

Why it fits cleanly

  • The endpoint speaks the OpenAI /v1/chat/completions dialect and accepts the key as a Bearer token, so it rides the existing openai-compatible transport with no request translation.
  • The response reports model as the routed model (not auto), so LiteLLM's cost_calculator attributes usage/cost to the concrete model with no special handling. The routing decision is surfaced under nadir_metadata.

Changes

  • litellm/llms/nadir/chat/transformation.pyNadirConfig(OpenAIGPTConfig), defaults api_base to https://api.getnadir.com/v1.
  • Registered nadir following the cerebras pattern: LlmProviders enum, openai_compatible_providers, LITELLM_CHAT_PROVIDERS, get_llm_provider resolution + api_base, __init__ model sets, lazy-import registry, utils.map_openai_params, get_supported_openai_params, main.py dispatch, env-key validation.
  • Added https://api.getnadir.com/v1 to openai_compatible_endpoints so a base_url-only config reverse-maps to the provider.
  • provider_endpoints_support.json entry (chat_completions).
  • Unit tests in tests/test_litellm/llms/nadir/ (11 passing).

Credentials via NADIR_API_KEY (or api_key=). Optional NADIR_API_BASE for self-hosted gateways.

Testing

  • pytest tests/test_litellm/llms/nadir/test_nadir.py — 11 passing (provider resolution, api_base default/override, env-key detection, reverse endpoint mapping, config wiring).
  • python tests/code_coverage_tests/check_provider_folders_documented.py — passes.
  • Verified no regression in get_llm_provider for openai/groq/cerebras.

Docs: the provider docs page is ready and will be submitted separately (the docs website now lives outside this repo).

@CLAassistant

CLAassistant commented Jul 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 1257479 to 693a8e6 Compare July 14, 2026 15:27
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/__init__.py 66.66% 1 Missing ⚠️
.../litellm_core_utils/get_supported_openai_params.py 50.00% 1 Missing ⚠️
litellm/llms/nadir/chat/transformation.py 96.29% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch 2 times, most recently from 31af6f4 to a43a0d4 Compare July 14, 2026 15:38
@doramirdor
doramirdor changed the base branch from main to litellm_oss_daily_2026_07_13 July 14, 2026 15:39
@doramirdor
doramirdor force-pushed the feat/nadir-provider branch 3 times, most recently from c8f020c to 1ef46d4 Compare July 14, 2026 15:58
@codspeed-hq

codspeed-hq Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing doramirdor:feat/nadir-provider (41a4643) with litellm_internal_staging (b0fac57)

Open in CodSpeed

Comment thread litellm/litellm_core_utils/get_llm_provider_logic.py Outdated
@veria-ai

veria-ai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

PR overview

This pull request adds the Nadir intelligent-router provider under the nadir/auto model, including provider resolution, authentication, response transformation, streaming, and cost metadata handling.

Four security issues remain open, while one has already been addressed. The most significant issue allows a caller-controlled Nadir endpoint to receive the process-wide API key, and another can expose the Nadir environment credential over plaintext HTTP. The remaining issues permit streaming requests to bypass spend accounting and untrusted cost metadata to reduce recorded spend.

Open issues (4)

Fixed/addressed: 1 · PR risk: 7/10

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 1ef46d4 to 48ae477 Compare July 14, 2026 16:51
@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 48ae477 to eea0e77 Compare July 22, 2026 12:22
@doramirdor
doramirdor requested a review from a team July 22, 2026 12:22
@doramirdor
doramirdor changed the base branch from litellm_oss_daily_2026_07_13 to litellm_oss_daily_2026_07_20 July 22, 2026 12:23
@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from eea0e77 to 7f66034 Compare July 22, 2026 12:28
@doramirdor
doramirdor changed the base branch from litellm_oss_daily_2026_07_20 to litellm_internal_staging July 22, 2026 12:34
@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 7f66034 to a4a86ce Compare July 22, 2026 12:34
@doramirdor

Copy link
Copy Markdown
Author

All checks are green (78/78) after rebasing onto litellm_internal_staging, and the Veria AI security finding on credential scoping was addressed: the env NADIR_API_KEY is never forwarded to a caller-supplied api_base. Ready for review whenever someone has a moment. 🙏

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from a4a86ce to fad27b3 Compare July 31, 2026 15:39
@doramirdor

Copy link
Copy Markdown
Author

Rebased onto current litellm_internal_staging (head is now fad27b3). The branch had drifted 508 commits behind, so the base is fresh again.

The diff is unchanged and still fully additive: 11 files, +242/-0, no existing behavior touched. tests/test_litellm/llms/nadir/test_nadir.py passes 18/18 locally against the new base.

Recap of the checks: CLA signed, Codecov patch coverage 87%, CodSpeed reports no performance change, and the Veria security finding on credential scoping was addressed (the env NADIR_API_KEY is never forwarded to a caller-supplied api_base).

This has been open since July 14 without a human review yet. Happy to split it up, rename anything, or make whatever changes you would like. Just say what you need to move it forward.

@krrish-berri-2

Copy link
Copy Markdown
Contributor

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from fad27b3 to 04ff3f2 Compare July 31, 2026 16:28
@doramirdor

Copy link
Copy Markdown
Author

Heads-up on one line in the diff: the rebase picked up the new type_discipline_gate.py, and lint failed on it.

nadir_models: Set = set() in litellm/__init__.py is LIT002 (mutable-collection construction). It is the same one-liner every sibling provider has, but the LIT002 budget is currently a ratchet (limit 27427, base total 27433), so adding one more trips the gate. Per the gate's own guidance I gave it a reason rather than raise the ceiling:

nadir_models: Set = set()  # mutable-ok: provider registry, filled from model_cost at import like every sibling provider

Verified locally: python scripts/type_discipline_gate.py --base origin/litellm_internal_staging now reports OK, and the provider tests still pass 18/18. Happy to take a different approach if you would rather the registry be built some other way.

@doramirdor

Copy link
Copy Markdown
Author

lint is green now. The remaining red check, code-quality, is not from this PR:

Unignored recursive functions found in ./litellm/proxy/management_endpoints/management_v1/list_framework.py: ['_render_all']

That file arrived in staging today via #35308 and is not touched by this PR (the diff is 11 files, all Nadir provider registration). Unrelated PR #35380 fails code-quality with the identical error, so it looks like it is breaking every PR whose merge base is newer than that commit. Nothing for me to fix here, but flagging it so the red X is not read as this branch's fault.

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 04ff3f2 to 3056dc1 Compare July 31, 2026 19:18
@doramirdor

Copy link
Copy Markdown
Author

Correction pushed, and it makes the PR narrower rather than wider.

get_supported_openai_params previously inherited OpenAIGPTConfig's full list, 32 params. Auditing Nadir's own request schema, only 7 of those are actually honored: temperature, max_tokens, top_p, frequency_penalty, presence_penalty, stream, response_format. Everything else is dropped at validation rather than forwarded to the routed model, so advertising it would have advertised a silent no-op.

Most importantly that includes tools / tool_choice / functions: Nadir does not support function calling today, and the previous list claimed it did. Better to catch that here than to have a user find it at runtime.

The declared set is now those 7 plus extra_headers and max_retries, which LiteLLM's transport handles client-side rather than sending in the body. Added a test that pins the exclusions so this cannot silently regress if the base list grows.

Head is now 3056dc1, still 11 files, +288/-0, lint and the type-discipline gate green, tests 19/19.

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

@doramirdor thank you for the contribution! Could you provide some no-mock e2e proof (e.g., screenshot) of this on a real proxy connected to the Nadir LLM API provider?

@greptile-apps

greptile-apps Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Nadir as an OpenAI-compatible intelligent-router provider with model resolution, credential scoping, parameter support, lazy configuration loading, and endpoint metadata.

  • Registers nadir/auto throughout provider discovery and chat-completion dispatch.
  • Adds a Nadir configuration using the shared OpenAI transport and a restricted supported-parameter set.
  • Adds environment-key, custom-base, reverse-mapping, and registration tests.
  • The routed response model currently does not resolve to the underlying provider’s pricing entry, breaking the intended cost attribution.

Confidence Score: 4/5

The routed-model cost attribution must be fixed before merging because successful Nadir calls can record missing or incorrect spend.

Nadir responses are normalized to Nadir-prefixed concrete model names, but no corresponding pricing entries or cross-provider resolution exists, so the shared cost calculator cannot price the actual routed model.

Files Needing Attention: litellm/llms/nadir/chat/transformation.py, litellm/init.py

Important Files Changed

Filename Overview
litellm/llms/nadir/chat/transformation.py Adds the Nadir OpenAI-compatible configuration, but inherited response attribution leaves routed models under the Nadir namespace and prevents underlying-model pricing lookup.
litellm/litellm_core_utils/get_llm_provider_logic.py Adds provider and endpoint resolution while correctly restricting environment-key reuse to the configured trusted base.
litellm/main.py Routes Nadir through the existing OpenAI-compatible completion transport.
litellm/utils.py Adds Nadir parameter mapping and environment validation using the provider configuration.
tests/test_litellm/llms/nadir/test_nadir.py Covers provider registration, credential scoping, endpoint resolution, and parameter wiring, but does not exercise routed-response cost calculation.
litellm/init.py Registers Nadir models and lazy configuration exports; the model set remains empty without Nadir-specific pricing entries.
provider_endpoints_support.json Advertises chat-completions support for the new provider.

Reviews (1): Last reviewed commit: "feat(providers): add Nadir intelligent-r..." | Re-trigger Greptile


class NadirConfig(OpenAIGPTConfig):
"""
Reference: https://getnadir.com/docs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Routed model pricing is lost

When Nadir returns a concrete routed model, the inherited OpenAI-compatible response conversion attributes it as nadir/<routed_model>, but no corresponding Nadir pricing entries or cross-provider mapping exist, causing completion cost and logged spend to be missing or incorrect.

Knowledge Base Used: LLM Provider Adapters

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 3056dc1 to d1af35b Compare August 2, 2026 00:13
@doramirdor

Copy link
Copy Markdown
Author

Good catch, and it reproduces. On the previous head:

>>> litellm.completion_cost(completion_response=ModelResponse(model="nadir/claude-haiku-4-5", ...))
Exception: This model isn't mapped yet. model=claude-haiku-4-5, custom_llm_provider=nadir

So every successful routed call would have thrown in the cost calculator. Fixed in d1af35b.

Rather than mirror every vendor's price list under a nadir/* namespace, which would go stale the moment the router's pool changes, I took the same approach the OpenRouter provider already uses: Nadir computes the cost of the call it actually made and returns it on the response, so transform_response surfaces it as the provider-reported cost.

cost = raw_response.json()["nadir_metadata"]["cost"]["total_cost_usd"]
hidden["additional_headers"]["llm_provider-x-litellm-response-cost"] = float(cost)

cost_calculator.get_response_cost_from_hidden_params picks that up and short-circuits the pricing lookup that was raising. Verified end to end:

response model : claude-haiku-4-5
cost via hidden: 0.00123

Three tests added: the reported cost is surfaced, the routed model name survives into response.model (overwriting it with auto would misattribute every request), and a response with no cost still returns a valid completion instead of failing. 22/22 pass, lint and the type-discipline gate green.

"max_tokens",
"presence_penalty",
"response_format",
"stream",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Streaming requests bypass spend accounting

An authenticated caller can repeatedly use stream=true without increasing their tracked spend. Streaming does not invoke transform_response, the generic stream assembler only propagates usage.cost, and there is no Nadir pricing entry to provide a fallback; map the streamed nadir_metadata.cost.total_cost_usd into usage.cost (or disable streaming until that accounting path exists).

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from d1af35b to 5b33319 Compare August 2, 2026 02:20
@doramirdor

Copy link
Copy Markdown
Author

@mateo-berri here is the no-mock run. Real proxy, real Nadir key, real routed completion, no stubs anywhere. It also answers @greptileai's P1, though not in the direction I expected.

$ litellm --config config.yaml --port 4000
LiteLLM: Proxy initialized with Config, Set models: nadir-auto

$ curl http://127.0.0.1:4000/v1/chat/completions \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
    -d '{"model":"nadir-auto","messages":[{"role":"user","content":"What is the capital of France? Answer in one word."}],"max_tokens":16}'

{
  "id": "bb1113de-bf45-4566-af6a-b52efe86cfb8",
  "model": "nadir-auto",
  "object": "chat.completion",
  "choices": [
    { "finish_reason": "stop", "index": 0,
      "message": { "content": "Paris", "role": "assistant" } }
  ],
  "usage": { "completion_tokens": 4, "prompt_tokens": 19, "total_tokens": 23 }
}

nadir_metadata.recommended_model  : bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0
nadir_metadata.cost.total_cost_usd: 0.002039

response headers:
  x-litellm-model-name            : nadir/auto
  x-litellm-model-api-base        : https://api.getnadir.com/v1
  x-litellm-response-cost-original: 0.0

The provider works: nadir-auto went out, the router picked Haiku, the answer came back through the proxy.

The cost line is 0.0, and that is the honest state

I had pushed a transform_response override that read Nadir's own computed cost off the response and surfaced it as llm_provider-x-litellm-response-cost, the way openrouter/chat/transformation.py does. Unit tests passed. This live run showed it never executes, so I have removed it rather than ship dead code.

The reason: nadir is in openai_compatible_providers, so completion() dispatches through _complete_custom_openai into OpenAIChatCompletion.completion. That resolves provider_config but only uses it for should_fake_stream (litellm/llms/openai/openai.py:618-634); it never calls provider_config.transform_response. Confirmed by wrapping the method and watching it not fire. OpenRouter avoids this precisely by not being in openai_compatible_providers and having its own _complete_openrouter dispatch branch.

I also tried removing nadir from that list. The routed model then survives as nadir/bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0 instead of collapsing to nadir/auto, but transform_response still is not reached and cost stays 0.0.

So greptile's P1 is real and reproducible on the live path, but fixing it properly means changing how this provider is dispatched, which is more than a provider addition should decide unilaterally. How would you like it handled? Happy to add a _complete_nadir branch mirroring _complete_openrouter in this PR, or to land the provider as-is with the caveat documented in the module docstring, and do dispatch as a follow-up. Your call.

Everything else is green: 19/19 tests, lint and the type-discipline gate pass. Head is 5b33319, 11 files, +292/-0.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

My recommendation is add _complete_nadir in this PR.

Why not ship with 0.0 cost:
Cost tracking is a primary LiteLLM feature, not a nice-to-have. A provider that silently reports 0.0 will generate bug reports and erode trust in the integration. The module docstring caveat will be read by essentially nobody — the 0.0 in their dashboard will be found by everyone.

Why the dispatch branch belongs here:
The diff already adds nadir to the openai_compatible_providers block at main.py:5542. Removing it from there and adding a dedicated elif branch is a net-neutral change to main.py complexity. OpenRouter set the precedent; the pattern is well-understood and the risk is low. This is self-contained within this PR's scope — you're not changing shared infrastructure, you're following an existing pattern to give Nadir the same dispatch path OpenRouter uses.

Concrete steps:

  1. Remove the or custom_llm_provider == "nadir" line from the openai_compatible_providers block in main.py
  2. Add an elif custom_llm_provider == "nadir": branch that calls OpenAIChatCompletion.completion with NadirConfig — mirror the openrouter block exactly
  3. Add transform_response back to NadirConfig. It was correct; it just wasn't reachable. Read the cost from nadir_metadata.cost.total_cost_usd in the response JSON body and store it in _hidden_params["additional_headers"]["llm_provider-x-litellm-response-cost"] exactly as OpenRouter does with usage.cost
  4. Drop the docstring caveat paragraph since the issue will be resolved

The transform_response skeleton you removed was architecturally right — the problem was dispatch path, not the logic. Reinstate it reading from the correct field and pair it with the dispatch branch.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 5b33319 to 2c79b75 Compare August 2, 2026 11:29
@doramirdor

Copy link
Copy Markdown
Author

Implemented, and verified with a live call this time rather than a unit test. Head 2c79b75.

@greptileai was right and I was wrong on one detail: I said there was no literal or custom_llm_provider == "nadir" line in main.py and that membership only lived in constants.py. There were both. Removing it from constants.py alone is exactly why my earlier experiment showed transform_response still not firing, and why I wrongly concluded the dispatch branch would not help.

What landed:

  1. main.py — dropped the literal nadir arm from the OpenAI-compatible block and added _complete_nadir, which routes through base_llm_http_handler.completion the same way _complete_openrouter does.
  2. utils.py — registered LlmProviders.NADIR -> NadirConfig() in ProviderConfigManager. Without this the new path raises Provider config not found.
  3. constants.py — removed nadir from openai_compatible_providers.
  4. NadirConfig.transform_response reinstated, reading nadir_metadata.cost.total_cost_usd into llm_provider-x-litellm-response-cost.

One deliberate difference from the OpenRouter branch: _complete_nadir does not fall back to get_secret_str("NADIR_API_KEY"). get_llm_provider already binds the env key to the trusted Nadir endpoint and withholds it when the caller supplies their own api_base, which is the credential-scoping fix from @veria-ai's earlier review. Re-reading the env var at dispatch would quietly undo it.

Live result, real key, real routed call:

content       : Paris
model         : bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0
response_cost : 0.002034

Previously that last line was 0.0. The routed model now survives into response.model instead of collapsing to nadir/auto, and the cost the router actually incurred reaches the calculator. Confirmed for both an explicit api_key= and the NADIR_API_KEY env path.

Tests: 23/23 in the Nadir suite, including a new TestNadirDispatch that pins nadir out of openai_compatible_providers and asserts ProviderConfigManager resolves NadirConfig, so a future refactor that quietly puts it back on the generic path fails loudly instead of silently reporting 0.0 spend again. Also ran tests/test_litellm/test_main.py plus tests/test_litellm/llms/openai: 632 passed. lint and the type-discipline gate are green.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The implementation looks correct. Let me verify the key claim about constants.py — the diff shows nadir being added, but to LITELLM_CHAT_PROVIDERS (line 513 context: "gigachat", "nvidia_nim", "cerebras", + "nadir", "baseten"), not to openai_compatible_providers. Those are two separate lists. The test_not_in_openai_compatible_providers assertion confirms nadir is absent from the right one.

The full implementation is solid:

  • Dispatch path: _complete_nadir mirrors _complete_openrouter — routes through base_llm_http_handler.completion so transform_response gets called. The 0.0 spend bug is fixed.
  • Credential scoping: _complete_nadir uses ctx.api_key or litellm.api_key without re-reading NADIR_API_KEY. The env-key-to-trusted-endpoint binding stays exclusively in get_llm_provider. Correct architecture.
  • ProviderConfigManager registration: LlmProviders.NADIR -> NadirConfig() in _build_provider_config_map is correct and test_provider_config_resolves pins it as a regression guard.
  • transform_response: try/except around cost extraction is the right call — a response with no nadir_metadata should not fail the call.
  • Regression guard: TestNadirDispatch will fail loudly if a future commit silently puts nadir back on the generic path.

One minor redundancy (not a problem): in _complete_nadir, the fallback chain ctx.api_base or litellm.api_base or get_secret_str("NADIR_API_BASE") or "https://api.getnadir.com/v1" is defensive padding since get_llm_provider already populates ctx.api_base before this branch executes. Harmless.

LGTM. The live call confirming response_cost: 0.002034 on a real routed model is the right verification for the cost attribution fix.

hidden = model_response._hidden_params
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Provider cost permits budget manipulation

Custom api_base endpoints are supported, so a caller with client-side credential passthrough can make their endpoint return a negative total_cost_usd. This value reaches successful-request spend updates unchanged, allowing the caller to reduce their key, user, or team spend before making server-funded requests. Parse the value and accept it only when math.isfinite(cost) and cost >= 0.

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 2c79b75 to 0afcfb9 Compare August 2, 2026 12:12
@doramirdor

Copy link
Copy Markdown
Author

Both CI failures fixed in 0afcfb9.

lint was the strict-rule gate, a different one from the type-discipline gate I had been running, so I missed it locally. Three violations, all in transformation.py, all from copying the base method signature verbatim:

  • TID251 x2: typing.Any and typing.List are banned imports. Swapped to object for the pass-through params and the builtin generic. Widening a parameter type is LSP-safe, and these values are only forwarded to super().
  • UP006: List[AllMessageValues] to list[AllMessageValues].
  • BLE001: the blind except Exception around cost extraction is now except (ValueError, KeyError, TypeError), which is what a non-JSON body or missing keys can actually raise. Narrower is better here anyway: a real bug in that block no longer gets swallowed.

The new file now imports no typing names at all. Note openrouter/chat/transformation.py still uses Any/List, but it predates the ratchet and is inside the budget baseline, so new code has to be stricter.

codecov/patch was 66.12% against a 69.94% target. The uncovered block was _complete_nadir itself, which no test exercised. Added TestNadirCompletionDispatch, which patches base_llm_http_handler.completion and asserts what the branch passes through, plus a few small config-surface tests. litellm/llms/nadir/chat/transformation.py is now at 96% and _complete_nadir is fully covered.

Those dispatch tests are worth more than the coverage number: one of them pins that the env NADIR_API_KEY is not forwarded when the caller supplies their own api_base. That is @veria-ai's credential-scoping finding from the first review, and it now fails loudly if anyone later adds an env fallback inside the dispatch branch.

Local state: 32/32 Nadir tests, test_main.py 57 passed, both the strict-rule and type-discipline gates green, ruff format clean.

@doramirdor

Copy link
Copy Markdown
Author

Friendly bump @mateo-berri — this has been green and idle since Aug 2.

Recap of where it landed after your no-mock request: that live run is what exposed the cost bug, so the ask paid off. _complete_nadir now routes through base_llm_http_handler like the OpenRouter branch, transform_response surfaces the router's own cost, and a real routed call reports response_cost: 0.002034 instead of 0.0.

Head 0afcfb9, 11 files, +551/-0, fully additive. All checks green including lint and codecov/patch. Happy to rebase or split it if that helps it move.

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch 3 times, most recently from e002531 to fb08e90 Compare August 11, 2026 07:22
Comment thread litellm/main.py
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Global API key sent to custom Nadir hosts

A caller who supplies a custom api_base can make provider resolution leave ctx.api_key unset, after which this fallback sends the process-wide litellm.api_key to that host as a Bearer token. Use only the credential scoped by get_llm_provider; it resolves the Nadir environment key for trusted endpoints and preserves explicitly supplied caller credentials.

Suggested change
api_key: Final = ctx.api_key or litellm.api_key
api_key: Final = ctx.api_key

@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from fb08e90 to 7142c02 Compare August 11, 2026 07:27
Nadir (https://getnadir.com) is an OpenAI-compatible intelligent router.
A single virtual model, nadir/auto, is classified server-side and routed to
the cheapest model that clears the quality bar. The response reports the
routed model in the model field, so LiteLLM cost tracking prices the real
underlying model.

- litellm/llms/nadir/chat/transformation.py: NadirConfig(OpenAIGPTConfig)
- register nadir across enum, provider lists, get_llm_provider, __init__,
  lazy imports, utils, get_supported_openai_params
- add https://api.getnadir.com/v1 to openai_compatible_endpoints so base_url
  only usage reverse-maps to the provider
- provider_endpoints_support.json entry (chat_completions only)
- docs page + unit tests (11 passing)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doramirdor
doramirdor force-pushed the feat/nadir-provider branch from 7142c02 to 41a4643 Compare August 11, 2026 07:33
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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Server credential can be sent over plaintext HTTP

_endpoint_matches_api_base compares only hostname and path, so http://api.getnadir.com/v1 reaches this branch. A caller permitted to supply client-side endpoints can select that URL, causing NADIR_API_KEY to be transmitted as a Bearer token over plaintext HTTP where a network-positioned attacker can capture it. Only load the environment credential when the supplied URL uses HTTPS; otherwise retain only a caller-supplied key or reject the endpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants