1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/// Macro that generates an object by setting its fields from tuples of field names and values.
/// This macro uses serde_json to deserialize field values from the LLM responses.
///
/// # Arguments
///
/// * `obj_type` - The type of object to create
/// * `tuples` - A vector of tuples where each tuple contains a field name and the content for that field
#[macro_export]
macro_rules! generate_from_tuples {
($obj_type:ty, $tuples:expr) => {{
use serde_json::{Map, Value};
// Helper function to intelligently parse and clean values based on common patterns
fn smart_parse_value(content: &str, field_name: &str) -> Value {
let cleaned = content.trim();
// Handle empty or null-like values
if cleaned.is_empty() || cleaned.eq_ignore_ascii_case("null") || cleaned.eq_ignore_ascii_case("none") {
return Value::Null;
}
// Try parsing as JSON first (for arrays, objects, quoted strings)
// This is more robust as it handles cases where LLM returns JSON strings
if let Ok(json_value) = serde_json::from_str::<Value>(cleaned) {
// If it's a JSON object with a single key that matches the field name,
// extract the inner value (common LLM response pattern)
if let Value::Object(obj) = &json_value {
let field_key = field_name.split('.').last().unwrap_or(field_name);
if obj.len() == 1 && obj.contains_key(field_key) {
return obj[field_key].clone();
}
}
return json_value;
}
// Handle boolean values (case-insensitive)
if cleaned.eq_ignore_ascii_case("true") {
return Value::Bool(true);
}
if cleaned.eq_ignore_ascii_case("false") {
return Value::Bool(false);
}
// Handle numeric values with currency symbols, commas, and other formatting
if let Some(numeric_value) = parse_numeric_value(cleaned) {
// Check if it's a whole number (integer)
if numeric_value.fract() == 0.0 && numeric_value >= 0.0 && numeric_value <= u64::MAX as f64 {
// Use integer representation for whole numbers
return Value::Number(serde_json::Number::from(numeric_value as u64));
} else {
// Use floating point for decimals
return Value::Number(serde_json::Number::from_f64(numeric_value).unwrap_or_else(|| serde_json::Number::from(0)));
}
}
// Default to string value
Value::String(cleaned.to_string())
}
// Helper function to parse numeric values with various formatting
fn parse_numeric_value(content: &str) -> Option<f64> {
let mut cleaned = content.to_string();
// Remove common currency symbols
cleaned = cleaned.replace('$', "");
cleaned = cleaned.replace('€', "");
cleaned = cleaned.replace('£', "");
cleaned = cleaned.replace('¥', "");
cleaned = cleaned.replace('₹', "");
// Remove commas (thousand separators)
cleaned = cleaned.replace(',', "");
// Remove spaces
cleaned = cleaned.replace(' ', "");
// Handle percentage
let is_percentage = cleaned.ends_with('%');
if is_percentage {
cleaned = cleaned.trim_end_matches('%').to_string();
}
// Try to parse as float
if let Ok(mut num) = cleaned.parse::<f64>() {
if is_percentage {
num /= 100.0; // Convert percentage to decimal
}
return Some(num);
}
None
}
// Helper function to set nested field values
fn set_nested_field(json_map: &mut Map<String, Value>, field_path: &str, value: Value) {
let parts: Vec<&str> = field_path.split('.').collect();
if parts.len() == 1 {
// Simple field, set directly
json_map.insert(parts[0].to_string(), value);
return;
}
// Nested field, create nested structure
let first_part = parts[0];
let remaining_path = parts[1..].join(".");
// Get or create the nested object
let nested_obj = json_map.entry(first_part.to_string())
.or_insert_with(|| Value::Object(Map::new()));
if let Value::Object(nested_map) = nested_obj {
set_nested_field(nested_map, &remaining_path, value);
}
}
// Create a JSON object from the field tuples
let mut json_map = Map::new();
for (field_name, content) in $tuples {
// Use smart parsing to handle various data types and formats
let value = smart_parse_value(&content, &field_name);
// Handle nested field paths
set_nested_field(&mut json_map, &field_name, value);
}
// Convert the JSON object to the target type
let json_value = Value::Object(json_map);
// First attempt full deserialization
match serde_json::from_value::<$obj_type>(json_value.clone()) {
Ok(result) => result,
Err(original_error) => {
// If full deserialization fails, perform field-by-field validation
let mut failed_fields = Vec::new();
let mut successful_fields = Vec::new();
if let Value::Object(ref map) = json_value {
// Create a default instance to test field compatibility
let default_instance = <$obj_type>::default();
let default_json = serde_json::to_value(&default_instance).unwrap_or(Value::Object(serde_json::Map::new()));
if let Value::Object(default_map) = default_json {
for (field_name, field_value) in map {
// Check if this field exists in the target struct
if default_map.contains_key(field_name) {
// Create a test object with default values but this specific field
let mut test_map = default_map.clone();
test_map.insert(field_name.clone(), field_value.clone());
let test_json = Value::Object(test_map);
match serde_json::from_value::<$obj_type>(test_json) {
Ok(_) => successful_fields.push(field_name.clone()),
Err(_) => failed_fields.push(field_name.clone()),
}
} else {
// Field doesn't exist in target struct
failed_fields.push(field_name.clone());
}
}
}
}
// If we have field-level information, create detailed error
if !failed_fields.is_empty() {
use crate::error::FieldDeserializationError;
panic!(
"{}",
FieldDeserializationError {
failed_fields,
successful_fields,
original_error: original_error.to_string(),
}
);
}
// Fallback to default if no specific field errors identified
<$obj_type>::default()
}
}
}};
}