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
use futures::StreamExt;
use std::io::{self, Write};
use unia::{
model::{Message, Part},
providers::{openai::OpenAI, Provider},
StreamingClient,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ============================================================================================
// Step 1: Setup Provider
// ============================================================================================
// Streaming allows you to receive the response in chunks as it is generated, which provides
// a better user experience for long responses.
//
// The `StreamingClient` trait adds the `request_stream` method to the client.
let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY must be set");
let client = OpenAI::create(api_key, "gpt-5".to_string());
let messages = vec![Message::User(vec![Part::Text {
content: "Write a haiku about Rust programming.".to_string(),
finished: true,
}])];
println!("Streaming response...");
// ============================================================================================
// Step 2: Initiate Stream
// ============================================================================================
// `request_stream` sends the request and returns a `Stream` (from the `futures` crate).
// This stream yields `Result<Response, ClientError>` items.
//
// IMPORTANT: Unlike many other libraries that yield "deltas" (just the new characters),
// unia yields the **entire generated response object so far** in every iteration.
// This makes it easier to reason about the state of the response, but requires you to
// calculate the difference if you want to print only the new characters.
let mut stream = client.request_stream(messages, vec![]).await?;
// ============================================================================================
// Step 3: Consume Stream
// ============================================================================================
// We use `while let Some(...)` to iterate over the stream until it is exhausted.
let mut last_len = 0;
while let Some(result) = stream.next().await {
match result {
Ok(response) => {
// Extract the full text content generated so far.
let content = response
.data
.first()
.and_then(|m| m.content())
.unwrap_or_default();
// Calculate the new text added in this chunk.
if content.len() > last_len {
let new_content = &content[last_len..];
print!("{}", new_content);
io::stdout().flush()?;
last_len = content.len();
}
}
Err(e) => {
eprintln!("\nError: {}", e);
break;
}
}
}
println!(); // Add a final newline
Ok(())
}