aimx/inference/
validate.rs

1//! Response validation for AIM (Agentic Inference Markup) workflows.
2//!
3//! This module provides the core functionality for validating and assigning 
4//! inference responses to AIM rules. The validation process ensures type safety 
5//! and proper assignment of values to rules that are designed to receive inference
6//! results from AI models.
7//!
8//! # Overview
9//!
10//! The validation system bridges the gap between raw AI model responses and 
11//! structured AIM workflow rules. It follows a systematic process:
12//!
13//! 1. **Rule Identification** - Iterates through all rules in an AIM structure
14//! 2. **Assignment Detection** - Identifies assignment rules (underscore prefixes)
15//! 3. **Response Matching** - Converts rule identifiers to uppercase for matching
16//! 4. **Type Validation** - Validates responses against rule type definitions
17//! 5. **Value Assignment** - Assigns validated values to rules via context
18//!
19//! This module is designed to be fault-tolerant, recognizing that AI model responses
20//! can be probabilistic and sometimes malformed.
21//!
22//! # Assignment Rule Convention
23//!
24//! AIM workflows use a special naming convention for rules that accept inference results:
25//! - **Assignment Rules**: Identifiers starting with `_` (e.g., `_summary`, `_sentiment`)
26//! - **Uppercase Matching**: Rules like `_example` match responses keyed as `EXAMPLE`
27//!
28//! This convention allows workflows to clearly separate inference-assigned values from
29//! statically defined ones.
30//!
31//! # Typical Usage Flow
32//!
33//! ```text
34//! AI Model Response → Response Parsing → Validation → Rule Assignment
35//! ```
36//!
37//! The module is typically used after parsing inference responses to automatically
38//! populate AIM rules with validated results.
39
40use crate::{
41    ContextLike,
42    Reference,
43    inference::Response,
44    WorkflowLike,
45};
46use std::{
47    collections::HashMap,
48    sync::Arc,
49};
50use anyhow::{anyhow, Result};
51
52/// Validates inference responses and assigns them to matching assignment rules in an AIM structure.
53///
54/// This function processes a collection of parsed inference responses and assigns them to
55/// corresponding assignment rules in the AIM structure. Assignment rules are identified by
56/// their underscore prefix (e.g., `_example`) which gets converted to an uppercase identifier
57/// (e.g., `EXAMPLE`) for matching with responses.
58///
59/// # Process
60///
61/// The validation follows a systematic workflow:
62///
63/// 1. **Rule Enumeration** - Iterates through all rules in the AIM structure
64/// 2. **Assignment Detection** - Identifies rules with underscore prefixes (`_`)  
65/// 3. **Identifier Conversion** - Converts rule identifiers to uppercase for matching
66/// 4. **Response Matching** - Finds responses with matching uppercase identifiers
67/// 5. **Type Validation** - Validates responses against rule type definitions
68/// 6. **Value Assignment** - Assigns validated values through the execution context
69///
70/// # Fault Tolerance
71///
72/// The validation process is intentionally fault-tolerant to handle the probabilistic
73/// nature of AI model responses:
74///
75/// - **Non-matching Responses**: Silently ignores responses without matching assignment rules
76/// - **Type Validation Failures**: Silently ignores responses that fail type conversion
77/// - **Partial Success**: Returns success if at least one response is validated
78///
79/// This design ensures that workflows continue even when some inference responses
80/// are malformed or unexpected.
81///
82/// # Parameters
83///
84/// * `workflow` - An [`Arc`] reference to the workflow containing assignment rules
85/// * `context` - A mutable reference to the execution context for value assignment
86/// * `responses` - A [`HashMap`] of parsed responses, keyed by uppercase identifiers
87///
88/// # Returns
89///
90/// * `Ok(usize)` - Number of successfully validated and assigned responses
91/// * `Err(anyhow::Error)` - If no responses were validated, containing the last error
92///
93/// # Example
94///
95/// ```rust
96/// use aimx::{
97///     inference::{validate_responses, Response, Item},
98///     Context, Workflow, Rule, Typedef, Expression, Value, Literal, Prefix
99/// };
100/// use std::collections::HashMap;
101/// use std::sync::Arc;
102///
103/// // Create a workflow with an assignment rule
104/// let mut workflow = Workflow::new();
105/// let rule = Rule::new(
106///     "_answer".to_string(), 
107///     Typedef::Number, 
108///     Expression::Empty, 
109///     Value::Empty
110/// );
111/// workflow.append_or_update(Some(rule));
112/// 
113/// // Create a context
114/// let mut context = Context::new();
115/// 
116/// // Create a response map with a matching response
117/// let mut responses = HashMap::new();
118/// let item = Item::Value(Prefix::None, "42".to_string());
119/// responses.insert("ANSWER".to_string(), Response::Single(item));
120/// 
121/// // Validate responses (this would assign 42 to the _answer rule)
122/// let result = validate_responses(Arc::new(workflow), &mut context, responses);
123/// // Note: In a real scenario, this would succeed if the context could properly set the value
124/// ```
125pub fn validate_responses(workflow: Arc<dyn WorkflowLike>, context: &mut dyn ContextLike, responses: HashMap<String, Response>) -> Result<usize> {
126    let mut error = anyhow!("Nothing to assign");
127    let mut validated_responses: usize = 0;
128
129    // Iterate through each rule in the workflow to find assignment rules
130    for rule in workflow.iter_rules() {
131        // Identify assignment rules (those starting with underscore prefix)
132        if rule.is_assignment() {
133            // Convert rule identifier to uppercase for matching with responses
134            // Example: "_example" becomes "EXAMPLE"
135            let ucid = rule.ucid();
136            
137            // Check if there's a matching response for this assignment rule
138            if let Some(response) = responses.get(&ucid) {
139                // Attempt to convert the response to the rule's expected type
140                // This performs type validation and conversion
141                if let Ok(value) = response.to_value(rule.typedef()) {
142                    // Create a reference to the rule for assignment
143                    let identifier = rule.identifier();
144                    let reference = Reference::One(identifier.to_string());
145                    
146                    // Assign the validated value through the context
147                    // The context handles the actual assignment mechanism
148                    match context.set_referenced(&reference, value) {
149                        Ok(_) => validated_responses += 1,
150                        Err(e) => error = e,
151                    }
152                }
153                // Note: Type conversion failures are silently ignored
154                // This is intentional fault tolerance for probabilistic AI responses
155            }
156            // Note: Non-matching responses are silently ignored
157            // This allows workflows to continue even with partial responses
158        }
159    }
160
161    // Return success if at least one response was validated
162    // Return error only if no responses could be assigned
163    if validated_responses > 0 {
164        Ok(validated_responses)
165    }
166    else {
167        Err(error)
168    }
169}