#![forbid(unsafe_code)]
#![cfg(feature = "evaluator-safety")]
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::aggregator::AllPass;
use crate::evaluator::Evaluator;
use crate::types::{EvalCase, EvalMetricResult, Invocation};
use super::{JudgeEvaluatorConfig, build_prompt_context, evaluate_with_builtin};
fn has_final_response(_case: &EvalCase, invocation: &Invocation) -> bool {
invocation
.final_response
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
}
fn has_user_prompt(case: &EvalCase, _invocation: &Invocation) -> bool {
!case.user_messages.is_empty()
}
fn with_safety_default(config: JudgeEvaluatorConfig) -> JudgeEvaluatorConfig {
if config.aggregator.is_some() {
config
} else {
config.with_aggregator(Arc::new(AllPass))
}
}
macro_rules! safety_evaluator {
(
$(#[$meta:meta])*
$name:ident, $eval_name:literal, $template:literal, $criterion:expr
) => {
$(#[$meta])*
pub struct $name {
config: JudgeEvaluatorConfig,
}
impl $name {
#[must_use]
pub fn new(config: JudgeEvaluatorConfig) -> Self {
Self {
config: with_safety_default(config),
}
}
#[must_use]
pub fn with_prompt(mut self, template: Arc<dyn crate::prompt::JudgePromptTemplate>) -> Self {
self.config = self.config.with_prompt(template);
self
}
#[must_use]
pub fn with_few_shot(mut self, examples: Vec<crate::types::FewShotExample>) -> Self {
self.config = self.config.with_few_shot(examples);
self
}
#[must_use]
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.config = self.config.with_system_prompt(prompt);
self
}
#[must_use]
pub fn with_output_schema(mut self, schema: serde_json::Value) -> Self {
self.config = self.config.with_output_schema(schema);
self
}
#[must_use]
pub fn with_use_reasoning(mut self, flag: bool) -> Self {
self.config = self.config.with_use_reasoning(flag);
self
}
#[must_use]
pub fn with_feedback_key(mut self, key: impl Into<String>) -> Self {
self.config = self.config.with_feedback_key(key);
self
}
#[must_use]
pub const fn config(&self) -> &JudgeEvaluatorConfig {
&self.config
}
}
impl $crate::evaluators::JudgeEvaluatorBuilder for $name {
fn judge_config_mut(&mut self) -> &mut JudgeEvaluatorConfig {
&mut self.config
}
}
impl Evaluator for $name {
fn name(&self) -> &'static str {
$eval_name
}
fn evaluate(
&self,
case: &EvalCase,
invocation: &Invocation,
) -> Option<EvalMetricResult> {
let criterion: fn(&EvalCase, &Invocation) -> bool = $criterion;
if !criterion(case, invocation) {
return None;
}
Some(evaluate_with_builtin(
$eval_name,
$template,
&self.config,
&build_prompt_context(&self.config, case, invocation),
))
}
}
};
}
safety_evaluator! {
HarmfulnessEvaluator,
"harmfulness",
"harmfulness_v0",
has_final_response
}
safety_evaluator! {
ToxicityEvaluator,
"toxicity",
"toxicity_v0",
has_final_response
}
safety_evaluator! {
FairnessEvaluator,
"fairness",
"fairness_v0",
has_final_response
}
safety_evaluator! {
PromptInjectionEvaluator,
"prompt_injection",
"prompt_injection_v0",
has_user_prompt
}
safety_evaluator! {
CodeInjectionEvaluator,
"code_injection",
"code_injection_v0",
has_user_prompt
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PIIClass {
Email,
Phone,
Ssn,
CreditCard,
IpAddress,
ApiKey,
PersonalName,
Address,
Other(String),
}
impl PIIClass {
#[must_use]
pub fn canonical_name(&self) -> String {
match self {
Self::Email => "email".into(),
Self::Phone => "phone".into(),
Self::Ssn => "ssn".into(),
Self::CreditCard => "credit_card".into(),
Self::IpAddress => "ip_address".into(),
Self::ApiKey => "api_key".into(),
Self::PersonalName => "personal_name".into(),
Self::Address => "address".into(),
Self::Other(name) => name.clone(),
}
}
#[must_use]
pub fn all_builtin() -> Vec<Self> {
vec![
Self::Email,
Self::Phone,
Self::Ssn,
Self::CreditCard,
Self::IpAddress,
Self::ApiKey,
Self::PersonalName,
Self::Address,
]
}
}
pub struct PIILeakageEvaluator {
config: JudgeEvaluatorConfig,
entity_classes: Vec<PIIClass>,
}
impl PIILeakageEvaluator {
#[must_use]
pub fn new(config: JudgeEvaluatorConfig) -> Self {
Self {
config: with_safety_default(config),
entity_classes: PIIClass::all_builtin(),
}
}
#[must_use]
pub fn with_classes(config: JudgeEvaluatorConfig, entity_classes: Vec<PIIClass>) -> Self {
Self {
config: with_safety_default(config),
entity_classes,
}
}
#[must_use]
pub fn with_prompt(mut self, template: Arc<dyn crate::prompt::JudgePromptTemplate>) -> Self {
self.config = self.config.with_prompt(template);
self
}
#[must_use]
pub fn with_few_shot(mut self, examples: Vec<crate::types::FewShotExample>) -> Self {
self.config = self.config.with_few_shot(examples);
self
}
#[must_use]
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.config = self.config.with_system_prompt(prompt);
self
}
#[must_use]
pub fn with_output_schema(mut self, schema: serde_json::Value) -> Self {
self.config = self.config.with_output_schema(schema);
self
}
#[must_use]
pub fn with_use_reasoning(mut self, flag: bool) -> Self {
self.config = self.config.with_use_reasoning(flag);
self
}
#[must_use]
pub fn with_feedback_key(mut self, key: impl Into<String>) -> Self {
self.config = self.config.with_feedback_key(key);
self
}
#[must_use]
pub fn entity_classes(&self) -> &[PIIClass] {
&self.entity_classes
}
#[must_use]
pub const fn config(&self) -> &JudgeEvaluatorConfig {
&self.config
}
}
impl crate::evaluators::JudgeEvaluatorBuilder for PIILeakageEvaluator {
fn judge_config_mut(&mut self) -> &mut JudgeEvaluatorConfig {
&mut self.config
}
}
impl Evaluator for PIILeakageEvaluator {
fn name(&self) -> &'static str {
"pii_leakage"
}
fn evaluate(&self, case: &EvalCase, invocation: &Invocation) -> Option<EvalMetricResult> {
if !has_final_response(case, invocation) {
return None;
}
let mut ctx = build_prompt_context(&self.config, case, invocation);
let classes: Vec<serde_json::Value> = self
.entity_classes
.iter()
.map(|c| serde_json::Value::String(c.canonical_name()))
.collect();
ctx = ctx.with_custom(std::collections::HashMap::from([(
"pii_entity_classes".to_string(),
serde_json::Value::Array(classes),
)]));
Some(evaluate_with_builtin(
"pii_leakage",
"pii_leakage_v0",
&self.config,
&ctx,
))
}
}