use super::runner::WorkspaceRetention;
use crate::evals::{RetainedWorkspaceInfo, TaskRun};
use schemars::{JsonSchema, Schema, schema_for};
use serde::Serialize;
use serde_json::Value;
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EvalFilesReport {
pub evals: Vec<EvalOutcome>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct EvalOutcome {
pub name: String,
pub passed: bool,
pub failures: Vec<String>,
pub tool_calls: Vec<EvalToolCall>,
#[serde(skip_serializing_if = "Option::is_none")]
pub judge: Option<JudgeSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_context: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retained_workspace: Option<RetainedWorkspaceInfo>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct EvalToolCall {
pub name: String,
pub arguments: Option<Value>,
pub raw_arguments: String,
}
impl EvalOutcome {
pub fn schema() -> Schema {
schema_for!(Self)
}
pub(crate) fn setup_failed(name: impl Into<String>, error: impl std::fmt::Display) -> Self {
Self::failed(name, vec![format!("workspace setup failed: {error}")])
}
pub(crate) fn failed(name: impl Into<String>, failures: Vec<String>) -> Self {
Self {
name: name.into(),
passed: false,
failures,
tool_calls: Vec::new(),
judge: None,
failure_context: None,
retained_workspace: None,
}
}
pub(crate) fn from_task_run(
name: impl Into<String>,
run: TaskRun,
failures: Vec<String>,
judge: Option<JudgeSummary>,
retention: WorkspaceRetention,
) -> Self {
let passed = failures.is_empty();
let failure_context = (!passed).then(|| run.failure_context());
let tool_calls = EvalToolCall::from_task_run(&run);
let retained_workspace = match retention {
WorkspaceRetention::Retain => Some(run.into_workspace().persist()),
WorkspaceRetention::Discard => None,
};
Self { name: name.into(), passed, failures, tool_calls, judge, failure_context, retained_workspace }
}
}
impl EvalToolCall {
fn from_task_run(run: &TaskRun) -> Vec<Self> {
run.transcript()
.all_tool_calls()
.map(|call| Self {
name: call.name.to_string(),
arguments: call.arguments_json().ok(),
raw_arguments: call.arguments.to_string(),
})
.collect()
}
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct JudgeSummary {
pub passed: bool,
pub score: f64,
pub reason: String,
pub criteria: Vec<JudgeCriterionSummary>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct JudgeCriterionSummary {
pub id: String,
pub description: String,
pub blocking: bool,
pub weight: f64,
pub threshold: f64,
pub score: f64,
pub reason: String,
}
impl EvalFilesReport {
pub fn passed(&self) -> bool {
self.evals.iter().all(|eval| eval.passed)
}
pub fn passed_count(&self) -> usize {
self.evals.iter().filter(|eval| eval.passed).count()
}
pub fn failed_count(&self) -> usize {
self.evals.iter().filter(|eval| !eval.passed).count()
}
}
impl JudgeSummary {
pub fn blocking_failures(&self) -> impl Iterator<Item = String> + '_ {
self.criteria
.iter()
.filter(|criterion| criterion.blocking && !criterion.passed())
.map(|criterion| format!("judge criterion `{}`: {}", criterion.id, criterion.reason))
}
}
impl JudgeCriterionSummary {
pub fn passed(&self) -> bool {
self.score >= self.threshold
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blocking_failures_report_only_blocking_criteria_below_threshold() {
let criterion = |id: &str, blocking, score| JudgeCriterionSummary {
id: id.to_string(),
description: "desc".to_string(),
blocking,
weight: 1.0,
threshold: 0.8,
score,
reason: format!("{id} reason"),
};
let summary = JudgeSummary {
passed: false,
score: 0.0,
reason: "r".to_string(),
criteria: vec![
criterion("met", true, 0.9),
criterion("failed", true, 0.5),
criterion("advisory", false, 0.0),
],
};
let failures: Vec<String> = summary.blocking_failures().collect();
assert_eq!(failures, vec!["judge criterion `failed`: failed reason".to_string()]);
}
}