Skip to content

Commit 2028886

Browse files
Document decorator-based event handler pattern for custom visualizers
- Add section on EventHandlerMixin and @Handles decorator - Show how to avoid long if/elif chains in on_event methods - Explain benefits: self-documenting, type-safe, extensible - Provide complete working example with best practices Co-authored-by: openhands <[email protected]>
1 parent 2430278 commit 2028886

File tree

1 file changed

+54
-2
lines changed

1 file changed

+54
-2
lines changed

sdk/guides/convo-custom-visualizer.mdx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,59 @@ def _handle_action_event(self, event: ActionEvent) -> None:
520520
# ... handle event
521521
```
522522

523-
### 2. Error Handling
523+
### 3. Event Handler Registration Pattern
524+
For cleaner, more maintainable code, avoid long if/elif chains in your `on_event` method. Instead, use a decorator-based handler registration pattern:
525+
526+
```python
527+
from typing import Dict, Callable, Type
528+
529+
def handles(event_type: Type[Event]):
530+
"""Decorator to register a method as an event handler."""
531+
def decorator(func):
532+
func._handles_event_type = event_type
533+
return func
534+
return decorator
535+
536+
class EventHandlerMixin:
537+
"""Mixin that provides event handler registration via decorators."""
538+
539+
def __init__(self, *args, **kwargs):
540+
super().__init__(*args, **kwargs)
541+
self._event_handlers: Dict[Type[Event], Callable[[Event], None]] = {}
542+
self._register_handlers()
543+
544+
def _register_handlers(self):
545+
"""Automatically discover and register event handlers."""
546+
for attr_name in dir(self):
547+
attr = getattr(self, attr_name)
548+
if hasattr(attr, '_handles_event_type'):
549+
event_type = attr._handles_event_type
550+
self._event_handlers[event_type] = attr
551+
552+
def on_event(self, event: Event) -> None:
553+
"""Dispatch events to registered handlers."""
554+
event_type = type(event)
555+
handler = self._event_handlers.get(event_type)
556+
if handler:
557+
handler(event)
558+
559+
class MyVisualizer(EventHandlerMixin, ConversationVisualizer):
560+
@handles(ActionEvent)
561+
def _handle_action_event(self, event: ActionEvent) -> None:
562+
print(f"Action: {event.tool_name}")
563+
564+
@handles(MessageEvent)
565+
def _handle_message_event(self, event: MessageEvent) -> None:
566+
print(f"Message: {event.llm_message.role}")
567+
```
568+
569+
This pattern provides:
570+
- **Self-documenting code**: `@handles(ActionEvent)` clearly shows what each method does
571+
- **Type safety**: The decorator enforces the event type relationship
572+
- **Easy extensibility**: Add new event types without touching dispatch logic
573+
- **No boilerplate**: No need to maintain separate dictionaries or long if/elif chains
574+
575+
### 4. Error Handling
524576
Always include error handling to prevent visualization issues from breaking conversations:
525577

526578
```python
@@ -535,7 +587,7 @@ def on_event(self, event):
535587
logging.warning(f"Visualizer failed: {e}")
536588
```
537589

538-
### 3. Performance Considerations
590+
### 5. Performance Considerations
539591
For high-frequency events, consider optimizing your visualization by filtering events or using efficient output methods like `flush=True` for immediate display.
540592

541593
## Using Your Custom Visualizer

0 commit comments

Comments
 (0)