fix(guardrails): treat the stored Javelin api_version default as unset 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
This commit is contained in:
mateo-berri 2026-09-19 02:29:43 -07:00
parent 6f4d1c5911
commit 24064e3b31
3 changed files with 44 additions and 2 deletions

View file

@ -19621,7 +19621,7 @@
}
}
},
"description": "\nUnified rate-limit error.\n\nEvery rate-limit condition surfaced by litellm \u2014 whether it originated from\nan upstream LLM provider, a vendor batch endpoint, or one of litellm's own\nproxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,\nmax-iterations, etc.) \u2014 is raised as an instance of this class.\n\nThe :attr:`category` attribute lets callers distinguish the source. See\n:class:`RateLimitErrorCategory` for the available values.\n"
"description": "\n Unified rate-limit error.\n\n Every rate-limit condition surfaced by litellm \u2014 whether it originated from\n an upstream LLM provider, a vendor batch endpoint, or one of litellm's own\n proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,\n max-iterations, etc.) \u2014 is raised as an instance of this class.\n\n The :attr:`category` attribute lets callers distinguish the source. See\n :class:`RateLimitErrorCategory` for the available values.\n "
},
"500": {
"content": {

View file

@ -21,6 +21,13 @@ AZURE_CONTENT_SAFETY_MAX_TEXT_LENGTH: Final = 10000
AZURE_CONTENT_SAFETY_TEXT_RECORD_LENGTH: Final = 1000
AZURE_CONTENT_SAFETY_DEFAULT_API_VERSION: Final = "2024-09-01"
JAVELIN_API_VERSION_STORED_BY_OLDER_RELEASES: Final = "v1"
def resolve_content_safety_api_version(configured: str | None) -> str:
if not configured or configured == JAVELIN_API_VERSION_STORED_BY_OLDER_RELEASES:
return AZURE_CONTENT_SAFETY_DEFAULT_API_VERSION
return configured
class AzureGuardrailBase:
@ -58,7 +65,7 @@ class AzureGuardrailBase:
Returns:
Parsed JSON response dict.
"""
api_version: Final = self.api_version or AZURE_CONTENT_SAFETY_DEFAULT_API_VERSION
api_version: Final = resolve_content_safety_api_version(self.api_version)
url: Final = f"{self.api_base}/contentsafety/{endpoint_path}?api-version={api_version}"
headers: Final = {
"Ocp-Apim-Subscription-Key": self.api_key,

View file

@ -494,3 +494,38 @@ async def test_config_without_api_version_calls_documented_azure_api_version():
assert mock_post.call_args.kwargs["url"] == (
"https://example.cognitiveservices.azure.com/contentsafety/text:analyze?api-version=2024-09-01"
)
@pytest.mark.parametrize(
("stored_api_version", "expected_api_version"),
[("v1", "2024-09-01"), ("2023-10-01", "2023-10-01")],
)
@pytest.mark.asyncio
async def test_guardrail_loaded_with_stored_api_version_calls_azure_at(stored_api_version, expected_api_version):
"""Releases before the api_version default fix saved every guardrail created
through the API or dashboard with Javelin's "v1", which Azure always answers
with 404. A row like that must reach Azure at the documented default, while a
real Azure version an admin chose is sent as written."""
handler = InMemoryGuardrailHandler()
registered = handler.initialize_guardrail(
guardrail={
"guardrail_name": f"azure-text-moderation-stored-{stored_api_version}",
"litellm_params": {
"guardrail": "azure/text_moderations",
"mode": "pre_call",
"api_key": "azure_text_moderation_api_key",
"api_base": "https://example.cognitiveservices.azure.com",
"api_version": stored_api_version,
},
}
)
assert registered is not None
guardrail = handler.guardrail_id_to_custom_guardrail[registered["guardrail_id"]]
with patch.object(guardrail.async_handler, "post", return_value=_moderation_response(0)) as mock_post:
result = await guardrail.apply_guardrail(inputs={"texts": ["hello"]}, request_data={}, input_type="request")
assert result == {"texts": ["hello"]}
assert mock_post.call_args.kwargs["url"] == (
f"https://example.cognitiveservices.azure.com/contentsafety/text:analyze?api-version={expected_api_version}"
)