| 448 | /// `Executor.get_stream_event_schema()`. |
| 449 | #[pyo3(signature = (workflow, policy=None, stream_mode=None))] |
| 450 | fn execute_streaming( |
| 451 | &mut self, |
| 452 | workflow: &Workflow, |
| 453 | policy: Option<&Bound<'_, GuardRailPolicyConfig>>, |
| 454 | stream_mode: Option<&str>, |
| 455 | ) -> PyResult<WorkflowStreamIterator> { |
| 456 | // Validate workflow |
| 457 | if workflow.inner.graph.node_count() == 0 { |
| 458 | return Err(validation_error( |
| 459 | "workflow", |
| 460 | None, |
| 461 | "Workflow cannot be empty", |
| 462 | )); |
| 463 | } |
| 464 | if let Err(e) = workflow.inner.validate() { |
| 465 | return Err(validation_error( |
| 466 | "workflow", |
| 467 | None, |
| 468 | &format!("Invalid workflow: {}", e), |
| 469 | )); |
| 470 | } |
| 471 | |
| 472 | // Parse stream mode (default: Updates) |
| 473 | let mode = stream_mode |
| 474 | .and_then(StreamMode::from_str_opt) |
| 475 | .unwrap_or(StreamMode::Updates); |
| 476 | |
| 477 | let workflow_clone = workflow.inner.clone(); |
| 478 | let llm_config = self.llm_config.inner.clone(); |
| 479 | let config = self.config.clone(); |
| 480 | let timeout_duration = config.timeout; |
| 481 | let guardrail_enforcer = policy.map(|p| { |
| 482 | let cfg = p.borrow().get_inner(); |
| 483 | Arc::new(GuardRail::enforcer_for(cfg, workflow_clone.id.to_string())) |
| 484 | }); |
| 485 | let guardrail_for_iterator = guardrail_enforcer.clone(); |
| 486 | |
| 487 | // Internal channel from core executor -> processor |
| 488 | let (core_event_tx, mut core_event_rx) = tokio::sync::mpsc::channel::<StreamEvent>(256); |
| 489 | // Public channel from processor -> Python iterator |
| 490 | let (event_tx, event_rx) = tokio::sync::mpsc::channel::<TimedStreamEvent>(256); |
| 491 | |
| 492 | // Stream mode for follow-up LLM calls in the live tool loop (Messages/All → token stream). |
| 493 | let tool_loop_stream_mode = mode; |
| 494 | |
| 495 | // Spawn the streaming executor on the shared Tokio runtime |
| 496 | get_runtime().spawn(async move { |
| 497 | let conditional_handlers = |
| 498 | match crate::workflow::node::build_core_conditional_handlers(&workflow_clone) { |
| 499 | Ok(h) => h, |
| 500 | Err(e) => { |
| 501 | let err_msg = e.to_string(); |
| 502 | let _ = core_event_tx |
| 503 | .send(StreamEvent::WorkflowFailed { |
| 504 | error: err_msg.clone(), |
| 505 | error_type: graphbit_core::error_type_from_string(&err_msg), |
| 506 | }) |
| 507 | .await; |