Repository navigation
feat(decisions): add Databricks ai_decide as a /v1/decisions provider and auto-router decider - #45200
Conversation
… and auto-router decider
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
…ors and keep the provider wiring under llms/
…Databricks to the dashboard's OSS classifier editor
…tion-mode cost-map entry
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f875922. Configure here.
…ng the Databricks decider onto BaseDecisionsConfig
…on an auto router A /model/new for an auto router lands on one replica. A request routed to that auto router on a sibling replica missed it, the model read-through loaded only the auto router's own row, and the pre-routing hook then picked a tier the sibling had not loaded, so the caller got a 400 "no healthy deployments for <tier>" until the next periodic reload. An auto router miss now runs the full add_deployment reconcile, tiers included. Found by the audit's two-worker rig on this PR's routing cells; CircleCI's one-worker proxy never opens the window.
…ricks auto-router classifier Wire cells for /v1/decisions with databricks/<endpoint> deployments, routing cells for the complexity router's databricks classifier on all three endpoints with the Postgres rows read back, management cells for the classifier's spend writes and for the evaluation-mode health check of a Databricks serving endpoint.
…wing the decisions IR and the /v1/systemone route split Main now serves System One bodies at /v1/systemone and the OpenAI decisions format at /v1/decisions, translating both through one IR. The Databricks config keeps its serving-endpoint URL and name validation on top of the IR base; both DatabricksDecisionsConfig and OpenAIDecisionsConfig stay registered. The System One cells and the endpoint unit test move to /v1/systemone, a wire cell proves an OpenAI-format /v1/decisions request reaches the serving endpoint as System One, and the evaluation-mode cost map guard covers the dedicated decider providers, since OpenAI decides with its chat models.
…ng hosted_vllm beside databricks decisions
TLDR
Problem this solves:
/v1/decisionsrefuseddatabricks/<endpoint>as an unknown providerprovider: jev,laya, orbespokeHow it solves it:
databricks, posting to{DATABRICKS_API_BASE}/<endpoint>/invocationsDATABRICKS_API_KEY, falling back toDATABRICKS_TOKENopensource_classifier_config.provider: databricksfor the auto router,model= the serving endpoint namedatabricks/<endpoint>, not Databricks's internal/mosaicml/local_model/v1/decisionsreturns the Databricks body unchanged,modelincludeddatabricks/<name>that is not a bare serving endpoint name (a slash,.., a query string, a space) answers 400must be the bare endpoint nameinstead of reaching Databricksdatabricks/databricks-openjev-qwen35-4bcost-map entry withmode: evaluationat a zero rate (Databricks publishes no per-token OpenJev price), so/healthprobes the decider through/v1/decisionsand Lens lists it with nomodel_infoneeded; a deployment'smodel_infoprices set the billed costUser Flow
Before: a platform team pointing the proxy at their Databricks OpenJev serving endpoint gets a 400 on every decision, and the auto router never boots
model: databricks/databricks-openjev-qwen35-4btomodel_listplus anauto_router/complexity_routerdeployment withopensource_classifier_config.provider: databricks, then starts the proxyopensource_classifier_config.provider Input should be 'jev', 'laya' or 'bespoke', andGET http://localhost:4000/v1/modelslists noauto"model": "databricks-decider", astate, and threequestionsUnknown Decisions provider 'databricks'. Supported providers: perplexity, typesafe, openrouter, cloudflare, strands_decider"model": "auto"returns 400There are no healthy deployments for this model, the same on /v1/responses and /v1/messagesAfter: the same config boots clean, decisions come back from the Databricks endpoint, and the auto router splits prompts across tiers
model_listentries and starts the proxyGET http://localhost:4000/v1/modelslistsautonext todatabricks-decideris_billing,intent: duplicate_charge,urgency) andx-litellm-model-name: databricks/databricks-openjev-qwen35-4b"model": "auto"and "What is the capital of France?" is answered by the SIMPLE tier (x-litellm-model-name: openai/gpt-5.6-luna), the same on /v1/responses and /v1/messagesx-litellm-model-name: openai/gpt-5.6)Linear ticket
Resolves LIT-9315
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
Last updated: 5cfd0b2. QA and /live-pr-risk ran on 5cfd0b2; the Admin UI leg ran on 1ccf0ee, and the commits after it change no dashboard code
Two proxies, each booted with
--num_workers 2from its own worktree and venv, on random ports: Before from the merge base9e20264607, After from the tip5cfd0b24aa. Both proxies run withLITELLM_LOCAL_MODEL_COST_MAP=Trueso each side reads its own checkout's cost map (the hosted map carries this PR's entry once it merges). Both load the same config; the Databricks workspace host is redacted as<workspace>in the outputs below. Real calls to the Databricks serving endpoint, TypeSafe, and OpenAIdecisions_body.json, the customer's support-triage shape:{"model":"databricks-decider","state":{"message":"My card was charged twice for the same order and I need a refund.","channel":"support"},"questions":{"is_billing":{"type":"noul","instructions":"Is this a billing-related request?","criteria":{"true":"The message concerns a charge, payment, invoice, or refund.","false":"The message does not concern billing."}},"intent":{"type":"choice","instructions":"Which intent best matches the message?","criteria":{"refund":"The customer requests a refund.","duplicate_charge":"The customer reports being charged more than once.","other":"Another request."}},"urgency":{"type":"score","instructions":"How urgent is the request?","criteria":["Can wait","Needs attention soon","Urgent"]}}}Before (9e20264)
The model list after boot
/v1/decisions with a databricks/ deployment
/v1/decisions control: the TypeSafe deployment, same body
auto router (provider: databricks), easy prompt on /v1/chat/completions
auto router (provider: databricks), hard prompt on /v1/chat/completions
auto router, easy prompt on /v1/responses
auto router, easy prompt on /v1/messages
/model/info for the decider deployment, no model_info in the config
/health for the decider deployment
After (5cfd0b2)
The model list after boot
/v1/decisions with a databricks/ deployment
/v1/decisions control: the TypeSafe deployment, same body
auto router (provider: databricks), easy prompt on /v1/chat/completions
auto router (provider: databricks), hard prompt on /v1/chat/completions
auto router, easy prompt on /v1/responses
auto router, easy prompt on /v1/messages
/model/info for the decider deployment, no model_info in the config
/health for the decider deployment
Admin UI (1ccf0ee)
The later commits add a cost-map entry and a unit test and touch no dashboard code, so the screenshots below stand.
The dashboard dev server (
npm run devinui/litellm-dashboard, http://localhost:3000) pointed at a proxy booted with--num_workers 2andstore_model_in_db: truefrom each side. Steps to reproduce:auto-databricks-uiand pick the "OSS Classifier" classifier typedatabricks-openjev-qwen35-4bcheap, Complex and Reasoning tiersstrong, Default modelcheap, then "Test Routing" with a lock-free ring buffer prompt: tier COMPLEX, decided by the OSS classifier, classifier modeldatabricks/databricks-openjev-qwen35-4b, confidence 79.2%, routed tostrongType
🆕 New Feature
Caveats (if any)
Low
databricks/databricks-openjev-qwen35-4bentry carries zero rates because Databricks bills the endpoint in DBUs and published no per-token price as of 2026-10-07 (its supported-models page is the entry's source). A deployment'smodel_infoprices set the decisions cost (verified live: 0.000238 with prices set) and adatabricks/<endpoint>price in a custom cost map prices the classifier; a guessed DBU conversion would pin a number nobody ownsdatabricks/*wildcard chat deployment now serves/v1/decisions: a missing endpoint or an over-long name answers Databricks's 404, and a chat, embeddings, or agent endpoint answers 400 with Databricks's own message, where the base answered 400 unknown provider. Backward incompatible on purpose, so one wildcard serves every endpoint; a per-endpoint allowlist would be new config nobody asked fordatabricks-openjev-qwen35-4b(a custom serving endpoint wrapping OpenJev) has no catalog entry, so/healthprobes it as chat and Lens hides it until its deployment setsmodel_info: {mode: evaluation}. The catalog keys Databricks's own system endpoint, and guessing the mode from an arbitrary endpoint name would misclassify chat endpointsapi_key, thenDATABRICKS_API_KEY, thenDATABRICKS_TOKEN); the OAuth M2M and databricks-sdk auth that chat deployments accept does not apply. OAuth for decisions is its own change against the shared Databricks auth helper/model/newor the dashboard answers 400no healthy deploymentsuntil the next DB poll loads it, seconds to a few minutes. Pre-existing for every DB-added deploymentdbrx-instruct,bge-large-en,gte-large-en,llama-2-70b-chat,meta-llama-3.1-70b-instruct) would price a decision with that entry through the shared Databricks prefix stripping. Pre-existing for chat deployments, and no ai_decide endpoint carries those namesmisc / Run testsandproxy-infra-root / Run tests(Unit Tests workflow, not ruleset-required) are red at the tip ontest_every_redis_producer_declares_a_key_familyandtest_gateway_plus_backend_covers_full_app, the same two failures as main's latest completed run https://github.com/BerriAI/litellm/actions/runs/37716406166. This PR touches none of the files those tests read, so the fix belongs on main/v1/systemone; the PR landing second renames one test route and the docs. Nothing to change here until it landsapi_url; the PR landing second merges one small_request_urlhunk. Nothing to change here until it lands/v1/decisionskeeps Databricks's ownmodelstring in the body; proxy headers and spend logs carrydatabricks/<endpoint>. Rewriting the body'smodelwould change the provider's wire format for every caller to fix a cosmetic mismatch/invocationsroute is, since it is the route Databricks documents for ai_decide endpoints and the one the customer's workspace exposes. The gateway route would need its own auth and body shape for a path nobody asked forFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
be80d3e passes /live-pr-risk
f875922 passes /live-pr-risk
5cfd0b2 passes /live-pr-risk