Skip to content

fix(decisions)!: refuse safety_identifier on providers that cannot take it unless drop_params drops it - #44955

Merged
mateo-berri merged 22 commits into
mainfrom
litellm_decisions_provider_config
Oct 10, 2026
Merged

mateo-berri merged 22 commits into
mainfrom
litellm_decisions_provider_config

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Decisions requests drop safety_identifier silently on System One providers
  • The caller believes the provider got it, but it never left the gateway
  • Every other route answers 400 for an unsupported param unless drop_params is on

How it solves it:

  • System One providers answer 400 naming the param
  • That covers Perplexity, TypeSafe, OpenRouter, StrandsDecider, Cloudflare, hosted vLLM, Databricks and Azure AI
  • Both body shapes, input on /v1/decisions and state on /v1/systemone
  • drop_params from settings, the deployment, the body or the SDK drops it instead
  • A non-string identifier answers 400, or is dropped under drop_params
  • OpenAI keeps the identifier on the wire from either body shape
  • Wire tests for all nine providers, and translation cases split by route

Intentional product change: a decisions request with safety_identifier to a System One provider without drop_params changes from 200 with the identifier dropped silently to 400, on /v1/decisions, /v1/systemone and the SDK. drop_params: true brings the 200 back with the identifier dropped

Breaking change

Anyone who sends safety_identifier to a System One deployment (Perplexity, TypeSafe, OpenRouter, StrandsDecider, Cloudflare, hosted vLLM, Databricks, or Microsoft-Decision-1 on Azure AI) without drop_params is affected. Those requests on /v1/decisions, /v1/systemone and litellm.decisions() now get 400 instead of 200. Set drop_params: true on the deployment, under litellm_settings, in the request body, or on litellm.drop_params to keep getting 200 with the identifier dropped. OpenAI deployments are not affected

User Flow

Before: a developer sends safety_identifier with a decisions request to a TypeSafe deployment, gets 200, and the identifier never leaves the gateway

  1. They add typesafe/jev-1.13.0 to model_list with model_info.mode: evaluation and start the proxy
  2. They send POST http://localhost:4000/v1/decisions with input, a questions array and "safety_identifier": "user-123", and get 200
  3. The same identifier on POST http://localhost:4000/v1/systemone with state and a questions map also gets 200
  4. TypeSafe never saw the identifier from either request, and neither response says so
  5. On an openai/gpt-6-luna deployment, OpenAI gets the identifier from the /v1/decisions body but not from the /v1/systemone one

After: both requests get a 400 naming the param, and drop_params: true brings the 200 back

  1. They add typesafe/jev-1.13.0 to model_list with model_info.mode: evaluation and start the proxy
  2. They send POST http://localhost:4000/v1/decisions with input, a questions array and "safety_identifier": "user-123", and get 400 typesafe does not support parameters: ['safety_identifier'] ... To drop these, set litellm.drop_params=True
  3. The same identifier on POST http://localhost:4000/v1/systemone with state and a questions map gets the same 400
  4. With drop_params: true on the deployment, under litellm_settings, or in the body, both requests get 200 with the identifier dropped
  5. On an openai/gpt-6-luna deployment, OpenAI gets the identifier from both bodies

Linear ticket

Resolves LIT-9282

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: 10080051a5, the PR's tip. Every case below ran at 10080051a5 against the merge base 2fcc780498, and the head proxy's source tree was checked against the commit's tree before the run

Live A/B with the same config and the same requests in the same order, on two proxies booted from the merge base and the tip. Each proxy runs two uvicorn workers on its own Postgres database. A forwarding recorder sits in front of the real TypeSafe, OpenAI, Azure AI (Microsoft-Decision-1) and Databricks APIs on each leg, so every provider call below is real and its wire body is compared base against head. $PROXY is the leg's port and $KEY its master key

Shared setup

config.yaml

model_list:
  - model_name: typesafe-strict
    litellm_params:
      model: typesafe/jev-1.13.0
      api_key: os.environ/TYPESAFE_API_KEY
      api_base: os.environ/WIRE_TYPESAFE
    model_info:
      mode: evaluation
  - model_name: typesafe-dropping
    litellm_params:
      model: typesafe/jev-1.13.0
      api_key: os.environ/TYPESAFE_API_KEY
      api_base: os.environ/WIRE_TYPESAFE
      drop_params: true
    model_info:
      mode: evaluation
  - model_name: openai-strict
    litellm_params:
      model: openai/gpt-6-luna
      api_key: os.environ/OPENAI_API_KEY
      api_base: os.environ/WIRE_OPENAI
    model_info:
      mode: evaluation
  - model_name: gpt-5-mini
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
  - model_name: azure-strict
    litellm_params:
      model: azure_ai/decision-1
      api_key: os.environ/AZ_DECISION_KEY
      api_base: os.environ/WIRE_AZURE
    model_info:
      mode: evaluation
      base_model: azure_ai/Microsoft-Decision-1
  - model_name: azure-dropping
    litellm_params:
      model: azure_ai/decision-1
      api_key: os.environ/AZ_DECISION_KEY
      api_base: os.environ/WIRE_AZURE
      drop_params: true
    model_info:
      mode: evaluation
      base_model: azure_ai/Microsoft-Decision-1
  - model_name: databricks-strict
    litellm_params:
      model: databricks/databricks-openjev-qwen35-4b
      api_key: os.environ/DATABRICKS_API_KEY
      api_base: os.environ/WIRE_DATABRICKS
    model_info:
      mode: evaluation
  - model_name: databricks-dropping
    litellm_params:
      model: databricks/databricks-openjev-qwen35-4b
      api_key: os.environ/DATABRICKS_API_KEY
      api_base: os.environ/WIRE_DATABRICKS
      drop_params: true
    model_info:
      mode: evaluation
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY

body.json, the OpenAI shape for /v1/decisions

{
  "model": "typesafe/jev-1.13.0",
  "input": "Ticket (billing): The export job hangs at 99% and never finishes",
  "questions": [
    {"type": "predicate", "name": "defect", "instructions": "Is this a defect?"},
    {"type": "choice", "name": "severity", "instructions": "How severe is it?",
     "choices": [{"value": "low", "description": "Cosmetic"}, {"value": "high", "description": "Blocks the user"}]},
    {"type": "score", "name": "confidence", "instructions": "How confident are you?",
     "levels": [{"label": "unsure", "description": "Not sure"}, {"label": "sure", "description": "Certain"}]}
  ]
}

jev_body.json, the System One shape for /v1/systemone

{
  "model": "typesafe-strict",
  "state": "Ticket (billing): The export job hangs at 99% and never finishes",
  "questions": {
    "defect": {"type": "noul", "instructions": "Is this a defect?"},
    "severity": {"type": "choice", "instructions": "How severe is it?", "criteria": {"low": "Cosmetic", "high": "Blocks the user"}},
    "confidence": {"type": "score", "instructions": "How confident are you?", "criteria": ["Not sure", "Certain"]}
  }
}

Every decisions call below is one of these two curls, with the model and the jq edit each step names

curl -sS -X POST $PROXY/v1/decisions -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d "$(jq --arg m "$MODEL" '.model=$m | <edit>' body.json)"
curl -sS -X POST $PROXY/v1/systemone -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d "$(jq --arg m "$MODEL" '.model=$m | <edit>' jev_body.json)"

Before (2fcc780)

TypeSafe with safety_identifier on /v1/decisions

  1. typesafe-strict with .safety_identifier="end-user-7"
  2. HTTP 200 {"model":"jev-1.13.0","answers":[{"type":"predicate","name":"defect","probability":0.87},{"type":"choice","name":"severity","choice":"high",...},{"type":"score","name":"confidence","score":0.47,...}],"usage":{"input_tokens":373,...}}
  3. The same request again, with .safety_identifier="", with a 5 KB identifier, and through the /decisions alias all return HTTP 200 with answers
  4. TypeSafe's recorder logged 25 POST bodies on this leg besides the health probes (16 from the proxy and 9 from the SDK calls below), and none of them carries safety_identifier

TypeSafe with safety_identifier on /v1/systemone

  1. typesafe-strict with .safety_identifier="end-user-7" on /v1/systemone, then the same through the /systemone alias
  2. HTTP 200 both times, {"model":"jev-1.13.0","answers":{"defect":{"type":"noul","noul":0.87},"severity":{"type":"choice","choice":"high",...},"confidence":{"type":"score","score":0.47,...}},"usage":{"input_tokens":368,"output_tokens":62}}

drop_params on the body, the deployment and litellm_settings

  1. typesafe-strict with .safety_identifier="end-user-7" | .drop_params=true returns HTTP 200 with answers on both routes
  2. typesafe-dropping with .safety_identifier="end-user-7" returns HTTP 200 with answers on both routes
  3. litellm_settings with drop_params: true was not run on this leg, which already answers 200 with no drop_params at all

Non-string safety_identifier

  1. typesafe-strict with .safety_identifier=7 or .safety_identifier=["end-user-7"] on /v1/decisions returns HTTP 400
litellm.BadRequestError: Invalid Decisions request: 1 validation error for OpenAIDecisionRequestBody
safety_identifier
  Input should be a valid string [type=string_type, input_value=7, input_type=int]
  1. .safety_identifier=7 on typesafe-dropping, and on typesafe-strict with .drop_params=true, return the same HTTP 400 on /v1/decisions
  2. .safety_identifier=7 on /v1/systemone returns HTTP 200 with answers on typesafe-strict, typesafe-dropping, openai-strict and azure-strict, with the identifier dropped every time

OpenAI with safety_identifier

  1. openai-strict with .safety_identifier="end-user-7" on /v1/decisions, with and without .drop_params=true
  2. HTTP 200 {"model":"gpt-6-luna","answers":[{"type":"predicate","name":"defect","probability":0.84},{"type":"choice","name":"severity","choice":"high",...},{"type":"score","name":"confidence","score":0.18,...}],...}, and both OpenAI wire bodies carry "safety_identifier":"end-user-7"
  3. The same identifier on /v1/systemone returns HTTP 200, and OpenAI's wire body has no safety_identifier

Azure AI and Databricks with safety_identifier

  1. azure-strict with .safety_identifier="end-user-7" on /v1/decisions and on /v1/systemone
  2. HTTP 200 both times, {"model":"microsoft-decision-1","answers":[{"type":"predicate","name":"defect","probability":0.982...},{"type":"choice","name":"severity","choice":"high",...},{"type":"score","name":"confidence","score":0.798...,...}],...}
  3. databricks-strict with the same identifier on both routes returns HTTP 200 both times, {"model":"databricks-openjev-qwen35-4b","answers":[{"type":"predicate","name":"defect","probability":0.651...},...],...}
  4. The body drop_params on azure-strict (both routes) and on databricks-strict (/v1/decisions), and the azure-dropping and databricks-dropping deployments on both routes, return HTTP 200
  5. Besides the health probes, Azure AI's recorder logged 8 POST bodies to /providers/microsoft/v1/systemone and Databricks's logged 6 to /serving-endpoints/databricks-openjev-qwen35-4b/invocations, and none of them carries safety_identifier

