Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions examples/01_standalone_sdk/31_insight_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Example demonstrating the Insight tool for session analysis.

This example shows how to use the Insight tool to analyze conversation
history and generate usage reports with optimization suggestions.

The Insight tool can be triggered by the agent when user types '/insight'.
"""

import os

from pydantic import SecretStr

from openhands.sdk import LLM, Agent, Conversation
from openhands.sdk.tool import Tool
from openhands.tools.insight import InsightAction, InsightObservation, InsightTool
from openhands.tools.preset.default import get_default_tools


# Configure LLM
api_key: str | None = os.getenv("LLM_API_KEY")
assert api_key is not None, "LLM_API_KEY environment variable is not set."

llm: LLM = LLM(
model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL", None),
usage_id="agent",
drop_params=True,
)

# Build tools list with Insight tool
tools = get_default_tools(enable_browser=False)

# Configure Insight tool with parameters
insight_params: dict[str, bool | str] = {}

# Add LLM configuration for Insight tool (uses same LLM as main agent)
insight_params["llm_model"] = llm.model
if llm.api_key:
if isinstance(llm.api_key, SecretStr):
insight_params["api_key"] = llm.api_key.get_secret_value()
else:
insight_params["api_key"] = llm.api_key
if llm.base_url:
insight_params["api_base"] = llm.base_url

# Add Insight tool to the agent
tools.append(Tool(name=InsightTool.name, params=insight_params))

# Create agent with Insight capabilities
agent: Agent = Agent(llm=llm, tools=tools)

# Start conversation
cwd: str = os.getcwd()
PERSISTENCE_DIR = os.path.expanduser("~/.openhands")
CONVERSATIONS_DIR = os.path.join(PERSISTENCE_DIR, "conversations")
conversation = Conversation(
agent=agent, workspace=cwd, persistence_dir=CONVERSATIONS_DIR
)

# Run insight analysis directly using execute_tool
print("\nRunning insight analysis on conversation history...")
try:
insight_result = conversation.execute_tool(
"insight",
InsightAction(
generate_html=True,
suggest_skills=True,
max_sessions=20,
),
)

# Cast to the expected observation type for type-safe access
if isinstance(insight_result, InsightObservation):
print(f"\n{insight_result.summary}")
print(f"\nSessions analyzed: {insight_result.sessions_analyzed}")

if insight_result.common_patterns:
print("\nCommon Patterns:")
for pattern in insight_result.common_patterns:
print(f" - {pattern}")

if insight_result.bottlenecks:
print("\nIdentified Bottlenecks:")
for bottleneck in insight_result.bottlenecks:
print(f" - {bottleneck}")

if insight_result.suggestions:
print("\nOptimization Suggestions:")
for i, suggestion in enumerate(insight_result.suggestions, 1):
print(f" {i}. {suggestion}")

if insight_result.report_path:
print(f"\nHTML Report generated: {insight_result.report_path}")
else:
print(f"Result: {insight_result.text}")

except KeyError as e:
print(f"Tool not available: {e}")

print("\n" + "=" * 80)
print("Insight tool example completed!")
print("=" * 80)

# Report cost
cost = llm.metrics.accumulated_cost
print(f"EXAMPLE_COST: {cost}")


# Alternative: Use conversation to trigger insight via message
# Uncomment to test triggering via conversation:
#
# conversation.send_message("Please analyze my usage with /insight")
# conversation.run()
18 changes: 18 additions & 0 deletions openhands-tools/openhands/tools/insight/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Insight tool for session analysis and personalization.

This tool provides session analysis capabilities by scanning historical
conversation data and generating usage reports with optimization suggestions.
"""

from openhands.tools.insight.definition import (
InsightAction,
InsightObservation,
InsightTool,
)


__all__ = [
"InsightTool",
"InsightAction",
"InsightObservation",
]
168 changes: 168 additions & 0 deletions openhands-tools/openhands/tools/insight/definition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""Insight tool definition.

