mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
Merge pull request #41941 from BerriAI/litellm_azure_content_safety_api_version_default
fix(guardrails): stop the Javelin api_version default leaking into Azure Content Safety
This commit is contained in:
commit
385932b4e3
7 changed files with 161 additions and 6 deletions
|
|
@ -11155,7 +11155,6 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "v1",
|
||||
"description": "API version for Javelin service",
|
||||
"title": "Api Version"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@ AZURE_CONTENT_SAFETY_MAX_TEXT_LENGTH: Final = 10000
|
|||
# chunk of N characters consumes ceil(N / 1000) text records.
|
||||
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:
|
||||
"""
|
||||
|
|
@ -43,7 +52,7 @@ class AzureGuardrailBase:
|
|||
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
|
||||
self.api_key = api_key
|
||||
self.api_base = api_base
|
||||
self.api_version: str = kwargs.get("api_version") or "2024-09-01"
|
||||
self.api_version: str | None = kwargs.get("api_version")
|
||||
|
||||
async def _post_to_content_safety(self, endpoint_path: str, request_body: dict[str, object]) -> dict[str, Any]:
|
||||
"""POST to an Azure Content Safety endpoint with standard auth headers.
|
||||
|
|
@ -56,7 +65,8 @@ class AzureGuardrailBase:
|
|||
Returns:
|
||||
Parsed JSON response dict.
|
||||
"""
|
||||
url: Final = f"{self.api_base}/contentsafety/{endpoint_path}?api-version={self.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,
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
|
|
@ -819,7 +819,7 @@ class JavelinGuardrailConfigModel(BaseModel):
|
|||
"""Configuration parameters for the Javelin guardrail"""
|
||||
|
||||
guard_name: str | None = Field(default=None, description="Name of the Javelin guard to use")
|
||||
api_version: str | None = Field(default="v1", description="API version for Javelin service")
|
||||
api_version: str | None = Field(default=None, description="API version for Javelin service")
|
||||
metadata: dict | None = Field(default=None, description="Additional metadata to send with requests")
|
||||
application: str | None = Field(default=None, description="Application name for Javelin service")
|
||||
config: dict | None = Field(default=None, description="Additional configuration for the guardrail")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from litellm.proxy._types import UserAPIKeyAuth
|
|||
from litellm.proxy.guardrails.guardrail_hooks.azure.prompt_shield import (
|
||||
AzureContentSafetyPromptShieldGuardrail,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_registry import InMemoryGuardrailHandler
|
||||
from litellm.types.guardrails import LitellmParams
|
||||
|
||||
|
||||
|
|
@ -635,3 +636,51 @@ def test_update_in_memory_litellm_params_dead_env_credential_rejected_untouched(
|
|||
|
||||
assert guardrail.api_key == "azure_prompt_shield_api_key"
|
||||
assert guardrail.price_per_1000_text_records == 0.38
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_without_api_version_calls_documented_azure_api_version():
|
||||
handler = InMemoryGuardrailHandler()
|
||||
registered = handler.initialize_guardrail(
|
||||
guardrail={
|
||||
"guardrail_name": "azure-prompt-shield-no-api-version",
|
||||
"litellm_params": {
|
||||
"guardrail": "azure/prompt_shield",
|
||||
"mode": "pre_call",
|
||||
"api_key": "azure_prompt_shield_api_key",
|
||||
"api_base": "https://example.cognitiveservices.azure.com",
|
||||
},
|
||||
}
|
||||
)
|
||||
assert registered is not None
|
||||
guardrail = handler.guardrail_id_to_custom_guardrail[registered["guardrail_id"]]
|
||||
assert isinstance(guardrail, AzureContentSafetyPromptShieldGuardrail)
|
||||
|
||||
with patch.object(guardrail.async_handler, "post", return_value=_shield_response(False)) 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"] == (
|
||||
"https://example.cognitiveservices.azure.com/contentsafety/text:shieldPrompt?api-version=2024-09-01"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_without_api_version_keeps_documented_azure_api_version():
|
||||
guardrail = _shield_guardrail()
|
||||
guardrail.update_in_memory_litellm_params(
|
||||
LitellmParams(
|
||||
guardrail="azure/prompt_shield",
|
||||
mode="pre_call",
|
||||
api_key="azure_prompt_shield_api_key",
|
||||
api_base="https://example.cognitiveservices.azure.com",
|
||||
)
|
||||
)
|
||||
|
||||
with patch.object(guardrail.async_handler, "post", return_value=_shield_response(False)) 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"] == (
|
||||
"https://example.cognitiveservices.azure.com/contentsafety/text:shieldPrompt?api-version=2024-09-01"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
from fastapi import HTTPException
|
||||
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.guardrails.guardrail_registry import InMemoryGuardrailHandler
|
||||
from litellm.proxy.guardrails.guardrail_hooks.azure.text_moderation import (
|
||||
AzureContentSafetyTextModerationGuardrail,
|
||||
)
|
||||
|
|
@ -463,3 +464,61 @@ async def test_apply_guardrail_handles_missing_texts_key():
|
|||
|
||||
mock_post.assert_not_called()
|
||||
assert result == {"images": ["x"]}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_without_api_version_calls_documented_azure_api_version():
|
||||
handler = InMemoryGuardrailHandler()
|
||||
registered = handler.initialize_guardrail(
|
||||
guardrail={
|
||||
"guardrail_name": "azure-text-moderation-no-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",
|
||||
},
|
||||
}
|
||||
)
|
||||
assert registered is not None
|
||||
guardrail = handler.guardrail_id_to_custom_guardrail[registered["guardrail_id"]]
|
||||
assert isinstance(guardrail, AzureContentSafetyTextModerationGuardrail)
|
||||
|
||||
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"] == (
|
||||
"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):
|
||||
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}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.proxy.guardrails.guardrail_hooks.javelin.javelin import JavelinGuardrail
|
||||
from litellm.proxy.guardrails.guardrail_registry import InMemoryGuardrailHandler
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_config_without_api_version_calls_javelin_v1():
|
||||
handler = InMemoryGuardrailHandler()
|
||||
registered = handler.initialize_guardrail(
|
||||
guardrail={
|
||||
"guardrail_name": "javelin-no-api-version",
|
||||
"litellm_params": {
|
||||
"guardrail": "javelin",
|
||||
"mode": "pre_call",
|
||||
"api_key": "javelin_api_key",
|
||||
"api_base": "https://javelin.example",
|
||||
"guard_name": "trustsafety",
|
||||
},
|
||||
}
|
||||
)
|
||||
assert registered is not None
|
||||
guardrail = handler.guardrail_id_to_custom_guardrail[registered["guardrail_id"]]
|
||||
assert isinstance(guardrail, JavelinGuardrail)
|
||||
assessments = [{"trustsafety": {"request_reject": False}}]
|
||||
response = Mock()
|
||||
response.json.return_value = {"assessments": assessments}
|
||||
|
||||
with patch.object(guardrail.async_handler, "post", return_value=response) as mock_post:
|
||||
result = await guardrail.call_javelin_guard(
|
||||
request={"input": {"text": "hello"}, "config": None, "metadata": None},
|
||||
event_type=GuardrailEventHooks.pre_call,
|
||||
)
|
||||
|
||||
assert result == {"assessments": assessments}
|
||||
assert mock_post.call_args.kwargs["url"] == "https://javelin.example/v1/guardrail/trustsafety/apply"
|
||||
3
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
3
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -31795,9 +31795,8 @@ export interface components {
|
|||
/**
|
||||
* Api Version
|
||||
* @description API version for Javelin service
|
||||
* @default v1
|
||||
*/
|
||||
api_version: string | null;
|
||||
api_version?: string | null;
|
||||
/**
|
||||
* Application
|
||||
* @description Application name for Javelin service
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue