1use futures::StreamExt;
31use openai_ergonomic::responses::tool_function;
32use openai_ergonomic::{Client, Error, Response, Result, ToolCallExt};
33use serde_json::json;
34use std::io::{self, Write};
35
36#[tokio::main]
37#[allow(clippy::too_many_lines)] async fn main() -> Result<()> {
39 tracing_subscriber::fmt().with_env_filter("info").init();
41
42 println!(" OpenAI Ergonomic Quickstart");
43 println!("==============================\n");
44
45 println!(" Step 1: Setting up the client");
50
51 let client = match Client::from_env() {
53 Ok(client_builder) => {
54 println!(" Client created successfully!");
55 client_builder.build()
56 }
57 Err(e) => {
58 eprintln!(" Failed to create client: {e}");
59 eprintln!(" Make sure you've set OPENAI_API_KEY environment variable");
60 eprintln!(" Example: export OPENAI_API_KEY=\"sk-your-key-here\"");
61 return Err(e);
62 }
63 };
64
65 println!("\n Step 2: Basic chat completion");
70
71 let builder = client.chat_simple("What is Rust programming language in one sentence?");
73 let response = client.send_chat(builder).await;
74
75 match response {
76 Ok(chat_response) => {
77 println!(" Got response!");
78 if let Some(content) = chat_response.content() {
79 println!(" AI: {content}");
80 }
81
82 if let Some(usage) = &chat_response.inner().usage {
84 println!(
85 " Usage: {} prompt + {} completion = {} total tokens",
86 usage.prompt_tokens, usage.completion_tokens, usage.total_tokens
87 );
88 }
89 }
90 Err(e) => {
91 println!(" Chat completion failed: {e}");
92 }
94 }
95
96 println!("\n Step 3: Chat with system context");
101
102 let builder = client.chat_with_system(
104 "You are a helpful coding mentor who explains things simply",
105 "Explain what a HashMap is in Rust",
106 );
107 let response = client.send_chat(builder).await;
108
109 match response {
110 Ok(chat_response) => {
111 println!(" Got contextual response!");
112 if let Some(content) = chat_response.content() {
113 println!(" Mentor: {content}");
114 }
115 }
116 Err(e) => {
117 println!(" Contextual chat failed: {e}");
118 }
119 }
120
121 println!("\n Step 4: Streaming response (real-time)");
126
127 print!(" AI is typing: ");
130 io::stdout().flush().unwrap();
131
132 let builder = client
133 .responses()
134 .user("Write a short haiku about programming")
135 .temperature(0.7);
136
137 let stream_result = client.send_responses_stream(builder).await;
139
140 match stream_result {
141 Ok(mut stream) => {
142 while let Some(chunk_result) = stream.next().await {
144 match chunk_result {
145 Ok(chunk) => {
146 if let Some(content) = chunk.content() {
147 print!("{content}");
148 io::stdout().flush().unwrap();
149 }
150 }
151 Err(e) => {
152 println!("\n Error processing chunk: {e}");
153 break;
154 }
155 }
156 }
157 println!(); }
159 Err(e) => {
160 println!("\n Failed to get streaming response: {e}");
161 }
162 }
163
164 println!("\n Step 5: Using tools/functions");
169
170 let weather_tool = tool_function(
173 "get_current_weather",
174 "Get the current weather for a given location",
175 json!({
176 "type": "object",
177 "properties": {
178 "location": {
179 "type": "string",
180 "description": "The city name, e.g. 'San Francisco, CA'"
181 },
182 "unit": {
183 "type": "string",
184 "enum": ["celsius", "fahrenheit"],
185 "description": "Temperature unit"
186 }
187 },
188 "required": ["location"]
189 }),
190 );
191
192 let builder = client
193 .responses()
194 .user("What's the weather like in Tokyo?")
195 .tool(weather_tool);
196 let response = client.send_responses(builder).await;
197
198 match response {
199 Ok(chat_response) => {
200 println!(" Got response with potential tool calls!");
201
202 let tool_calls = chat_response.tool_calls();
204 if !tool_calls.is_empty() {
205 println!(" AI requested tool calls:");
206 for tool_call in tool_calls {
207 let function_name = tool_call.function_name();
208 println!(" Function: {function_name}");
209 let function_args = tool_call.function_arguments();
210 println!(" Arguments: {function_args}");
211
212 println!(" In a real app, you'd call your weather API here");
215 }
216 } else if let Some(content) = chat_response.content() {
217 println!(" AI: {content}");
218 }
219 }
220 Err(e) => {
221 println!(" Tool calling example failed: {e}");
222 }
223 }
224
225 println!("\n Step 6: Error handling patterns");
230
231 let builder = client.chat_simple(""); let bad_response = client.send_chat(builder).await;
234
235 match bad_response {
236 Ok(response) => {
237 println!(" Unexpectedly succeeded with empty message");
238 if let Some(content) = response.content() {
239 println!(" AI: {content}");
240 }
241 }
242 Err(Error::Api {
243 status, message, ..
244 }) => {
245 println!(" API Error (HTTP {status}):");
246 println!(" Message: {message}");
247 println!(" This is normal - we sent an invalid request");
248 }
249 Err(Error::RateLimit { .. }) => {
250 println!(" Rate limited - you're sending requests too fast");
251 println!(" In a real app, you'd implement exponential backoff");
252 }
253 Err(Error::Http(_)) => {
254 println!(" HTTP/Network error");
255 println!(" Check your internet connection and API key");
256 }
257 Err(e) => {
258 println!(" Other error: {e}");
259 }
260 }
261
262 println!("\n Step 7: Complete real-world example");
267 println!("Building a simple AI assistant that can:");
268 println!("- Answer questions with context");
269 println!("- Track conversation costs");
270 println!("- Handle errors gracefully");
271
272 let mut total_tokens = 0;
273
274 let questions = [
276 "What is the capital of France?",
277 "What's special about that city?",
278 "How many people live there?",
279 ];
280
281 for (i, question) in questions.iter().enumerate() {
282 println!("\n User: {question}");
283
284 let builder = client
285 .responses()
286 .system(
287 "You are a knowledgeable geography expert. Keep answers concise but informative.",
288 )
289 .user(*question)
290 .temperature(0.1); let response = client.send_responses(builder).await;
292
293 match response {
294 Ok(chat_response) => {
295 if let Some(content) = chat_response.content() {
296 println!(" Assistant: {content}");
297 }
298
299 if let Some(usage) = chat_response.usage() {
301 total_tokens += usage.total_tokens;
302 println!(
303 " This exchange: {} tokens (Running total: {})",
304 usage.total_tokens, total_tokens
305 );
306 }
307 }
308 Err(e) => {
309 println!(" Question {} failed: {}", i + 1, e);
310 }
312 }
313 }
314
315 println!("\n Quickstart Complete!");
320 println!("======================");
321 println!("You've successfully:");
322 println!(" Created an OpenAI client");
323 println!(" Made basic chat completions");
324 println!(" Used streaming responses");
325 println!(" Implemented tool/function calling");
326 println!(" Handled errors gracefully");
327 println!(" Built a complete conversational AI");
328 println!("\n Total tokens used in examples: {total_tokens}");
329 println!(
330 " Estimated cost: ~${:.4} (assuming GPT-4 pricing)",
331 f64::from(total_tokens) * 0.03 / 1000.0
332 );
333
334 println!("\n Next Steps:");
335 println!("- Check out other examples in the examples/ directory");
336 println!("- Read the documentation: https://docs.rs/openai-ergonomic");
337 println!("- Explore advanced features like vision, audio, and assistants");
338 println!("- Build your own AI-powered applications!");
339
340 Ok(())
341}
342
343#[allow(dead_code)]
348async fn robust_chat_call(client: &Client, message: &str) -> Result<String> {
349 const MAX_RETRIES: usize = 3;
350 let mut last_error = None;
351
352 for attempt in 1..=MAX_RETRIES {
353 let builder = client.chat_simple(message);
354 match client.send_chat(builder).await {
355 Ok(response) => {
356 if let Some(content) = response.content() {
357 return Ok(content.to_string());
358 }
359 return Err(Error::Api {
360 status: 200,
361 message: "No content in response".to_string(),
362 error_type: None,
363 error_code: None,
364 });
365 }
366 Err(Error::RateLimit { .. }) if attempt < MAX_RETRIES => {
367 let delay = std::time::Duration::from_millis(1000 * attempt as u64);
369 tokio::time::sleep(delay).await;
370 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
372 }
373 Err(e) => {
374 last_error = Some(e);
375 if attempt < MAX_RETRIES {
376 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
378 }
379 }
380 }
381 }
382
383 Err(last_error.unwrap_or_else(|| Error::Api {
384 status: 0,
385 message: "Unknown error after retries".to_string(),
386 error_type: None,
387 error_code: None,
388 }))
389}