Skip to content

fix(guardrails): stop the Javelin api_version default leaking into Azure Content Safety - #41941

Merged
mateo-berri merged 3 commits into
mainfrom
litellm_azure_content_safety_api_version_default
Sep 19, 2026
Merged

mateo-berri merged 3 commits into
mainfrom
litellm_azure_content_safety_api_version_default

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Azure Content Safety guardrails without api_version call Azure with api-version=v1
  • Azure answers 404, so with default_on: true every chat completion fails
  • Javelin's v1 default lives in the shared guardrail params model and leaks into Azure
  • Guardrails created through POST /guardrails have that v1 saved in the database

How it solves it:

  • The shared api_version field now defaults to None; Javelin already fills in v1 itself
  • The Azure guardrails fall back to 2024-09-01 at request time
  • A stored v1 on an Azure guardrail is treated as unset, so existing rows heal on upgrade
  • Regression tests build the Azure guardrails with no api_version and with a stored v1, and assert the URL

Decision worth a reviewer's eye: POST /guardrails and PUT /guardrails/{id} save every default, so guardrails created on older releases carry api_version: "v1" that nobody typed. Azure Content Safety has never accepted v1 (the control below is a 404), so the Azure guardrails now read a stored v1 as "not set" and use 2024-09-01. The alternatives were a data migration that nulls the stored value (it would not reach deployments that boot with --use_prisma_db_push, and migrations here should not rewrite rows), leaving those rows broken with a documented workaround, or changing the writer to save only the fields the admin set (the right long-term fix, but it touches every guardrail provider, so it is a follow-up)

User Flow

Before: a proxy admin adds an Azure Content Safety guardrail without api_version, in config.yaml or through the API, and every chat completion through the proxy comes back 404

  1. The proxy admin declares azure/text_moderations and azure/prompt_shield guardrails in config.yaml with api_key, api_base, mode: pre_call, default_on: true, and no api_version, then starts the proxy
  2. A developer sends POST https://litellm-domain/v1/chat/completions with {"model": "gpt-5.6", "messages": [{"role": "user", "content": "Say hello in five words."}]}
  3. The response is 404 with {"detail":{"error":"{\"error\":{\"code\":\"404\",\"message\": \"Resource not found\"}}"}}, and the Azure resource's request log shows a call to /contentsafety/text:analyze?api-version=v1
  4. Every model behind the proxy fails the same way, and POST https://litellm-domain/v1/messages returns the same 404 as an Anthropic-shaped not_found_error, until the admin adds api_version: "2024-09-01" to each Azure guardrail and restarts
  5. An admin who instead created the guardrail with POST https://litellm-domain/guardrails (or the dashboard's Guardrails page) and left api_version out sees "api_version": "v1" on it in GET https://litellm-domain/v2/guardrails/list, a value they never typed
  6. Requests guarded by that stored guardrail get the same 404

After: the same guardrails with no api_version work, because they call Azure at its documented default version, and a guardrail saved by an older release starts working after the upgrade without being edited

  1. The proxy admin declares the same two guardrails in config.yaml with no api_version and starts the proxy
  2. The developer sends the same POST https://litellm-domain/v1/chat/completions
  3. The response is 200 with the model's answer, and the Azure resource's request log shows /contentsafety/text:analyze?api-version=2024-09-01 and /contentsafety/text:shieldPrompt?api-version=2024-09-01
  4. The admin never has to add api_version; an explicit value such as 2024-02-15-preview still wins
  5. The guardrail created earlier with POST https://litellm-domain/guardrails still shows "api_version": "v1" in GET https://litellm-domain/v2/guardrails/list, and a guardrail created after the upgrade shows null
  6. Requests guarded by either one get 200

Relevant issues

Affected release

Regression in v1.77.7 (the shared v1 default landed in that release; the Azure guardrails predate it)

Linear ticket

