Skip to content

feat(decisions): add Databricks ai_decide as a /v1/decisions provider and auto-router decider - #45200

Merged
kerrylu-berri merged 11 commits into
mainfrom
litellm_databricks_ai_decide_decisions
Oct 9, 2026
Merged

kerrylu-berri merged 11 commits into
mainfrom
litellm_databricks_ai_decide_decisions

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • /v1/decisions refused databricks/<endpoint> as an unknown provider
  • The auto router classifier only accepted provider: jev, laya, or bespoke
  • A customer serving OpenJev (ai_decide) on Databricks could use neither

How it solves it:

  • New Decisions provider databricks, posting to {DATABRICKS_API_BASE}/<endpoint>/invocations
  • Keyed by DATABRICKS_API_KEY, falling back to DATABRICKS_TOKEN
  • opensource_classifier_config.provider: databricks for the auto router, model = the serving endpoint name
  • Classifier logs and cost rows carry databricks/<endpoint>, not Databricks's internal /mosaicml/local_model
  • /v1/decisions returns the Databricks body unchanged, model included
  • A databricks/<name> that is not a bare serving endpoint name (a slash, .., a query string, a space) answers 400 must be the bare endpoint name instead of reaching Databricks
  • Admin UI: Add Auto Router -> OSS Classifier offers a Databricks radio, and Classifier tuning takes the serving endpoint name
  • A databricks/databricks-openjev-qwen35-4b cost-map entry with mode: evaluation at a zero rate (Databricks publishes no per-token OpenJev price), so /health probes the decider through /v1/decisions and Lens lists it with no model_info needed; a deployment's model_info prices set the billed cost

User 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

  1. The admin adds model: databricks/databricks-openjev-qwen35-4b to model_list plus an auto_router/complexity_router deployment with opensource_classifier_config.provider: databricks, then starts the proxy
  2. The proxy log reports the auto router deployment refused: opensource_classifier_config.provider Input should be 'jev', 'laya' or 'bespoke', and GET http://localhost:4000/v1/models lists no auto
  3. They send POST http://localhost:4000/v1/decisions with "model": "databricks-decider", a state, and three questions
  4. They get 400 Unknown Decisions provider 'databricks'. Supported providers: perplexity, typesafe, openrouter, cloudflare, strands_decider
  5. POST http://localhost:4000/v1/chat/completions with "model": "auto" returns 400 There are no healthy deployments for this model, the same on /v1/responses and /v1/messages

