mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-01 07:03:02 +00:00
parent
2751cfcad3
commit
6105aaa63e
6 changed files with 109 additions and 6 deletions
|
|
@ -16484,6 +16484,14 @@
|
|||
"title": "BedrockChecksSensitiveInformationModel",
|
||||
"type": "object"
|
||||
},
|
||||
"BedrockGuardrailOutputScope": {
|
||||
"enum": [
|
||||
"INTERVENTIONS",
|
||||
"FULL"
|
||||
],
|
||||
"title": "BedrockGuardrailOutputScope",
|
||||
"type": "string"
|
||||
},
|
||||
"BlockedWord": {
|
||||
"description": "Represents a blocked word with its action and optional description",
|
||||
"properties": {
|
||||
|
|
@ -18716,6 +18724,17 @@
|
|||
],
|
||||
"description": "Optional parameters for the guardrail"
|
||||
},
|
||||
"outputScope": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BedrockGuardrailOutputScope"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "ApplyGuardrail response scope. FULL includes detected and non-detected findings. Omit to retain Bedrock's default of returning detected findings only."
|
||||
},
|
||||
"output_parse_pii": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ from litellm.proxy.guardrails.anthropic_sse import (
|
|||
)
|
||||
from litellm.types.guardrails import (
|
||||
BedrockChecksConfigModel,
|
||||
BedrockGuardrailOutputScope,
|
||||
BedrockGuardrailStreamingParams,
|
||||
GuardrailEventHooks,
|
||||
LitellmParams,
|
||||
|
|
@ -289,6 +290,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
streaming_buffer_release_on_scan: bool | None = None,
|
||||
streaming_scan_in_background: bool | None = None,
|
||||
async_handler: AsyncHTTPHandler | None = None,
|
||||
outputScope: BedrockGuardrailOutputScope | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.async_handler = async_handler or get_async_httpx_client(
|
||||
|
|
@ -309,6 +311,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
)
|
||||
self.guardrailIdentifier = guardrailIdentifier
|
||||
self.guardrailVersion = guardrailVersion
|
||||
self.outputScope = outputScope
|
||||
self.guardrail_provider = "bedrock"
|
||||
self.chunk_budget_chars = chunk_budget_chars
|
||||
self.contextual_grounding_from_messages = contextual_grounding_from_messages
|
||||
|
|
@ -1280,10 +1283,10 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
AWS billed them to ``completed_chunk_usages``, and the attempt log sums those
|
||||
with the blocking call's own usage.
|
||||
"""
|
||||
bedrock_request_data: Final = {
|
||||
**base_request_data,
|
||||
"content": content,
|
||||
}
|
||||
bedrock_request_data: Final = dict(base_request_data)
|
||||
bedrock_request_data["content"] = content
|
||||
if self.outputScope is not None and "outputScope" not in bedrock_request_data:
|
||||
bedrock_request_data["outputScope"] = self.outputScope.value
|
||||
prepared_request: Final = await run_aws_signing(
|
||||
self._prepare_request,
|
||||
credentials=credentials,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ def initialize_bedrock(litellm_params: LitellmParams, guardrail: Guardrail):
|
|||
event_hook=litellm_params.mode,
|
||||
guardrailIdentifier=litellm_params.guardrailIdentifier,
|
||||
guardrailVersion=litellm_params.guardrailVersion,
|
||||
outputScope=litellm_params.outputScope,
|
||||
checks=litellm_params.checks if isinstance(litellm_params.checks, BedrockChecksConfigModel) else None,
|
||||
content_filter_threshold=litellm_params.content_filter_threshold,
|
||||
prompt_attack_threshold=litellm_params.prompt_attack_threshold,
|
||||
|
|
|
|||
|
|
@ -604,6 +604,11 @@ class BedrockChecksConfigModel(LiteLLMBaseModel):
|
|||
return self
|
||||
|
||||
|
||||
class BedrockGuardrailOutputScope(Enum):
|
||||
INTERVENTIONS = "INTERVENTIONS"
|
||||
FULL = "FULL"
|
||||
|
||||
|
||||
class BedrockGuardrailConfigModel(LiteLLMBaseModel):
|
||||
"""Configuration parameters for the AWS Bedrock guardrail"""
|
||||
|
||||
|
|
@ -612,6 +617,11 @@ class BedrockGuardrailConfigModel(LiteLLMBaseModel):
|
|||
default=None,
|
||||
description="The version of your Bedrock guardrail (e.g., DRAFT or version number)",
|
||||
)
|
||||
outputScope: BedrockGuardrailOutputScope | None = Field(
|
||||
default=None,
|
||||
description="ApplyGuardrail response scope. FULL includes detected and non-detected findings. "
|
||||
"Omit to retain Bedrock's default of returning detected findings only.",
|
||||
)
|
||||
disable_exception_on_block: bool | None = Field(
|
||||
default=False,
|
||||
description="If True, will not raise an exception when the guardrail is blocked. Useful for OpenWebUI where exceptions can end the chat flow.",
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import (
|
|||
BedrockGuardrail,
|
||||
_redact_pii_matches,
|
||||
)
|
||||
from litellm.proxy.guardrails.guardrail_initializers import initialize_bedrock
|
||||
from litellm.proxy.utils import ProxyLogging
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.guardrails import BedrockGuardrailOutputScope, Guardrail, GuardrailEventHooks, LitellmParams
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import (
|
||||
BedrockContentItem,
|
||||
BedrockTextContent,
|
||||
|
|
@ -1020,6 +1021,64 @@ def _anthropic_tool_result_conversation(
|
|||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("scope", [None, "INTERVENTIONS", "FULL"])
|
||||
async def test_configured_output_scope_reaches_bedrock_and_preserves_healthy_assessments(
|
||||
monkeypatch: pytest.MonkeyPatch, respx_mock: respx.MockRouter, scope: str | None
|
||||
) -> None:
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
params: Final = LitellmParams.model_validate(
|
||||
{
|
||||
"guardrail": "bedrock",
|
||||
"mode": "post_call",
|
||||
"guardrailIdentifier": "test-guardrail",
|
||||
"guardrailVersion": "DRAFT",
|
||||
"contextual_grounding_from_messages": True,
|
||||
"aws_access_key_id": "test-access-key",
|
||||
"aws_secret_access_key": "test-secret-key",
|
||||
"aws_region_name": "us-east-1",
|
||||
"aws_bedrock_runtime_endpoint": "https://bedrock.example",
|
||||
**({"outputScope": scope} if scope is not None else {}),
|
||||
}
|
||||
)
|
||||
guardrail: Final = initialize_bedrock(params, Guardrail(guardrail_name="grounding", litellm_params=params))
|
||||
assessment: Final = {
|
||||
"contextualGroundingPolicy": {
|
||||
"filters": [{"type": "GROUNDING", "score": 0.96, "threshold": 0.8, "detected": False, "action": "NONE"}]
|
||||
}
|
||||
}
|
||||
|
||||
def provider(request: httpx.Request) -> httpx.Response:
|
||||
body: Final = json.loads(request.content)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={"action": "NONE", "assessments": [assessment] if body.get("outputScope") == "FULL" else []},
|
||||
)
|
||||
|
||||
route: Final = respx_mock.post("https://bedrock.example/guardrail/test-guardrail/version/DRAFT/apply").mock(
|
||||
side_effect=provider
|
||||
)
|
||||
request_data: Final = {
|
||||
"messages": [
|
||||
{"role": "system", "content": "The tower is 330 metres tall."},
|
||||
{"role": "user", "content": "How tall is the tower?"},
|
||||
]
|
||||
}
|
||||
result: Final = await guardrail.apply_guardrail(
|
||||
inputs={"texts": ["The tower is 330 metres tall."]}, request_data=request_data, input_type="response"
|
||||
)
|
||||
|
||||
sent: Final = json.loads(route.calls.last.request.content)
|
||||
if scope is None:
|
||||
assert "outputScope" not in sent
|
||||
else:
|
||||
assert sent["outputScope"] == scope
|
||||
assert sent["source"] == "OUTPUT"
|
||||
assert result["texts"] == ["The tower is 330 metres tall."]
|
||||
recorded: Final = request_data["metadata"]["standard_logging_guardrail_information"][-1]["guardrail_response"]
|
||||
assert recorded["assessments"] == ([assessment] if scope == "FULL" else [])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_during_call_hook_skips_bedrock_call_for_tool_result_only_turn():
|
||||
"""A tool_result-only latest user turn must not post an empty content list to Bedrock.
|
||||
|
|
@ -1805,7 +1864,11 @@ async def test_make_bedrock_api_request_logging_event_type_for_spend_logs():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_make_bedrock_api_request_filters_dynamic_evaluation_overrides():
|
||||
guardrail = BedrockGuardrail(guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT")
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="test-guardrail",
|
||||
guardrailVersion="DRAFT",
|
||||
outputScope=BedrockGuardrailOutputScope.INTERVENTIONS,
|
||||
)
|
||||
mock_credentials = MagicMock()
|
||||
mock_credentials.access_key = "test-access-key"
|
||||
mock_credentials.secret_key = "test-secret-key"
|
||||
|
|
|
|||
7
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
7
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -27607,6 +27607,11 @@ export interface components {
|
|||
/** Entities */
|
||||
entities: components["schemas"]["BedrockChecksSensitiveInformationEntityItem"][];
|
||||
};
|
||||
/**
|
||||
* BedrockGuardrailOutputScope
|
||||
* @enum {string}
|
||||
*/
|
||||
BedrockGuardrailOutputScope: "INTERVENTIONS" | "FULL";
|
||||
/** BillingPeriod */
|
||||
BillingPeriod: {
|
||||
/**
|
||||
|
|
@ -37417,6 +37422,8 @@ export interface components {
|
|||
only_scan_new_messages: boolean | null;
|
||||
/** @description Optional parameters for the guardrail */
|
||||
optional_params?: components["schemas"]["CiscoAIDefenseGuardrailConfigModelOptionalParams"] | null;
|
||||
/** @description ApplyGuardrail response scope. FULL includes detected and non-detected findings. Omit to retain Bedrock's default of returning detected findings only. */
|
||||
outputScope?: components["schemas"]["BedrockGuardrailOutputScope"] | null;
|
||||
/**
|
||||
* Output Parse Pii
|
||||
* @description When True, LiteLLM will replace the masked text with the original text in the response
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue