pub mod artifact;
pub mod conditions;
pub mod footprint;
pub mod judge;
pub mod model;
pub mod report;
pub mod routing_eval;
pub mod scorers;
pub mod suite;
pub mod testbench;
use anyhow::Result;
use conditions::{Condition, DEFAULT_BUDGET_TOKENS, assemble};
use model::{ModelRequest, ModelRunner};
use report::{AbReport, PairRecord, ReportConfig};
use scorers::score_task;
use suite::EvalSuite;
pub(crate) fn sha256_hex(bytes: &[u8]) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(bytes);
crate::core::agent_identity::hex_encode(&hasher.finalize())
}
pub(crate) const SYSTEM_PROMPT: &str = "You are a precise engineering assistant. Answer using only the provided CONTEXT. \
If the context does not contain the answer, say so. Be concise and correct.";
#[derive(Debug, Clone, Copy)]
pub struct AbRunConfig {
pub budget_tokens: usize,
pub report: ReportConfig,
}
impl Default for AbRunConfig {
fn default() -> Self {
Self {
budget_tokens: DEFAULT_BUDGET_TOKENS,
report: ReportConfig::default(),
}
}
}
pub(crate) fn build_request(context: &str, prompt: &str) -> ModelRequest {
ModelRequest {
system: SYSTEM_PROMPT.to_string(),
user: format!("CONTEXT:\n{context}\n\nTASK:\n{prompt}"),
}
}
pub fn run_ab(
suite: &EvalSuite,
suite_name: &str,
runner: &dyn ModelRunner,
cfg: &AbRunConfig,
) -> Result<AbReport> {
let mut records = Vec::with_capacity(suite.tasks.len());
for task in &suite.tasks {
let workspace = task.workspace_path(&suite.dir);
let base_ctx = assemble(
Condition::Baseline,
&workspace,
task.query(),
cfg.budget_tokens,
)?;
let lean_ctx = assemble(
Condition::LeanCtx,
&workspace,
task.query(),
cfg.budget_tokens,
)?;
let base_resp = runner.run(&build_request(&base_ctx.text, &task.prompt))?;
let lean_resp = runner.run(&build_request(&lean_ctx.text, &task.prompt))?;
let base_score = score_task(task, &base_resp.text, &workspace)?;
let lean_score = score_task(task, &lean_resp.text, &workspace)?;
records.push(PairRecord {
task_id: task.id.clone(),
domain: task.domain.label().to_string(),
baseline_value: base_score.value,
lean_ctx_value: lean_score.value,
baseline_passed: base_score.passed,
lean_ctx_passed: lean_score.passed,
baseline_tokens: base_ctx.tokens,
lean_ctx_tokens: lean_ctx.tokens,
baseline_context_digest: base_ctx.digest,
lean_ctx_context_digest: lean_ctx.digest,
baseline_answer_digest: base_resp.digest(),
lean_ctx_answer_digest: lean_resp.digest(),
});
}
Ok(AbReport::build(
suite_name,
cfg.budget_tokens,
runner.fingerprint().clone(),
records,
cfg.report,
))
}
#[cfg(test)]
mod tests {
use super::*;
use model::{ModelFingerprint, ModelParams, ModelResponse, RecordedRunner, Recording};
use std::path::PathBuf;
fn workspace(dir: &std::path::Path) {
std::fs::write(
dir.join("a-noise.md"),
"Completely unrelated notes about weather, cats, and lunch plans.\n".repeat(1_000),
)
.unwrap();
std::fs::write(
dir.join("z-answer.md"),
"Consolidation persists artifacts to bm25, graph, knowledge and session stores.",
)
.unwrap();
}
#[test]
fn full_pipeline_runs_and_scores_deterministically() {
let root = tempfile::tempdir().unwrap();
let ws = root.path().join("corpus");
std::fs::create_dir_all(&ws).unwrap();
workspace(&ws);
let raw = r#"{"id":"t1","domain":"qa","prompt":"Which stores does consolidation persist to?","workspace":"corpus","answers":["bm25 graph knowledge session"]}"#;
let suite = EvalSuite::parse(raw, root.path().to_path_buf()).unwrap();
let task = &suite.tasks[0];
let cfg = AbRunConfig::default();
let base_ctx = assemble(Condition::Baseline, &ws, task.query(), cfg.budget_tokens).unwrap();
let lean_ctx = assemble(Condition::LeanCtx, &ws, task.query(), cfg.budget_tokens).unwrap();
let base_req = build_request(&base_ctx.text, &task.prompt);
let lean_req = build_request(&lean_ctx.text, &task.prompt);
assert_ne!(
base_req.key(),
lean_req.key(),
"fixture must keep baseline and lean-ctx requests distinct",
);
let fp = ModelFingerprint {
provider: model::PROVIDER_RECORDED.into(),
endpoint: "test".into(),
params: ModelParams {
model: "fixture".into(),
..ModelParams::default()
},
};
let mut rec = Recording::new(fp);
rec.entries
.insert(base_req.key(), ModelResponse::new("I don't know."));
rec.entries.insert(
lean_req.key(),
ModelResponse::new("bm25, graph, knowledge and session"),
);
let runner = RecordedRunner::new(rec);
let report = run_ab(&suite, "fixture-suite", &runner, &cfg).unwrap();
assert_eq!(report.records.len(), 1);
assert!(
report.stats.lean_ctx_mean > report.stats.baseline_mean,
"lean-ctx answer should outscore the baseline: {:?}",
report.stats
);
let report2 = run_ab(&suite, "fixture-suite", &runner, &cfg).unwrap();
assert_eq!(
artifact::determinism_digest(&report),
artifact::determinism_digest(&report2)
);
}
#[test]
fn run_ab_propagates_recorded_miss() {
let root = tempfile::tempdir().unwrap();
let ws = root.path().join("corpus");
std::fs::create_dir_all(&ws).unwrap();
workspace(&ws);
let raw = r#"{"id":"t1","domain":"qa","prompt":"q","workspace":"corpus","answers":["x"]}"#;
let suite = EvalSuite::parse(raw, root.path().to_path_buf()).unwrap();
let fp = ModelFingerprint {
provider: model::PROVIDER_RECORDED.into(),
endpoint: "test".into(),
params: ModelParams::default(),
};
let runner = RecordedRunner::new(Recording::new(fp));
assert!(run_ab(&suite, "s", &runner, &AbRunConfig::default()).is_err());
let _ = PathBuf::new();
}
}
#[cfg(test)]
mod accuracy_suite_tests {
use super::*;
use std::path::Path;
use suite::Domain;
fn load_accuracy_suite() -> EvalSuite {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("eval/accuracy-suite.ndjson");
EvalSuite::load(&path).expect("committed accuracy suite must load + validate")
}
#[test]
fn accuracy_suite_has_all_three_shapes() {
let suite = load_accuracy_suite();
let qa = suite
.tasks
.iter()
.filter(|t| t.domain == Domain::Qa)
.count();
let code = suite
.tasks
.iter()
.filter(|t| t.domain == Domain::Code)
.count();
assert!(qa >= 2, "need needle + long-context QA, got {qa}");
assert!(code >= 1, "need a code-edit task, got {code}");
}
#[test]
fn lean_ctx_compression_preserves_every_qa_answer() {
let suite = load_accuracy_suite();
let budget = AbRunConfig::default().budget_tokens;
let qa = suite.tasks.iter().filter(|t| t.domain == Domain::Qa);
for task in qa {
let ws = task.workspace_path(&suite.dir);
let ctx = assemble(Condition::LeanCtx, &ws, task.query(), budget)
.unwrap_or_else(|e| panic!("assemble {}: {e:#}", task.id));
let score = score_task(task, &ctx.text, &ws)
.unwrap_or_else(|e| panic!("score {}: {e:#}", task.id));
assert!(
score.passed,
"lean-ctx compression dropped the answer for '{}' ({})",
task.id, score.detail
);
}
}
#[test]
fn json_crush_condition_preserves_answer_and_beats_baseline() {
let suite = load_accuracy_suite();
let budget = AbRunConfig::default().budget_tokens;
let task = suite
.tasks
.iter()
.find(|t| t.id == "jsonqa-operator-clearance")
.expect("json-qa fixture present");
let ws = task.workspace_path(&suite.dir);
let crushed = assemble(Condition::JsonCrush, &ws, task.query(), budget)
.unwrap_or_else(|e| panic!("assemble json_crush: {e:#}"));
let baseline = assemble(Condition::Baseline, &ws, task.query(), budget)
.unwrap_or_else(|e| panic!("assemble baseline: {e:#}"));
let score = score_task(task, &crushed.text, &ws)
.unwrap_or_else(|e| panic!("score {}: {e:#}", task.id));
assert!(
score.passed,
"json_crush dropped the answer for '{}' ({})",
task.id, score.detail
);
assert!(
crushed.tokens < baseline.tokens,
"json_crush ({}) must beat the raw baseline ({}) on a redundant array",
crushed.tokens,
baseline.tokens
);
}
#[test]
fn code_task_is_solvable_and_stub_fails() {
let suite = load_accuracy_suite();
let task = suite
.tasks
.iter()
.find(|t| t.domain == Domain::Code)
.expect("code task present");
let ws = task.workspace_path(&suite.dir);
let reference = "factorial() { n=$1; [ \"$n\" -le 1 ] && { echo 1; return; }; \
r=1; i=2; while [ \"$i\" -le \"$n\" ]; do r=$((r * i)); i=$((i + 1)); done; echo \"$r\"; }";
let good = score_task(task, reference, &ws).unwrap();
assert!(good.passed, "reference solution must pass: {}", good.detail);
let stub = "factorial() { echo 0; }";
let bad = score_task(task, stub, &ws).unwrap();
assert!(!bad.passed, "wrong solution must fail the unit test");
}
}