After: the same config boots clean, decisions come back from the Databricks endpoint, and the auto router splits prompts across tiers

  1. The admin adds the same model_list entries and starts the proxy
  2. The proxy log is clean and GET http://localhost:4000/v1/models lists auto next to databricks-decider
  3. They send the same POST http://localhost:4000/v1/decisions and get 200 with an answer per question (is_billing, intent: duplicate_charge, urgency) and x-litellm-model-name: databricks/databricks-openjev-qwen35-4b
  4. POST http://localhost:4000/v1/chat/completions with "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/messages
  5. The same route with a lock-free ring buffer design question is answered by the COMPLEX tier (x-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

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. 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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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 2 from its own worktree and venv, on random ports: Before from the merge base 9e20264607, After from the tip 5cfd0b24aa. Both proxies run with LITELLM_LOCAL_MODEL_COST_MAP=True so 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 OpenAI

model_list:
  - model_name: databricks-decider
    litellm_params:
      model: databricks/databricks-openjev-qwen35-4b
      api_base: os.environ/DATABRICKS_API_BASE
      api_key: os.environ/DATABRICKS_TOKEN
  - model_name: typesafe-decider
    litellm_params:
      model: typesafe/jev-latest
      api_key: os.environ/TYPESAFE_API_KEY
  - model_name: cheap
    litellm_params:
      model: openai/gpt-5.6-luna
      api_key: os.environ/OPENAI_API_KEY
  - model_name: strong
    litellm_params:
      model: openai/gpt-5.6
      api_key: os.environ/OPENAI_API_KEY
  - model_name: auto
    litellm_params:
      model: auto_router/complexity_router
      complexity_router_default_model: cheap
      complexity_router_config:
        tiers:
          SIMPLE: cheap
          COMPLEX: strong
        classifier_type: oss_classifier
        opensource_classifier_config:
          provider: databricks
          model: databricks-openjev-qwen35-4b
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY

decisions_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

curl -s $PROXY/v1/models -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c "[.data[].id] | sort"
["cheap","databricks-decider","strong","typesafe-decider"]

# proxy stdout at boot:
16:20:57 - LiteLLM Router:ERROR: router.py:9430 - Error creating deployment: 1 validation error for ComplexityRouterConfig
opensource_classifier_config.provider
  Input should be 'jev', 'laya' or 'bespoke' [type=literal_error, input_value='databricks', input_type=str]

/v1/decisions with a databricks/ deployment

curl -s -i $PROXY/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d @decisions_body.json | grep -E "^HTTP|^x-litellm-model-name|^x-litellm-model-api-base|^\{"
HTTP/1.1 400 Bad Request
{"error":{"message":"litellm.BadRequestError: Unknown Decisions provider 'databricks'. Supported providers: perplexity, typesafe, openrouter, cloudflare, strands_decider\n\nLiteLLM: model group 'databricks-decider' failed with the error above. No fallback was attempted.","type":"invalid_request_error","param":null,"code":"400"}}

/v1/decisions control: the TypeSafe deployment, same body

curl -s $PROXY/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(jq -c ".model=\"typesafe-decider\"" decisions_body.json)" | jq -c "{model, intent: .answers.intent.choice, urgency: .answers.urgency.score, error: .error.message}"
{"model":"jev-1.13.0","intent":"duplicate_charge","urgency":1.16,"error":null}

auto router (provider: databricks), easy prompt on /v1/chat/completions

curl -s -D headers.txt $PROXY/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"messages\":[{\"role\":\"user\",\"content\":\"What is the capital of France? Answer in one word.\"}]}" | jq -c "{model, content: .choices[0].message.content, error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:" headers.txt | tr -d "
"
{"model":null,"content":null,"error":"litellm.BadRequestError: You passed in model=auto. There are no healthy deployments for this model\n\nLiteLLM: model group 'auto' failed with the error above. No fallback was attempted."}
x-litellm-response-cost: 0

auto router (provider: databricks), hard prompt on /v1/chat/completions

curl -s -D headers.txt $PROXY/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"messages\":[{\"role\":\"user\",\"content\":\"Design a lock-free multi-producer multi-consumer ring buffer for a 64-core NUMA system. Prove its linearizability, analyze false sharing under contention, and give the memory-ordering fences needed on ARMv9 versus x86-TSO. Keep it under 150 words.\"}]}" | jq -c "{model, content: .choices[0].message.content[:80], error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:" headers.txt | tr -d "
"
{"model":null,"content":null,"error":"litellm.BadRequestError: You passed in model=auto. There are no healthy deployments for this model\n\nLiteLLM: model group 'auto' failed with the error above. No fallback was attempted."}
x-litellm-response-cost: 0

auto router, easy prompt on /v1/responses

curl -s -D headers.txt $PROXY/v1/responses -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"input\":\"What is 2+2? Answer with just the number.\"}" | jq -c "{model, output_text: [.output[]?.content[]?.text] | join(\"\"), error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:" headers.txt | tr -d "
"
{"model":null,"output_text":"","error":"litellm.BadRequestError: You passed in model=auto. There are no healthy deployments for this model\n\nLiteLLM: model group 'auto' failed with the error above. No fallback was attempted."}
x-litellm-response-cost: 0

auto router, easy prompt on /v1/messages

curl -s -D headers.txt $PROXY/v1/messages -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"max_tokens\":20,\"messages\":[{\"role\":\"user\",\"content\":\"Name one primary color. One word.\"}]}" | jq -c "{model, text: [.content[]?.text] | join(\"\"), error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:" headers.txt | tr -d "
"
{"model":null,"text":"","error":"litellm.BadRequestError: You passed in model=auto. There are no healthy deployments for this model\n\nLiteLLM: model group 'auto' failed with the error above. No fallback was attempted."}
x-litellm-response-cost: 0

/model/info for the decider deployment, no model_info in the config

curl -s $PROXY/model/info -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c ".data[] | select(.model_name==\"databricks-decider\") | {model_name, model: .litellm_params.model, mode: .model_info.mode, input_cost_per_token: .model_info.input_cost_per_token, output_cost_per_token: .model_info.output_cost_per_token}"
{"model_name":"databricks-decider","model":"databricks/databricks-openjev-qwen35-4b","mode":null,"input_cost_per_token":null,"output_cost_per_token":null}

/health for the decider deployment

curl -s $PROXY/health -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c "{healthy: [.healthy_endpoints[] | select(.model | startswith(\"databricks\")) | {model, api_base}], unhealthy: [.unhealthy_endpoints[] | select(.model | startswith(\"databricks\")) | {model, api_base, exception_status, error: .error[:160]}]}"
{"healthy":[],"unhealthy":[{"model":"databricks/databricks-openjev-qwen35-4b","api_base":"https://<workspace>.azuredatabricks.net/serving-endpoints","exception_status":400,"error":"litellm.BadRequestError: DatabricksException - {\"error_code\":\"BAD_REQUEST\",\"message\":\"BAD_REQUEST: Invalid endpoint type: chat/completions is not supported by q"}]}

After (5cfd0b2)

The model list after boot

curl -s $PROXY/v1/models -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c "[.data[].id] | sort"
["auto","cheap","databricks-decider","strong","typesafe-decider"]

/v1/decisions with a databricks/ deployment

curl -s -i $PROXY/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d @decisions_body.json | grep -E "^HTTP|^x-litellm-model-name|^x-litellm-model-api-base|^\{" | tr -d "\r"
HTTP/1.1 200 OK
x-litellm-model-name: databricks/databricks-openjev-qwen35-4b
x-litellm-model-api-base: https://<workspace>.azuredatabricks.net/serving-endpoints
{"model":"/mosaicml/local_model","answers":{"is_billing":{"type":"noul","noul":0.9796676459153025},"intent":{"type":"choice","choice":"duplicate_charge","confidence":0.9652768882846836,"probabilities":{"refund":0.022973337505349804,"duplicate_charge":0.9768512588564555,"other":0.00017540363819449625}},"urgency":{"type":"score","score":1.9016174776422046,"confidence":0.8524262164633067,"legend":{"0":"Can wait","1":"Needs attention soon","2":"Urgent"},"probabilities":{"0":0.006936897169280873,"1":0.08450872801923372,"2":0.9085543748114854}}},"usage":{"input_tokens":238,"output_tokens":0}}

/v1/decisions control: the TypeSafe deployment, same body

curl -s $PROXY/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(jq -c ".model=\"typesafe-decider\"" decisions_body.json)" | jq -c "{model, intent: .answers.intent.choice, urgency: .answers.urgency.score, error: .error.message}"
{"model":"jev-1.13.0","intent":"duplicate_charge","urgency":1.16,"error":null}

auto router (provider: databricks), easy prompt on /v1/chat/completions

curl -s -D headers.txt $PROXY/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"messages\":[{\"role\":\"user\",\"content\":\"What is the capital of France? Answer in one word.\"}]}" | jq -c "{model, content: .choices[0].message.content, error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:|^x-litellm-classifier-cost" headers.txt | tr -d "\r"
{"model":"auto","content":"Paris","error":null}
x-litellm-model-name: openai/gpt-5.6-luna
x-litellm-response-cost: 8.4e-06
x-litellm-classifier-cost: 0.0

auto router (provider: databricks), hard prompt on /v1/chat/completions

curl -s -D headers.txt $PROXY/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"messages\":[{\"role\":\"user\",\"content\":\"Design a lock-free multi-producer multi-consumer ring buffer for a 64-core NUMA system. Prove its linearizability, analyze false sharing under contention, and give the memory-ordering fences needed on ARMv9 versus x86-TSO. Keep it under 150 words.\"}]}" | jq -c "{model, content: .choices[0].message.content[:80], error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:|^x-litellm-classifier-cost" headers.txt | tr -d "\r"
{"model":"auto","content":"Use LCRQ: linked, fixed-size power-of-two rings. Each cache-line-aligned slot is","error":null}
x-litellm-model-name: openai/gpt-5.6
x-litellm-response-cost: 0.053304000000000004
x-litellm-classifier-cost: 0.0

auto router, easy prompt on /v1/responses

curl -s -D headers.txt $PROXY/v1/responses -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"input\":\"What is 2+2? Answer with just the number.\"}" | jq -c "{model, output_text: [.output[]?.content[]?.text] | join(\"\"), error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:|^x-litellm-classifier-cost" headers.txt | tr -d "\r"
{"model":"auto","output_text":"4","error":null}
x-litellm-model-name: openai/gpt-5.6-luna
x-litellm-response-cost: 9.800000000000001e-06
x-litellm-classifier-cost: 0.0

auto router, easy prompt on /v1/messages

curl -s -D headers.txt $PROXY/v1/messages -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "{\"model\":\"auto\",\"max_tokens\":20,\"messages\":[{\"role\":\"user\",\"content\":\"Name one primary color. One word.\"}]}" | jq -c "{model, text: [.content[]?.text] | join(\"\"), error: .error.message}"; grep -i -E "^x-litellm-model-name|^x-litellm-response-cost:|^x-litellm-classifier-cost" headers.txt | tr -d "\r"
{"model":"auto","text":"Blue","error":null}
x-litellm-model-name: openai/gpt-5.6-luna
x-litellm-response-cost: 8.8e-06
x-litellm-classifier-cost: 0.0

/model/info for the decider deployment, no model_info in the config

curl -s $PROXY/model/info -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c ".data[] | select(.model_name==\"databricks-decider\") | {model_name, model: .litellm_params.model, mode: .model_info.mode, input_cost_per_token: .model_info.input_cost_per_token, output_cost_per_token: .model_info.output_cost_per_token}"
{"model_name":"databricks-decider","model":"databricks/databricks-openjev-qwen35-4b","mode":"evaluation","input_cost_per_token":0.0,"output_cost_per_token":0.0}

/health for the decider deployment

curl -s $PROXY/health -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c "{healthy: [.healthy_endpoints[] | select(.model | startswith(\"databricks\")) | {model, api_base}], unhealthy: [.unhealthy_endpoints[] | select(.model | startswith(\"databricks\")) | {model, api_base, exception_status, error: .error[:160]}]}"
{"healthy":[{"model":"databricks/databricks-openjev-qwen35-4b","api_base":"https://<workspace>.azuredatabricks.net/serving-endpoints"}],"unhealthy":[]}

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 dev in ui/litellm-dashboard, http://localhost:3000) pointed at a proxy booted with --num_workers 2 and store_model_in_db: true from each side. Steps to reproduce:

  1. Open http://localhost:3000/models-and-endpoints/, click the "Auto-Routers" tab, then "Add Auto Router"
  2. Name it auto-databricks-ui and pick the "OSS Classifier" classifier type
  3. Before: the OSS provider row offers Jev, Laya, and Bespoke Nimble only, so a Databricks endpoint cannot be configured from the UI

pr45200-1ccf0eeb59-ui_before_add_auto_router_oss.png

  1. After: a Databricks radio appears

pr45200-1ccf0eeb59-ui_after_oss_provider_radios.png

  1. Under "Advanced settings" -> "Classifier tuning", the Databricks note and the Classifier Model field take the serving endpoint name databricks-openjev-qwen35-4b

pr45200-1ccf0eeb59-ui_after_classifier_tuning_databricks.png

  1. Simple and Medium tiers cheap, Complex and Reasoning tiers strong, Default model cheap, then "Test Routing" with a lock-free ring buffer prompt: tier COMPLEX, decided by the OSS classifier, classifier model databricks/databricks-openjev-qwen35-4b, confidence 79.2%, routed to strong

pr45200-1ccf0eeb59-ui_after_test_routing_databricks.png

  1. "Add Auto Router": the toast reads "Successfully created Auto Router: auto-databricks-ui" and the row shows up after "Refresh models"

pr45200-1ccf0eeb59-ui_after_auto_routers_table.png

  1. The new router serves traffic through the proxy
curl -s -i $PROXY/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"auto-databricks-ui","messages":[{"role":"user","content":"What is the capital of France? Answer in one word."}]}' | grep -E "^HTTP|^x-litellm-(model-name|model-group|complexity-router)|^\{" | tr -d "\r" | jq -R -r 'fromjson? | .choices[0].message.content // .'
HTTP/1.1 200 OK
x-litellm-model-name: openai/gpt-5.6-luna
x-litellm-model-group: auto-databricks-ui
x-litellm-complexity-router-tier: SIMPLE
x-litellm-complexity-router-cause: jev_classifier
Paris
  1. Click the row, then "Edit Auto Router": the Databricks radio and the endpoint name come back filled in; "Save Changes" answers "Auto router configuration updated successfully", and the hard prompt still routes to the COMPLEX tier

