use super::{ReviewContext, ReviewDecision};
pub trait HumanReviewer: Send + Sync {
fn review(&self, ctx: ReviewContext<'_>) -> ReviewDecision;
fn on_progress(&self, _iteration: u32, _score: f64) {}
fn on_iteration_start(&self, _iteration: u32) {}
fn on_iteration_complete(&self, _iteration: u32, _score: f64) {}
fn on_complete(&self, _final_score: f64, _total_iterations: u32) {}
fn on_error(&self, _error: &str) {}
}
#[async_trait::async_trait]
pub trait AsyncHumanReviewer: Send + Sync {
async fn review(&self, ctx: ReviewContext<'_>) -> ReviewDecision;
async fn on_progress(&self, _iteration: u32, _score: f64) {}
async fn on_iteration_start(&self, _iteration: u32) {}
async fn on_iteration_complete(&self, _iteration: u32, _score: f64) {}
async fn on_complete(&self, _final_score: f64, _total_iterations: u32) {}
async fn on_error(&self, _error: &str) {}
}
#[derive(Debug, Clone, Default)]
pub struct AutoAcceptReviewer;
impl HumanReviewer for AutoAcceptReviewer {
fn review(&self, _ctx: ReviewContext<'_>) -> ReviewDecision {
ReviewDecision::Accept
}
}
#[derive(Debug, Clone)]
pub struct ThresholdReviewer {
pub threshold: f64,
}
impl ThresholdReviewer {
pub fn new(threshold: f64) -> Self {
Self { threshold }
}
}
impl HumanReviewer for ThresholdReviewer {
fn review(&self, ctx: ReviewContext<'_>) -> ReviewDecision {
if ctx.score >= self.threshold {
ReviewDecision::AcceptFinal
} else {
ReviewDecision::Accept
}
}
}
#[derive(Debug)]
pub struct RecordingReviewer {
pub contexts: std::sync::Mutex<Vec<RecordedReview>>,
pub default_decision: ReviewDecision,
}
impl Default for RecordingReviewer {
fn default() -> Self {
Self {
contexts: std::sync::Mutex::new(Vec::new()),
default_decision: ReviewDecision::Accept,
}
}
}
#[derive(Debug, Clone)]
pub struct RecordedReview {
pub iteration: u32,
pub score: f64,
pub trigger: super::ReviewTrigger,
pub output: String,
}
impl RecordingReviewer {
pub fn new() -> Self {
Self {
contexts: std::sync::Mutex::new(Vec::new()),
default_decision: ReviewDecision::Accept,
}
}
pub fn with_default(mut self, decision: ReviewDecision) -> Self {
self.default_decision = decision;
self
}
pub fn recordings(&self) -> Vec<RecordedReview> {
self.contexts
.lock()
.expect("reviewer recordings lock poisoned")
.clone()
}
}
impl HumanReviewer for RecordingReviewer {
fn review(&self, ctx: ReviewContext<'_>) -> ReviewDecision {
let recorded = RecordedReview {
iteration: ctx.iteration,
score: ctx.score,
trigger: ctx.trigger,
output: ctx.output.to_string(),
};
self.contexts
.lock()
.expect("reviewer recordings lock poisoned")
.push(recorded);
self.default_decision.clone()
}
}
pub struct CallbackReviewer<F>
where
F: Fn(ReviewContext<'_>) -> ReviewDecision + Send + Sync,
{
callback: F,
}
impl<F> CallbackReviewer<F>
where
F: Fn(ReviewContext<'_>) -> ReviewDecision + Send + Sync,
{
pub fn new(callback: F) -> Self {
Self { callback }
}
}
impl<F> HumanReviewer for CallbackReviewer<F>
where
F: Fn(ReviewContext<'_>) -> ReviewDecision + Send + Sync,
{
fn review(&self, ctx: ReviewContext<'_>) -> ReviewDecision {
(self.callback)(ctx)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auto_accept_reviewer() {
let reviewer = AutoAcceptReviewer;
let ctx = ReviewContext::new(0, 10, 0.5, "output", super::super::ReviewTrigger::Interval);
let decision = reviewer.review(ctx);
assert!(matches!(decision, ReviewDecision::Accept));
}
#[test]
fn test_threshold_reviewer_accept() {
let reviewer = ThresholdReviewer::new(0.8);
let ctx = ReviewContext::new(0, 10, 0.9, "output", super::super::ReviewTrigger::Interval);
let decision = reviewer.review(ctx);
assert!(matches!(decision, ReviewDecision::AcceptFinal));
}
#[test]
fn test_threshold_reviewer_continue() {
let reviewer = ThresholdReviewer::new(0.8);
let ctx = ReviewContext::new(0, 10, 0.5, "output", super::super::ReviewTrigger::Interval);
let decision = reviewer.review(ctx);
assert!(matches!(decision, ReviewDecision::Accept));
}
#[test]
fn test_recording_reviewer() {
let reviewer = RecordingReviewer::new();
let ctx1 = ReviewContext::new(0, 10, 0.5, "output1", super::super::ReviewTrigger::Interval);
reviewer.review(ctx1);
let ctx2 = ReviewContext::new(1, 10, 0.7, "output2", super::super::ReviewTrigger::Interval);
reviewer.review(ctx2);
let recordings = reviewer.recordings();
assert_eq!(recordings.len(), 2);
assert_eq!(recordings[0].iteration, 0);
assert_eq!(recordings[1].iteration, 1);
}
#[test]
fn test_callback_reviewer() {
let reviewer = CallbackReviewer::new(|ctx| {
if ctx.score >= 0.9 {
ReviewDecision::AcceptFinal
} else {
ReviewDecision::Accept
}
});
let ctx = ReviewContext::new(0, 10, 0.95, "output", super::super::ReviewTrigger::Interval);
let decision = reviewer.review(ctx);
assert!(matches!(decision, ReviewDecision::AcceptFinal));
}
}