use std::path::Path;
use anyhow::{Context, Result};
use super::super::build_request;
use super::super::conditions::{Condition, assemble};
use super::super::judge::LlmJudge;
use super::super::model::{ModelFingerprint, ModelResponse, Recording};
use super::super::suite::{Domain, EvalSuite, Task};
use super::clone;
use super::lockfile::TestbenchLock;
const ARMS: [Condition; 2] = [Condition::Baseline, Condition::LeanCtx];
pub trait CannedModel {
fn answer(&self, repo: &str, task: &Task, condition: Condition) -> String;
fn judge(&self, repo: &str, task: &Task, candidate: &str) -> String;
}
pub fn build_recording(
lock: &TestbenchLock,
cache_dir: &Path,
fingerprint: ModelFingerprint,
budget_tokens: usize,
model: &dyn CannedModel,
) -> Result<Recording> {
let mut rec = Recording::new(fingerprint);
for entry in &lock.repos {
let repo_dir = clone::materialize(entry, lock.dir(), cache_dir)?;
let suite_path = lock.dir().join(&entry.suite);
let raw = std::fs::read_to_string(&suite_path)
.with_context(|| format!("reading suite {}", suite_path.display()))?;
let suite = EvalSuite::parse(&raw, repo_dir)
.with_context(|| format!("parsing suite for repo {}", entry.name))?;
for task in &suite.tasks {
let ws = task.workspace_path(&suite.dir);
for condition in ARMS {
let ctx = assemble(condition, &ws, task.query(), budget_tokens)?;
let answer = model.answer(&entry.name, task, condition);
let req = build_request(&ctx.text, &task.prompt);
rec.entries
.insert(req.key(), ModelResponse::new(answer.clone()));
if task.domain == Domain::Qa {
let jreq = LlmJudge::request(task, &answer);
let verdict = model.judge(&entry.name, task, &answer);
rec.entries.insert(jreq.key(), ModelResponse::new(verdict));
}
}
}
}
Ok(rec)
}