openai-ergonomic 0.5.2

Ergonomic Rust wrapper for OpenAI API
Documentation
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#![allow(clippy::uninlined_format_args)]
//! Comprehensive chat completions example.
//!
//! This example demonstrates advanced chat completion features including:
//! - Multi-turn conversation management
//! - Message history tracking
//! - System, user, and assistant messages
//! - Streaming chat responses
//! - Token usage tracking
//! - Error handling patterns
//!
//! Run with: `cargo run --example chat_comprehensive`

use openai_ergonomic::{Client, Error, Response};
use std::collections::VecDeque;
use std::io::{self, Write};

/// Represents a conversation turn with role and content.
#[derive(Debug, Clone)]
struct ConversationTurn {
    role: String,
    content: String,
    token_count: Option<i32>,
}

/// Manages conversation history and token tracking.
#[derive(Debug)]
struct ConversationManager {
    history: VecDeque<ConversationTurn>,
    max_history: usize,
    total_tokens_used: i32,
    system_message: Option<String>,
}

impl ConversationManager {
    /// Create a new conversation manager with optional system message.
    const fn new(system_message: Option<String>, max_history: usize) -> Self {
        Self {
            history: VecDeque::new(),
            max_history,
            total_tokens_used: 0,
            system_message,
        }
    }

    /// Add a user message to the conversation history.
    fn add_user_message(&mut self, content: String) {
        self.add_turn(ConversationTurn {
            role: "user".to_string(),
            content,
            token_count: None,
        });
    }

    /// Add an assistant message to the conversation history.
    fn add_assistant_message(&mut self, content: String, token_count: Option<i32>) {
        self.add_turn(ConversationTurn {
            role: "assistant".to_string(),
            content,
            token_count,
        });
    }

    /// Add a turn to the history, managing the maximum size.
    fn add_turn(&mut self, turn: ConversationTurn) {
        if self.history.len() >= self.max_history {
            self.history.pop_front();
        }
        self.history.push_back(turn);
    }

    /// Update total token usage from a response.
    #[allow(clippy::missing_const_for_fn)] // example focuses on runtime bookkeeping; const fn isn't meaningfully useful here
    fn update_token_usage(&mut self, prompt_tokens: i32, completion_tokens: i32) {
        let total = prompt_tokens + completion_tokens;
        self.total_tokens_used += total;
    }

    /// Display conversation history.
    fn display_history(&self) {
        println!("\n=== Conversation History ===");

        if let Some(ref system) = self.system_message {
            println!("System: {system}");
            println!();
        }

        for (i, turn) in self.history.iter().enumerate() {
            let token_info = turn
                .token_count
                .map_or_else(String::new, |tokens| format!(" ({tokens} tokens)"));

            println!(
                "{}. {}{}: {}",
                i + 1,
                turn.role
                    .chars()
                    .next()
                    .unwrap()
                    .to_uppercase()
                    .collect::<String>()
                    + &turn.role[1..],
                token_info,
                turn.content
            );
        }

        println!("\nTotal tokens used: {}", self.total_tokens_used);
        println!("Messages in history: {}", self.history.len());
        println!("=============================\n");
    }

    /// Get conversation turns for API request.
    fn get_conversation_for_api(&self) -> Vec<(String, String)> {
        let mut messages = Vec::new();

        // Add system message if present
        if let Some(ref system) = self.system_message {
            messages.push(("system".to_string(), system.clone()));
        }

        // Add conversation history
        for turn in &self.history {
            messages.push((turn.role.clone(), turn.content.clone()));
        }

        messages
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("OpenAI Ergonomic - Comprehensive Chat Example");
    println!("============================================");
    println!();

    // Create client from environment variables
    let client = match Client::from_env() {
        Ok(client_builder) => {
            println!(" Client initialized successfully");
            client_builder.build()
        }
        Err(e) => {
            eprintln!(" Failed to initialize client: {e}");
            eprintln!("Make sure OPENAI_API_KEY environment variable is set");
            return Err(e.into());
        }
    };

    // Initialize conversation manager with system message
    let system_message = "You are a helpful AI assistant. Provide concise, informative responses. \
                          Always be polite and professional. If asked about your capabilities, \
                          explain what you can help with clearly."
        .to_string();

    let mut conversation = ConversationManager::new(Some(system_message), 10);

    println!(" Conversation manager initialized (max history: 10 messages)");
    println!(" System message configured");
    println!();

    // Demonstrate conversation features
    demonstrate_basic_chat(&client, &mut conversation).await?;
    demonstrate_multi_turn_chat(&client, &mut conversation).await?;
    demonstrate_streaming_chat(&client, &mut conversation).await?;
    demonstrate_token_tracking(&client, &mut conversation).await?;
    demonstrate_error_handling(&client).await?;

    // Final conversation summary
    conversation.display_history();

    println!(" Chat comprehensive example completed successfully!");
    println!("This example demonstrated:");
    println!("  • Multi-turn conversation management");
    println!("  • Message history tracking and rotation");
    println!("  • System message configuration");
    println!("  • Token usage monitoring");
    println!("  • Error handling patterns");
    println!("  • Streaming response handling");

    Ok(())
}

/// Demonstrate basic chat completion.
async fn demonstrate_basic_chat(
    client: &Client,
    conversation: &mut ConversationManager,
) -> Result<(), Box<dyn std::error::Error>> {
    println!(" Example 1: Basic Chat Completion");
    println!("----------------------------------");

    let user_message = "Hello! Can you explain what you can help me with?";
    conversation.add_user_message(user_message.to_string());

    println!("User: {user_message}");
    print!("Assistant: ");
    io::stdout().flush()?;

    // Build the chat request with conversation history
    let messages = conversation.get_conversation_for_api();
    let mut chat_builder = client.chat();

    for (role, content) in messages {
        match role.as_str() {
            "system" => chat_builder = chat_builder.system(content),
            "user" => chat_builder = chat_builder.user(content),
            "assistant" => chat_builder = chat_builder.assistant(content),
            _ => {} // Ignore unknown roles
        }
    }

    // Send the request
    let response = client.send_chat(chat_builder.temperature(0.7)).await?;

    if let Some(content) = response.content() {
        println!("{content}");
        conversation.add_assistant_message(content.to_string(), None);

        // Track token usage if available
        if let Some(usage) = response.usage() {
            conversation.update_token_usage(usage.prompt_tokens, usage.completion_tokens);
        }
    } else {
        println!("No response content received");
    }

    println!();
    Ok(())
}

/// Demonstrate multi-turn conversation.
async fn demonstrate_multi_turn_chat(
    client: &Client,
    conversation: &mut ConversationManager,
) -> Result<(), Box<dyn std::error::Error>> {
    println!(" Example 2: Multi-turn Conversation");
    println!("------------------------------------");

    let questions = vec![
        "What's the capital of France?",
        "What's the population of that city?",
        "Can you tell me an interesting fact about it?",
    ];

    for question in questions {
        conversation.add_user_message(question.to_string());

        println!("User: {question}");
        print!("Assistant: ");
        io::stdout().flush()?;

        // Build chat request with full conversation history
        let messages = conversation.get_conversation_for_api();
        let mut chat_builder = client.chat();

        for (role, content) in messages {
            match role.as_str() {
                "system" => chat_builder = chat_builder.system(content),
                "user" => chat_builder = chat_builder.user(content),
                "assistant" => chat_builder = chat_builder.assistant(content),
                _ => {}
            }
        }

        let response = client.send_chat(chat_builder.temperature(0.3)).await?;

        if let Some(content) = response.content() {
            println!("{content}");
            conversation.add_assistant_message(content.to_string(), None);

            // Track token usage
            if let Some(usage) = response.usage() {
                conversation.update_token_usage(usage.prompt_tokens, usage.completion_tokens);
            }
        }

        println!();
        // Small delay between questions for readability
        tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

/// Demonstrate streaming chat response.
async fn demonstrate_streaming_chat(
    _client: &Client,
    conversation: &mut ConversationManager,
) -> Result<(), Box<dyn std::error::Error>> {
    println!(" Example 3: Streaming Chat Response");
    println!("------------------------------------");

    // Add user message for streaming example
    let streaming_question = "Can you write a short poem about programming?";
    conversation.add_user_message(streaming_question.to_string());

    println!("User: {streaming_question}");
    println!("Assistant (streaming): ");

    // Note: Streaming is not yet fully implemented in the client
    // This is a placeholder showing the intended API
    println!(" Streaming functionality is being implemented...");
    println!("Future implementation will show real-time token-by-token responses");

    // Simulate what streaming would look like
    let simulated_response = "Programming flows like poetry in motion,\nEach function a verse, each loop a devotion.\nVariables dance through memory's halls,\nWhile algorithms answer logic's calls.";

    // Simulate typing effect
    for char in simulated_response.chars() {
        print!("{char}");
        io::stdout().flush()?;
        tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
    }
    println!("\n");

    // Add the response to conversation history
    conversation.add_assistant_message(simulated_response.to_string(), None);

    Ok(())
}

/// Demonstrate token usage tracking.
async fn demonstrate_token_tracking(
    client: &Client,
    conversation: &mut ConversationManager,
) -> Result<(), Box<dyn std::error::Error>> {
    println!(" Example 4: Token Usage Tracking");
    println!("---------------------------------");

    let efficiency_question = "In one sentence, what is machine learning?";
    conversation.add_user_message(efficiency_question.to_string());

    println!("User: {efficiency_question}");
    print!("Assistant: ");
    io::stdout().flush()?;

    // Build chat request
    let messages = conversation.get_conversation_for_api();
    let mut chat_builder = client.chat().max_completion_tokens(50); // Limit tokens for demo

    for (role, content) in messages {
        match role.as_str() {
            "system" => chat_builder = chat_builder.system(content),
            "user" => chat_builder = chat_builder.user(content),
            "assistant" => chat_builder = chat_builder.assistant(content),
            _ => {}
        }
    }

    let response = client.send_chat(chat_builder).await?;

    if let Some(content) = response.content() {
        println!("{content}");

        // Display detailed token usage
        if let Some(usage) = response.usage() {
            println!("\n Token Usage Breakdown:");
            println!("  Prompt tokens: {}", usage.prompt_tokens);
            println!("  Completion tokens: {}", usage.completion_tokens);
            println!("  Total tokens: {}", usage.total_tokens);

            conversation.update_token_usage(usage.prompt_tokens, usage.completion_tokens);

            conversation.add_assistant_message(content.to_string(), Some(usage.completion_tokens));
        } else {
            conversation.add_assistant_message(content.to_string(), None);
        }
    }

    println!();
    Ok(())
}

/// Demonstrate error handling patterns.
async fn demonstrate_error_handling(client: &Client) -> Result<(), Box<dyn std::error::Error>> {
    println!("  Example 5: Error Handling Patterns");
    println!("------------------------------------");

    println!("Testing various error scenarios...\n");

    // Test 1: Invalid model
    println!("Test 1: Invalid model name");
    let invalid_model_builder = client.chat()
        .user("Hello")
        // Note: We can't easily test invalid model without modifying the builder
        // This shows the pattern for handling errors
        .temperature(0.7);

    match client.send_chat(invalid_model_builder).await {
        Ok(_) => println!(" Request succeeded (model validation not yet implemented)"),
        Err(e) => match &e {
            Error::Api {
                status, message, ..
            } => {
                println!(" API Error ({status}): {message}");
            }
            Error::Http(reqwest_err) => {
                println!(" HTTP Error: {reqwest_err}");
            }
            Error::InvalidRequest(msg) => {
                println!(" Invalid Request: {msg}");
            }
            _ => {
                println!(" Unexpected Error: {e}");
            }
        },
    }

    // Test 2: Empty message validation
    println!("\nTest 2: Empty message validation");
    let empty_builder = client.chat(); // No messages added

    match client.send_chat(empty_builder).await {
        Ok(_) => println!(" Empty request unexpectedly succeeded"),
        Err(Error::InvalidRequest(msg)) => {
            println!(" Validation caught empty request: {msg}");
        }
        Err(e) => {
            println!(" Unexpected error type: {e}");
        }
    }

    // Test 3: Configuration errors
    println!("\nTest 3: Configuration validation");
    println!(" Client configuration is valid (created successfully)");

    println!("\n  Error handling patterns demonstrated:");
    println!("  • API error classification");
    println!("  • Request validation");
    println!("  • Network error handling");
    println!("  • Configuration validation");

    println!();
    Ok(())
}