Python SDK

  1. In the leg's venv, litellm.decisions(model="typesafe/jev-1.13.0", questions=..., api_key=key, api_base=wire) with input= (OpenAI shape) or state= (System One shape), safety_identifier, and the drop_params source each line names
  2. Output
sdk_plain ok jev-1.13.0 ['defect'] 287 21
sdk_strict_sid ok jev-1.13.0 ['defect'] 287 21
sdk_call_drop_params_sid ok jev-1.13.0 ['defect'] 287 21
sdk_state_strict_sid ok jev-1.13.0 ['defect'] 287 21
sdk_state_strict_sid_int ok jev-1.13.0 ['defect'] 287 21
sdk_state_call_drop_params_sid ok jev-1.13.0 ['defect'] 287 21
sdk_call_drop_params_sid_int BadRequestError 400 litellm.BadRequestError: Invalid Decisions request: 1 validation error for OpenAIDecisionRequestBody safety_identifier   Input should be a valid string [type=string_type,
sdk_state_call_drop_params_sid_int ok jev-1.13.0 ['defect'] 287 21
sdk_global_drop_params_sid ok jev-1.13.0 ['defect'] 287 21
sdk_state_global_drop_params_sid ok jev-1.13.0 ['defect'] 287 21

Spend rows

  1. curl -sS $PROXY/spend/logs -H "Authorization: Bearer $KEY" 12 s after the last request
  2. 48 rows. The decisions success rows are 16 for TypeSafe (9 at 0.000015666 and 7 at 0.000015456), 8 for Azure AI (4 at 0.000003822 and 4 at 0.000004032), 6 for Databricks at 0.0 (the cost map has no price for this serving endpoint) and 5 for gpt-6-luna
  3. The 2 failure rows are the no-key 401 and one non-string identifier at typesafe-strict. The deleted-key 401 row was not written yet when the logs were read

Unchanged surfaces

  1. GET /health returns 200 with all eight deployments healthy, and /health/liveliness and /health/readiness return 200
  2. The OpenAI-shape body on POST /v1/systemone returns HTTP 400 2 validation errors for DecisionsRequestBody state Field required [type=missing, input_value={'model': 'typesafe-stric...entifier': 'end-user-7'}, input_type=dict] ...
  3. No Authorization header returns HTTP 401 Authentication Error, No api key passed in.
  4. POST /v1/chat/completions on gpt-5-mini returns 200, streaming returns chunks=3 done=1, and POST /v1/messages returns 200
  5. POST /key/generate scoped to typesafe-dropping returns 200, a decisions request with safety_identifier on that key returns 200, GET /key/info and POST /key/delete return 200, and the deleted key gets 401

After (1008005)

TypeSafe with safety_identifier on /v1/decisions

  1. typesafe-strict with .safety_identifier="end-user-7"
  2. HTTP 400
{"error":{"message":"litellm.UnsupportedParamsError: typesafe does not support parameters: ['safety_identifier'], for model=typesafe/jev-1.13.0. To drop these, set `litellm.drop_params=True` or for proxy:\n\n`litellm_settings:\n drop_params: true`\n\n\nLiteLLM: model group 'typesafe-strict' failed with the error above. No fallback was attempted.","type":"invalid_request_error","param":null,"code":"400"}}
  1. The same request again, with .safety_identifier="", with a 5 KB identifier, and through the /decisions alias all return the same HTTP 400
  2. TypeSafe's recorder logged 21 POST bodies on this leg besides the health probes (10 from the proxy, 7 from the SDK calls below and 4 from the litellm_settings restart), and none of them carries safety_identifier. The refused requests never reached TypeSafe

TypeSafe with safety_identifier on /v1/systemone

  1. typesafe-strict with .safety_identifier="end-user-7" on /v1/systemone, then the same through the /systemone alias
  2. The same HTTP 400 UnsupportedParamsError both times

drop_params on the body, the deployment and litellm_settings

  1. typesafe-strict with .safety_identifier="end-user-7" | .drop_params=true returns HTTP 200 with answers on both routes
  2. typesafe-dropping with .safety_identifier="end-user-7" returns HTTP 200 with answers on both routes
  3. The head proxy restarted with litellm_settings: {drop_params: true} added to config.yaml. typesafe-strict with .safety_identifier="end-user-7" and with .safety_identifier=7 returns HTTP 200 on both routes ({"model":"jev-1.13.0","answers":[defect, severity, confidence],...}), and all four TypeSafe wire bodies carry only model, questions and state

Non-string safety_identifier

  1. typesafe-strict with .safety_identifier=7 or .safety_identifier=["end-user-7"] on /v1/decisions returns HTTP 400
litellm.BadRequestError: Invalid Decisions request: 1 validation error for safety_identifier
  Input should be a valid string [type=string_type, input_value=7, input_type=int]
    For further information visit https://errors.pydantic.dev/2.13/v/string_type

LiteLLM: model group 'typesafe-strict' failed with the error above. No fallback was attempted.
  1. .safety_identifier=7 on typesafe-dropping, and on typesafe-strict with .drop_params=true, return HTTP 200 with answers on /v1/decisions, and the TypeSafe wire body has no safety_identifier
  2. .safety_identifier=7 on /v1/systemone returns the same HTTP 400 on typesafe-strict, openai-strict and azure-strict, each naming its own model group, and HTTP 200 with the identifier dropped on typesafe-dropping

OpenAI with safety_identifier

  1. openai-strict with .safety_identifier="end-user-7" on /v1/decisions, with and without .drop_params=true
  2. HTTP 200 with the same gpt-6-luna answers as before, and both OpenAI wire bodies carry "safety_identifier":"end-user-7"
  3. The same identifier on /v1/systemone returns HTTP 200, and OpenAI's wire body now carries "safety_identifier":"end-user-7" too

Azure AI and Databricks with safety_identifier

  1. azure-strict with .safety_identifier="end-user-7" on /v1/decisions and on /v1/systemone
  2. HTTP 400 both times
{"error":{"message":"litellm.UnsupportedParamsError: azure_ai does not support parameters: ['safety_identifier'], for model=azure_ai/decision-1. To drop these, set `litellm.drop_params=True` or for proxy:\n\n`litellm_settings:\n drop_params: true`\n\n\nLiteLLM: model group 'azure-strict' failed with the error above. No fallback was attempted.","type":"invalid_request_error","param":null,"code":"400"}}
  1. databricks-strict with the same identifier on both routes returns the same HTTP 400, naming databricks, model=databricks/databricks-openjev-qwen35-4b and model group databricks-strict
  2. The body drop_params on azure-strict (both routes) and on databricks-strict (/v1/decisions), and the azure-dropping and databricks-dropping deployments on both routes, return HTTP 200 as on base
  3. Besides the health probes, Azure AI's recorder logged 5 POST bodies and Databricks's logged 4, one fewer for each refused request, and none of them carries safety_identifier

Python SDK

  1. The same calls in the head leg's venv
  2. Output
sdk_plain ok jev-1.13.0 ['defect'] 287 21
sdk_strict_sid UnsupportedParamsError 400 litellm.UnsupportedParamsError: typesafe does not support parameters: ['safety_identifier'], for model=typesafe/jev-1.13.0. To drop these, set `litellm.drop_params=True`
sdk_call_drop_params_sid ok jev-1.13.0 ['defect'] 287 21
sdk_state_strict_sid UnsupportedParamsError 400 litellm.UnsupportedParamsError: typesafe does not support parameters: ['safety_identifier'], for model=typesafe/jev-1.13.0. To drop these, set `litellm.drop_params=True`
sdk_state_strict_sid_int BadRequestError 400 litellm.BadRequestError: Invalid Decisions request: 1 validation error for safety_identifier   Input should be a valid string [type=string_type, input_value=7, input_type
sdk_state_call_drop_params_sid ok jev-1.13.0 ['defect'] 287 21
sdk_call_drop_params_sid_int ok jev-1.13.0 ['defect'] 287 21
sdk_state_call_drop_params_sid_int ok jev-1.13.0 ['defect'] 287 21
sdk_global_drop_params_sid ok jev-1.13.0 ['defect'] 287 21
sdk_state_global_drop_params_sid ok jev-1.13.0 ['defect'] 287 21

Spend rows

  1. curl -sS $PROXY/spend/logs -H "Authorization: Bearer $KEY" 12 s after the last request
  2. 53 rows. The decisions success rows are 10 for TypeSafe (6 at 0.000015666 and 4 at 0.000015456), 5 for Azure AI (2 at 0.000003822 and 3 at 0.000004032), 4 for Databricks at 0.0 and 4 for gpt-6-luna. That is one fewer for every request the head now refuses, plus one more TypeSafe row for each non-string identifier it now drops
  3. The 19 failure rows are one per refused request at spend 0, each naming its model group (11 typesafe-strict, 3 azure-strict, 2 databricks-strict and 1 openai-strict), plus the no-key and deleted-key 401s

Unchanged surfaces

  1. GET /health returns 200 with all eight deployments healthy, and /health/liveliness and /health/readiness return 200
  2. The OpenAI-shape body on POST /v1/systemone returns HTTP 400 with the same two validation errors, and its echoed input is now printed as mappingproxy({'model': 't...iption': 'Certain'}]}]}) (Low caveat below)
  3. No Authorization header returns HTTP 401 Authentication Error, No api key passed in.
  4. POST /v1/chat/completions on gpt-5-mini returns 200, streaming returns chunks=3 done=1, and POST /v1/messages returns 200
  5. POST /key/generate scoped to typesafe-dropping returns 200, a decisions request with safety_identifier on that key returns 200, GET /key/info and POST /key/delete return 200, and the deleted key gets 401

Both legs' OpenAI recorders also log two GET /v1/models calls per proxy boot, which is a rig artifact, and the head leg booted twice because of the litellm_settings case

/live-pr-risk

Breaking

None. Every dependent path that answered on the base leg answers the same on the head leg, except the requests this PR names below

Backward incompatible

POST /v1/decisions, /decisions, /v1/systemone, /systemone and litellm.decisions() with safety_identifier on a System One deployment answer 400 (base answers 200 with the identifier dropped) unless drop_params is set on the deployment, under litellm_settings, in the body, or on litellm.drop_params. All four were driven live above, and the 400 was driven live on TypeSafe, Azure AI and Databricks. A non-string identifier follows the same rule, so a System One-shape body with one changes from 200 to 400 and an OpenAI-shape body with drop_params changes from 400 to 200. That 400 now names safety_identifier and the model group instead of OpenAIDecisionRequestBody. Each refused request now writes a failure spend row at spend 0 naming its model group. This ships under @mateo-berri's standing drop_params rule (2026-10-02, decided on #40775), which says an unsupported or malformed param answers 400 unless drop_params drops it, the convention every other route follows. Recorded as the Medium caveats below

Regression risk

Perplexity, OpenRouter, Cloudflare, StrandsDecider and hosted vLLM share the refuse path with TypeSafe, Azure AI and Databricks, and the scripted-upstream wire tests in this PR cover them, but they were not driven live here. Guardrails on /v1/decisions now see a non-string identifier that the route refused before routing on base. LLM Shield's request collector skips non-string values (traced, not driven), and chat routes already pass any JSON value through to guardrails. The Lens signal judge no longer calls decisions in process since #45529, so it is not a caller here

