mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Fix compliance playground batch scoring bug, add display_name support
The compliance playground was sending all texts in a single batch API call, but the content filter raises HTTPException on the first blocked text. This caused a single blocked/allowed result to be applied to all rows, producing incorrect scores (e.g. 41% instead of 100%). Fix by sending each text individually to get per-text results with progressive UI updates. Also add display_name field support for category YAML files so denied_financial_advice shows as "Denied Financial / Investment Advice" in the UI dropdown. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
116e0a8470
commit
eed91b1dc4
3 changed files with 50 additions and 43 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue