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
//! This test file demonstrates validation with the Grok client.
//!
//! These tests require a valid xAI API key in the environment:
//!
//! ```bash
//! export XAI_API_KEY=your_key_here
//! cargo test --test grok_validation_test
//! ```
#[cfg(test)]
mod grok_validation_tests {
use rstructor::{GrokClient, GrokModel, Instructor, LLMClient, RStructorError};
use serde::{Deserialize, Serialize};
// Define a data model with validation rules
#[derive(Instructor, Serialize, Deserialize, Debug)]
#[llm(description = "Information about a product")]
struct ProductInfo {
#[llm(description = "Name of the product")]
name: String,
#[llm(description = "Price in USD", example = 29.99)]
price: f32,
#[llm(description = "Stock quantity", example = 100)]
stock: u32,
#[llm(description = "Product category", example = "Electronics")]
category: String,
}
impl ProductInfo {
fn validate(&self) -> rstructor::Result<()> {
// Product name can't be empty
if self.name.trim().is_empty() {
return Err(RStructorError::ValidationError(
"Product name cannot be empty".to_string(),
));
}
// Price must be positive
if self.price < 0.0 {
return Err(RStructorError::ValidationError(format!(
"Price must be positive, got ${:.2}",
self.price
)));
}
// Price should be reasonable (less than $1 million)
if self.price > 1_000_000.0 {
return Err(RStructorError::ValidationError(format!(
"Price seems unreasonably high: ${:.2}",
self.price
)));
}
// Category can't be empty
if self.category.trim().is_empty() {
return Err(RStructorError::ValidationError(
"Product category cannot be empty".to_string(),
));
}
Ok(())
}
}
// Test validation catching an invalid price
#[cfg(feature = "grok")]
#[tokio::test]
async fn test_grok_validation_fails_with_negative_price() {
// Test with empty string to use XAI_API_KEY env var
let client = GrokClient::from_env()
.expect("XAI_API_KEY must be set for this test")
.model(GrokModel::Grok43)
.temperature(0.0);
// First get a valid product response
let prompt = "Describe a smartphone product with realistic details";
let valid_result = client.materialize::<ProductInfo>(prompt).await;
// Fail test if API call fails
assert!(
valid_result.is_ok(),
"API call failed: {:?}",
valid_result.err()
);
// Now create a new ProductInfo with an invalid price
let invalid_product = ProductInfo {
name: "Price Test Product".to_string(),
price: -10.0, // Negative price - invalid
stock: 100,
category: "Electronics".to_string(),
};
// Validate it - should fail with price error
let validation_result = invalid_product.validate();
// Check that validation failed
assert!(
validation_result.is_err(),
"Validation should fail with negative price"
);
// Check that the error is about price
if let Err(RStructorError::ValidationError(msg)) = validation_result {
assert!(
msg.contains("Price") || msg.contains("price"),
"Error should mention price: {}",
msg
);
} else if let Err(e) = validation_result {
panic!("Expected ValidationError about price, got: {:?}", e);
}
}
// Test validation catching an unreasonably high price
#[cfg(feature = "grok")]
#[tokio::test]
async fn test_grok_validation_fails_with_extreme_price() {
// For this test, we'll manually create a ProductInfo with extreme price
// We don't even need to make an API call for this test
// Create a product info with an unreasonably high price
let invalid_product = ProductInfo {
name: "Extreme Price Product".to_string(),
price: 2_000_000.0, // Way too high
stock: 100,
category: "Electronics".to_string(),
};
// Validate it - should fail with price error
let validation_result = invalid_product.validate();
// Check that validation failed
assert!(
validation_result.is_err(),
"Validation should fail with extreme price"
);
// Check that the error is about price
if let Err(RStructorError::ValidationError(msg)) = validation_result {
assert!(
msg.contains("Price") || msg.contains("price") || msg.contains("high"),
"Error should mention price: {}",
msg
);
} else if let Err(e) = validation_result {
panic!("Expected ValidationError about price, got: {:?}", e);
}
}
// Valid data test
#[cfg(feature = "grok")]
#[tokio::test]
async fn test_grok_validation_succeeds_with_valid_data() {
// This test demonstrates successful validation with reasonable data
// Test with empty string to use XAI_API_KEY env var
let client = GrokClient::from_env()
.expect("XAI_API_KEY must be set for this test")
.model(GrokModel::Grok43)
.temperature(0.0); // Use deterministic temperature for consistent results
// Normal prompt asking for product information
let prompt = "Describe a laptop product with realistic details";
// Should succeed validation
let result = client.materialize::<ProductInfo>(prompt).await;
// Fail test if API call fails
assert!(result.is_ok(), "API call failed: {:?}", result.err());
// Check the data looks reasonable
let product = result.unwrap();
assert!(!product.name.is_empty());
assert!(product.price > 0.0 && product.price < 1_000_000.0);
assert!(!product.category.is_empty());
}
}