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}