extract_person_macro/
extract_person_macro.rs

1/// Example: Extract Person Information with Macros
2///
3/// This example demonstrates how to use the `#[derive(BamlSchema)]` macro
4/// to automatically generate IR from native Rust types. Compare this with
5/// the manual approach in `extract_person.rs` to see how much simpler it is!
6///
7/// To run this example:
8/// ```bash
9/// export OPENAI_API_KEY="your-api-key-here"
10/// cargo run --example extract_person_macro
11/// ```
12
13use simplify_baml::*;
14use simplify_baml_macros::BamlSchema;
15use std::collections::HashMap;
16use std::env;
17
18// Define types using derive macros - much cleaner than manual IR building!
19
20#[derive(BamlSchema)]
21#[baml(description = "Calendar month of the year")]
22enum Month {
23    January,
24    February,
25    March,
26    April,
27    May,
28    June,
29    July,
30    August,
31    September,
32    October,
33    November,
34    December,
35}
36
37#[derive(BamlSchema)]
38#[baml(description = "Information about a person")]
39struct Person {
40    #[baml(description = "Full name of the person")]
41    name: String,
42
43    #[baml(description = "Age in years")]
44    age: i64,
45
46    #[baml(description = "Month of birth, if mentioned")]
47    #[baml(rename = "birthMonth")]
48    birth_month: Option<Month>,
49
50    #[baml(description = "Job title or profession, if mentioned")]
51    occupation: Option<String>,
52}
53
54#[tokio::main]
55async fn main() -> anyhow::Result<()> {
56    println!("=== BAML Macro Example: Extract Person ===\n");
57    println!("✨ Using #[derive(BamlSchema)] to automatically generate IR!\n");
58
59    // Step 1: Build IR using the registry - automatically collects all schema definitions
60    let ir = BamlSchemaRegistry::new()
61        .register::<Month>()
62        .register::<Person>()
63        .build_with_functions(vec![Function {
64            name: "ExtractPerson".to_string(),
65            inputs: vec![Field {
66                name: "text".to_string(),
67                field_type: FieldType::String,
68                optional: false,
69                description: Some("Text containing person information to extract".to_string()),
70            }],
71            output: FieldType::Class("Person".to_string()),
72            prompt_template: r#"Extract the person's information from the following text:
73
74{{ text }}
75
76Please extract: name, age, birth month (if mentioned), and occupation (if mentioned)."#
77                .to_string(),
78            client: "openai".to_string(),
79        }]);
80
81    println!("📊 IR built successfully with {} classes and {} enums",
82        ir.classes.len(),
83        ir.enums.len()
84    );
85
86    // Print what was generated
87    for class in &ir.classes {
88        println!("   - Class: {} ({} fields)", class.name, class.fields.len());
89    }
90    for enum_def in &ir.enums {
91        println!("   - Enum: {} ({} variants)", enum_def.name, enum_def.values.len());
92    }
93    println!();
94
95    // Step 2: Configure the LLM client
96    let api_key = env::var("OPENAI_API_KEY").unwrap_or_else(|_| {
97        eprintln!("Warning: OPENAI_API_KEY not set. Using mock response.");
98        "mock".to_string()
99    });
100
101    let client = if api_key == "mock" {
102        println!("Using mock mode (no real API calls)\n");
103        LLMClient::openai(api_key, "gpt-4o-mini".to_string())
104    } else {
105        LLMClient::openai(api_key, "gpt-4o-mini".to_string())
106    };
107
108    // Step 3: Build the runtime
109    let runtime = RuntimeBuilder::new()
110        .ir(ir)
111        .client("openai", client)
112        .build();
113
114    // Step 4: Prepare input parameters
115    let mut params = HashMap::new();
116    params.insert(
117        "text".to_string(),
118        BamlValue::String(
119            "John Smith is 30 years old and was born in March. He works as a software engineer."
120                .to_string(),
121        ),
122    );
123
124    println!(
125        "Input text: {}",
126        params.get("text").unwrap().as_string().unwrap()
127    );
128    println!("\nExecuting BAML function 'ExtractPerson'...\n");
129
130    // Step 5: Execute the function
131    match runtime.execute("ExtractPerson", params).await {
132        Ok(result) => {
133            println!("Success! Parsed result:");
134            print_result(&result);
135        }
136        Err(e) => {
137            eprintln!("Error: {}", e);
138            eprintln!("\nNote: If you haven't set OPENAI_API_KEY, this is expected.");
139            eprintln!("Set it with: export OPENAI_API_KEY='your-key-here'");
140        }
141    }
142
143    println!("\n=== Comparison ===");
144    println!("Without macros: ~70 lines of manual IR construction");
145    println!("With macros: ~25 lines of struct/enum definitions");
146    println!("Savings: Less tedious, more readable, and type-safe!");
147
148    Ok(())
149}
150
151/// Pretty print the result
152fn print_result(value: &BamlValue) {
153    match value {
154        BamlValue::Map(map) => {
155            println!("{{");
156            for (key, val) in map {
157                print!("  {}: ", key);
158                match val {
159                    BamlValue::String(s) => println!("\"{}\"", s),
160                    BamlValue::Int(i) => println!("{}", i),
161                    BamlValue::Float(f) => println!("{}", f),
162                    BamlValue::Bool(b) => println!("{}", b),
163                    BamlValue::Null => println!("null"),
164                    _ => println!("{:?}", val),
165                }
166            }
167            println!("}}");
168        }
169        _ => println!("{:?}", value),
170    }
171}