Resolves LIT-7686

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/test_litellm/<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)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Every case ran on both sides from its own worktree and venv, booted with --num_workers 2 on a random free port, against the real Azure Content Safety resource, real OpenAI gpt-5.6, and real Anthropic claude-opus-5. The guardrail api_base points at a small forwarding recorder (http://127.0.0.1:46846) that logs the path and body keys of every request and forwards it unchanged to the real Azure resource, so both sides show exactly what LiteLLM sent Azure. Same configs and same requests on both sides; the two database cases share one Postgres

config.yaml for the first case (no api_version on either guardrail). The explicit case adds api_version: "2024-02-15-preview" to both guardrails, and the database case drops the guardrails: block and adds store_model_in_db: true:

model_list:
  - model_name: gpt-5.6
    litellm_params:
      model: openai/gpt-5.6
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude-opus-5
    litellm_params:
      model: anthropic/claude-opus-5
      api_key: os.environ/ANTHROPIC_API_KEY

guardrails:
  - guardrail_name: azure-text-moderation
    litellm_params:
      guardrail: azure/text_moderations
      mode: pre_call
      api_key: os.environ/AZURE_CONTENT_SAFETY_API_KEY
      api_base: os.environ/AZURE_CONTENT_SAFETY_API_BASE
      default_on: true
  - guardrail_name: azure-prompt-shield
    litellm_params:
      guardrail: azure/prompt_shield
      mode: pre_call
      api_key: os.environ/AZURE_CONTENT_SAFETY_API_KEY
      api_base: os.environ/AZURE_CONTENT_SAFETY_API_BASE
      default_on: true

general_settings:
  master_key: sk-lit7686-qa

Boot: python litellm/proxy/proxy_cli.py --config <config> --port <port> --num_workers 2 --detailed_debug (the database cases add --use_prisma_db_push and a DATABASE_URL)

Requests, three of each per case:

curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:$PORT/v1/chat/completions -H 'Authorization: Bearer sk-lit7686-qa' -H 'Content-Type: application/json' \
  -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"Say hello in five words."}]}'
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-lit7686-qa' -H 'Content-Type: application/json' \
  -d '{"model":"gpt-5.6","input":"Say hello in five words."}'
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:$PORT/v1/messages -H 'Authorization: Bearer sk-lit7686-qa' -H 'Content-Type: application/json' \
  -d '{"model":"claude-opus-5","max_tokens":64,"messages":[{"role":"user","content":"Say hello in five words."}]}'

The stored guardrail in the database case was created once, on the merge base, with this request and no api_version (sent by a four-line script that reads the key from an env file, so the key never reaches the shell history):

curl -s http://127.0.0.1:$PORT/guardrails -H 'Authorization: Bearer sk-lit7686-qa' -H 'Content-Type: application/json' \
  -d '{"guardrail":{"guardrail_name":"azure-text-moderation-ui","litellm_params":{"guardrail":"azure/text_moderations","mode":"pre_call","default_on":true,"api_key":"'"$AZURE_CONTENT_SAFETY_API_KEY"'","api_base":"http://127.0.0.1:46846"}}}'

Control, the same call sent straight through the recorder to Azure at both versions, to show the 404 is the version and not the resource:

--- api-version=2024-09-01 via recorder ---
{"blocklistsMatch":[],"categoriesAnalysis":[{"category":"Hate","severity":0},{"category":"Sexual","severity":0},{"category":"SelfHarm","severity":0},{"category":"Violence","severity":0}]}
HTTP 200
--- api-version=v1 via recorder ---
{"error":{"code":"404","message": "Resource not found"}}
HTTP 404

Before (5f1268c, merge base)

