aimx/inference/
modifier.rs

1use crate::{
2    aim::{ContextLike, Prefix, Rule, WorkflowLike},
3    expressions::ExpressionLike,
4    inference::OutputFormat,
5    values::Value,
6};
7use std::{collections::HashMap, sync::Arc};
8
9/// Collects workflow modifier rules into a prompt description.
10///
11/// Built from a `WorkflowLike` and `ContextLike`. Interprets rules as:
12/// - `ROLE`: system role/persona.
13/// - `INSTRUCTIONS`: global instructions.
14/// - `OUTPUT`: explicit output-format specification or free text.
15/// - `SYSTEM`: overrides default system prompt string.
16/// - `content`: main user content.
17/// - UPPERCASE rules: additional modifiers in [`Modifier::others`].
18/// - Assignment (`name`) + matching UPPERCASE format rules: captured as
19///   field specifications and examples.
20#[derive(Debug, PartialEq, Clone)]
21pub struct Modifier {
22    /// Value from `ROLE`.
23    role: Option<Arc<Value>>,
24    /// Value from `INSTRUCTIONS`.
25    instructions: Option<Arc<Value>>,
26    /// Value from `OUTPUT`.
27    output_format: Option<Arc<Value>>,
28    /// System prompt (default or `SYSTEM`).
29    system: Arc<str>,
30    /// User content from `content`.
31    user: Option<Arc<Value>>,
32    /// Other UPPERCASE modifiers.
33    others: Vec<(Arc<str>, Arc<Value>)>,
34    /// Assignment rules keyed by uppercased identifier.
35    assignments: HashMap<Arc<str>, Arc<Rule>>,
36    /// Assignment formats and examples.
37    examples: Vec<(Arc<str>, Arc<Value>)>,
38}
39
40impl Default for Modifier {
41    fn default() -> Self {
42        Self {
43            role: None,
44            instructions: None,
45            output_format: None,
46            system: Arc::from("You are a helpful assistant"),
47            user: None,
48            others: Vec::new(),
49            assignments: HashMap::new(),
50            examples: Vec::new(),
51        }
52    }
53}
54
55impl Modifier {
56    pub fn new(workflow: Arc<dyn WorkflowLike>, context: &mut dyn ContextLike) -> Self {
57        let mut modifier = Self::default();
58
59        // ----- FIND ASSIGNMENTS -----
60        // Iterate each rule
61        for rule in workflow.iter_rules() {
62            // Select assignment rules (rule identifiers with leading underscores)
63            if rule.is_assignment() {
64                // Convert _identifier (leading underscore) to IDENTIFIER (UPPERCASE)
65                let ucid = rule.ucid();
66                // Create a map of ucid inference rules
67                modifier.assignments.insert(ucid, rule.clone());
68            }
69        }
70
71        // Iterate each rule
72        for rule in workflow.iter_rules() {
73            let identifier = rule.identifier();
74            // content := USER TEXT (user prompt)
75            if &*identifier == "content" {
76                if let Ok(value) = rule.evaluate(context) {
77                    modifier.user = Some(value);
78                }
79            // Select modifiers (rule identifiers that are UPPERCASE)
80            } else if rule.is_modifier() {
81                // Role persona
82                if &*identifier == "ROLE" {
83                    if let Ok(value) = rule.evaluate(context) {
84                        modifier.role = Some(value);
85                    }
86                // Inference instructions
87                } else if &*identifier == "INSTRUCTIONS" {
88                    if let Ok(value) = rule.evaluate(context) {
89                        modifier.instructions = Some(value);
90                    }
91                // Output format instructions and extraction source examples
92                } else if &*identifier == "OUTPUT" {
93                    if let Ok(value) = rule.evaluate(context) {
94                        modifier.output_format = Some(value);
95                    }
96                // System prompt
97                } else if &*identifier == "SYSTEM" {
98                    if let Ok(value) = rule.evaluate(context) {
99                        modifier.system = value.to_pretty_str(Prefix::None);
100                    }
101                // Specific rule format, specification and examples
102                } else if rule.is_format() && modifier.assignments.contains_key(&identifier) {
103                    if let Ok(value) = rule.evaluate(context) {
104                        modifier.examples.push((identifier, value));
105                    }
106                // Other general prompt modifiers
107                } else if let Ok(value) = rule.evaluate(context) {
108                    modifier.others.push((identifier, value));
109                }
110            }
111        }
112
113        modifier
114    }
115
116    pub fn system_prompt(&self) -> Arc<str> {
117        self.system.clone()
118    }
119
120    /// Write the role section.
121    pub fn role(&self, format: &mut OutputFormat) {
122        if let Some(value) = &self.role {
123            format.value(value);
124        } else {
125            format.text("You are a helpful assistant.");
126        }
127        format.section_end();
128    }
129
130    /// Write all other UPPERCASE modifiers as sections.
131    pub fn others(&self, format: &mut OutputFormat) {
132        if !self.others.is_empty() {
133            for (identifier, value) in &self.others {
134                format.section_start(identifier);
135                format.unordered_value(value);
136            }
137            format.section_end();
138        }
139    }
140
141    /// Write INSTRUCTIONS from explicit rule or derived from assignment formats.
142    pub fn instructions(&self, format: &mut OutputFormat) {
143        format.section_start("INSTRUCTIONS");
144        if let Some(value) = self.instructions.clone() {
145            format.value(&value);
146        } else if !self.examples.is_empty() {
147            format.text("Derive the following values from the content:");
148        }
149        for (identifier, value) in self.examples.clone() {
150            if let Value::Format(f) = &*value {
151                format.single_item(&identifier, &f.instruction());
152            }
153        }
154        format.section_end();
155    }
156
157    /// Write OUTPUT FORMAT from explicit rule or assignment formats.
158    pub fn output_format(&self, format: &mut OutputFormat) {
159        format.section_start("OUTPUT FORMAT");
160        if let Some(value) = self.output_format.clone() {
161            if let Value::Format(f) = &*value {
162                format.text(&f.instruction());
163            } else {
164                format.value(&value);
165            }
166        } else if !self.examples.is_empty() {
167            format.text("Provide your answer in this exact format:");
168        }
169        for (identifier, value) in self.examples.clone() {
170            if let Value::Format(f) = &*value {
171                format.single_line_tagged(&identifier, &f.template());
172            }
173        }
174        format.section_end();
175    }
176
177    /// Write EXAMPLES from `OUTPUT` and assignment formats, if present.
178    pub fn examples(&self, format: &mut OutputFormat) {
179        format.section_start("EXAMPLES");
180        let mut index: usize = 0;
181        let mut length: usize = 1;
182        while index < length {
183            if let Some(value) = self.output_format.clone() && let Value::Format(f) = &*value {
184                length = f.examples().len();
185                if index < length {
186                    format.single_line_quoted("USER TEXT", &f.examples()[index]);
187                }
188            }
189            for (identifier, value) in self.examples.clone() {
190                if let Value::Format(f) = &*value {
191                    length = f.examples().len();
192                    if index < length {
193                        format.single_line(&identifier, &f.examples()[index]);
194                    }
195                }
196            }
197            format.section_end();
198            index += 1;
199        }
200    }
201
202    /// Write USER TEXT section if `content` exists.
203    pub fn user_text(&self, format: &mut OutputFormat) {
204        if let Some(value) = &self.user {
205            format.section_start("USER TEXT");
206            format.value(value);
207            format.section_end();
208        }
209    }
210}