mirror of
https://github.com/whit3rabbit/anyllm-proxy.git
synced 2026-09-22 00:00:50 +00:00
- 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>
70 lines
2.3 KiB
Rust
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(())
|
|
}
|