pr45200-1ccf0eeb59-ui_after_edit_classifier_tuning_databricks.png

curl -s -i $PROXY/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"auto-databricks-ui","messages":[{"role":"user","content":"Design a lock-free MPMC ring buffer for a 64-core NUMA system and prove linearizability. Under 100 words."}]}' | grep -E "^HTTP|^x-litellm-(model-name|model-group|complexity-router)" | tr -d "\r"
HTTP/1.1 200 OK
x-litellm-model-name: openai/gpt-5.6
x-litellm-model-group: auto-databricks-ui
x-litellm-complexity-router-tier: COMPLEX
x-litellm-complexity-router-cause: jev_classifier

Type

🆕 New Feature

Caveats (if any)

Low

  • Decisions and classifier calls to the OpenJev endpoint cost 0 by the catalog: the databricks/databricks-openjev-qwen35-4b entry 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's model_info prices set the decisions cost (verified live: 0.000238 with prices set) and a databricks/<endpoint> price in a custom cost map prices the classifier; a guessed DBU conversion would pin a number nobody owns
  • A databricks/* 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 for
  • A Databricks decider on an endpoint other than databricks-openjev-qwen35-4b (a custom serving endpoint wrapping OpenJev) has no catalog entry, so /health probes it as chat and Lens hides it until its deployment sets model_info: {mode: evaluation}. The catalog keys Databricks's own system endpoint, and guessing the mode from an arbitrary endpoint name would misclassify chat endpoints
  • Decisions auth is a PAT (api_key, then DATABRICKS_API_KEY, then DATABRICKS_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
  • An auto router added through /model/new or the dashboard answers 400 no healthy deployments until the next DB poll loads it, seconds to a few minutes. Pre-existing for every DB-added deployment
  • A serving endpoint named like a legacy cost-map entry (dbrx-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 names
  • misc / Run tests and proxy-infra-root / Run tests (Unit Tests workflow, not ruleset-required) are red at the tip on test_every_redis_producer_declares_a_key_family and test_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
  • feat(decisions): serve System One format at /v1/systemone and OpenAI format at /v1/decisions #45184 moves the System One body to /v1/systemone; the PR landing second renames one test route and the docs. Nothing to change here until it lands
  • feat(complexity_router): allow exact Jev classifier api_url for Cloudflare Clef #44445 adds an exact classifier api_url; the PR landing second merges one small _request_url hunk. Nothing to change here until it lands
  • /v1/decisions keeps Databricks's own model string in the body; proxy headers and spend logs carry databricks/<endpoint>. Rewriting the body's model would change the provider's wire format for every caller to fix a cosmetic mismatch
  • The Unity Catalog AI Gateway route is not used; the serving endpoint's /invocations route 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 for

Final 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

@devin-ai-integration

devin-ai-integration Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86364% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...itellm/proxy/common_utils/registry_read_through.py 94.73% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Adds Databricks as a decisions provider and classifier option.

The PR appears safe to merge, with no new blocking issues found

What we checked:

  • Classifier costs stay unchanged: Both the classifier's reported cost and its logged cost use input and output token prices only

Summary

Adds Databricks serving endpoints to /v1/decisions, the auto-router classifier, and the dashboard. Changes since the previous review only add zero cache prices to the matching entries in both catalogs

  • No new actionable issues found
  • Both earlier threads were checked. Endpoint names are validated before URL construction, and credential lookup and response rewriting now live in the provider adapter
  • mateo-berri explicitly accepts zero catalog pricing, wildcard decisions support, PAT-only auth, custom endpoints needing mode: evaluation, existing deployment-load delays and legacy price-name collisions, and retaining Databricks's response model

Reviews (6) · Last reviewed commit: "fix(cost-map): declare zero cache rates ..." · Reviewed by Greptile

Comment thread litellm/llms/databricks/decisions/transformation.py Outdated
Comment thread litellm/router_strategy/complexity_router/complexity_router.py Outdated
@codspeed

codspeed Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_databricks_ai_decide_decisions (3acae4b) with main (5b8f0a4)

Open in CodSpeed

…ors and keep the provider wiring under llms/
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

…Databricks to the dashboard's OSS classifier editor
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

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.

✅ 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.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

…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.
kerrylu-berri
kerrylu-berri previously approved these changes Oct 9, 2026
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.

2 participants