Dependency graph

_request_safety_identifier validates the identifier as a string, or drops a non-string one under drop_params, and _provider_ir_request returns the IR request, the IR request with the identifier dropped, or _UnsupportedSafetyIdentifier, which _prepare_call raises as UnsupportedParamsError before the HTTP call. _prepare_call serves litellm.decisions and litellm.adecisions (verified live, SDK case), Router.adecisions (verified live through the proxy), POST /v1/decisions and /decisions (verified live), POST /v1/systemone and /systemone (verified live), and the /health evaluation probe (verified live on both legs, eight deployments healthy). _fields_checked_before_routing in _process_decisions is shared by both routes and was verified live on both. On /v1/systemone the identifier was already an extra field, so only the echoed error text changed there. BaseDecisionsConfig.supports_safety_identifier is a new attribute that defaults to False. OpenAI overrides it (verified live), and TypeSafe, Azure AI and Databricks inherit it (verified live), as do Perplexity, OpenRouter, Cloudflare, StrandsDecider and hosted vLLM (tested by the wire tests). No UI, config-key, DB, or lockfile change, and no legacy-suite hit for the changed symbols

Not verified

Perplexity, OpenRouter, Cloudflare, StrandsDecider and hosted vLLM live, and a guardrail receiving a non-string identifier live

/audit (round 3 of 3 at e547414)

Round 3 ran at e547414e1d against the merge base 097d018dbf, and every one of its 77 cells passed. 6ed3e65910, 48fd7420ea and 21d083399a changed product code after that round. Then two merges of main (883302e102 and 4c3a6049ab) brought the Databricks and Azure AI decisions providers into this PR's refuse path, and 10080051a5 changed the refusal into a returned value that _prepare_call raises. A fourth round did not run under the three-round cap. The 20 cells those commits and merges added or changed ran head-only at 10080051a5 on the GCE box and passed twice (20 passed in 40.09s, then 20 passed in 41.45s). The base and control lanes were not run again at the tip

tests/integration/providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[azure_ai]
tests/integration/providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[azure_ai]
tests/integration/providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[azure_ai]
tests/integration/providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[databricks]
tests/integration/providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[databricks]
tests/integration/providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[databricks]
tests/integration/providers/test_decisions_openai_format_wire.py::test_a_non_string_safety_identifier_is_refused_as_invalid_unless_the_deployment_drops_params[numeric-openai_format]
tests/integration/providers/test_decisions_openai_format_wire.py::test_a_non_string_safety_identifier_is_refused_as_invalid_unless_the_deployment_drops_params[numeric-system_one_format]
tests/integration/providers/test_decisions_openai_format_wire.py::test_a_non_string_safety_identifier_is_refused_as_invalid_unless_the_deployment_drops_params[list-openai_format]
tests/integration/providers/test_decisions_openai_format_wire.py::test_a_non_string_safety_identifier_is_refused_as_invalid_unless_the_deployment_drops_params[list-system_one_format]
tests/integration/providers/test_decisions_openai_format_wire.py::test_a_system_one_format_safety_identifier_is_refused_unless_the_deployment_drops_params
tests/integration/providers/test_decisions_openai_format_wire.py::test_openai_keeps_a_system_one_format_safety_identifier_on_the_wire
tests/integration/providers/test_decisions_openai_format_wire.py::test_openai_keeps_the_safety_identifier_on_the_wire_without_drop_params
tests/integration/providers/test_decisions_openai_format_wire.py::test_invalid_bodies_are_refused_at_the_gateway_without_an_upstream_call
tests/integration/providers/test_decisions_openai_format_wire.py::test_every_string_safety_identifier_is_refused_or_dropped_like_the_usual_one[empty]
tests/integration/providers/test_decisions_openai_format_wire.py::test_every_string_safety_identifier_is_refused_or_dropped_like_the_usual_one[5kb]
tests/integration/providers/test_decisions_openai_format_wire.py::test_a_request_body_drop_params_drops_the_safety_identifier_like_chat
tests/integration/providers/test_databricks_decisions_wire.py::test_an_openai_format_request_at_v1_decisions_reaches_the_serving_endpoint_as_system_one
tests/integration/providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[azure_ai]
tests/integration/providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[azure_ai]
Round 3 matrix at e547414, 77 cells

