feat(providers): add Nadir intelligent-router provider (nadir/auto) - #33227
feat(providers): add Nadir intelligent-router provider (nadir/auto)#33227doramirdor wants to merge 1 commit into
Conversation
1257479 to
693a8e6
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
31af6f4 to
a43a0d4
Compare
c8f020c to
1ef46d4
Compare
PR overviewThis pull request adds the Nadir intelligent-router provider under the 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 |
1ef46d4 to
48ae477
Compare
48ae477 to
eea0e77
Compare
eea0e77 to
7f66034
Compare
7f66034 to
a4a86ce
Compare
|
All checks are green (78/78) after rebasing onto |
a4a86ce to
fad27b3
Compare
|
Rebased onto current The diff is unchanged and still fully additive: 11 files, +242/-0, no existing behavior touched. 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 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. |
|
fad27b3 to
04ff3f2
Compare
|
Heads-up on one line in the diff: the rebase picked up the new
nadir_models: Set = set() # mutable-ok: provider registry, filled from model_cost at import like every sibling providerVerified locally: |
|
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 |
04ff3f2 to
3056dc1
Compare
|
Correction pushed, and it makes the PR narrower rather than wider.
Most importantly that includes The declared set is now those 7 plus Head is now |
|
@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 SummaryAdds Nadir as an OpenAI-compatible intelligent-router provider with model resolution, credential scoping, parameter support, lazy configuration loading, and endpoint metadata.
Confidence Score: 4/5The 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
|
| 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 |
There was a problem hiding this comment.
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
3056dc1 to
d1af35b
Compare
|
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=nadirSo every successful routed call would have thrown in the cost calculator. Fixed in Rather than mirror every vendor's price list under a cost = raw_response.json()["nadir_metadata"]["cost"]["total_cost_usd"]
hidden["additional_headers"]["llm_provider-x-litellm-response-cost"] = float(cost)
Three tests added: the reported cost is surfaced, the routed model name survives into |
| "max_tokens", | ||
| "presence_penalty", | ||
| "response_format", | ||
| "stream", |
There was a problem hiding this comment.
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).
d1af35b to
5b33319
Compare
|
@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. The provider works: The cost line is 0.0, and that is the honest stateI had pushed a The reason: I also tried removing 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 Everything else is green: 19/19 tests, |
|
My recommendation is add Why not ship with 0.0 cost: Why the dispatch branch belongs here: Concrete steps:
The Tip: You can customize Greptile's behavior for this repo with |
5b33319 to
2c79b75
Compare
|
Implemented, and verified with a live call this time rather than a unit test. Head @greptileai was right and I was wrong on one detail: I said there was no literal What landed:
One deliberate difference from the OpenRouter branch: Live result, real key, real routed call: Previously that last line was Tests: 23/23 in the Nadir suite, including a new |
|
The implementation looks correct. Let me verify the key claim about The full implementation is solid:
One minor redundancy (not a problem): in LGTM. The live call confirming |
| 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) |
There was a problem hiding this comment.
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.
2c79b75 to
0afcfb9
Compare
|
Both CI failures fixed in
The new file now imports no
Those dispatch tests are worth more than the coverage number: one of them pins that the env Local state: 32/32 Nadir tests, |
|
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. Head |
e002531 to
fb08e90
Compare
| 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 |
There was a problem hiding this comment.
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.
| api_key: Final = ctx.api_key or litellm.api_key | |
| api_key: Final = ctx.api_key |
fb08e90 to
7142c02
Compare
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>
7142c02 to
41a4643
Compare
| 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") |
There was a problem hiding this comment.
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.
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.Why it fits cleanly
/v1/chat/completionsdialect and accepts the key as a Bearer token, so it rides the existing openai-compatible transport with no request translation.modelas the routed model (notauto), so LiteLLM'scost_calculatorattributes usage/cost to the concrete model with no special handling. The routing decision is surfaced undernadir_metadata.Changes
litellm/llms/nadir/chat/transformation.py—NadirConfig(OpenAIGPTConfig), defaultsapi_basetohttps://api.getnadir.com/v1.nadirfollowing thecerebraspattern:LlmProvidersenum,openai_compatible_providers,LITELLM_CHAT_PROVIDERS,get_llm_providerresolution + api_base,__init__model sets, lazy-import registry,utils.map_openai_params,get_supported_openai_params,main.pydispatch, env-key validation.https://api.getnadir.com/v1toopenai_compatible_endpointsso a base_url-only config reverse-maps to the provider.provider_endpoints_support.jsonentry (chat_completions).tests/test_litellm/llms/nadir/(11 passing).Credentials via
NADIR_API_KEY(orapi_key=). OptionalNADIR_API_BASEfor 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.get_llm_providerfor openai/groq/cerebras.Docs: the provider docs page is ready and will be submitted separately (the docs website now lives outside this repo).