ai-agents-reasoning 1.0.0-rc.15

Reasoning and reflection capabilities for AI Agents framework
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvaluationResult {
    pub passed: bool,
    pub confidence: f32,
    pub criteria_results: Vec<CriterionResult>,
}

impl EvaluationResult {
    pub fn new(passed: bool, confidence: f32) -> Self {
        Self {
            passed,
            confidence,
            criteria_results: Vec::new(),
        }
    }

    pub fn with_criteria(mut self, criteria: Vec<CriterionResult>) -> Self {
        self.criteria_results = criteria;
        self
    }

    pub fn passed_all(&self) -> bool {
        self.criteria_results.iter().all(|c| c.passed)
    }

    pub fn failed_criteria(&self) -> impl Iterator<Item = &CriterionResult> {
        self.criteria_results.iter().filter(|c| !c.passed)
    }

    pub fn passing_criteria(&self) -> impl Iterator<Item = &CriterionResult> {
        self.criteria_results.iter().filter(|c| c.passed)
    }

    pub fn pass_rate(&self) -> f32 {
        if self.criteria_results.is_empty() {
            return if self.passed { 1.0 } else { 0.0 };
        }
        let passed = self.criteria_results.iter().filter(|c| c.passed).count();
        passed as f32 / self.criteria_results.len() as f32
    }
}

impl Default for EvaluationResult {
    fn default() -> Self {
        Self {
            passed: false,
            confidence: 0.0,
            criteria_results: Vec::new(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriterionResult {
    pub criterion: String,
    pub passed: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
}

impl CriterionResult {
    pub fn pass(criterion: impl Into<String>) -> Self {
        Self {
            criterion: criterion.into(),
            passed: true,
            reason: None,
        }
    }

    pub fn fail(criterion: impl Into<String>, reason: impl Into<String>) -> Self {
        Self {
            criterion: criterion.into(),
            passed: false,
            reason: Some(reason.into()),
        }
    }

    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
        self.reason = Some(reason.into());
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReflectionAttempt {
    pub response: String,
    pub evaluation: EvaluationResult,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub feedback: Option<String>,
}

impl ReflectionAttempt {
    pub fn new(response: impl Into<String>, evaluation: EvaluationResult) -> Self {
        Self {
            response: response.into(),
            evaluation,
            feedback: None,
        }
    }

    pub fn with_feedback(mut self, feedback: impl Into<String>) -> Self {
        self.feedback = Some(feedback.into());
        self
    }

    pub fn passed(&self) -> bool {
        self.evaluation.passed
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_evaluation_result_creation() {
        let result = EvaluationResult::new(true, 0.95);
        assert!(result.passed);
        assert_eq!(result.confidence, 0.95);
        assert!(result.criteria_results.is_empty());
    }

    #[test]
    fn test_evaluation_result_with_criteria() {
        let criteria = vec![
            CriterionResult::pass("Addresses question"),
            CriterionResult::fail("Complete response", "Response is truncated"),
            CriterionResult::pass("Helpful"),
        ];

        let result = EvaluationResult::new(false, 0.6).with_criteria(criteria);

        assert!(!result.passed);
        assert!(!result.passed_all());
        assert_eq!(result.failed_criteria().count(), 1);
        assert_eq!(result.passing_criteria().count(), 2);
        assert!((result.pass_rate() - 0.666).abs() < 0.01);
    }

    #[test]
    fn test_evaluation_result_pass_rate() {
        let empty = EvaluationResult::new(true, 1.0);
        assert_eq!(empty.pass_rate(), 1.0);

        let empty_failed = EvaluationResult::new(false, 0.0);
        assert_eq!(empty_failed.pass_rate(), 0.0);
    }

    #[test]
    fn test_criterion_result_pass() {
        let criterion = CriterionResult::pass("Response is clear");
        assert!(criterion.passed);
        assert_eq!(criterion.criterion, "Response is clear");
        assert!(criterion.reason.is_none());
    }

    #[test]
    fn test_criterion_result_fail() {
        let criterion = CriterionResult::fail("Accurate information", "Contains factual errors");
        assert!(!criterion.passed);
        assert_eq!(criterion.criterion, "Accurate information");
        assert_eq!(
            criterion.reason,
            Some("Contains factual errors".to_string())
        );
    }

    #[test]
    fn test_criterion_result_with_reason() {
        let criterion = CriterionResult::pass("Good response").with_reason("Excellent formatting");
        assert!(criterion.passed);
        assert_eq!(criterion.reason, Some("Excellent formatting".to_string()));
    }

    #[test]
    fn test_reflection_attempt_creation() {
        let evaluation = EvaluationResult::new(true, 0.9);
        let attempt = ReflectionAttempt::new("This is my response", evaluation);

        assert_eq!(attempt.response, "This is my response");
        assert!(attempt.passed());
        assert!(attempt.feedback.is_none());
    }

    #[test]
    fn test_reflection_attempt_with_feedback() {
        let evaluation = EvaluationResult::new(false, 0.4);
        let attempt = ReflectionAttempt::new("Initial response", evaluation)
            .with_feedback("Be more specific");

        assert!(!attempt.passed());
        assert_eq!(attempt.feedback, Some("Be more specific".to_string()));
    }

    #[test]
    fn test_evaluation_result_serde() {
        let criteria = vec![
            CriterionResult::pass("Clear"),
            CriterionResult::fail("Complete", "Missing details"),
        ];
        let result = EvaluationResult::new(false, 0.7).with_criteria(criteria);

        let json = serde_json::to_string(&result).unwrap();
        let parsed: EvaluationResult = serde_json::from_str(&json).unwrap();

        assert!(!parsed.passed);
        assert_eq!(parsed.confidence, 0.7);
        assert_eq!(parsed.criteria_results.len(), 2);
    }

    #[test]
    fn test_reflection_attempt_serde() {
        let evaluation = EvaluationResult::new(true, 0.95);
        let attempt = ReflectionAttempt::new("Response text", evaluation);

        let json = serde_json::to_string(&attempt).unwrap();
        let parsed: ReflectionAttempt = serde_json::from_str(&json).unwrap();

        assert_eq!(parsed.response, "Response text");
        assert!(parsed.passed());
    }
}