Stream and print the response in real-time Returns the complete response as a string after streaming Usage in Python: response = await chunks.streaming_response() # Chunks are printed in real-time, then full response is returned
(&'a self, py: Python<'a>)
| 1243 | /// response = await chunks.streaming_response() |
| 1244 | /// # Chunks are printed in real-time, then full response is returned |
| 1245 | fn streaming_response<'a>(&'a self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> { |
| 1246 | let provider = Arc::clone(&self.provider); |
| 1247 | let prompt = self.prompt.clone(); |
| 1248 | let max_tokens = self.max_tokens; |
| 1249 | let temperature = self.temperature; |
| 1250 | let stream = Arc::clone(&self.stream); |
| 1251 | let initialized = Arc::clone(&self.initialized); |
| 1252 | |
| 1253 | pyo3_async_runtimes::tokio::future_into_py(py, async move { |
| 1254 | // Phase 1: Initialize stream if needed (using OnceCell - no lock held during init) |
| 1255 | let init_result = initialized |
| 1256 | .get_or_try_init(|| async { |
| 1257 | // Build request |
| 1258 | let mut request = LlmRequest::new(prompt.clone()); |
| 1259 | if let Some(tokens) = max_tokens { |
| 1260 | request = request.with_max_tokens(tokens); |
| 1261 | } |
| 1262 | if let Some(temp) = temperature { |
| 1263 | request = request.with_temperature(temp); |
| 1264 | } |
| 1265 | |
| 1266 | // Get stream from provider (NO MUTEX HELD during this await!) |
| 1267 | let guard = provider.read().await; |
| 1268 | let provider_stream = guard.stream(request).await.map_err(to_py_error)?; |
| 1269 | drop(guard); // Explicitly drop read lock before acquiring mutex |
| 1270 | |
| 1271 | // Now briefly hold mutex just to store the stream |
| 1272 | let mut stream_guard = stream.lock().await; |
| 1273 | *stream_guard = Some(Box::pin(provider_stream)); |
| 1274 | drop(stream_guard); // Explicitly release mutex |
| 1275 | |
| 1276 | Ok::<(), PyErr>(()) |
| 1277 | }) |
| 1278 | .await; |
| 1279 | |
| 1280 | // Check if initialization failed |
| 1281 | init_result?; |
| 1282 | |
| 1283 | // Phase 2: Collect and print chunks in real-time |
| 1284 | let mut full_response = String::new(); |
| 1285 | |
| 1286 | // Hold mutex for streaming - but now we're just polling, not doing heavy I/O |
| 1287 | let mut stream_guard = stream.lock().await; |
| 1288 | if let Some(ref mut s) = *stream_guard { |
| 1289 | while let Some(result) = s.next().await { |
| 1290 | match result { |
| 1291 | Ok(response) => { |
| 1292 | // Print chunk immediately (to stdout) - handle errors gracefully |
| 1293 | print!("{}", response.content); |
| 1294 | use std::io::Write; |
| 1295 | let _ = std::io::stdout().flush(); // Don't panic if flush fails |
| 1296 | |
| 1297 | // Accumulate for final response |
| 1298 | full_response.push_str(&response.content); |
| 1299 | } |
| 1300 | Err(e) => { |
| 1301 | // Always propagate errors |
| 1302 | return Err(to_py_error(e)); |
nothing calls this directly
no test coverage detected