use crate::expect::Expectation;
use crate::harness::RunArtifacts;
pub trait Scorer {
type World;
type Expect;
fn score(
&self,
expect: &Self::Expect,
artifacts: &RunArtifacts,
world: &Self::World,
) -> (String, bool);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct BuiltinScorer;
impl Scorer for BuiltinScorer {
type World = ();
type Expect = Expectation;
fn score(&self, expect: &Expectation, artifacts: &RunArtifacts, _world: &()) -> (String, bool) {
match expect.evaluate(artifacts) {
Ok(result) => result,
Err(err) => (format!("{} [invalid: {err}]", expect.label()), false),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::harness::ToolCall;
use serde_json::json;
fn artifacts() -> RunArtifacts {
RunArtifacts {
tool_calls: vec![ToolCall::new(
"calculator",
json!({"op": "add", "a": 2, "b": 2}),
)],
final_text: Some("The answer is 4".to_owned()),
..RunArtifacts::default()
}
}
#[test]
fn builtin_scorer_evaluates_over_artifacts() {
let art = artifacts();
let (label, passed) = BuiltinScorer.score(
&Expectation::CalledToolWith {
tool: "calculator".into(),
args: json!({"op": "add"}),
},
&art,
&(),
);
assert!(passed);
assert!(label.starts_with("CalledToolWith("));
let (_, passed) = BuiltinScorer.score(
&Expectation::FinalNumberEquals {
value: 4.0,
tolerance: 0.0,
},
&art,
&(),
);
assert!(passed);
}
#[test]
fn builtin_scorer_reports_bad_regex_as_failed_labeled_predicate() {
let (label, passed) = BuiltinScorer.score(
&Expectation::FinalTextMatches { regex: "(".into() },
&artifacts(),
&(),
);
assert!(!passed, "a malformed regex scores as a failed predicate");
assert!(
label.contains("invalid:"),
"label names the regex error: {label}"
);
}
}