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}