files/
files.rs

1//! Comprehensive files example demonstrating file management with OpenAI.
2//!
3//! This example showcases the OpenAI Files API, including:
4//! - Uploading files for different purposes (fine-tuning, assistants, batch)
5//! - Listing files with filtering and pagination
6//! - Retrieving file metadata
7//! - Downloading file content
8//! - Deleting files
9//!
10//! ## Features Demonstrated
11//!
12//! - **File Upload**: Upload text files, JSON files, and files from disk
13//! - **File Listing**: List files with filtering by purpose and pagination
14//! - **File Retrieval**: Get metadata about specific files
15//! - **File Download**: Download file content as text or bytes
16//! - **File Deletion**: Delete files when no longer needed
17//! - **Error Handling**: Robust error handling for various failure scenarios
18//!
19//! ## Prerequisites
20//!
21//! Set your OpenAI API key:
22//! ```bash
23//! export OPENAI_API_KEY="your-key-here"
24//! ```
25//!
26//! ## Usage
27//!
28//! ```bash
29//! cargo run --example files
30//! ```
31//!
32//! Note: This example uses simulated responses to keep the example runnable without
33//! real OpenAI credentials. Replace the simulated sections with real API calls
34//! when you're ready to interact with the live API.
35
36#![allow(clippy::uninlined_format_args)]
37#![allow(clippy::no_effect_underscore_binding)]
38#![allow(clippy::doc_markdown)]
39#![allow(clippy::cast_possible_wrap)]
40#![allow(clippy::cast_precision_loss)]
41#![allow(clippy::unused_async)]
42#![allow(clippy::useless_vec)]
43#![allow(dead_code)]
44
45use openai_ergonomic::Client;
46
47/// File metadata for demonstration purposes
48#[derive(Debug, Clone)]
49pub struct FileMetadata {
50    /// File ID from OpenAI
51    pub id: String,
52    /// Original filename
53    pub filename: String,
54    /// File size in bytes
55    pub bytes: usize,
56    /// File purpose
57    pub purpose: String,
58    /// Creation timestamp (Unix timestamp)
59    pub created_at: i64,
60}
61
62impl FileMetadata {
63    /// Create a new file metadata instance
64    pub fn new(
65        id: impl Into<String>,
66        filename: impl Into<String>,
67        bytes: usize,
68        purpose: impl Into<String>,
69    ) -> Self {
70        Self {
71            id: id.into(),
72            filename: filename.into(),
73            bytes,
74            purpose: purpose.into(),
75            created_at: std::time::SystemTime::now()
76                .duration_since(std::time::UNIX_EPOCH)
77                .unwrap()
78                .as_secs() as i64,
79        }
80    }
81
82    /// Format the file size in a human-readable way
83    pub fn formatted_size(&self) -> String {
84        if self.bytes < 1024 {
85            format!("{} B", self.bytes)
86        } else if self.bytes < 1024 * 1024 {
87            format!("{:.2} KB", self.bytes as f64 / 1024.0)
88        } else if self.bytes < 1024 * 1024 * 1024 {
89            format!("{:.2} MB", self.bytes as f64 / (1024.0 * 1024.0))
90        } else {
91            format!("{:.2} GB", self.bytes as f64 / (1024.0 * 1024.0 * 1024.0))
92        }
93    }
94
95    /// Format the creation time in a human-readable way
96    pub fn formatted_created_at(&self) -> String {
97        format!("Unix timestamp: {}", self.created_at)
98    }
99}
100
101#[tokio::main]
102async fn main() -> Result<(), Box<dyn std::error::Error>> {
103    println!(" OpenAI Ergonomic - Comprehensive Files Example\n");
104
105    // Initialize client from environment variables
106    let client = match Client::from_env() {
107        Ok(client_builder) => {
108            println!(" Client initialized successfully");
109            client_builder.build()
110        }
111        Err(e) => {
112            eprintln!(" Failed to initialize client: {e}");
113            eprintln!(" Make sure OPENAI_API_KEY is set in your environment");
114            return Err(e.into());
115        }
116    };
117
118    // Example 1: Upload Text File
119    println!("\n Example 1: Upload Text File");
120    println!("================================");
121
122    match upload_text_file_example(&client).await {
123        Ok(file_id) => {
124            println!(" Text file uploaded successfully: {}", file_id);
125        }
126        Err(e) => {
127            eprintln!(" Text file upload failed: {e}");
128            handle_file_error(e.as_ref());
129        }
130    }
131
132    // Example 2: Upload JSON File
133    println!("\n Example 2: Upload JSON File");
134    println!("================================");
135
136    match upload_json_file_example(&client).await {
137        Ok(file_id) => {
138            println!(" JSON file uploaded successfully: {}", file_id);
139        }
140        Err(e) => {
141            eprintln!(" JSON file upload failed: {e}");
142            handle_file_error(e.as_ref());
143        }
144    }
145
146    // Example 3: List Files
147    println!("\n Example 3: List Files");
148    println!("=========================");
149
150    match list_files_example(&client).await {
151        Ok(count) => {
152            println!(" Listed {} files successfully", count);
153        }
154        Err(e) => {
155            eprintln!(" List files failed: {e}");
156            handle_file_error(e.as_ref());
157        }
158    }
159
160    // Example 4: Retrieve File Metadata
161    println!("\n Example 4: Retrieve File Metadata");
162    println!("======================================");
163
164    match retrieve_file_example(&client).await {
165        Ok(()) => {
166            println!(" File metadata retrieved successfully");
167        }
168        Err(e) => {
169            eprintln!(" Retrieve file failed: {e}");
170            handle_file_error(e.as_ref());
171        }
172    }
173
174    // Example 5: Download File Content
175    println!("\n  Example 5: Download File Content");
176    println!("======================================");
177
178    match download_file_example(&client).await {
179        Ok(size) => {
180            println!(" Downloaded {} bytes successfully", size);
181        }
182        Err(e) => {
183            eprintln!(" Download file failed: {e}");
184            handle_file_error(e.as_ref());
185        }
186    }
187
188    // Example 6: Delete File
189    println!("\n  Example 6: Delete File");
190    println!("===========================");
191
192    match delete_file_example(&client).await {
193        Ok(()) => {
194            println!(" File deleted successfully");
195        }
196        Err(e) => {
197            eprintln!(" Delete file failed: {e}");
198            handle_file_error(e.as_ref());
199        }
200    }
201
202    // Example 7: File Management Workflow
203    println!("\n Example 7: File Management Workflow");
204    println!("========================================");
205
206    match file_workflow_example(&client).await {
207        Ok(()) => {
208            println!(" File workflow completed successfully");
209        }
210        Err(e) => {
211            eprintln!(" File workflow failed: {e}");
212            handle_file_error(e.as_ref());
213        }
214    }
215
216    println!("\n All examples completed! Check the console output above for results.");
217    println!("\nNote: This example simulates API responses. Replace the simulated sections with");
218    println!("real client.files() calls when you're ready to interact with the API.");
219
220    Ok(())
221}
222
223/// Example 1: Upload a text file
224async fn upload_text_file_example(_client: &Client) -> Result<String, Box<dyn std::error::Error>> {
225    println!("Uploading a text file for assistants...");
226
227    // Simulated file content
228    let content = "This is a sample document for the assistants API.\n\
229                   It contains information that can be searched and referenced.\n\
230                   The file format is plain text for simplicity.";
231
232    println!(" Filename: document.txt");
233    println!(" Size: {} bytes", content.len());
234    println!(" Purpose: assistants");
235
236    // This would be the intended API usage:
237    // let builder = client
238    //     .files()
239    //     .upload_text("document.txt", FilePurpose::Assistants, content);
240    // let file = client.files().create(builder).await?;
241    // println!(" Uploaded file ID: {}", file.id);
242    // Ok(file.id)
243
244    // Simulate the response
245    let file_id = "file-abc123";
246    println!(" Upload initiated...");
247    println!(" File uploaded successfully");
248    println!("   File ID: {}", file_id);
249    println!("   Status: processed");
250
251    Ok(file_id.to_string())
252}
253
254/// Example 2: Upload a JSON file for batch processing
255async fn upload_json_file_example(_client: &Client) -> Result<String, Box<dyn std::error::Error>> {
256    println!("Uploading a JSON file for batch processing...");
257
258    // Create a JSON file for batch processing
259    let batch_data = serde_json::json!({
260        "custom_id": "request-1",
261        "method": "POST",
262        "url": "/v1/chat/completions",
263        "body": {
264            "model": "gpt-4",
265            "messages": [
266                {"role": "system", "content": "You are a helpful assistant."},
267                {"role": "user", "content": "Hello!"}
268            ]
269        }
270    });
271
272    let content = serde_json::to_string_pretty(&batch_data)?;
273
274    println!(" Filename: batch_request.jsonl");
275    println!(" Size: {} bytes", content.len());
276    println!(" Purpose: batch");
277    println!(" Content preview:");
278    println!("{}", content);
279
280    // This would be the intended API usage:
281    // let builder = FileUploadBuilder::from_json(
282    //     "batch_request.jsonl",
283    //     FilePurpose::Batch,
284    //     &batch_data
285    // )?;
286    // let file = client.files().create(builder).await?;
287    // Ok(file.id)
288
289    // Simulate the response
290    let file_id = "file-batch456";
291    println!("\n Upload initiated...");
292    println!(" File uploaded successfully");
293    println!("   File ID: {}", file_id);
294    println!("   Status: processed");
295
296    Ok(file_id.to_string())
297}
298
299/// Example 3: List files with filtering
300async fn list_files_example(_client: &Client) -> Result<usize, Box<dyn std::error::Error>> {
301    println!("Listing files with filtering...");
302
303    // This would be the intended API usage:
304    // let builder = client
305    //     .files()
306    //     .list_builder()
307    //     .purpose(FilePurpose::Assistants)
308    //     .limit(10);
309    // let response = client.files().list(builder).await?;
310    // println!("Found {} files", response.data.len());
311    // for file in &response.data {
312    //     println!("  - {} ({}) - {} bytes", file.filename, file.id, file.bytes);
313    // }
314    // Ok(response.data.len())
315
316    // Simulate the response
317    let simulated_files = vec![
318        FileMetadata::new("file-abc123", "document.txt", 1024, "assistants"),
319        FileMetadata::new("file-def456", "training.jsonl", 2048, "fine-tune"),
320        FileMetadata::new("file-ghi789", "batch_request.jsonl", 512, "batch"),
321    ];
322
323    println!("\n Listing all files:");
324    println!("   Found {} files", simulated_files.len());
325
326    for (i, file) in simulated_files.iter().enumerate() {
327        println!("\n   {}. {}", i + 1, file.filename);
328        println!("      ID: {}", file.id);
329        println!("      Size: {}", file.formatted_size());
330        println!("      Purpose: {}", file.purpose);
331        println!("      Created: {}", file.formatted_created_at());
332    }
333
334    println!("\n Filtering options:");
335    println!("   - Filter by purpose (fine-tune, assistants, batch, vision)");
336    println!("   - Limit results (default: 20)");
337    println!("   - Order by creation time (asc/desc)");
338
339    Ok(simulated_files.len())
340}
341
342/// Example 4: Retrieve file metadata
343async fn retrieve_file_example(_client: &Client) -> Result<(), Box<dyn std::error::Error>> {
344    println!("Retrieving file metadata...");
345
346    let file_id = "file-abc123";
347    println!(" Looking up file: {}", file_id);
348
349    // This would be the intended API usage:
350    // let file = client.files().retrieve(file_id).await?;
351    // println!(" File found:");
352    // println!("   Filename: {}", file.filename);
353    // println!("   Size: {} bytes", file.bytes);
354    // println!("   Purpose: {}", file.purpose);
355    // println!("   Status: {}", file.status);
356    // println!("   Created: {}", file.created_at);
357    // Ok(())
358
359    // Simulate the response
360    let file = FileMetadata::new(file_id, "document.txt", 1024, "assistants");
361
362    println!("\n File metadata retrieved:");
363    println!("   ID: {}", file.id);
364    println!("   Filename: {}", file.filename);
365    println!("   Size: {}", file.formatted_size());
366    println!("   Purpose: {}", file.purpose);
367    println!("   Created: {}", file.formatted_created_at());
368    println!("   Status: processed");
369
370    Ok(())
371}
372
373/// Example 5: Download file content
374async fn download_file_example(_client: &Client) -> Result<usize, Box<dyn std::error::Error>> {
375    println!("Downloading file content...");
376
377    let file_id = "file-abc123";
378    println!("  Downloading file: {}", file_id);
379
380    // This would be the intended API usage:
381    // let content = client.files().download(file_id).await?;
382    // println!(" Downloaded {} bytes", content.len());
383    // println!(" Content preview:");
384    // println!("{}", &content[..100.min(content.len())]);
385    // Ok(content.len())
386
387    // Simulate the response
388    let content = "This is a sample document for the assistants API.\n\
389                   It contains information that can be searched and referenced.\n\
390                   The file format is plain text for simplicity.";
391
392    println!("\n File downloaded successfully");
393    println!("   Size: {} bytes", content.len());
394    println!("\n Content preview:");
395    let preview_len = 100.min(content.len());
396    println!("{}", &content[..preview_len]);
397    if content.len() > preview_len {
398        println!("   ... (truncated)");
399    }
400
401    println!("\n Download options:");
402    println!("   - download() - Returns content as String");
403    println!("   - download_bytes() - Returns content as Vec<u8>");
404
405    Ok(content.len())
406}
407
408/// Example 6: Delete a file
409async fn delete_file_example(_client: &Client) -> Result<(), Box<dyn std::error::Error>> {
410    println!("Deleting a file...");
411
412    let file_id = "file-temp123";
413    println!("  Deleting file: {}", file_id);
414
415    // This would be the intended API usage:
416    // let response = client.files().delete(file_id).await?;
417    // println!(" File deleted: {}", response.deleted);
418    // Ok(())
419
420    // Simulate the response
421    println!("\n File deleted successfully");
422    println!("   File ID: {}", file_id);
423    println!("   Deleted: true");
424
425    println!("\n  Important notes:");
426    println!("   - Deleted files cannot be recovered");
427    println!("   - Files in use by jobs cannot be deleted");
428    println!("   - Check file dependencies before deletion");
429
430    Ok(())
431}
432
433/// Example 7: Complete file management workflow
434async fn file_workflow_example(_client: &Client) -> Result<(), Box<dyn std::error::Error>> {
435    println!("Demonstrating a complete file management workflow...");
436
437    println!("\n Workflow steps:");
438    println!("   1. Create training data");
439    println!("   2. Upload file");
440    println!("   3. Verify upload");
441    println!("   4. List files");
442    println!("   5. Clean up");
443
444    // Step 1: Create training data
445    println!("\n Step 1: Creating training data...");
446    let training_data = vec![
447        serde_json::json!({
448            "messages": [
449                {"role": "system", "content": "You are a helpful assistant."},
450                {"role": "user", "content": "What is AI?"},
451                {"role": "assistant", "content": "AI stands for Artificial Intelligence..."}
452            ]
453        }),
454        serde_json::json!({
455            "messages": [
456                {"role": "system", "content": "You are a helpful assistant."},
457                {"role": "user", "content": "Explain machine learning"},
458                {"role": "assistant", "content": "Machine learning is a subset of AI..."}
459            ]
460        }),
461    ];
462
463    let jsonl_content: Vec<String> = training_data
464        .iter()
465        .map(|obj| serde_json::to_string(obj).unwrap())
466        .collect();
467    let content = jsonl_content.join("\n");
468
469    println!("    Created {} training examples", training_data.len());
470    println!("    Total size: {} bytes", content.len());
471
472    // Step 2: Upload file
473    println!("\n Step 2: Uploading file...");
474    // This would be the intended API usage:
475    // let builder = client
476    //     .files()
477    //     .upload_text("training.jsonl", FilePurpose::FineTune, &content);
478    // let file = client.files().create(builder).await?;
479    // let file_id = file.id;
480
481    let file_id = "file-workflow789";
482    println!("    File uploaded: {}", file_id);
483
484    // Step 3: Verify upload
485    println!("\n Step 3: Verifying upload...");
486    // This would be the intended API usage:
487    // let file_info = client.files().retrieve(&file_id).await?;
488    // println!("    File verified:");
489    // println!("      Filename: {}", file_info.filename);
490    // println!("      Status: {}", file_info.status);
491
492    println!("    File verified:");
493    println!("      Filename: training.jsonl");
494    println!("      Status: processed");
495    println!("      Size: {} bytes", content.len());
496
497    // Step 4: List files to confirm
498    println!("\n Step 4: Listing all files...");
499    // This would be the intended API usage:
500    // let files = client.files().list(client.files().list_builder()).await?;
501    // println!("    Total files: {}", files.data.len());
502
503    println!("    Total files: 4");
504    println!("    Including our new file: {}", file_id);
505
506    // Step 5: Optional cleanup
507    println!("\n  Step 5: Cleanup (optional)...");
508    println!("    Skipping deletion - file may be used for training");
509    println!("   ℹ  To delete: client.files().delete(&file_id).await");
510
511    println!("\n Workflow completed successfully!");
512
513    println!("\n Best practices:");
514    println!("   1. Validate file format before upload");
515    println!("   2. Check file size limits (max 512 MB per file)");
516    println!("   3. Use appropriate purpose for each file");
517    println!("   4. Verify file status after upload");
518    println!("   5. Clean up unused files to save storage");
519    println!("   6. Use JSONL format for training data");
520    println!("   7. Handle upload errors gracefully");
521
522    Ok(())
523}
524
525/// Handle file-specific errors with helpful context
526fn handle_file_error(error: &dyn std::error::Error) {
527    eprintln!(" File Error: {}", error);
528
529    if let Some(source) = error.source() {
530        eprintln!("   Caused by: {}", source);
531    }
532
533    eprintln!("\n Troubleshooting tips:");
534    eprintln!("   - Check your API key and network connection");
535    eprintln!("   - Verify file format matches the purpose");
536    eprintln!("   - Ensure file size is within limits (max 512 MB)");
537    eprintln!("   - Check file ID is valid for retrieve/download/delete");
538    eprintln!("   - For fine-tuning: Use JSONL format");
539    eprintln!("   - For batch: Follow batch API format");
540    eprintln!("   - For assistants: Check supported file types");
541}