config.yaml guardrails with no api_version

  1. POST /v1/chat/completions, all three attempts

    {"detail":{"error":"{\"error\":{\"code\":\"404\",\"message\": \"Resource not found\"}}"}}
    HTTP 404
    
  2. POST /v1/responses, all three attempts

    {"id":"resp_fUMsX_MyCMdHaVgV4OohOvH0yOYPHBrvUGUCCoEJnOmVLfWXrJUCGYUAdnhDsiHG9biAAkXITbNM5g1OWwaMV5M5876zH5bXZJSnEecN6CB0ISaefjESaItbZjRxNpojJcIh-oqoKdb8c2-Xf2DLTU85juwc7wsLYf0JLYqQ4hsl9dNBEsNOScK0E3KbNI1jUwl1z_qVJt-dO48Y...
    HTTP 200
    
  3. POST /v1/messages, all three attempts

    {"type":"error","error":{"type":"not_found_error","message":"{\"error\":{\"code\":\"404\",\"message\": \"Resource not found\"}}"}}
    HTTP 404
    
  4. What reached Azure for the whole case: six calls, every one at api-version=v1, every one 404, and prompt shield never reached because text moderation failed first

    09:44:40 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:44:41 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:44:41 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:44:47 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:44:48 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:44:48 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    

Guardrail saved by POST /guardrails on the older release

  1. GET /v2/guardrails/list shows the guardrail with a version nobody typed: azure-text-moderation-ui ... "api_version": "v1"

  2. POST /v1/chat/completions, all three attempts

    {"detail":{"error":"{\"error\":{\"code\":\"404\",\"message\": \"Resource not found\"}}"}}
    HTTP 404
    
  3. POST /v1/responses, all three attempts

    {"id":"resp_xnaIOhIYXC2A81sUliysCDrLVwvdZDQZ8tMO6D5gjsyPLDOtgOR_EJje_NHCBe0cDDEhJPyzZf0VEwDIUGFMF06aePQZWM9jlgvWi_RzpndXANVt3llIJzFRiSLl0JYCf9_42bprcEjQdjs622Q5yM8t_Hg9aZLC48gkMOc77-mrQ787w7DZ-wXbav8Ri8k1A48QvCBjRmEtZLpX...
    HTTP 200
    
  4. POST /v1/messages, all three attempts

    {"type":"error","error":{"type":"not_found_error","message":"{\"error\":{\"code\":\"404\",\"message\": \"Resource not found\"}}"}}
    HTTP 404
    
  5. What reached Azure for the whole case: six calls, all at api-version=v1, all 404

    09:43:11 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:43:14 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:43:15 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:43:32 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:43:34 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    09:43:35 POST /contentsafety/text:analyze?api-version=v1 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 404
    

Explicit api_version in config.yaml

  1. POST /v1/chat/completions, all three attempts

    {"id":"chatcmpl-EPlpyzl0XZ33SQsBosmehqdbUOVmp","created":1789810794,"model":"gpt-5.6","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"Hello there, hope you’re well!","role":"...
    HTTP 200
    
  2. POST /v1/responses, all three attempts

    {"id":"resp_5LaPEgroOrpysl5ZbADo76MnSYSK-O6uzNRuMabOCAON9Pf55g7tX5GPwTaiTKMSxYG92wTnuEd8ZU3t8-oQqF9M2KBczP8zsywqjpnkKVXXxt9PcVXSu2fXyV_FUEyJr9R-TAv_GWQl95G7w3YSS_RuEYnT8JwH0zrAnf6kQ7YegWuhLwMu8yRhpaFEAsC7g7btd0YwSd6v1cxk...
    HTTP 200
    
  3. POST /v1/messages, all three attempts

    {"model":"claude-opus-5","id":"msg_011CfCaR9PTQQvdPRJqFCKWN","type":"message","role":"assistant","content":[{"type":"text","text":"Hello there, how are you?"}],"container":null,"stop_reason":"end_turn","stop_sequence":nu...
    HTTP 200
    
  4. What reached Azure for the whole case: twelve calls, all at the configured 2024-02-15-preview, all 200

    09:39:51 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:39:52 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:39:56 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:39:56 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:39:59 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:40:00 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:40:09 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:40:09 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:40:11 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:40:12 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:40:14 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:40:14 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    

GET /v2/guardrails/list for the config.yaml guardrails

  1. Both guardrails report "api_version": "v1" although config.yaml never set it

    "guardrail_name": "azure-text-moderation",
    "litellm_params": {
     ...
     "api_key": "<redacted>",
     "api_version": "v1",
     "block_on_violation": true,
    

After (24064e3)

The tip since then is e327a6a, which only removes test docstrings, so it cannot change behavior and the proof above stands at 24064e3

config.yaml guardrails with no api_version

  1. POST /v1/chat/completions, all three attempts

    {"id":"chatcmpl-EPlin202kESdp4XhQz9XIt6QQ8dIT","created":1789810349,"model":"gpt-5.6","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"Hello, it’s wonderful meeting you.","rol...
    HTTP 200
    
  2. POST /v1/responses, all three attempts

    {"id":"resp_mLVctwewcWR445PDViZSo9cgrey5SmTUj7DICO-eZcBnu-gvvSR0PD0Eo5-dn3Qz1tkxC9iASfpPi-TQyIzQUhUn0p_RYiZ4lf6vWWyykcHo3IGaLvrpxNj9oMsNZ2j1qGT27Jo7kghjw4S3IgVqwn3mlewa7wAnI6AVaLb2WVLFWKk4hP8Av_VwFwuP6Rj-t91dNQTaSND9C5Lr...
    HTTP 200
    
  3. POST /v1/messages, all three attempts

    {"model":"claude-opus-5","id":"msg_011CfCZrwMGSiHsC7v2FSoFb","type":"message","role":"assistant","content":[{"type":"text","text":"Hello there, how are you?"}],"container":null,"stop_reason":"end_turn","stop_sequence":nu...
    HTTP 200
    
  4. What reached Azure for the whole case: twelve calls, every one at api-version=2024-09-01, every one 200, both guardrails reached, and the text:analyze body keys match the before side, so the version is the only thing that changed on the wire

    09:32:25 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:32:26 POST /contentsafety/text:shieldPrompt?api-version=2024-09-01 body_keys=['documents', 'userPrompt'] -> 200
    09:32:31 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:32:32 POST /contentsafety/text:shieldPrompt?api-version=2024-09-01 body_keys=['documents', 'userPrompt'] -> 200
    09:32:36 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:32:37 POST /contentsafety/text:shieldPrompt?api-version=2024-09-01 body_keys=['documents', 'userPrompt'] -> 200
    09:32:52 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:32:52 POST /contentsafety/text:shieldPrompt?api-version=2024-09-01 body_keys=['documents', 'userPrompt'] -> 200
    09:32:55 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:32:56 POST /contentsafety/text:shieldPrompt?api-version=2024-09-01 body_keys=['documents', 'userPrompt'] -> 200
    09:32:58 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:32:58 POST /contentsafety/text:shieldPrompt?api-version=2024-09-01 body_keys=['documents', 'userPrompt'] -> 200
    

Guardrail saved by POST /guardrails on the older release

  1. GET /v2/guardrails/list on the upgraded proxy still shows the same row with "api_version": "v1"; nothing rewrote it

  2. POST /v1/chat/completions, all three attempts

    {"id":"chatcmpl-EPlmO4aDTr4Q5LsqI6J1NloAdz9ru","created":1789810572,"model":"gpt-5.6","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"Hello there, hope you’re well!","role":"...
    HTTP 200
    
  3. POST /v1/responses, all three attempts

    {"id":"resp_O39isejyrCihQea8PCcxPF3FqELMJzXVgH-SevDen3lw8njnw7iiL8EQ1bLLg5Ry7c0tbwEysxZdxzdUvvp5yr44hv1JQV0ooWPdG7im_Ea0iHPy2YA3zIMeDxqzh6K7ohkJryM6Z0EltgE8wgw16kY5I14iVhS-Wap69LzjPCRxjq0o45k5A23pdiYQM1d8RC_UDA6u2ep61MoR...
    HTTP 200
    
  4. POST /v1/messages, all three attempts

    {"model":"claude-opus-5","id":"msg_011CfCa8coVj7fGdNSigRgCt","type":"message","role":"assistant","content":[{"type":"text","text":"Hello there, how are you?"}],"container":null,"stop_reason":"end_turn","stop_sequence":nu...
    HTTP 200
    
  5. What reached Azure for the whole case: six calls, all at api-version=2024-09-01, all 200, from the same database row, which was not edited

    09:36:11 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:36:13 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:36:16 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:36:25 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:36:27 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:36:28 POST /contentsafety/text:analyze?api-version=2024-09-01 body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    
  6. A second guardrail created on the PR head with the same POST /guardrails lists as azure-text-moderation-created-at-tip api_version= None, and four more chat completions with both guardrails active are all 200, with eight text:analyze?api-version=2024-09-01 -> 200 calls at Azure

Explicit api_version in config.yaml

  1. POST /v1/chat/completions, all three attempts

    {"id":"chatcmpl-EPlk9PxSdMJ6vmcsL52G1mJc6DhWk","created":1789810433,"model":"gpt-5.6","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"Hello there, wonderful person today!","r...
    HTTP 200
    
  2. POST /v1/responses, all three attempts

    {"id":"resp_nB8uj_KmhMaEzmk8Y-ylgPUs5g6C7-82_iMzRWRSOJ3kA1He_UFIgnAPdH-eimn-epvEfb2y8VzuUe9APFtksBfGXyTYYlqL6M-vHYBtX4pi7kYly8wtKzXkQI7A40fbPQDyCfQuZ-0b4H19Ke8UIcF1OPrxKthcBKcEEcGRKKcZ5SBGwaAr8RjwdZy0muRjmHO_OcqYQPRbOZwq...
    HTTP 200
    
  3. POST /v1/messages, all three attempts

    {"model":"claude-opus-5","id":"msg_011CfCZxgoKppywS1T5v6sXA","type":"message","role":"assistant","content":[{"type":"text","text":"Hello there, how are you?"}],"container":null,"stop_reason":"end_turn","stop_sequence":nu...
    HTTP 200
    
  4. What reached Azure for the whole case: twelve calls, all at the configured 2024-02-15-preview, all 200, so an explicit version still wins

    09:33:52 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:33:53 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:33:55 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:33:56 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:33:58 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:33:58 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:34:10 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:34:10 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:34:12 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:34:13 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    09:34:16 POST /contentsafety/text:analyze?api-version=2024-02-15-preview body_keys=['blocklistNames', 'categories', 'haltOnBlocklistHit', 'outputType', 'text'] -> 200
    09:34:16 POST /contentsafety/text:shieldPrompt?api-version=2024-02-15-preview body_keys=['documents', 'userPrompt'] -> 200
    

GET /v2/guardrails/list for the config.yaml guardrails

  1. The only difference in the whole response (keys and host redacted, then diff base tip) is that the two guardrails stop reporting a v1 they never had. GET /guardrails/list is byte-identical on both sides

    15d14
    <     "api_version": "v1",
    102d100
    <     "api_version": "v1",
    

Observed alongside the fix (this PR leaves every one of these alone):

  • Azure guardrails skip /v1/responses input entirely, both sides
  • Merge base sends 2024-09-01 for a guardrail stored with null
  • Deleted guardrail kept running about a minute in one worker
  • Javelin's v1 default is unit-tested only, no live credentials

Type

🐛 Bug Fix

Caveats (if any)

Backward incompatible

Each of these is an observable change /live-pr-risk found at 24064e3. All of them are inherent to the fix shape LIT-7686 names in its "Done looks like" (make the Azure default win over v1), so the ticket author approved shipping them there and none has a fixing commit

  • api_version: "v1" typed on purpose on an Azure guardrail now calls Azure at 2024-09-01
    • It was a guaranteed 404 before, so nothing working changes (stored-row leg above: 6 calls at v1 all 404 at base, 6 calls at 2024-09-01 all 200 at the tip)
  • GET /v2/guardrails/list stops showing "api_version": "v1" on guardrails that never set it
    • Only those two lines differ between the base and tip listings; GET /guardrails/list is identical
  • Custom guardrail classes stop receiving a stray api_version="v1" kwarg when the config never set one
    • Traced through the registry's model_dump(exclude_none=True), not driven live; no in-repo class reads it besides Azure and Javelin
  • Stored rows keep "v1"; the guardrail writer still saves every default (follow-up)
    • The Azure base reads that stored value as unset, so the unedited row works at 2024-09-01 while the listing still shows v1

Low

  • The Javelin UI form keeps its own v1 default; only the runtime model changed
  • Javelin's runtime v1 fallback is unit-tested only, no live Javelin credentials
  • ci/circleci: litellm_utils_testing is red at the tip on test_models_by_provider alone, the same test that fails on main's own pipelines 89834 and 89818; nothing in this diff touches it
  • core-utils / Run tests failed once on the wall-clock flake test_detailed_timing_pre_processing_uses_receive_anchor, green at the two earlier tips and on main, fix in flight in test(response_metadata): anchor detailed-timing test on a fixed instant instead of wall clock #41946; the re-run of that job passed, so every required check is green at the tip

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
  • 24064e3 passes /live-pr-risk

…ure Content Safety

LitellmParams mixes every provider config model into one class, so the
Javelin api_version default of "v1" reached the Azure Content Safety
guardrails whenever config.yaml omitted api_version and Azure answered 404.
The shared field now defaults to None, Javelin keeps filling in "v1" itself,
and the Azure guardrails fall back to the documented 2024-09-01 at request
time so a DB update that omits api_version stays on the default too.
@devin-ai-integration

devin-ai-integration Bot commented Sep 19, 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

@mateo-berri mateo-berri added run-ci backport-stable P0 regression fix only (Urgent ticket): cherry-pick onto the baking rc line before the stable tag labels Sep 19, 2026
@codspeed

codspeed Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_azure_content_safety_api_version_default (e327a6a) with main (5fc510a)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with the Azure compatibility behavior and Javelin fallback covered by focused regression tests

Summary

This PR prevents the shared Javelin API-version default from leaking into Azure Content Safety guardrails.

  • The shared configuration model now leaves api_version unset by default
  • Azure Content Safety resolves missing and legacy stored v1 values to 2024-09-01
  • Javelin retains its provider-specific v1 runtime fallback
  • Regression tests cover omitted, legacy stored, and explicitly configured versions
  • Generated OpenAPI and dashboard schema artifacts reflect the optional field

Reviews (2) · Last reviewed commit: "test(guardrails): drop regression docstr..."

Comment thread tests/test_litellm/proxy/guardrails/guardrail_hooks/test_javelin.py Outdated
@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…t for Azure Content Safety

Guardrails created through POST /guardrails on older releases have api_version "v1" saved in the database, because the writer persists every default. Azure Content Safety never accepts that value, so those guardrails kept answering 404 after the default moved to None. The Azure base now resolves "v1" to 2024-09-01 the same way it resolves a missing value. Also restores the OpenAPI snapshot line that a Python 3.14 regeneration had dedented
@mateo-berri mateo-berri added run-ci and removed run-ci labels Sep 19, 2026
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri mateo-berri added run-ci and removed run-ci labels Sep 19, 2026
@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 e327a6a. 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

@mateo-berri
mateo-berri merged commit 385932b into main Sep 19, 2026
163 of 177 checks passed
@mateo-berri
mateo-berri deleted the litellm_azure_content_safety_api_version_default branch September 19, 2026 10:09
@mateo-berri mateo-berri removed the backport-stable P0 regression fix only (Urgent ticket): cherry-pick onto the baking rc line before the stable tag label Sep 26, 2026
yuneng-berri added a commit that referenced this pull request Oct 1, 2026
…-x-89a550

chore(release): backport #41941, #41880, #43956 to stable/1.101.x and cut 1.101.4
keppy pushed a commit to keppy/litellm that referenced this pull request Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant