use rig::vector_store::VectorStoreIndexDyn;
use serde::{Deserialize, Serialize};
use tracing::instrument;
use crate::dataset::Qrels;
use crate::error::Result;
use crate::harness::RetrievalHarness;
use crate::report::{MultiReport, ReportDiff};
use crate::retrieval::RetrievalMetric;
pub struct EvalShadowStore<'s> {
baseline: &'s dyn VectorStoreIndexDyn,
candidate: &'s dyn VectorStoreIndexDyn,
k: usize,
concurrency: usize,
}
impl<'s> EvalShadowStore<'s> {
#[must_use]
pub fn new(
baseline: &'s dyn VectorStoreIndexDyn,
candidate: &'s dyn VectorStoreIndexDyn,
k: usize,
) -> Self {
Self {
baseline,
candidate,
k,
concurrency: 1,
}
}
#[must_use]
pub fn with_concurrency(mut self, concurrency: usize) -> Self {
self.concurrency = concurrency.max(1);
self
}
#[must_use]
pub fn k(&self) -> usize {
self.k
}
#[instrument(level = "debug", skip(self, qrels, metrics), fields(k = self.k, concurrency = self.concurrency, queries = qrels.queries.len(), metrics = metrics.len()))]
pub async fn run(
&self,
qrels: &Qrels,
metrics: &[Box<dyn RetrievalMetric>],
) -> Result<ShadowEvalReport> {
let baseline_harness =
RetrievalHarness::new(self.baseline, self.k).with_concurrency(self.concurrency);
let candidate_harness =
RetrievalHarness::new(self.candidate, self.k).with_concurrency(self.concurrency);
let baseline_run = baseline_harness.run(qrels, metrics);
let current_run = candidate_harness.run(qrels, metrics);
let (baseline, current) = futures::try_join!(baseline_run, current_run)?;
let diff = current.diff(&baseline)?;
Ok(ShadowEvalReport {
baseline,
current,
diff,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShadowEvalReport {
pub baseline: MultiReport,
pub current: MultiReport,
pub diff: ReportDiff,
}
impl ShadowEvalReport {
#[must_use]
pub fn with_metadata(mut self, dataset: impl Into<String>, store: impl Into<String>) -> Self {
let dataset = dataset.into();
let store = store.into();
self.baseline = self
.baseline
.with_dataset(dataset.clone())
.with_store(store.clone());
self.current = self.current.with_dataset(dataset).with_store(store);
self
}
#[must_use]
pub fn to_markdown(&self) -> String {
format!(
"## Baseline\n{}\n## Current\n{}\n## Delta\n{}",
self.baseline.to_markdown(),
self.current.to_markdown(),
self.diff.to_markdown()
)
}
}