aimx/values/
eval.rs

1//! Evaluation scoring and statistics for AIMX expressions.
2//!
3//! This module provides the [`Eval`] type, which represents evaluation scoring
4//! statistics used in agentic workflow applications. An `Eval` value tracks
5//! success counts and total attempts, enabling statistical analysis of workflow
6//! performance, inference accuracy, and task completion rates.
7//!
8//! # Overview
9//!
10//! The [`Eval`] struct maintains two counters:
11//! - `score`: Number of successful evaluations/attempts
12//! - `count`: Total number of evaluations/attempts
13//!
14//! This enables calculation of success rates and statistical metrics for
15//! workflow performance monitoring and optimization.
16//!
17//! # Usage
18//!
19//! ```rust
20//! use aimx::values::Eval;
21//! use aimx::Value;
22//!
23//! // Create an evaluation with 3 successes out of 5 attempts
24//! let eval_value = Eval::new(3, 5);
25//! assert!(eval_value.is_eval());
26//!
27//! // To access the inner Eval struct, match on the Value::Eval variant
28//! if let Value::Eval(eval) = &eval_value {
29//!     // Record additional results
30//!     let passed = eval.set_pass();  // Increments both score and count
31//!     let failed = eval.set_fail();  // Increments count only
32//!
33//!     // Calculate success rate
34//!     let success_rate = eval.score();  // Returns Value::Literal(Literal::Number(0.6))
35//! }
36//! ```
37//!
38//! # Integration
39//!
40//! [`Eval`] values are integrated into the AIMX expression system as [`crate::value::Value::Eval`]
41//! variants. They can be parsed from strings, evaluated in contexts, and formatted
42//! for display like any other AIMX value.
43//!
44//! # Examples
45//!
46//! ## Parsing Evaluation Values
47//!
48//! ```rust
49//! use aimx::values::parse_eval;
50//!
51//! let input = "7, 10";
52//! let (remaining, eval_value) = parse_eval(input).unwrap();
53//! assert!(remaining.is_empty());
54//! assert!(eval_value.is_eval());
55//! ```
56//!
57//! ## Using in Expressions
58//!
59//! ```rust
60//! use aimx::{values::Eval, evaluate::ExpressionLike, context::Context};
61//!
62//! let eval = Eval::new(8, 10);
63//! let mut context = Context::new();
64//! let result = eval.evaluate(&mut context).unwrap();
65//! assert!(result.is_eval());
66//! ```
67//!
68//! ## Statistical Analysis
69//!
70//! ```rust
71//! use aimx::values::Eval;
72//! use aimx::Value;
73//!
74//! // Track performance over multiple iterations
75//! let eval_value = Eval::new(0, 0);
76//! 
77//! // Simulate some results (this is just an example)
78//! // We need to extract the Eval struct to call its methods
79//! let eval_value = if let Value::Eval(eval) = &eval_value {
80//!     eval.set_pass() // 1/1
81//! } else {
82//!     panic!("Expected Eval value");
83//! };
84//!
85//! let eval_value = if let Value::Eval(eval) = &eval_value {
86//!     eval.set_pass() // 2/2
87//! } else {
88//!     panic!("Expected Eval value");
89//! };
90//!
91//! let eval_value = if let Value::Eval(eval) = &eval_value {
92//!     eval.set_fail() // 2/3
93//! } else {
94//!     panic!("Expected Eval value");
95//! };
96//! 
97//! // Get success rate (2/3 = 0.666...)
98//! let success_rate = if let Value::Eval(eval) = &eval_value {
99//!     eval.score()
100//! } else {
101//!     panic!("Expected Eval value");
102//! };
103//!
104//! assert!(success_rate.is_number());
105//! ```
106
107use nom::{
108    IResult, Parser,
109    combinator::{map, opt},
110    bytes::complete::tag,
111    character::complete::multispace0,
112    sequence::delimited,
113};
114use crate::{
115    ContextLike,
116    ExpressionLike,
117    Literal,
118    literals::parse_unsigned,
119    Value,
120    Writer,
121};
122use std::fmt;
123use anyhow::Result;
124
125/// Evaluation scoring statistics for workflow performance tracking.
126///
127/// The `Eval` struct represents statistical data about evaluation attempts,
128/// typically used to track success rates, inference accuracy, or task completion
129/// metrics in agentic workflow applications.
130///
131/// # Fields
132///
133/// - `score`: Number of successful evaluations/attempts
134/// - `count`: Total number of evaluations/attempts
135///
136/// # Examples
137///
138/// ```rust
139/// use aimx::values::Eval;
140///
141/// // Create evaluation with 75% success rate
142/// let eval_value = Eval::new(3, 4);
143/// assert!(eval_value.is_eval());
144/// 
145/// // Record additional results by extracting the Eval struct
146/// let after_pass = if let aimx::Value::Eval(eval) = &eval_value {
147///     eval.set_pass()  // Now 4/5
148/// } else {
149///     panic!("Expected Eval value");
150/// };
151/// 
152/// let after_fail = if let aimx::Value::Eval(eval) = &after_pass {
153///     eval.set_fail()  // Now 4/6
154/// } else {
155///     panic!("Expected Eval value");
156/// };
157///
158/// // Calculate current success rate
159/// let rate = if let aimx::Value::Eval(eval) = &after_fail {
160///     eval.score()
161/// } else {
162///     panic!("Expected Eval value");
163/// };
164/// ```
165///
166/// # Statistical Properties
167///
168/// - **Success Rate**: `score / count` as a floating-point number
169/// - **Sample Size**: `count` determines statistical significance
170/// - **Confidence**: Higher `count` values provide more reliable metrics
171///
172/// # Integration
173///
174/// `Eval` values are wrapped in [`crate::value::Value::Eval`] variants and can
175/// be used throughout the AIMX expression system. They implement the
176/// [`crate::evaluate::ExpressionLike`] trait for seamless integration.
177#[derive(Debug, Clone, PartialEq)]
178pub struct Eval {
179    score: u32,
180    count: u32,
181}
182
183impl Eval {
184    /// Create a new evaluation value with the specified score and count.
185    ///
186    /// This method constructs an `Eval` value wrapped in a [`crate::value::Value::Eval`]
187    /// variant. The resulting value can be used directly in AIMX expressions.
188    ///
189    /// # Arguments
190    ///
191    /// * `score` - Number of successful evaluations/attempts
192    /// * `count` - Total number of evaluations/attempts
193    ///
194    /// # Returns
195    ///
196    /// Returns a [`crate::value::Value::Eval`] variant containing the evaluation statistics.
197    ///
198    /// # Examples
199    ///
200    /// ```rust
201    /// use aimx::values::Eval;
202    ///
203    /// let eval_value = Eval::new(5, 10);
204    /// assert!(eval_value.is_eval());
205    /// ```
206    pub fn new(score: u32, count: u32,) -> Value {
207        Value::Eval(Eval{score, count})
208    }
209
210    /// Record a successful evaluation attempt.
211    ///
212    /// This method returns a new evaluation value with both the score and count
213    /// incremented by 1, representing a successful outcome.
214    ///
215    /// # Returns
216    ///
217    /// Returns a new [`crate::value::Value::Eval`] with updated statistics.
218    ///
219    /// # Examples
220    ///
221    /// ```rust
222    /// use aimx::values::Eval;
223    ///
224    /// let eval_value = Eval::new(3, 5);
225    /// assert!(eval_value.is_eval());
226    /// 
227    /// let after_success = if let aimx::Value::Eval(eval) = &eval_value {
228    ///     eval.set_pass()
229    /// } else {
230    ///     panic!("Expected Eval value");
231    /// };
232    /// // Now represents 4 successes out of 6 attempts
233    /// assert!(after_success.is_eval());
234    /// ```
235    pub fn set_pass(&self) -> Value {
236        Self::new(self.score + 1, self.count + 1)
237    }
238
239    /// Record a failed evaluation attempt.
240    ///
241    /// This method returns a new evaluation value with only the count
242    /// incremented by 1, representing a failed outcome.
243    ///
244    /// # Returns
245    ///
246    /// Returns a new [`crate::value::Value::Eval`] with updated statistics.
247    ///
248    /// # Examples
249    ///
250    /// ```rust
251    /// use aimx::values::Eval;
252    ///
253    /// let eval_value = Eval::new(3, 5);
254    /// assert!(eval_value.is_eval());
255    /// 
256    /// let after_failure = if let aimx::Value::Eval(eval) = &eval_value {
257    ///     eval.set_fail()
258    /// } else {
259    ///     panic!("Expected Eval value");
260    /// };
261    /// // Now represents 3 successes out of 6 attempts
262    /// assert!(after_failure.is_eval());
263    /// ```
264    pub fn set_fail(&self) -> Value {
265        Self::new(self.score, self.count + 1)
266    }
267
268    /// Calculate and return the success rate as a numeric value.
269    ///
270    /// This method computes the success rate as `score / count` and returns
271    /// it as a [`crate::value::Value::Literal`] containing a floating-point number.
272    /// If count is 0, returns 0.0 to avoid division by zero.
273    ///
274    /// # Returns
275    ///
276    /// Returns a [`crate::value::Value::Literal`] with the calculated success rate.
277    ///
278    /// # Examples
279    ///
280    /// ```rust
281    /// use aimx::values::Eval;
282    ///
283    /// let eval_value = Eval::new(3, 4);
284    /// assert!(eval_value.is_eval());
285    /// 
286    /// let rate = if let aimx::Value::Eval(eval) = &eval_value {
287    ///     eval.score()
288    /// } else {
289    ///     panic!("Expected Eval value");
290    /// };
291    /// // rate is Value::Literal(Literal::Number(0.75))
292    /// assert!(rate.is_number());
293    /// ```
294    pub fn score(&self) -> Value {
295        if self.count == 0 {
296            Value::Literal(Literal::Number(0.0))
297        } else {
298            Value::Literal(Literal::Number(self.score as f64 / self.count as f64))
299        }
300    }
301}
302
303/// Parse an evaluation value from string input.
304///
305/// This function parses a string representation of an evaluation value in the
306/// format `"score, count"`, where both `score` and `count` are unsigned integers.
307/// The input is expected to be a comma-separated pair of numbers with optional
308/// whitespace around the comma.
309///
310/// # Arguments
311///
312/// * `input` - The input string to parse. Expected format: `"score, count"`
313///
314/// # Returns
315///
316/// Returns an `IResult<&str, Value>` containing the remaining input and the
317/// parsed evaluation value as a [`crate::value::Value::Eval`] variant.
318///
319/// # Examples
320///
321/// ```rust
322/// use aimx::values::parse_eval;
323///
324/// let input = "7, 10";
325/// let (remaining, eval_value) = parse_eval(input).unwrap();
326/// assert!(remaining.is_empty());
327/// assert!(eval_value.is_eval());
328/// 
329/// // Also accepts formats without spaces
330/// let input = "5,8";
331/// let (_, eval_value) = parse_eval(input).unwrap();
332/// assert!(eval_value.is_eval());
333/// ```
334pub fn parse_eval(input: &str) -> IResult<&str, Value> {
335    map(
336        (
337            parse_unsigned,
338            parse_comma_separator,
339            parse_unsigned,
340        ),
341        |(score, _, count)| Value::Eval(Eval{score, count}),
342    )
343    .parse(input)
344}
345
346/// Parse a comma separator with optional whitespace.
347///
348/// This helper function parses a comma character with optional whitespace
349/// around it. It's used internally by [`parse_eval`] to separate the score
350/// and count values in the input string.
351///
352/// # Arguments
353///
354/// * `input` - The input string to parse
355///
356/// # Returns
357///
358/// Returns an `IResult<&str, &str>` containing the remaining input and
359/// the parsed comma separator.
360fn parse_comma_separator(input: &str) -> IResult<&str, &str> {
361    delimited(
362        opt(multispace0),
363        tag(","),
364        opt(multispace0)
365    ).parse(input)
366}
367
368impl ExpressionLike for Eval {
369    /// Evaluate this evaluation value in the provided context.
370    ///
371    /// Since `Eval` values are constant (they represent statistical data),
372    /// this method simply returns a clone of the current value without
373    /// performing any context-dependent computation.
374    ///
375    /// # Arguments
376    ///
377    /// * `_context` - The evaluation context (unused for `Eval` values)
378    ///
379    /// # Returns
380    ///
381    /// Returns `Ok(Value::Eval(self.clone()))` containing the evaluation statistics.
382    fn evaluate(&self, _context: &mut dyn ContextLike) -> Result<Value> {
383        Ok(Value::Eval(self.clone()))
384    }
385
386    /// Write this evaluation value to the provided writer.
387    ///
388    /// This method formats the evaluation statistics as `"score, count"`
389    /// and writes them to the writer using the appropriate formatting.
390    ///
391    /// # Arguments
392    ///
393    /// * `writer` - The writer to write the formatted evaluation to
394    fn write(&self, writer: &mut Writer) {
395        writer.write_unsigned(self.score);
396        writer.write_str(", ");
397        writer.write_unsigned(self.count);
398    }
399    
400    /// Convert this evaluation value to a sanitized string representation.
401    ///
402    /// This method produces a string with special characters escaped
403    /// to make it safe for various contexts. For `Eval` values, this
404    /// is equivalent to the standard string representation.
405    ///
406    /// # Returns
407    ///
408    /// A sanitized string representation of this evaluation value.
409    fn to_sanitized(&self) -> String {
410        let mut writer = Writer::sanitizer();
411        self.write(&mut writer);
412        writer.finish()
413    }
414    
415    /// Convert this evaluation value to a formula string representation.
416    ///
417    /// This method produces a string with proper quoting and escaping
418    /// for use in formulas. For `Eval` values, this is equivalent to
419    /// the standard string representation.
420    ///
421    /// # Returns
422    ///
423    /// A formula string representation of this evaluation value.
424    fn to_formula(&self) -> String {
425        let mut writer = Writer::formulizer();
426        self.write(&mut writer);
427        writer.finish()
428    }
429}
430
431impl fmt::Display for Eval {
432    /// Format this evaluation value for display.
433    ///
434    /// This implementation formats the evaluation statistics as `"score, count"`
435    /// using the standard string representation.
436    ///
437    /// # Arguments
438    ///
439    /// * `f` - The formatter to write to
440    ///
441    /// # Returns
442    ///
443    /// A `fmt::Result` indicating success or failure of the formatting operation.
444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        let mut writer = Writer::stringizer();
446        self.write(&mut writer);
447        write!(f, "{}", writer.finish())
448    }
449}