use std::path::PathBuf;
use std::sync::Arc;
use crate::evaluator::Evaluator;
use crate::evaluators::{
JudgeEvaluatorConfig, build_prompt_context, dispatch_judge, finish_metric_result,
materialize_case_attachments,
};
use crate::prompt::PromptTemplateRegistry;
use crate::score::Score;
use crate::types::{EvalCase, EvalMetricResult, Invocation};
use crate::url_filter::{DefaultUrlFilter, UrlFilter};
pub struct ImageSafetyEvaluator {
name: &'static str,
config: JudgeEvaluatorConfig,
eval_set_root: PathBuf,
url_filter: Arc<dyn UrlFilter>,
}
impl ImageSafetyEvaluator {
#[must_use]
pub fn new(config: JudgeEvaluatorConfig, eval_set_root: impl Into<PathBuf>) -> Self {
Self {
name: "image_safety",
config,
eval_set_root: eval_set_root.into(),
url_filter: Arc::new(DefaultUrlFilter),
}
}
#[must_use]
pub const fn with_name(mut self, name: &'static str) -> Self {
self.name = name;
self
}
#[must_use]
pub fn with_url_filter(mut self, filter: Arc<dyn UrlFilter>) -> Self {
self.url_filter = filter;
self
}
#[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 ImageSafetyEvaluator {
fn judge_config_mut(&mut self) -> &mut JudgeEvaluatorConfig {
&mut self.config
}
}
impl Evaluator for ImageSafetyEvaluator {
fn name(&self) -> &'static str {
self.name
}
fn evaluate(&self, case: &EvalCase, invocation: &Invocation) -> Option<EvalMetricResult> {
if case.attachments.is_empty() {
return None;
}
let builtin = PromptTemplateRegistry::builtin()
.get("image_safety_v0")
.expect("image_safety_v0 registered on PR #818");
let materialize =
materialize_case_attachments(case, &self.eval_set_root, &*self.url_filter);
let materialized = match crate::evaluators::block_on(materialize) {
Ok(materialized) => materialized,
Err(err) => {
return Some(EvalMetricResult {
evaluator_name: self.name.to_string(),
score: Score::fail(),
details: Some(format!("attachment materialization failed: {err}")),
});
}
};
let mut custom = std::collections::HashMap::new();
custom.insert(
"materialized_attachments".to_string(),
serde_json::Value::Number(serde_json::Number::from(materialized.len())),
);
let ctx = build_prompt_context(&self.config, case, invocation).with_custom(custom);
let outcome = match crate::evaluators::block_on(dispatch_judge(&self.config, builtin, &ctx))
{
Ok(outcome) => outcome,
Err(err) => {
return Some(EvalMetricResult {
evaluator_name: self.name.to_string(),
score: Score::fail(),
details: Some(format!("dispatch error: {err}")),
});
}
};
Some(finish_metric_result(self.name.to_string(), outcome))
}
}