//! Opt-in tests against real inference endpoints. Activated with //! `TC_LIVE_LLM=1` plus `ANTHROPIC_API_KEY` (Anthropic) or //! `TC_OPENAI_COMPAT_URL` (e.g. a local Ollama at //! `http://127.0.0.1:11434/v1` with `TC_OPENAI_COMPAT_MODEL` set). //! CI runs these in a dedicated credentialed job; the default suite uses //! the scripted provider, which exercises the identical seam. use futures::StreamExt; use tc_llm::{ AnthropicProvider, ChatMessage, ChatRequest, ChatRole, ContentPart, LlmEvent, LlmProvider, OpenAiCompatProvider, }; fn live_enabled() -> bool { std::env::var("TC_LIVE_LLM").as_deref() == Ok("1") } fn simple_request(model: &str) -> ChatRequest { ChatRequest { system: "Answer in exactly one short sentence.".into(), messages: vec![ChatMessage { role: ChatRole::User, parts: vec![ContentPart::text("Say the word 'pong'.")], }], tools: vec![], model: model.into(), max_tokens: 64, } } async fn collect_text(provider: &dyn LlmProvider, request: ChatRequest) -> String { let mut stream = provider.stream(request).await.expect("stream opens"); let mut text = String::new(); while let Some(event) = stream.next().await { if let LlmEvent::TextDelta(t) = event.expect("stream event") { text.push_str(&t); } } text } #[tokio::test] async fn anthropic_streams_text() { if !live_enabled() { eprintln!("skipped: set TC_LIVE_LLM=1 to run"); return; } let Ok(key) = std::env::var("ANTHROPIC_API_KEY") else { eprintln!("skipped: ANTHROPIC_API_KEY not set"); return; }; let provider = AnthropicProvider::new(key); let text = collect_text(&provider, simple_request("claude-haiku-4-5-20251001")).await; assert!(text.to_lowercase().contains("pong"), "got: {text}"); } #[tokio::test] async fn openai_compat_streams_text() { if !live_enabled() { eprintln!("skipped: set TC_LIVE_LLM=1 to run"); return; } let Ok(url) = std::env::var("TC_OPENAI_COMPAT_URL") else { eprintln!("skipped: TC_OPENAI_COMPAT_URL not set"); return; }; let model = std::env::var("TC_OPENAI_COMPAT_MODEL").unwrap_or_else(|_| "qwen2.5:0.5b".into()); let provider = OpenAiCompatProvider::new(url, None); let text = collect_text(&provider, simple_request(&model)).await; assert!(text.to_lowercase().contains("pong"), "got: {text}"); }