This module provides the InsightTool for analyzing conversation sessions
and generating usage reports with optimization suggestions.
"""

from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, override

from pydantic import Field

from openhands.sdk.io import LocalFileStore
from openhands.sdk.llm import ImageContent, TextContent
from openhands.sdk.tool import Action, Observation, ToolDefinition, register_tool


if TYPE_CHECKING:
from openhands.sdk.conversation.state import ConversationState


# ==================== Action Schema ====================


class InsightAction(Action):
"""Action to generate session insights and usage report."""

generate_html: bool = Field(
default=True,
description="Whether to generate an HTML report dashboard",
)
suggest_skills: bool = Field(
default=True,
description="Whether to suggest new skills based on usage patterns",
)
max_sessions: int = Field(
default=50,
description="Maximum number of recent sessions to analyze",
)


# ==================== Observation Schema ====================


class InsightObservation(Observation):
"""Observation from insight analysis."""

summary: str = Field(
default="", description="Summary of the session analysis"
)
sessions_analyzed: int = Field(
default=0, description="Number of sessions analyzed"
)
common_patterns: list[str] = Field(
default_factory=list, description="Common usage patterns identified"
)
bottlenecks: list[str] = Field(
default_factory=list, description="Identified bottlenecks or issues"
)
suggestions: list[str] = Field(
default_factory=list, description="Optimization suggestions"
)
report_path: str | None = Field(
default=None, description="Path to generated HTML report"
)

@property
@override
def to_llm_content(self) -> Sequence[TextContent | ImageContent]:
"""Convert observation to LLM-readable content."""
parts = []

if self.summary:
parts.append(f"## Session Analysis Summary\n{self.summary}")

parts.append(f"\n**Sessions Analyzed:** {self.sessions_analyzed}")

if self.common_patterns:
parts.append("\n### Common Patterns")
for pattern in self.common_patterns:
parts.append(f"- {pattern}")

if self.bottlenecks:
parts.append("\n### Identified Bottlenecks")
for bottleneck in self.bottlenecks:
parts.append(f"- {bottleneck}")

if self.suggestions:
parts.append("\n### Optimization Suggestions")
for i, suggestion in enumerate(self.suggestions, 1):
parts.append(f"{i}. {suggestion}")

if self.report_path:
parts.append(f"\n**HTML Report:** {self.report_path}")

return [TextContent(text="\n".join(parts))]


# ==================== Tool Description ====================

_INSIGHT_DESCRIPTION = """Analyze conversation history and generate usage insights.

This tool scans historical session data to identify:
- Common usage patterns and workflows
- Bottlenecks and recurring issues
- Opportunities for optimization

Use this tool when:
- User requests '/insight' or wants to analyze their usage
- You need to understand user patterns for personalization
- User wants suggestions for workflow improvements

The tool can generate an HTML dashboard report and suggest new skills
to automate repetitive tasks."""


# ==================== Tool Definition ====================


class InsightTool(ToolDefinition[InsightAction, InsightObservation]):
"""Tool for analyzing sessions and generating insights."""

@classmethod
@override
def create(
cls,
conv_state: "ConversationState",
llm_model: str | None = None,
api_key: str | None = None,
api_base: str | None = None,
) -> Sequence[ToolDefinition[Any, Any]]:
"""Initialize insight tool with executor parameters.

Args:
conv_state: Conversation state (required by registry)
llm_model: LLM model to use for analysis
api_key: API key for LLM
api_base: Base URL for LLM

Returns:
Sequence containing InsightTool instance
"""
# conv_state required by registry but not used - state passed at runtime
_ = conv_state

# Import here to avoid circular imports
from openhands.tools.insight.executor import InsightExecutor

file_store = LocalFileStore(root="~/.openhands")

executor = InsightExecutor(
file_store=file_store,
llm_model=llm_model,
api_key=api_key,
api_base=api_base,
)

return [
cls(
description=_INSIGHT_DESCRIPTION,
action_type=InsightAction,
observation_type=InsightObservation,
executor=executor,
)
]


# Automatically register the tool when this module is imported
register_tool(InsightTool.name, InsightTool)
Loading