uploads/
uploads.rs

1//! Comprehensive uploads example demonstrating large file uploads with OpenAI.
2//!
3//! This example showcases the OpenAI Uploads API, which allows uploading large files
4//! in multiple parts for improved reliability and performance.
5//!
6//! ## Features Demonstrated
7//!
8//! - **Upload Creation**: Initialize multipart uploads for large files
9//! - **Part Upload**: Upload file parts in chunks
10//! - **Upload Completion**: Finalize uploads after all parts are uploaded
11//! - **Error Handling**: Robust error handling for upload failures
12//! - **Progress Tracking**: Monitor upload progress
13//!
14//! ## Prerequisites
15//!
16//! Set your OpenAI API key:
17//! ```bash
18//! export OPENAI_API_KEY="your-key-here"
19//! ```
20//!
21//! ## Usage
22//!
23//! ```bash
24//! cargo run --example uploads
25//! ```
26//!
27//! ## When to Use Uploads API
28//!
29//! The Uploads API is designed for large files (>512 MB). For smaller files,
30//! use the standard Files API instead.
31
32#![allow(clippy::uninlined_format_args)]
33#![allow(clippy::no_effect_underscore_binding)]
34#![allow(clippy::doc_markdown)]
35#![allow(clippy::cast_possible_wrap)]
36#![allow(clippy::cast_precision_loss)]
37#![allow(clippy::too_many_lines)]
38#![allow(clippy::missing_docs_in_private_items)]
39#![allow(clippy::unused_async)]
40#![allow(clippy::cast_possible_truncation)]
41#![allow(clippy::cast_lossless)]
42#![allow(clippy::items_after_statements)]
43#![allow(clippy::cast_sign_loss)]
44#![allow(unused_variables)]
45#![allow(unused_imports)]
46#![allow(missing_docs)]
47#![allow(dead_code)]
48
49use openai_ergonomic::{builders::uploads::UploadBuilder, Client, UploadPurpose};
50
51/// Upload metadata for demonstration
52#[derive(Debug, Clone)]
53pub struct UploadInfo {
54    pub id: String,
55    pub filename: String,
56    pub bytes: i32,
57    pub purpose: String,
58    pub status: String,
59}
60
61impl UploadInfo {
62    pub fn new(
63        id: impl Into<String>,
64        filename: impl Into<String>,
65        bytes: i32,
66        purpose: impl Into<String>,
67        status: impl Into<String>,
68    ) -> Self {
69        Self {
70            id: id.into(),
71            filename: filename.into(),
72            bytes,
73            purpose: purpose.into(),
74            status: status.into(),
75        }
76    }
77
78    pub fn display(&self) {
79        println!("  Upload ID: {}", self.id);
80        println!("  Filename: {}", self.filename);
81        println!(
82            "  Size: {} bytes ({:.2} MB)",
83            self.bytes,
84            self.bytes as f64 / (1024.0 * 1024.0)
85        );
86        println!("  Purpose: {}", self.purpose);
87        println!("  Status: {}", self.status);
88    }
89
90    pub fn formatted_size(&self) -> String {
91        let bytes = self.bytes as f64;
92        if bytes < 1024.0 {
93            format!("{:.0} B", bytes)
94        } else if bytes < 1024.0 * 1024.0 {
95            format!("{:.2} KB", bytes / 1024.0)
96        } else if bytes < 1024.0 * 1024.0 * 1024.0 {
97            format!("{:.2} MB", bytes / (1024.0 * 1024.0))
98        } else {
99            format!("{:.2} GB", bytes / (1024.0 * 1024.0 * 1024.0))
100        }
101    }
102}
103
104#[tokio::main]
105async fn main() -> Result<(), Box<dyn std::error::Error>> {
106    println!(" OpenAI Ergonomic - Comprehensive Uploads Example\n");
107
108    // Initialize client from environment variables
109    println!(" Initializing OpenAI client...");
110    let client = match Client::from_env() {
111        Ok(c) => {
112            println!(" Client initialized successfully\n");
113            c.build()
114        }
115        Err(e) => {
116            eprintln!(" Failed to initialize client: {}", e);
117            eprintln!(" Make sure OPENAI_API_KEY is set");
118            return Ok(());
119        }
120    };
121
122    // Example 1: Create multipart upload for a large file
123    println!();
124    println!(" Example 1: Create Multipart Upload");
125    println!("\n");
126
127    // Simulate a large file
128    let filename = "large_training_dataset.jsonl";
129    let file_size_mb = 750; // 750 MB
130    let file_size_bytes = file_size_mb * 1024 * 1024;
131    let mime_type = "application/jsonl";
132
133    println!("Creating multipart upload...");
134    println!("  Filename: {}", filename);
135    println!("  Size: {} MB ({} bytes)", file_size_mb, file_size_bytes);
136    println!("  Purpose: fine-tune");
137    println!("  MIME Type: {}", mime_type);
138
139    let builder = client.uploads().builder(
140        filename,
141        UploadPurpose::FineTune,
142        file_size_bytes,
143        mime_type,
144    );
145
146    println!("\n Note: This would create a real multipart upload session.");
147    println!("   Commented out to avoid accidental API calls.\n");
148
149    // Uncomment to actually create upload:
150    // match client.uploads().create(builder).await {
151    //     Ok(upload) => {
152    //         println!(" Upload session created successfully!");
153    //         println!("  Upload ID: {}", upload.id);
154    //         println!("  Status: {}", upload.status);
155    //         println!("  Expires At: {}", upload.expires_at);
156    //     }
157    //     Err(e) => {
158    //         eprintln!(" Failed to create upload: {}", e);
159    //     }
160    // }
161
162    // Simulate upload creation for demonstration
163    let demo_upload = UploadInfo::new(
164        "upload-demo123",
165        filename,
166        file_size_bytes,
167        "fine-tune",
168        "pending",
169    );
170    println!(" Demo Upload Created:");
171    demo_upload.display();
172
173    // Example 2: Upload file parts
174    println!("\n");
175    println!(" Example 2: Upload File Parts");
176    println!("\n");
177
178    let upload_id = "upload-demo123";
179    let part_size_mb = 64; // Upload in 64 MB chunks
180    let total_parts = (file_size_mb + part_size_mb - 1) / part_size_mb; // Ceiling division
181
182    println!(
183        "Uploading {} parts ({} MB each)...\n",
184        total_parts, part_size_mb
185    );
186
187    for part_num in 1..=total_parts {
188        let progress_percent = (part_num as f64 / total_parts as f64) * 100.0;
189
190        println!(
191            " Uploading part {}/{} ({:.1}% complete)",
192            part_num, total_parts, progress_percent
193        );
194
195        // In a real implementation, you would:
196        // 1. Read the file chunk from disk
197        // 2. Upload it to the part URL provided by OpenAI
198        // 3. Track the part ID for completion
199
200        // Uncomment to actually upload parts:
201        // let part_data = read_file_chunk(filename, part_num, part_size_mb)?;
202        // match upload_part(upload_id, part_num, &part_data).await {
203        //     Ok(part_id) => {
204        //         println!("   Part {} uploaded (ID: {})", part_num, part_id);
205        //     }
206        //     Err(e) => {
207        //         eprintln!("   Failed to upload part {}: {}", part_num, e);
208        //         break;
209        //     }
210        // }
211    }
212
213    println!("\n All {} parts uploaded successfully", total_parts);
214
215    // Example 3: Complete the upload
216    println!("\n");
217    println!(" Example 3: Complete Upload");
218    println!("\n");
219
220    println!("Completing upload: {}\n", upload_id);
221
222    // Uncomment to actually complete upload:
223    // match complete_upload(upload_id, part_ids).await {
224    //     Ok(file) => {
225    //         println!(" Upload completed successfully!");
226    //         println!("  File ID: {}", file.id);
227    //         println!("  Filename: {}", file.filename);
228    //         println!("  Status: ready");
229    //         println!("  Purpose: {}", file.purpose);
230    //     }
231    //     Err(e) => {
232    //         eprintln!(" Failed to complete upload: {}", e);
233    //     }
234    // }
235
236    println!(" Demo: Would finalize the upload and create a file object");
237    println!("  File ID: file-abc123");
238    println!("  Filename: {}", filename);
239    println!("  Status: ready");
240
241    // Example 4: Upload smaller file (alternative approach)
242    println!("\n");
243    println!(" Example 4: Upload Smaller File");
244    println!("\n");
245
246    let small_filename = "training_data.jsonl";
247    let small_size_mb = 10;
248    let small_size_bytes = small_size_mb * 1024 * 1024;
249
250    println!("Creating upload for smaller file...");
251    println!("  Filename: {}", small_filename);
252    println!("  Size: {} MB", small_size_mb);
253    println!("  Purpose: assistants");
254
255    let small_builder = client.uploads().builder(
256        small_filename,
257        UploadPurpose::Assistants,
258        small_size_bytes,
259        "application/jsonl",
260    );
261
262    println!("\n Note: For files < 512 MB, consider using the regular Files API");
263    println!("   The Uploads API is optimized for large files.");
264
265    // Example 5: Error handling and retry
266    println!("\n");
267    println!(" Example 5: Error Handling & Retry");
268    println!("\n");
269
270    println!("Demonstrating retry logic for failed part uploads...\n");
271
272    let max_retries = 3;
273    let failed_part = 5;
274
275    for retry in 1..=max_retries {
276        println!(" Attempt {} to upload part {}", retry, failed_part);
277
278        // Simulate upload attempt
279        // In a real implementation:
280        // match upload_part(upload_id, failed_part, &part_data).await {
281        //     Ok(part_id) => {
282        //         println!("   Upload succeeded");
283        //         break;
284        //     }
285        //     Err(e) => {
286        //         if retry < max_retries {
287        //             println!("    Upload failed, retrying... ({})", e);
288        //             tokio::time::sleep(Duration::from_secs(2_u64.pow(retry))).await;
289        //         } else {
290        //             eprintln!("   Upload failed after {} attempts: {}", max_retries, e);
291        //         }
292        //     }
293        // }
294    }
295
296    println!("\n Tip: Implement exponential backoff for retry logic");
297
298    // Example 6: Upload progress tracking
299    println!("\n");
300    println!(" Example 6: Progress Tracking");
301    println!("\n");
302
303    struct UploadProgress {
304        total_bytes: i32,
305        uploaded_bytes: i32,
306        total_parts: i32,
307        uploaded_parts: i32,
308    }
309
310    impl UploadProgress {
311        fn percentage(&self) -> f64 {
312            (self.uploaded_bytes as f64 / self.total_bytes as f64) * 100.0
313        }
314
315        fn eta_seconds(&self, bytes_per_second: f64) -> i32 {
316            let remaining_bytes = self.total_bytes - self.uploaded_bytes;
317            (remaining_bytes as f64 / bytes_per_second) as i32
318        }
319
320        fn display(&self, bytes_per_second: f64) {
321            let progress_bar_width = 40;
322            let filled = ((self.percentage() / 100.0) * progress_bar_width as f64) as usize;
323            let empty = progress_bar_width - filled;
324
325            print!("  [");
326            print!("{}", "".repeat(filled));
327            print!("{}", "".repeat(empty));
328            print!("] ");
329
330            println!(
331                "{:.1}% ({}/{} parts, {} MB/s, ETA: {}s)",
332                self.percentage(),
333                self.uploaded_parts,
334                self.total_parts,
335                bytes_per_second / (1024.0 * 1024.0),
336                self.eta_seconds(bytes_per_second)
337            );
338        }
339    }
340
341    let progress = UploadProgress {
342        total_bytes: file_size_bytes,
343        uploaded_bytes: (file_size_bytes as f64 * 0.65) as i32,
344        total_parts,
345        uploaded_parts: (total_parts as f64 * 0.65) as i32,
346    };
347
348    println!("Current upload progress:");
349    progress.display(10.0 * 1024.0 * 1024.0); // 10 MB/s
350
351    // Summary
352    println!("\n");
353    println!(" Summary");
354    println!("\n");
355
356    println!(" Uploads API examples completed!");
357    println!("\n Key Takeaways:");
358    println!("  • Uploads API is designed for large files (>512 MB)");
359    println!("  • Files are uploaded in parts for reliability");
360    println!("  • Each part can be retried independently");
361    println!("  • Progress can be tracked during upload");
362    println!("  • Upload must be completed after all parts are uploaded");
363
364    println!("\n Best Practices:");
365    println!("  1. Use appropriate part sizes (typically 64 MB)");
366    println!("  2. Implement retry logic with exponential backoff");
367    println!("  3. Track progress and provide user feedback");
368    println!("  4. Handle upload cancellation gracefully");
369    println!("  5. Verify file integrity after upload");
370
371    println!("\n When to Use:");
372    println!("  • Large training datasets for fine-tuning");
373    println!("  • Big files for assistants (>512 MB)");
374    println!("  • Batch processing input files");
375    println!("  • Any file where reliability is critical");
376
377    println!("\n Example completed successfully!");
378
379    Ok(())
380}