|
| 1 | +# Graph Execution Debugging and Event System |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This PR introduces a comprehensive event-based debugging system for Langflow graph execution, enabling detailed tracking and analysis of graph state mutations during execution. The implementation uses a pure observer pattern that provides zero overhead when not in use, making it production-safe. |
| 6 | + |
| 7 | +## Key Features |
| 8 | + |
| 9 | +### 🎯 Graph Mutation Event System |
| 10 | + |
| 11 | +- **Event Infrastructure**: New `GraphMutationEvent` system that tracks all graph state changes with before/after snapshots |
| 12 | +- **Observer Pattern**: Pure observer pattern implementation with `register_observer()` and `unregister_observer()` methods |
| 13 | +- **Zero Overhead**: Fast path when no observers are registered, ensuring no performance impact in production |
| 14 | +- **Serializable Events**: Events can be serialized to dictionaries for replay and storage |
| 15 | + |
| 16 | +### 📊 Event-Based Recording |
| 17 | + |
| 18 | +- **EventRecorder**: Observer that captures all graph mutations during execution |
| 19 | +- **EventBasedRecording**: Rich recording object with analysis methods: |
| 20 | + - `get_events_by_type()` - Filter events by type |
| 21 | + - `get_events_for_vertex()` - Get all events for a specific vertex |
| 22 | + - `get_queue_evolution()` - Track how the execution queue changes over time |
| 23 | + - `get_dependency_changes()` - Monitor dependency modifications |
| 24 | + - `show_summary()`, `show_timeline()`, `show_events_for_component()` - Visualization methods |
| 25 | +- **Save/Load**: Recordings can be saved to and loaded from files for later analysis |
| 26 | + |
| 27 | +### 🔧 Graph Execution Improvements |
| 28 | + |
| 29 | +#### Loop Component Enhancements |
| 30 | +- **Synchronized Dependencies**: Loop component now properly updates both `run_predecessors` and `run_map` to keep dependency structures synchronized |
| 31 | +- **State Reset**: New `reset_loop_state()` method for clean loop state management between executions |
| 32 | +- **Better Documentation**: Added critical comments explaining the relationship between dependency structures |
| 33 | + |
| 34 | +#### Graph Manager Refactoring |
| 35 | +- **Async Methods**: Made `remove_from_predecessors()` and `remove_vertex_from_runnables()` async for consistency |
| 36 | +- **Sync Variants**: Added `mark_branch_sync()` for synchronous contexts (used by custom components) |
| 37 | +- **Centralized Mutations**: All graph mutations now go through centralized methods that emit events |
| 38 | + |
| 39 | +### 🧪 Testing Infrastructure |
| 40 | + |
| 41 | +#### Execution Path Validation |
| 42 | +- **Path Equivalence Testing**: New test suite that validates both `async_start()` and `arun()` execution paths produce identical results |
| 43 | +- **Test Data Flows**: Uses test flows that don't require API keys for reliable CI testing |
| 44 | +- **Comprehensive Tracing**: `ExecutionTracer` captures complete execution traces for comparison |
| 45 | + |
| 46 | +#### Event System Tests |
| 47 | +- **Mutation Event Tests**: Tests for queue operations, dependency updates, and event emission |
| 48 | +- **Event Recorder Tests**: Tests for event capture, queue evolution tracking, and recording analysis |
| 49 | +- **Graph Mutation Tests**: Tests ensuring both dependency structures stay synchronized |
| 50 | + |
| 51 | +### 🛠️ Component Validation Improvements |
| 52 | + |
| 53 | +- **TYPE_CHECKING Block Support**: Component validation now properly handles `TYPE_CHECKING` blocks, extracting imports needed for `get_type_hints()` to work correctly |
| 54 | +- **Better Error Handling**: Improved error handling for components defined in notebooks or REPL environments |
| 55 | +- **Source Code Extraction**: More robust source code extraction with graceful fallbacks |
| 56 | + |
| 57 | +## Technical Details |
| 58 | + |
| 59 | +### Event Types Tracked |
| 60 | + |
| 61 | +- `queue_extended` - When vertices are added to the execution queue |
| 62 | +- `queue_dequeued` - When vertices are removed from the queue |
| 63 | +- `dependency_added` - When dynamic dependencies are added |
| 64 | +- `vertex_marked` - When vertex states change (ACTIVE/INACTIVE) |
| 65 | + |
| 66 | +### Architecture |
| 67 | + |
| 68 | +``` |
| 69 | +Graph |
| 70 | + ├── register_observer() / unregister_observer() |
| 71 | + ├── _emit_event() - Emits events to all observers |
| 72 | + └── All mutations → emit before/after events |
| 73 | + ├── extend_run_queue() |
| 74 | + ├── add_dynamic_dependency() |
| 75 | + ├── mark_branch_sync() |
| 76 | + └── remove_from_predecessors() |
| 77 | +``` |
| 78 | + |
| 79 | +### Usage Example |
| 80 | + |
| 81 | +```python |
| 82 | +from lfx.graph.graph.base import Graph |
| 83 | +from lfx.debug.event_recorder import record_graph_with_events |
| 84 | + |
| 85 | +# Record graph execution |
| 86 | +graph = Graph.from_payload(flow_data) |
| 87 | +recording = await record_graph_with_events(graph, "My Flow") |
| 88 | + |
| 89 | +# Analyze the recording |
| 90 | +recording.show_summary() |
| 91 | +recording.show_timeline() |
| 92 | + |
| 93 | +# Get specific insights |
| 94 | +queue_evolution = recording.get_queue_evolution() |
| 95 | +dependency_changes = recording.get_dependency_changes() |
| 96 | + |
| 97 | +# Save for later analysis |
| 98 | +recording.save("flow_recording.pkl") |
| 99 | +``` |
| 100 | + |
| 101 | +## Files Changed |
| 102 | + |
| 103 | +### New Files |
| 104 | +- `src/lfx/src/lfx/debug/__init__.py` - Debug module initialization |
| 105 | +- `src/lfx/src/lfx/debug/events.py` - GraphMutationEvent and observer types |
| 106 | +- `src/lfx/src/lfx/debug/event_recorder.py` - EventRecorder and EventBasedRecording |
| 107 | +- `src/backend/tests/unit/graph/test_execution_path_validation.py` - Execution path equivalence tests |
| 108 | +- `src/backend/tests/unit/graph/test_execution_path_equivalence.py` - Execution tracing utilities |
| 109 | +- `src/backend/tests/unit/graph/test_event_recorder.py` - Event recorder tests |
| 110 | +- `src/backend/tests/unit/graph/test_graph_mutation_events.py` - Mutation event tests |
| 111 | + |
| 112 | +### Modified Files |
| 113 | +- `src/lfx/src/lfx/graph/graph/base.py` - Added observer pattern, event emission |
| 114 | +- `src/lfx/src/lfx/graph/graph/runnable_vertices_manager.py` - Made methods async |
| 115 | +- `src/lfx/src/lfx/components/logic/loop.py` - Improved dependency synchronization |
| 116 | +- `src/lfx/src/lfx/custom/custom_component/component.py` - Better error handling |
| 117 | +- `src/lfx/src/lfx/custom/custom_component/custom_component.py` - Use mark_branch_sync |
| 118 | +- `src/lfx/src/lfx/custom/validate.py` - TYPE_CHECKING block support |
| 119 | +- `pyproject.toml` - Added marimo dependency for debugging notebooks |
| 120 | + |
| 121 | +## Benefits |
| 122 | + |
| 123 | +1. **Debugging**: Comprehensive visibility into graph execution state changes |
| 124 | +2. **Testing**: Better test coverage with execution path validation |
| 125 | +3. **Reliability**: Synchronized dependency structures prevent bugs |
| 126 | +4. **Performance**: Zero overhead when debugging is not active |
| 127 | +5. **Extensibility**: Easy to add new event types and observers |
| 128 | + |
| 129 | +## Testing |
| 130 | + |
| 131 | +- ✅ All existing tests pass |
| 132 | +- ✅ New execution path validation tests pass |
| 133 | +- ✅ Event system tests pass |
| 134 | +- ✅ Loop component tests pass with improved dependency handling |
| 135 | + |
| 136 | +## Breaking Changes |
| 137 | + |
| 138 | +None - This is a purely additive change. The event system is opt-in and has zero overhead when not used. |
| 139 | + |
| 140 | +## Future Work |
| 141 | + |
| 142 | +- [ ] Add more event types (vertex execution start/end, memory updates, etc.) |
| 143 | +- [ ] Create visualization tools for event recordings |
| 144 | +- [ ] Add event filtering and querying capabilities |
| 145 | +- [ ] Integrate with Langflow UI for real-time debugging |
| 146 | + |
0 commit comments