diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/categories/denied_financial_advice.yaml b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/categories/denied_financial_advice.yaml index 6141ca773da..20a6acb4859 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/categories/denied_financial_advice.yaml +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/categories/denied_financial_advice.yaml @@ -11,6 +11,7 @@ # Precision: 100%, Recall: 100%, F1: 100%, Latency: <0.1ms # Run: pytest tests/.../topic_blocker/test_eval.py -k InvestmentContentFilter -v -s category_name: "denied_financial_advice" +display_name: "Denied Financial / Investment Advice" description: "Detects requests for personalized financial advice, investment recommendations, or financial planning that should be provided by licensed financial advisors" default_action: "BLOCK" diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.py b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.py index cf5df3b3bfb..fc799c411f8 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.py +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.py @@ -162,9 +162,11 @@ def get_available_content_categories() -> List[Dict[str, str]]: category_data = yaml.safe_load(f) if category_data and "category_name" in category_data: - # Create display name from category name (convert harmful_self_harm -> Harmful Self Harm) - display_name = ( - category_data["category_name"].replace("_", " ").title() + # Use explicit display_name from YAML if provided, + # otherwise auto-generate from category_name + display_name = category_data.get( + "display_name", + category_data["category_name"].replace("_", " ").title(), ) available_categories.append( diff --git a/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx b/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx index ccd3dcfe8d5..dc18773df6a 100644 --- a/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx +++ b/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx @@ -532,55 +532,59 @@ export default function ComplianceUI({ status: "pending", })); setTestResults(pendingResults); - try { - const { inputs, guardrail_errors } = await testPoliciesAndGuardrails( - accessToken, - { - policy_names: - selectedPolicies.length > 0 ? selectedPolicies : undefined, - guardrail_names: - selectedGuardrails.length > 0 ? selectedGuardrails : undefined, - inputs: { texts: allTexts }, - request_data: {}, - input_type: "request", - } - ); - const actualResult: "blocked" | "allowed" = - guardrail_errors.length > 0 ? "blocked" : "allowed"; - const triggeredBy = - guardrail_errors.length > 0 - ? guardrail_errors - .map((e) => `${e.guardrail_name}: ${e.message}`) - .join("; ") - : undefined; - const returnedTexts: (string | undefined)[] = - Array.isArray(inputs?.texts) ? inputs.texts : []; - setTestResults( - pendingResults.map((row, index) => ({ - ...row, + // Send each text individually to get per-text blocked/allowed results. + // Sending all texts in a single batch doesn't work because the guardrail + // raises an HTTPException on the first blocked text, skipping the rest. + const updatedResults = [...pendingResults]; + for (let i = 0; i < allTexts.length; i++) { + try { + const { inputs, guardrail_errors } = await testPoliciesAndGuardrails( + accessToken, + { + policy_names: + selectedPolicies.length > 0 ? selectedPolicies : undefined, + guardrail_names: + selectedGuardrails.length > 0 ? selectedGuardrails : undefined, + inputs: { texts: [allTexts[i]] }, + request_data: {}, + input_type: "request", + } + ); + const actualResult: "blocked" | "allowed" = + guardrail_errors.length > 0 ? "blocked" : "allowed"; + const triggeredBy = + guardrail_errors.length > 0 + ? guardrail_errors + .map((e) => `${e.guardrail_name}: ${e.message}`) + .join("; ") + : undefined; + const returnedText = + Array.isArray(inputs?.texts) && inputs.texts.length > 0 + ? inputs.texts[0] + : undefined; + updatedResults[i] = { + ...updatedResults[i], actualResult, isMatch: - (row.expectedResult === "fail" && actualResult === "blocked") || - (row.expectedResult === "pass" && actualResult === "allowed"), + (updatedResults[i].expectedResult === "fail" && actualResult === "blocked") || + (updatedResults[i].expectedResult === "pass" && actualResult === "allowed"), triggeredBy, - returnedText: returnedTexts[index], + returnedText, status: "complete" as const, - })) - ); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err); - setTestResults( - pendingResults.map((row) => ({ - ...row, + }; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err); + updatedResults[i] = { + ...updatedResults[i], actualResult: "blocked" as const, isMatch: false, triggeredBy: `Error: ${errorMessage}`, status: "complete" as const, - })) - ); - } finally { - setIsRunning(false); + }; + } + setTestResults([...updatedResults]); } + setIsRunning(false); }, [ accessToken, selectedPromptIds,