-
Notifications
You must be signed in to change notification settings - Fork 3
Document custom visualizer feature #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
83b676c
Document custom visualizer feature
openhands-agent 6b735f2
Update custom visualizer documentation for improved example
openhands-agent ace4583
Comprehensive rewrite of custom visualizer documentation
openhands-agent 4d4e4da
Address review feedback: remove 'production-ready', focus on custom v…
openhands-agent f1c7377
Add custom visualizer guide to navigation
openhands-agent 0395944
Add link to default visualizer source code
openhands-agent 88c6064
Update custom visualizer documentation to match actual code patterns
openhands-agent ebfb5e4
Add comprehensive event types table to custom visualizer documentation
openhands-agent 2430278
Improve custom visualizer documentation with event properties and ren…
openhands-agent 2028886
Document decorator-based event handler pattern for custom visualizers
openhands-agent 91f0bb9
Update custom visualizer documentation for new API
openhands-agent d735bc2
Merge pr-77 branch with documentation updates
openhands-agent 7fb6b5e
Merge branch 'main' into feature/improve-visualizer-api
xingyaoww 22a6021
Update documentation to reflect new visualizer API from PR #1025
openhands-agent 20029e6
Merge branch 'main' into feature/improve-visualizer-api
xingyaoww 7ed212a
Update convo-custom-visualizer.mdx
xingyaoww 0326367
Merge branch 'main' into feature/improve-visualizer-api
jpshackelford File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| --- | ||
| title: Custom Visualizer | ||
| description: Customize conversation visualization by creating custom visualizers or configuring the default visualizer. | ||
| --- | ||
|
|
||
| <Note> | ||
| This example is available on GitHub: [examples/01_standalone_sdk/26_custom_visualizer.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/26_custom_visualizer.py) | ||
| </Note> | ||
|
|
||
| The SDK provides flexible visualization options. You can use the default rich-formatted visualizer, customize it with highlighting patterns, or build completely custom visualizers by subclassing `ConversationVisualizerBase`. | ||
|
|
||
| ## Basic Example | ||
|
|
||
| ```python icon="python" expandable examples/01_standalone_sdk/26_custom_visualizer.py | ||
| """Custom Visualizer Example | ||
|
|
||
| This example demonstrates how to create and use a custom visualizer by subclassing | ||
| ConversationVisualizer. This approach provides: | ||
| - Clean, testable code with class-based state management | ||
| - Direct configuration (just pass the visualizer instance to visualizer parameter) | ||
| - Reusable visualizer that can be shared across conversations | ||
| - Better separation of concerns compared to callback functions | ||
| - Event handler registration to avoid long if/elif chains | ||
|
|
||
| This demonstrates how you can pass a ConversationVisualizer instance directly | ||
| to the visualizer parameter for clean, reusable visualization logic. | ||
| """ | ||
|
|
||
| import logging | ||
| import os | ||
|
|
||
| from pydantic import SecretStr | ||
|
|
||
| from openhands.sdk import LLM, Conversation | ||
| from openhands.sdk.conversation.visualizer import ConversationVisualizerBase | ||
| from openhands.sdk.event import ( | ||
| Event, | ||
| ) | ||
| from openhands.tools.preset.default import get_default_agent | ||
|
|
||
|
|
||
| class MinimalVisualizer(ConversationVisualizerBase): | ||
| """A minimal visualizer that print the raw events as they occur.""" | ||
|
|
||
| def __init__(self, name: str | None = None): | ||
| """Initialize the minimal progress visualizer. | ||
|
|
||
| Args: | ||
| name: Optional name to identify the agent/conversation. | ||
| Note: This simple visualizer doesn't use it in output, | ||
| but accepts it for compatibility with the base class. | ||
| """ | ||
| # Initialize parent - state will be set later via initialize() | ||
| super().__init__(name=name) | ||
|
|
||
| def on_event(self, event: Event) -> None: | ||
| """Handle events for minimal progress visualization.""" | ||
| print(f"\n\n[EVENT] {type(event).__name__}: {event.model_dump_json()[:200]}...") | ||
|
|
||
|
|
||
| api_key = os.getenv("LLM_API_KEY") | ||
| assert api_key is not None, "LLM_API_KEY environment variable is not set." | ||
| model = os.getenv("LLM_MODEL", "openhands/claude-sonnet-4-5-20250929") | ||
| base_url = os.getenv("LLM_BASE_URL") | ||
| llm = LLM( | ||
| model=model, | ||
| api_key=SecretStr(api_key), | ||
| base_url=base_url, | ||
| usage_id="agent", | ||
| ) | ||
| agent = get_default_agent(llm=llm, cli_mode=True) | ||
|
|
||
| # ============================================================================ | ||
| # Configure Visualization | ||
| # ============================================================================ | ||
| # Set logging level to reduce verbosity | ||
| logging.getLogger().setLevel(logging.WARNING) | ||
|
|
||
| # Start a conversation with custom visualizer | ||
| cwd = os.getcwd() | ||
| conversation = Conversation( | ||
| agent=agent, | ||
| workspace=cwd, | ||
| visualizer=MinimalVisualizer(), | ||
| ) | ||
|
|
||
| # Send a message and let the agent run | ||
| print("Sending task to agent...") | ||
| conversation.send_message("Write 3 facts about the current project into FACTS.txt.") | ||
| conversation.run() | ||
| print("Task completed!") | ||
|
|
||
| # Report cost | ||
| cost = llm.metrics.accumulated_cost | ||
| print(f"EXAMPLE_COST: ${cost:.4f}") | ||
| ``` | ||
|
|
||
| ```bash Running the Example | ||
| export LLM_API_KEY="your-api-key" | ||
| cd agent-sdk | ||
| uv run python examples/01_standalone_sdk/26_custom_visualizer.py | ||
| ``` | ||
|
|
||
| ## Visualizer Configuration Options | ||
|
|
||
| The `visualizer` parameter in `Conversation` controls how events are displayed: | ||
|
|
||
| ```python | ||
| from openhands.sdk import Conversation | ||
| from openhands.sdk.conversation import DefaultConversationVisualizer, ConversationVisualizerBase | ||
|
|
||
| # Option 1: Use default visualizer (enabled by default) | ||
| conversation = Conversation(agent=agent, workspace=workspace) | ||
|
|
||
| # Option 2: Disable visualization | ||
| conversation = Conversation(agent=agent, workspace=workspace, visualizer=None) | ||
|
|
||
| # Option 3: Pass a visualizer class (will be instantiated automatically) | ||
| conversation = Conversation(agent=agent, workspace=workspace, visualizer=DefaultConversationVisualizer) | ||
|
|
||
| # Option 4: Pass a configured visualizer instance | ||
| custom_viz = DefaultConversationVisualizer( | ||
| name="MyAgent", | ||
| highlight_regex={r"^Reasoning:": "bold cyan"} | ||
| ) | ||
| conversation = Conversation(agent=agent, workspace=workspace, visualizer=custom_viz) | ||
|
|
||
| # Option 5: Use custom visualizer class | ||
| class MyVisualizer(ConversationVisualizerBase): | ||
| def on_event(self, event): | ||
| print(f"Event: {event}") | ||
|
|
||
| conversation = Conversation(agent=agent, workspace=workspace, visualizer=MyVisualizer()) | ||
| ``` | ||
|
|
||
| ## Customizing the Default Visualizer | ||
|
|
||
| `DefaultConversationVisualizer` uses Rich panels and supports customization through configuration: | ||
|
|
||
| ```python | ||
| from openhands.sdk.conversation import DefaultConversationVisualizer | ||
|
|
||
| # Configure highlighting patterns using regex | ||
| custom_visualizer = DefaultConversationVisualizer( | ||
| name="MyAgent", # Prefix panel titles with agent name | ||
| highlight_regex={ | ||
| r"^Reasoning:": "bold cyan", # Lines starting with "Reasoning:" | ||
| r"^Thought:": "bold green", # Lines starting with "Thought:" | ||
| r"^Action:": "bold yellow", # Lines starting with "Action:" | ||
| r"\[ERROR\]": "bold red", # Error markers anywhere | ||
| r"\*\*(.*?)\*\*": "bold", # Markdown bold **text** | ||
| }, | ||
| skip_user_messages=False, # Show user messages | ||
| ) | ||
|
|
||
| conversation = Conversation( | ||
| agent=agent, | ||
| workspace=workspace, | ||
| visualizer=custom_visualizer | ||
| ) | ||
| ``` | ||
|
|
||
| **When to use**: Perfect for customizing colors and highlighting without changing the panel-based layout. | ||
|
|
||
| ## Creating Custom Visualizers | ||
|
|
||
| For complete control over visualization, subclass `ConversationVisualizerBase`: | ||
|
|
||
| ```python | ||
| from openhands.sdk.conversation import ConversationVisualizerBase | ||
| from openhands.sdk.event import ActionEvent, ObservationEvent, AgentErrorEvent, Event | ||
|
|
||
| class MinimalVisualizer(ConversationVisualizerBase): | ||
| """A minimal visualizer that prints raw event information.""" | ||
|
|
||
| def __init__(self, name: str | None = None): | ||
| super().__init__(name=name) | ||
| self.step_count = 0 | ||
|
|
||
| def on_event(self, event: Event) -> None: | ||
| """Handle each event.""" | ||
| if isinstance(event, ActionEvent): | ||
| self.step_count += 1 | ||
| tool_name = event.tool_name or "unknown" | ||
| print(f"Step {self.step_count}: {tool_name}") | ||
|
|
||
| elif isinstance(event, ObservationEvent): | ||
| print(f" → Result received") | ||
|
|
||
| elif isinstance(event, AgentErrorEvent): | ||
| print(f"❌ Error: {event.error}") | ||
|
|
||
| # Use your custom visualizer | ||
| conversation = Conversation( | ||
| agent=agent, | ||
| workspace=workspace, | ||
| visualizer=MinimalVisualizer(name="Agent") | ||
| ) | ||
| ``` | ||
|
|
||
| ### Key Methods | ||
|
|
||
| **`__init__(self, name: str | None = None)`** | ||
| - Initialize your visualizer with optional configuration | ||
| - `name` parameter is available from the base class for agent identification | ||
| - Call `super().__init__(name=name)` to initialize the base class | ||
|
|
||
| **`initialize(self, state: ConversationStateProtocol)`** | ||
| - Called automatically by `Conversation` after state is created | ||
| - Provides access to conversation state and statistics via `self._state` | ||
| - Override if you need custom initialization, but call `super().initialize(state)` | ||
|
|
||
| **`on_event(self, event: Event)`** *(required)* | ||
| - Called for each conversation event | ||
| - Implement your visualization logic here | ||
| - Access conversation stats via `self.conversation_stats` property | ||
|
|
||
| **When to use**: When you need a completely different output format, custom state tracking, or integration with external systems. | ||
|
|
||
| ## Next Steps | ||
|
|
||
| Now that you understand custom visualizers, explore these related topics: | ||
|
|
||
| - **[Events](/sdk/arch/events)** - Learn more about different event types | ||
| - **[Conversation Metrics](/sdk/guides/metrics)** - Track LLM usage, costs, and performance data | ||
| - **[Send Messages While Running](/sdk/guides/convo-send-message-while-running)** - Interactive conversations with real-time updates | ||
| - **[Pause and Resume](/sdk/guides/convo-pause-and-resume)** - Control agent execution flow with custom logic | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these the correct next steps based on the location of the example in relation to other examples and other documentation?