Generate a structured JSON object with streaming partial updates. Calls `on_partial` with progressively more complete partial objects as tokens arrive. Returns the final validated object. In streaming mode, `max_repair_attempts` defaults to 0 because a repair would reset the partial object stream (confusing for consumers).
(
client: &dyn LlmClient,
req: &StructuredRequest,
on_partial: PartialObjectCallback,
)
| 192 | /// In streaming mode, `max_repair_attempts` defaults to 0 because a repair |
| 193 | /// would reset the partial object stream (confusing for consumers). |
| 194 | pub async fn generate_streaming( |
| 195 | client: &dyn LlmClient, |
| 196 | req: &StructuredRequest, |
| 197 | on_partial: PartialObjectCallback, |
| 198 | ) -> Result<StructuredResult> { |
| 199 | let mode = resolve_mode(req.mode, client.native_structured_support()); |
| 200 | let messages = build_initial_messages(req, mode); |
| 201 | let system = build_system_prompt(req, mode); |
| 202 | let tools = build_tools(req, mode); |
| 203 | let directive = build_directive(req, mode); |
| 204 | |
| 205 | let cancel_token = CancellationToken::new(); |
| 206 | let mut rx = client |
| 207 | .complete_streaming_structured(&messages, Some(&system), &tools, &directive, cancel_token) |
| 208 | .await |
| 209 | .context("LLM streaming call failed during structured generation")?; |
| 210 | |
| 211 | let mut json_buffer = String::new(); |
| 212 | let mut last_valid_partial: Option<Value> = None; |
| 213 | let mut final_response: Option<super::LlmResponse> = None; |
| 214 | let mut last_parse_len: usize = 0; |
| 215 | // Minimum bytes of new data before attempting a partial parse (reduces CPU) |
| 216 | const PARSE_THRESHOLD: usize = 8; |
| 217 | |
| 218 | while let Some(event) = rx.recv().await { |
| 219 | match event { |
| 220 | StreamEvent::ToolUseInputDelta(delta) if mode == StructuredMode::Tool => { |
| 221 | if final_response.is_some() { |
| 222 | continue; |
| 223 | } |
| 224 | json_buffer.push_str(&delta); |
| 225 | if json_buffer.len() - last_parse_len >= PARSE_THRESHOLD { |
| 226 | if let Some(partial) = try_parse_partial_json(&json_buffer) { |
| 227 | if last_valid_partial.as_ref() != Some(&partial) { |
| 228 | on_partial(&partial); |
| 229 | last_valid_partial = Some(partial); |
| 230 | } |
| 231 | } |
| 232 | last_parse_len = json_buffer.len(); |
| 233 | } |
| 234 | } |
| 235 | StreamEvent::TextDelta(delta) if mode != StructuredMode::Tool => { |
| 236 | if final_response.is_some() { |
| 237 | continue; |
| 238 | } |
| 239 | json_buffer.push_str(&delta); |
| 240 | if json_buffer.len() - last_parse_len >= PARSE_THRESHOLD { |
| 241 | if let Some(json_start) = find_json_start(&json_buffer) { |
| 242 | let candidate = &json_buffer[json_start..]; |
| 243 | if let Some(partial) = try_parse_partial_json(candidate) { |
| 244 | if last_valid_partial.as_ref() != Some(&partial) { |
| 245 | on_partial(&partial); |
| 246 | last_valid_partial = Some(partial); |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | last_parse_len = json_buffer.len(); |
| 251 | } |