aimx/inference/
prompt.rs

1//! Prompt generation for AI model inference
2//!
3//! This module generates structured prompts from AIMX workflow rules for AI inference. It transforms
4//! modifier rules (UPPERCASE identifiers) and assignment rules (`_` prefix) into well-formatted
5//! prompts that guide AI models to produce the desired outputs.
6//!
7//! # Overview
8//!
9//! When an AIMX expression contains an inference call (e.g., `$tool.function_name(args)`),
10//! the prompt generation system:
11//!
12//! 1. **Scans workflow rules** for relevant modifier and assignment rules
13//! 2. **Structures the prompt** into logical sections (ROLE, INSTRUCTIONS, OUTPUT FORMAT, etc.)
14//! 3. **Formats output rules** with examples and validation criteria
15//! 4. **Generates system and user prompts** optimized for the model's capability level
16//!
17//! # Core Concepts
18//!
19//! ## Modifier Rules
20//!
21//! Modifier rules are workflow rules with UPPERCASE identifiers that construct the prompt:
22//!
23//! - **`ROLE`**: Defines the AI's persona and primary function
24//! - **`INSTRUCTIONS`**: Provides task-specific guidance
25//! - **`OUTPUT`**: Specifies the desired output format and validation rules
26//! - **`SYSTEM`**: Overrides the default system prompt
27//! - **Other UPPERCASE rules**: General prompt modifiers and guidelines
28//!
29//! ## Assignment Rules
30//!
31//! Assignment rules (prefixed with `_`) are the targets that AI inference will populate:
32//!
33//! - `_variable_name`: Populated by AI with the parsed value
34//! - Rules with `Value::Format` provide formatting instructions and examples
35//!
36//! ## Capability-Based Formatting
37//!
38//! The prompt structure adapts to the model's capability level:
39//!
40//! - **`Capability::Minimal`**: Simple prompts with basic formatting
41//! - **`Capability::Limited`**: Moderate complexity with section headers
42//! - **`Capability::Standard`**: Full formatting with rich examples and instructions
43//!
44//! # Prompt Structure
45//!
46//! The generated prompt follows this standardized structure:
47//!
48//! ```text
49//! ROLE SECTION
50//! You are a helpful assistant.
51//!
52//! GUIDELINES SECTION
53//! - First guideline
54//! - Second guideline
55//! 
56//! INSTRUCTIONS SECTION
57//! Derive the following values from the content:
58//! - variable_name: Description of what to extract
59//! 
60//! OUTPUT FORMAT SECTION
61//! Provide your answer in this exact format:
62//! variable_name: <expected_format>
63//! 
64//! EXAMPLES SECTION
65//! USER TEXT: "Example input text"
66//! variable_name: Example output value
67//! 
68//! USER TEXT SECTION
69//! Actual input text to process
70//! ```
71//!
72//! # Error Handling
73//!
74//! The module returns [`anyhow::Result`] for error handling. Common errors include:
75//!
76//! - Missing assignment rules (nothing to populate)
77//! - Invalid rule configurations
78//! - Formatting errors during prompt generation
79//!
80//! # Integration with AIMX Workflows
81//!
82//! This module is typically used internally by the inference system when processing
83//! expressions like `$tool.function_name(args)`. The generated prompts are sent to
84//! AI providers, and the responses are used to populate assignment rules in workflows.
85
86use crate::{
87    inference::Capability,
88    WorkflowLike,
89    Rule,
90    Value,
91    Writer,
92    Prefix,
93};
94use std::{
95    collections::HashMap,
96    sync::Arc,
97};
98use anyhow::{Result, anyhow};
99
100/// Internal formatting helper for generating structured prompts
101///
102/// This struct handles the formatting details for different capability levels,
103/// ensuring that prompts are appropriately structured for the target AI model.
104/// It manages section headers, delimiters, and formatting styles.
105struct OutputFormat {
106    /// The writer used to generate the formatted prompt output
107    pub writer: Writer,
108    /// Prefix style for ordered lists (currently unused)
109    #[allow(dead_code)]
110    ordered: Prefix,
111    /// Prefix style for unordered lists
112    unordered: Prefix,
113    /// Prefix string for section headers
114    section_prefix: &'static str,
115    /// Suffix string for section headers
116    section_suffix: &'static str,
117    /// Delimiter between keys and values
118    delimiter: &'static str,
119    /// Delimiter for multiline content (currently unused)
120    #[allow(dead_code)]    
121    multiline: &'static str,
122    /// Suffix for each line
123    suffix: &'static str,
124}
125
126impl OutputFormat {
127    /// Create a new OutputFormat configured for the given capability level
128    ///
129    /// Different capability levels use different formatting styles to match the
130    /// capabilities of various AI models:
131    ///
132    /// # Capability Levels
133    ///
134    /// - **`Minimal`**: Simple formatting with brackets and newlines, suitable for
135    ///   basic models that struggle with complex formatting
136    /// - **`Limited`**: Moderate formatting with section headers and colons,
137    ///   appropriate for models with moderate comprehension abilities
138    /// - **`Standard`**: Rich formatting with markdown-style headers and structured
139    ///   delimiters, ideal for advanced models that can handle complex layouts
140    fn new(capability: &Capability) -> Self {
141        match capability {
142            Capability::Minimal => OutputFormat {
143                writer: Writer::formulizer(),
144
145                ordered: Prefix::None,
146                unordered: Prefix::None,
147                section_prefix: "[",
148                section_suffix: "]\n",
149                delimiter: "\n",
150                multiline: "\n",
151                suffix: "\n",
152            },
153            Capability::Limited => OutputFormat {
154                writer: Writer::formulizer(),
155
156                ordered: Prefix::Unordered,
157                unordered: Prefix::Unordered,
158                section_prefix: "===",
159                section_suffix: "===\n",
160                delimiter: ":\n",
161                multiline: ":\n",
162                suffix: "\n",
163            },
164            Capability::Standard => OutputFormat {
165                writer: Writer::formulizer(),
166
167                ordered: Prefix::Ordered,
168                unordered: Prefix::Unordered,
169                section_prefix: "## ",
170                section_suffix: "\n",
171                delimiter: ": ",
172                multiline: ":\n",
173                suffix: "\n",
174            },
175/*
176todo!() // Add xml and json maybe?
177*/
178        }
179    }
180    
181    /// Start a new section with the given title
182    ///
183    /// This method writes the section prefix, the section title, and the section suffix
184    /// to the internal writer. The exact formatting depends on the capability level
185    /// configured for this OutputFormat instance.
186    ///
187    /// # Arguments
188    ///
189    /// * `string` - The title of the section to start
190    fn section_start(&mut self, string: &str) {
191        self.writer.write_str(self.section_prefix);
192        self.writer.write_str(string);
193        self.writer.write_str(self.section_suffix);
194    }
195    
196    /// End the current section
197    ///
198    /// This method writes the section suffix to the internal writer, effectively
199    /// ending the current section. The exact formatting depends on the capability
200    /// level configured for this OutputFormat instance.
201    fn section_end(&mut self) {
202        self.writer.write_str(self.suffix);
203    }
204    
205    /// Write a single item with key-value formatting
206    ///
207    /// This method writes a key-value pair with appropriate formatting based on
208    /// the configured capability level. For unordered lists, it adds a bullet point
209    /// prefix before the key.
210    ///
211    /// # Arguments
212    ///
213    /// * `key` - The key or label for the item
214    /// * `text` - The value or description for the item
215    fn single_item(&mut self, key: &str, text: &str) {
216        if self.unordered == Prefix::Unordered {
217            self.writer.write_str("- ");
218        }
219        self.writer.write_str(key);
220        self.writer.write_str(self.delimiter);
221        self.writer.write_str(text);
222        self.writer.write_str(self.suffix);
223    }
224    
225    /// Write a single line with key-value formatting
226    ///
227    /// This method writes a key-value pair using the configured delimiter and suffix.
228    /// Unlike `single_item`, it does not add any list prefix regardless of the
229    /// unordered setting.
230    ///
231    /// # Arguments
232    ///
233    /// * `key` - The key or label for the line
234    /// * `text` - The value or content for the line
235    fn single_line(&mut self, key: &str, text: &str) {
236        self.writer.write_str(key);
237        self.writer.write_str(self.delimiter);
238        self.writer.write_str(text);
239        self.writer.write_str(self.suffix);
240    }
241    
242    /// Write a single line with quoted text
243    ///
244    /// This method writes a key-value pair where the value is enclosed in quotes.
245    /// It automatically chooses between double quotes and single quotes based on
246    /// whether the text content contains double quotes.
247    ///
248    /// # Arguments
249    ///
250    /// * `key` - The key or label for the line
251    /// * `text` - The text content to be quoted
252    fn single_line_quoted(&mut self, key: &str, text: &str) {
253        self.writer.write_str(key);
254        self.writer.write_str(self.delimiter);
255        if text.contains('"') {
256            self.writer.write_char('\'');
257            self.writer.write_str(text);
258            self.writer.write_char('\'');
259        }
260        else {
261            self.writer.write_char('"');
262            self.writer.write_str(text);
263            self.writer.write_char('"');
264        }
265        self.writer.write_str(self.suffix);
266    }
267    
268    /// Write a single line with tagged text (e.g., `<value>`)
269    ///
270    /// This method writes a key-value pair where the value is enclosed in angle brackets.
271    /// This is typically used for format specifications in output sections.
272    ///
273    /// # Arguments
274    ///
275    /// * `key` - The key or label for the line
276    /// * `text` - The text content to be enclosed in angle brackets
277    fn single_line_tagged(&mut self, key: &str, text: &str) {
278        self.writer.write_str(key);
279        self.writer.write_str(self.delimiter);
280        self.writer.write_char('<');
281        self.writer.write_str(text);
282        self.writer.write_char('>');
283        self.writer.write_str(self.suffix);
284    }
285    
286    /// Write a value using the writer's print_value method
287    ///
288    /// This method writes a Value using the writer's print_value method with no prefix.
289    /// It's typically used for writing rule values directly.
290    ///
291    /// # Arguments
292    ///
293    /// * `value` - The Value to write
294    fn value(&mut self, value: &Value) {
295        self.writer.print_value(&Prefix::None, value);
296        self.writer.write_str("\n");
297    }
298    
299    /// Write plain text
300    ///
301    /// This method writes plain text followed by a newline.
302    ///
303    /// # Arguments
304    ///
305    /// * `string` - The text to write
306    fn text(&mut self, string: &str) {
307        self.writer.write_str(string);
308        self.writer.write_str("\n");
309    }
310    
311    #[allow(dead_code)]
312    /// Write a value with ordered prefix formatting
313    ///
314    /// This method writes a Value using the writer's print_value method with
315    /// ordered prefix formatting. Currently unused but available for future use.
316    ///
317    /// # Arguments
318    ///
319    /// * `value` - The Value to write
320    fn ordered_value(&mut self, value: &Value) {
321        self.writer.print_value(&self.ordered, value);
322        self.writer.write_str("\n");
323    }
324    
325    /// Write a value with unordered prefix formatting
326    ///
327    /// This method writes a Value using the writer's print_value method with
328    /// unordered prefix formatting. It's typically used for writing modifier rules
329    /// in guideline sections.
330    ///
331    /// # Arguments
332    ///
333    /// * `value` - The Value to write
334    fn unordered_value(&mut self, value: &Value) {
335        self.writer.print_value(&self.unordered, value);
336        self.writer.write_str("\n");
337    }
338}
339
340/// Generate structured prompts from workflow rules for AI model inference
341///
342/// This function analyzes a workflow and converts its rules into a structured prompt
343/// suitable for AI model inference. It handles modifier rules (UPPERCASE identifiers)
344/// and assignment rules (`_` prefix) to create a well-formatted prompt that guides
345/// AI models to produce the desired outputs.
346///
347/// # Arguments
348///
349/// * `workflow` - An Arc-wrapped workflow containing the rules to process
350/// * `capability` - The capability level of the target AI model, which determines
351///   the formatting complexity of the generated prompt
352///
353/// # Returns
354///
355/// Returns a tuple containing:
356/// - `system_prompt`: The system-level prompt (can be overridden by SYSTEM rule)
357/// - `user_prompt`: The structured user prompt with sections and formatting
358///
359/// # Errors
360///
361/// Returns an error if:
362/// - No assignment rules are found (nothing to populate)
363/// - Workflow rules cannot be processed
364///
365pub fn generate_prompt(workflow: Arc<dyn WorkflowLike>, capability: &Capability) -> Result<(String, String)> {
366    let mut format = OutputFormat::new(capability);
367
368    // "ROLE" keyword modifier
369    let mut role_modifier: Option<Rule> = None;
370    // "INSTRUCTIONS" keyword modifier
371    let mut instructions_modifier: Option<Rule> = None;
372    // "OUTPUT_FORMAT" keyword modifier
373    let mut output_modifier: Option<Rule> = None;
374    // "SYSTEM" prompt (default)
375    let mut system_prompt: String = "You are a helpful assistant".to_owned();
376
377    // "content" keyword
378    let mut content: Option<Rule> = None;
379
380    // General prompt modifier_rules
381    let mut modifier_rules: Vec<Rule> = Vec::new();
382    // Output rules
383    let mut output_rules: HashMap<String, Rule> = HashMap::new();
384    // Output rule formatting and examples
385    let mut format_rules: Vec<Rule> = Vec::new();
386
387    // Iterate each rule
388    for rule in workflow.iter_rules() {
389        // Select inference output rules (rule identifiers with leading underscores)
390        if rule.is_assignment() {
391            // Create a map of ucid inference rules
392            let ucid = rule.ucid();
393            output_rules.insert(ucid, rule.clone());
394        }
395    }
396    if output_rules.len() == 0 {
397        return Err(anyhow!("Nothing To Assign"))
398    }
399    // Iterate each rule
400    for rule in workflow.iter_rules() {
401        let identifier = rule.identifier();
402        // content := user text
403        if identifier == "content" {
404            content = Some(rule.clone());
405        // Select modifier rules (rule identifiers that are all uppercase)
406        } else if rule.is_modifier() {
407            // Role persona
408            if identifier == "ROLE" {
409                role_modifier = Some(rule.clone());
410            // Agentic instructions
411            } else if identifier == "INSTRUCTIONS" {
412                instructions_modifier = Some(rule.clone());
413            // Output instructions and example user text
414            } else if identifier == "OUTPUT" {
415                output_modifier = Some(rule.clone());
416            // System prompt
417            } else if identifier == "SYSTEM" {
418                let value = rule.value();
419                system_prompt = value.to_pretty(Prefix::None);
420            // Specific rule format, specification and examples
421            } else if rule.is_format() && output_rules.contains_key(identifier) {
422                format_rules.push(rule.clone());
423            // General prompt modifiers
424            } else {
425                modifier_rules.push(rule.clone());
426            }
427        }
428    }
429
430    // ROLE keyword modifier
431    // You are a precise data extraction assistant. Your task is to extract specific weather information from user text.
432    //
433    if let Some(rule) = role_modifier {
434        format.value(rule.value());
435    } else {
436        format.text("You are a helpful assistant.");
437    }
438    format.section_end();
439
440    // General prompt modifier_rules
441    //
442    // ===GUIDELINES===
443    // - If a value is not present, use "Unknown".
444    // - Extract only the specific values requested.
445    // - Do not include additional commentary.
446    // - When multiple temperatures are mentioned, extract the first complete temperature value (number + optional unit)
447    // - A "temperature" is a numeric value that represents heat measurement, not other measurements like swell height, pressure, etc."
448
449    for rule in modifier_rules {
450        format.section_start(rule.identifier());
451        format.unordered_value(rule.value());
452    }
453    format.section_end();
454
455    // INSTRUCTIONS keyword modifier
456    //
457    // ===INSTRUCTIONS===
458    // Extract the following values from the user text:
459    // - TEMPERATURE: The numeric temperature value
460    // - UNIT: The temperature unit (Fahrenheit, Celsius, F, C, or "Unknown" if not specified)
461    //
462    format.section_start("INSTRUCTIONS");
463    if let Some(rule) = instructions_modifier {
464        format.value(rule.value());
465    } else {
466        format.text("Derive the following values from the content:");
467    }
468    for rule in &format_rules {
469        if let Value::Format(f) = rule.value() {
470            format.single_item(rule.identifier(), f.instruction());
471        }
472    }
473    format.section_end();
474
475    // OUTPUT_FORMAT keyword modifier
476    //
477    // ===OUTPUT FORMAT===
478    // Provide your answer in this exact format:
479    // TEMPERATURE: <number>
480    // UNIT: <Fahrenheit|Celsius|F|C|Unknown>
481    //
482    format.section_start("OUTPUT FORMAT");
483    if let Some(rule) = &output_modifier {
484        if let Value::Format(f) = rule.value() {
485            format.text(f.instruction());
486        } else {
487            format.value(rule.value());
488        }
489    } else {
490        format.text("Provide your answer in this exact format:");
491    }
492    for rule in &format_rules {
493        if let Value::Format(f) = rule.value() {
494            format.single_line_tagged(rule.identifier(), f.format());
495        }
496    }
497    format.section_end();
498
499    // ===EXAMPLES===
500    // USER TEXT: "It's 72 degrees Fahrenheit today"
501    // TEMPERATURE: 72
502    // UNIT: Fahrenheit
503    //
504    // USER TEXT: "I saw the temperature was 25C at noon"
505    // TEMPERATURE: 25
506    // UNIT: C
507    //
508    // USER TEXT: "It was really hot at 34 degrees"
509    // TEMPERATURE: 34
510    // UNIT: Unknown
511    //
512    // USER TEXT: "No weather information here"
513    // TEMPERATURE: Unknown
514    // UNIT: Unknown
515    //
516    format.section_start("EXAMPLES");
517    let mut index: usize = 0;
518    let mut length: usize = 1;
519    while index < length {
520        if let Some(rule) = &output_modifier {
521            if let Value::Format(f) = rule.value() {
522                length = f.array().len();
523                if index < length {
524                    format.single_line_quoted("USER TEXT", &f.array()[index]);
525                }
526            }
527        }
528        for rule in &format_rules {
529            if let Value::Format(f) = rule.value() {
530                length = f.array().len();
531                if index < length {
532                    format.single_line(rule.identifier(), &f.array()[index]);
533                }
534            }
535        }
536        format.section_end();
537        index += 1;
538    }
539
540    // ===USER TEXT===
541    if let Some(rule) = &content {
542        format.section_start("USER TEXT");
543        format.value(rule.value());
544        format.section_end();
545    }
546
547    Ok((system_prompt, format.writer.finish()))
548}