Merge base 097d018, head e547414, rig lit9282h on the GCE box (three legs, two workers each, own database, Redis and scripted upstream per leg, CI's Python 3.12), group providers, INTEGRATION_WORKERS=1 and INTEGRATION_PROXY_READY_SECONDS=240 as CI sets them. The lanes ran one after another because every cell takes the shared provider fixture, which binds the host's port 8191 (two lanes cannot hold it at once). The summaries are round-lanes_base.json, round-lanes_ctl.json and round-lanes_head.json

Lane Leg Wall Summary
base base 70 s 41 collected, 33 passed, 8 failed, 0 skipped, pytest 58s
ctl ctl 161 s 36 collected, 36 passed, 0 failed, 0 skipped, pytest 152s
head-run1 head 98 s 41 collected, 41 passed, 0 failed, 0 skipped, pytest 89s
head-run2 head 102 s 41 collected, 41 passed, 0 failed, 0 skipped, pytest 95s
Row Cell base head run1 head run2 ctl Expected Result
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[perplexity] green green green - base green, head green twice PASS
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[typesafe] green green green - base green, head green twice PASS
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[openrouter] green green green - base green, head green twice PASS
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[strands_decider] green green green - base green, head green twice PASS
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[cloudflare] green green green - base green, head green twice PASS
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[hosted_vllm] green green green - base green, head green twice PASS
A1 providers/test_decisions_openai_format_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cost_map[openai] green green green - base green, head green twice PASS
A2 providers/test_decisions_openai_format_wire.py::test_the_unversioned_alias_serves_the_same_request green green green - base green, head green twice PASS
D1 providers/test_decisions_openai_format_wire.py::test_repeated_identical_requests_each_reach_the_upstream_and_are_each_billed green green green - base green, head green twice PASS
A3 providers/test_decisions_openai_format_wire.py::test_sdk_sync_and_async_clients_send_the_same_request green green green - base green, head green twice PASS
D1 providers/test_decisions_openai_format_wire.py::test_gateway_only_fields_stay_at_the_gateway_and_tags_reach_the_spend_log green green green - base green, head green twice PASS
B1 providers/test_decisions_openai_format_wire.py::test_invalid_bodies_are_refused_at_the_gateway_without_an_upstream_call green green green - base green, head green twice PASS
B2 providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[perplexity] green green green - base green, head green twice PASS
B2 providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[typesafe] green green green - base green, head green twice PASS
B2 providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[openrouter] green green green - base green, head green twice PASS
B2 providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[strands_decider] green green green - base green, head green twice PASS
B2 providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[cloudflare] green green green - base green, head green twice PASS
B2 providers/test_decisions_openai_format_wire.py::test_system_one_providers_refuse_images_at_the_gateway_without_an_upstream_call[hosted_vllm] green green green - base green, head green twice PASS
B3 providers/test_decisions_openai_format_wire.py::test_openai_forwards_image_input_in_its_own_message_shape green green green - base green, head green twice PASS
A4 providers/test_decisions_openai_format_wire.py::test_a_message_list_input_reaches_a_system_one_provider_as_its_flattened_text green green green - base green, head green twice PASS
C1 providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[perplexity] red green green - base red, head green twice PASS
C1 providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[typesafe] red green green - base red, head green twice PASS
C1 providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[openrouter] red green green - base red, head green twice PASS
C1 providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[strands_decider] red green green - base red, head green twice PASS
C1 providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[cloudflare] red green green - base red, head green twice PASS
C1 providers/test_decisions_openai_format_wire.py::test_safety_identifier_is_refused_by_system_one_providers_unless_the_deployment_drops_params[hosted_vllm] red green green - base red, head green twice PASS
C2 providers/test_decisions_openai_format_wire.py::test_every_string_safety_identifier_is_refused_or_dropped_like_the_usual_one[empty] red green green - base red, head green twice PASS
C2 providers/test_decisions_openai_format_wire.py::test_every_string_safety_identifier_is_refused_or_dropped_like_the_usual_one[5kb] red green green - base red, head green twice PASS
C5 providers/test_decisions_openai_format_wire.py::test_a_request_body_drop_params_drops_the_safety_identifier_like_chat green green green - base green, head green twice PASS
C3 providers/test_decisions_openai_format_wire.py::test_openai_keeps_the_safety_identifier_on_the_wire_without_drop_params green green green - base green, head green twice PASS
C4 providers/test_decisions_openai_format_wire.py::test_litellm_settings_drop_params_drops_the_safety_identifier_for_a_strict_deployment green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_cloudflare.py::test_decisions_basic_cloudflare[cloudflare/@cf/cloudflare/clef-basic] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_cloudflare.py::test_decisions_basic_cloudflare[cloudflare/@cf/cloudflare/clef-systemone] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_openrouter.py::test_decisions_basic_openrouter[openrouter/typesafe/jev-1.13-basic] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_openrouter.py::test_decisions_basic_openrouter[openrouter/typesafe/jev-1.13-systemone] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_perplexity.py::test_decisions_basic_perplexity[perplexity/pplx-decider-v1-27b-basic] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_perplexity.py::test_decisions_basic_perplexity[perplexity/pplx-decider-v1-27b-systemone] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_strands_decider.py::test_decisions_basic_strands_decider[strands_decider/strands-decider-2B-hobson-v19-basic] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_strands_decider.py::test_decisions_basic_strands_decider[strands_decider/strands-decider-2B-hobson-v19-systemone] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_typesafe.py::test_decisions_basic_typesafe[typesafe/jev-1.13.0-basic] green green green - base green, head green twice PASS
A5 translation/decisions/basic/test_decisions_basic_typesafe.py::test_decisions_basic_typesafe[typesafe/jev-1.13.0-systemone] green green green - base green, head green twice PASS
E1 providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[perplexity] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[typesafe] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[openrouter] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[strands_decider] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[hosted_vllm] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_each_provider_gets_its_own_path_key_and_body_and_is_billed[cloudflare] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_test_connection_evaluation_mode_uses_typesafe_decisions_path - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_repeated_identical_requests_each_reach_the_upstream_and_are_each_billed - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_sdk_sync_and_async_clients_send_the_same_request - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_gateway_only_fields_stay_at_the_gateway_and_tags_reach_the_spend_log - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_invalid_bodies_are_refused_at_the_gateway_without_an_upstream_call - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unknown_model_is_refused_like_chat - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_key_checks_match_chat - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_request_body_api_base_is_refused_like_chat_without_an_upstream_call - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_a_deployment_without_a_key_sends_the_provider_env_key_to_its_configured_api_base - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_a_deployment_opted_into_client_api_base_sends_decisions_and_chat_to_the_body_api_base - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_a_config_pass_through_at_v1_decisions_keeps_answering_and_the_native_api_serves_system_one - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_upstream_errors_keep_their_status_and_log_an_unbilled_failure[401] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_upstream_errors_keep_their_status_and_log_an_unbilled_failure[429] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_upstream_errors_keep_their_status_and_log_an_unbilled_failure[500] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_upstream_success_without_answers_is_a_gateway_side_server_error - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[perplexity] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[typesafe] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[openrouter] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[strands_decider] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[hosted_vllm] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_unreachable_upstream_fails_only_its_own_deployment[cloudflare] - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_an_unreachable_openrouter_deployment_reports_a_connection_error_on_chat_embeddings_and_decisions - - - green ctl green PASS
E1 providers/test_decisions_wire.py::test_sdk_openrouter_connection_failures_raise_a_connection_error - - - green ctl green PASS
B4 providers/test_decisions_wire.py::test_a_deployment_whose_provider_has_no_decisions_support_is_refused_naming_every_supported_provider - - - green ctl green PASS
E1 providers/test_decisions_chaos.py::test_burst_over_both_routes_bills_each_call_once_with_its_own_status - - - green ctl green PASS
E1 providers/test_decisions_chaos.py::test_worker_sigkill_mid_burst_leaves_the_sibling_serving_the_default_model - - - green ctl green PASS
E1 providers/test_decisions_chaos.py::test_upstream_outage_fails_its_calls_and_recovery_on_the_same_port_restores_them - - - green ctl green PASS
E2 translation/decisions/basic/test_decisions_basic_hosted_vllm.py::test_decisions_basic_hosted_vllm[hosted_vllm/Qwen/Qwen3-0.6B-basic] - - - green ctl green PASS
E2 translation/decisions/basic/test_decisions_basic_hosted_vllm.py::test_decisions_basic_hosted_vllm[hosted_vllm/Qwen/Qwen3-0.6B-decisions] - - - green ctl green PASS
E2 translation/decisions/basic/test_decisions_basic_hosted_vllm.py::test_decisions_basic_hosted_vllm[hosted_vllm/Qwen/Qwen3-0.6B-predicate_rejected] - - - green ctl green PASS

Every cell ran at e547414, and the two head runs collected and passed the same selections with no skips and no retries

Type

🐛 Bug Fix

Caveats (if any)

Medium

  • A decisions request with safety_identifier to a System One deployment now answers 400
    • v1.104.2 and v1.105.0-rc.3 answer 200 and drop the identifier silently
    • it covers /v1/decisions, /v1/systemone, their aliases, and litellm.decisions() with input= or state=
    • drop_params: true on the deployment, under litellm_settings, in the body, or on litellm.drop_params brings the 200 back
    • it ranks Medium and not Severe, because the old 200 never carried the identifier to the provider and the 400 is the convention every other route follows
  • A non-string safety_identifier changes outcome on both body shapes
    • a System One-shape body with one goes from 200 to 400
    • an OpenAI-shape body with one under drop_params goes from 400 to 200
    • the 400 now names safety_identifier and the model group instead of OpenAIDecisionRequestBody

Low

  • Audit round 4 did not run under the three-round cap
    • the 20 cells the later commits and merges touched passed head-only at 10080051a5, twice
  • A malformed body's 400 now prints the echoed input as mappingproxy(...) instead of a dict
    • the status and the validation errors are unchanged, and the fix is on a follow-up ticket
  • A refused request now writes a failure spend row at spend 0
    • each row names the model group and the end user
  • strands_decider has no key in the vault, so its wire cells run against the scripted upstream only
    • the 400 fires before any provider call, which the TypeSafe, Azure AI and Databricks legs drove live
  • additional_drop_params: ["safety_identifier"] on a decisions deployment is not honored
    • drop_params: true is the remedy, since on decisions it drops only this one param
  • Perplexity, OpenRouter, Cloudflare and hosted vLLM are covered by the scripted wire cells only
    • they share the refuse path with the four providers the live A/B drove
  • test(decisions): post System One bodies to /v1/systemone in the translation bases #45327 moved the basic translation cases to /v1/systemone, and this PR moves them back
    • this PR keeps the basic case at /v1/decisions and adds a systemone case with the System One body

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

Link to Devin session: https://app.devin.ai/sessions/a7a7d712811643b1a6416531d7d99cfa
Open in Devin Desktop: https://app.devin.ai/desktop/session/a7a7d712811643b1a6416531d7d99cfa?variant=devin
Requested by: @kerry-berri

  • 1008005 passes /live-pr-risk
  • 1008005 passes /audit (round 4 not run under the cap, the 20 cells changed since round 3 passed head-only at 1008005, and the error-text echo ships as a Low caveat with its fix on a follow-up ticket)

Note

Medium Risk
Intentional breaking API change: System One decisions deployments return 400 when callers send safety_identifier without drop_params; OpenAI behavior is preserved and extended for System One bodies.

Overview
Breaking: Decisions requests that include safety_identifier against System One–style providers (default supports_safety_identifier=False) now return 400 UnsupportedParamsError naming the param, instead of 200 with the field dropped silently. This applies to the SDK, /v1/decisions, and /v1/systemone (and aliases). Setting drop_params (global, deployment, request body, or litellm_settings) drops the identifier and restores success, matching chat routes.

The decisions stack validates safety_identifier as an optional string (non-strings are 400 unless dropped), threads it into the IR for System One bodies, and gates upstream transformation via _provider_ir_request. OpenAI sets supports_safety_identifier=True and keeps the field on the wire for both OpenAI- and System One–shaped requests.

The proxy pre-routing body check ignores safety_identifier so it can ride on /v1/systemone without failing schema validation before routing. Wire, unit, and translation tests cover refuse/drop/forward behavior across providers.

Reviewed by Cursor Bugbot for commit 1008005. Bugbot is set up for automated code reviews on this repo. Configure here.

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor Author

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

@CLAassistant

CLAassistant commented Oct 6, 2026 •

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ mateo-berri
❌ kerry


kerry seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge with its documented breaking change.

Summary

Decisions requests now reject unsupported safety_identifier values unless drop_params drops them. OpenAI keeps valid identifiers from either request shape.

  • Decisions calls refuse unsupported safety_identifier values unless you drop them.

Reviews (19) · Last reviewed commit: "refactor(decisions): return the unsuppor..." · Reviewed by Greptile

Comment thread litellm/llms/base_llm/decisions/systemone.py Outdated
Comment thread litellm/llms/base_llm/decisions/systemone.py Outdated
Comment thread litellm/llms/base_llm/decisions/systemone.py Outdated
Comment thread tests/unit/decisions/test_main.py Outdated

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

Stale Bugbot comment from a previous run.

@codspeed

codspeed Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 31 untouched benchmarks


Comparing litellm_decisions_provider_config (1008005) with main (985574f)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (fb547a0) during the generation of this report, so 985574f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.11765% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/decisions/main.py 92.85% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_decisions_provider_config branch from 2fc120b to 5079ae2 Compare October 6, 2026 23:10
@devin-ai-integration
devin-ai-integration Bot added this pull request to stack #44970 October 6, 2026 23:14

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

Stale Bugbot comment from a previous run.

Comment thread litellm/utils.py
Comment thread litellm/decisions/main.py Outdated

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

Stale Bugbot comment from a previous run.

Comment thread litellm/utils.py Outdated

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

Stale Bugbot comment from a previous run.

@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_decisions_provider_config branch from 6e1e349 to ec950e0 Compare October 7, 2026 07:05
Comment thread tests/integration/providers/test_decisions_wire.py Outdated

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

Stale Bugbot comment from a previous 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.

Stale Bugbot comment from a previous 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.

Stale Bugbot comment from a previous 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.

Stale Bugbot comment from a previous run.

@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_decisions_provider_config branch from 309da70 to 3b94243 Compare October 7, 2026 18:48
@devin-ai-integration
devin-ai-integration Bot removed this pull request from stack #44970 October 7, 2026 18:50
@devin-ai-integration
devin-ai-integration Bot added this pull request to stack #45131 October 7, 2026 18:51
@devin-ai-integration devin-ai-integration Bot changed the title feat(decisions): follow the OpenAI Decisions spec and dispatch through provider configs feat(decisions): switch /v1/decisions to the OpenAI Decisions schema Oct 7, 2026

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

Stale Bugbot comment from a previous run.

@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_decisions_provider_config branch from 3b94243 to 6908e4a Compare October 7, 2026 19:11

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

Stale Bugbot comment from a previous run.

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

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.

Stale Bugbot comment from a previous run.

A malformed safety_identifier now follows the drop_params convention in both body shapes: it answers 400 without drop_params and is dropped before the provider call with it. A string identifier on OpenAI stays on the wire either way.
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

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.

Stale Bugbot comment from a previous run.

Comment thread litellm/decisions/main.py
… under drop_params

The route checked the whole body before routing, so a non-string
safety_identifier answered 400 even when the deployment or the body set
drop_params. The route now leaves that field to the Decisions call, which
knows every drop_params source
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

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.

Stale Bugbot comment from a previous run.

# Conflicts:
#	litellm/decisions/main.py
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

Comment thread litellm/decisions/main.py Outdated
# Conflicts:
#	tests/unit/decisions/test_main.py
@mateo-berri mateo-berri changed the title fix(decisions): refuse safety_identifier on providers that cannot take it unless drop_params drops it fix(decisions)!: refuse safety_identifier on providers that cannot take it unless drop_params drops it Oct 10, 2026
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

Comment thread litellm/decisions/main.py Outdated
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

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 1008005. Configure here.

@mateo-berri mateo-berri 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.

LGTM

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