Skip to content
Open
47 changes: 47 additions & 0 deletions examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Example: Using GPT-5 preset with ApplyPatchTool for file editing.

This example demonstrates how to enable the GPT-5 preset, which swaps the
standard claude-style FileEditorTool for ApplyPatchTool.

Usage:
export OPENAI_API_KEY=... # or set LLM_API_KEY
# Optionally set a model (we recommend a mini variant if available):
# export LLM_MODEL=(
# "openai/gpt-5.2-mini" # or fallback: "openai/gpt-5.1-mini" or "openai/gpt-5.1"
# )

uv run python examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py
"""

import os

from openhands.sdk import LLM, Agent, Conversation
from openhands.tools.preset.gpt5 import get_gpt5_agent


# Resolve API key from env
api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY")
if not api_key:
raise SystemExit("Please set OPENAI_API_KEY or LLM_API_KEY to run this example.")

model = os.getenv("LLM_MODEL", "openai/gpt-5.1")
base_url = os.getenv("LLM_BASE_URL", None)

llm = LLM(model=model, api_key=api_key, base_url=base_url)

# Build an agent with the GPT-5 preset (ApplyPatchTool-based editing)
agent: Agent = get_gpt5_agent(llm)

# Run in the current working directory
cwd = os.getcwd()
conversation = Conversation(agent=agent, workspace=cwd)

conversation.send_message(
"Create (or update) a file named GPT5_DEMO.txt at the repo root with "
"two short lines describing this repository."
)
conversation.run()

# Report cost
cost = llm.metrics.accumulated_cost
print(f"EXAMPLE_COST: {cost}")
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
from openhands.tools.terminal import TerminalTool


# Route logs based on whether we're using a proxy (Vertex route) or direct Gemini
_log_dir = "logs/vertex" if os.getenv("LLM_BASE_URL") else "logs/gemini"
# Route logs in their own directory for easy tracing
_log_dir = "logs/gemini"
os.makedirs(_log_dir, exist_ok=True)

llm = LLM(
Expand Down Expand Up @@ -49,4 +49,7 @@

conversation.send_message("Now delete the FACTS.txt file you just created.")
conversation.run()
print("All done!")

# Report cost
cost = llm.metrics.accumulated_cost
print(f"EXAMPLE_COST: {cost}")
2 changes: 2 additions & 0 deletions openhands-tools/openhands/tools/preset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@

from .default import get_default_agent
from .gemini import get_gemini_agent, get_gemini_tools
from .gpt5 import get_gpt5_agent
from .planning import get_planning_agent


__all__ = [
"get_default_agent",
"get_gemini_agent",
"get_gemini_tools",
"get_gpt5_agent",
"get_planning_agent",
]
78 changes: 78 additions & 0 deletions openhands-tools/openhands/tools/preset/gpt5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""GPT-5 preset configuration for OpenHands agents.

This preset uses ApplyPatchTool for file edits instead of the default
claude-style FileEditorTool. It mirrors the Gemini preset pattern by
providing optional helpers without changing global defaults.
"""

from openhands.sdk import Agent
from openhands.sdk.context.condenser import LLMSummarizingCondenser
from openhands.sdk.context.condenser.base import CondenserBase
from openhands.sdk.llm.llm import LLM
from openhands.sdk.logger import get_logger
from openhands.sdk.tool import Tool


logger = get_logger(__name__)


def register_gpt5_tools(enable_browser: bool = True) -> None:
"""Register the GPT-5 tool set (terminal, apply_patch, task_tracker, browser)."""
from openhands.tools.apply_patch import ApplyPatchTool
from openhands.tools.task_tracker import TaskTrackerTool
from openhands.tools.terminal import TerminalTool

logger.debug(f"Tool: {TerminalTool.name} registered.")
logger.debug(f"Tool: {ApplyPatchTool.name} registered.")
logger.debug(f"Tool: {TaskTrackerTool.name} registered.")

if enable_browser:
from openhands.tools.browser_use import BrowserToolSet

logger.debug(f"Tool: {BrowserToolSet.name} registered.")


def get_gpt5_tools(enable_browser: bool = True) -> list[Tool]:
"""Get the GPT-5 tool specifications using ApplyPatchTool for edits.

Args:
enable_browser: Whether to include browser tools.
"""
register_gpt5_tools(enable_browser=enable_browser)

from openhands.tools.apply_patch import ApplyPatchTool
from openhands.tools.task_tracker import TaskTrackerTool
from openhands.tools.terminal import TerminalTool

tools: list[Tool] = [
Tool(name=TerminalTool.name),
Tool(name=ApplyPatchTool.name),
Tool(name=TaskTrackerTool.name),
]
if enable_browser:
from openhands.tools.browser_use import BrowserToolSet

tools.append(Tool(name=BrowserToolSet.name))
return tools


def get_gpt5_condenser(llm: LLM) -> CondenserBase:
"""Get the default condenser for the GPT-5 preset."""
return LLMSummarizingCondenser(llm=llm, max_size=80, keep_first=4)


def get_gpt5_agent(llm: LLM, cli_mode: bool = False) -> Agent:
"""Get an Agent configured with ApplyPatchTool-based file editing.

This does not change defaults globally; users can opt into it explicitly.
"""
tools = get_gpt5_tools(enable_browser=not cli_mode)
agent = Agent(
llm=llm,
tools=tools,
system_prompt_kwargs={"cli_mode": cli_mode},
condenser=get_gpt5_condenser(
llm=llm.model_copy(update={"usage_id": "condenser"})
),
)
return agent
Loading