Files
whit3rabbitandClaude Opus 4.7 6de126893f chore: fix all clippy lints across workspace
- tools_map: use std::slice::from_ref over &[clone()] in 4 tests
- gemini_streaming_map: rewrite match as matches!
- backend/mod.rs: move impl BackendClient before #[cfg(test)] mod tests
- middleware: replace .filter().last() with .rfind() (xff parsing)
- middleware: drop dead `|| true` in is_ip_allowed smoke test
- sse: drop blank line between doc comment and assert_sse_ok
- streaming example: collapse nested if-let into outer match arms
- tool_execution tests: array literal over vec! for one-off slices
- live_bedrock: use is_some_and instead of map_or(false, _)
- live_api / live_responses: contains() over iter().any() on &[&str]

All test-only / example changes; no production behavior change.
1130 tests pass, fmt clean, clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 19:11:35 -05:00

70 lines
2.3 KiB
Rust

//! Streaming example: receive tokens incrementally as they arrive.
//!
//! ```bash
//! CHAT_COMPLETIONS_URL=https://api.openai.com/v1/chat/completions \
//! OPENAI_API_KEY=sk-... \
//! cargo run --example streaming -p anyllm_client
//! ```
use anyllm_client::{Client, ClientError};
use anyllm_translate::anthropic::{Delta, MessageCreateRequest, StreamEvent};
use futures::StreamExt;
use std::io::Write;
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
async fn run() -> Result<(), ClientError> {
let url = std::env::var("CHAT_COMPLETIONS_URL")
.unwrap_or_else(|_| "https://api.openai.com/v1/chat/completions".to_string());
let api_key = std::env::var("OPENAI_API_KEY").unwrap_or_default();
let client = Client::builder().base_url(&url).api_key(&api_key).build()?;
let req: MessageCreateRequest = serde_json::from_str(
r#"{
"model": "claude-sonnet-4-6",
"max_tokens": 512,
"messages": [
{"role": "user", "content": "Write a haiku about Rust programming."}
]
}"#,
)
.expect("static request JSON is valid");
// messages_stream() returns (stream, rate_limit_headers).
// The stream yields StreamEvent items translated from the backend's SSE chunks.
let (mut stream, rate_limits) = client.messages_stream(&req).await?;
while let Some(event) = stream.next().await {
match event? {
StreamEvent::ContentBlockDelta {
delta: Delta::TextDelta { text },
..
} => {
// TextDelta carries incremental text. Print immediately without buffering.
print!("{text}");
std::io::stdout().flush().ok();
}
StreamEvent::MessageDelta { usage: Some(u), .. } => {
eprintln!("\n[output tokens: {}]", u.output_tokens);
}
StreamEvent::MessageStop {} => {
println!(); // ensure a trailing newline
}
_ => {} // MessageStart, ContentBlockStart/Stop, Ping are informational
}
}
if let Some(remaining) = &rate_limits.requests_remaining {
eprintln!("[requests remaining: {remaining}]");
}
Ok(())
}