Skip to content

Commit 69da85f

Browse files
authored
Merge branch 'main' into docs/workspace-types-and-saas-runtime-mode
2 parents e9650dc + de07572 commit 69da85f

3 files changed

Lines changed: 263 additions & 0 deletions

File tree

docs.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@
237237
"sdk/guides/plugins",
238238
"sdk/guides/convo-persistence",
239239
"sdk/guides/context-condenser",
240+
"sdk/guides/agent-settings",
240241
"sdk/guides/agent-delegation",
241242
"sdk/guides/parallel-tool-execution",
242243
"sdk/guides/task-tool-set",
@@ -252,6 +253,7 @@
252253
"sdk/guides/llm-registry",
253254
"sdk/guides/llm-routing",
254255
"sdk/guides/llm-reasoning",
256+
"sdk/guides/gpt5-preset",
255257
"sdk/guides/llm-streaming",
256258
"sdk/guides/llm-image-input",
257259
"sdk/guides/llm-error-handling",

sdk/guides/agent-settings.mdx

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
title: Agent Settings
3+
description: Configure, serialize, and recreate agents from structured settings.
4+
---
5+
6+
import RunExampleCode from "/sdk/shared-snippets/how-to-run-example.mdx";
7+
8+
> A ready-to-run example is available [here](#ready-to-run-example)!
9+
10+
`AgentSettings` gives you a structured, serializable way to define an agent's model, tools, and optional subsystems like the condenser. Use it when you want to store agent configuration in JSON, send it over an API, or rebuild agents from validated settings later.
11+
12+
## Why Use AgentSettings
13+
14+
- Keep agent configuration as data instead of wiring everything together imperatively.
15+
- Validate settings with Pydantic before creating an agent.
16+
- Serialize and deserialize settings for storage, transport, or UI-driven configuration.
17+
- Create different agent variants by changing only the settings payload.
18+
19+
## Build Settings
20+
21+
Create an `AgentSettings` object with the same ingredients you would normally pass to an `Agent`.
22+
23+
```python icon="python" focus={8, 11, 12, 13}
24+
from pydantic import SecretStr
25+
26+
from openhands.sdk import AgentSettings, LLM, Tool
27+
from openhands.sdk.settings import CondenserSettings
28+
from openhands.tools.file_editor import FileEditorTool
29+
from openhands.tools.terminal import TerminalTool
30+
31+
settings = AgentSettings(
32+
llm=LLM(
33+
model="anthropic/claude-sonnet-4-5-20250929",
34+
api_key=SecretStr("your-api-key"),
35+
),
36+
tools=[
37+
Tool(name=TerminalTool.name),
38+
Tool(name=FileEditorTool.name),
39+
],
40+
condenser=CondenserSettings(enabled=True, max_size=50),
41+
)
42+
```
43+
44+
## Serialize and Restore Settings
45+
46+
Because `AgentSettings` is a Pydantic model, you can dump it to JSON-compatible data and restore it later.
47+
48+
```python icon="python" focus={1, 2}
49+
payload = settings.model_dump(mode="json")
50+
restored = AgentSettings.model_validate(payload)
51+
```
52+
53+
This is useful when:
54+
55+
- Saving agent configuration in a database
56+
- Sending settings through an API
57+
- Letting users edit agent configuration in a form-based UI
58+
- Rehydrating the same agent setup in another process
59+
60+
## Create an Agent from Settings
61+
62+
Once validated, create a working agent directly from the settings object.
63+
64+
```python icon="python" focus={1}
65+
agent = settings.create_agent()
66+
```
67+
68+
You can then pass that agent into a `Conversation`, or derive another agent by changing the settings payload. For example, the full example below also shows how removing `FileEditorTool` and disabling the condenser produces a different agent configuration without rewriting the rest of the setup.
69+
70+
## Ready-to-run Example
71+
72+
<Note>
73+
This example is available on GitHub: [examples/01_standalone_sdk/46_agent_settings.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/01_standalone_sdk/46_agent_settings.py)
74+
</Note>
75+
76+
```python icon="python" expandable examples/01_standalone_sdk/46_agent_settings.py
77+
"""Create, serialize, and deserialize AgentSettings, then build a working agent.
78+
79+
Demonstrates:
80+
1. Configuring an agent entirely through AgentSettings (LLM, tools, condenser).
81+
2. Serializing settings to JSON and restoring them.
82+
3. Building an Agent from settings via ``create_agent()``.
83+
4. Running a short conversation to prove the settings take effect.
84+
5. Changing the tool list and showing the agent's capabilities change.
85+
"""
86+
87+
import json
88+
import os
89+
90+
from pydantic import SecretStr
91+
92+
from openhands.sdk import LLM, AgentSettings, Conversation, Tool
93+
from openhands.sdk.settings import CondenserSettings
94+
from openhands.tools.file_editor import FileEditorTool
95+
from openhands.tools.terminal import TerminalTool
96+
97+
98+
# ── 1. Build settings ────────────────────────────────────────────────────
99+
api_key = os.getenv("LLM_API_KEY")
100+
assert api_key is not None, "LLM_API_KEY environment variable is not set."
101+
102+
settings = AgentSettings(
103+
llm=LLM(
104+
model=os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
105+
api_key=SecretStr(api_key),
106+
base_url=os.getenv("LLM_BASE_URL"),
107+
),
108+
tools=[
109+
Tool(name=TerminalTool.name),
110+
Tool(name=FileEditorTool.name),
111+
],
112+
condenser=CondenserSettings(enabled=True, max_size=50),
113+
)
114+
115+
# ── 2. Serialize → JSON → deserialize ────────────────────────────────────
116+
payload = settings.model_dump(mode="json")
117+
print("Serialized settings (JSON):")
118+
print(json.dumps(payload, indent=2, default=str)[:800], "")
119+
print()
120+
121+
restored = AgentSettings.model_validate(payload)
122+
assert restored.condenser.enabled is True
123+
assert restored.condenser.max_size == 50
124+
assert len(restored.tools) == 2
125+
print("✓ Roundtrip deserialization successful — all fields preserved")
126+
print()
127+
128+
# ── 3. Create agent from settings and run a task ─────────────────────────
129+
agent = settings.create_agent()
130+
print(f"Agent created: llm.model={agent.llm.model}")
131+
print(f" tools={[t.name for t in agent.tools]}")
132+
print(f" condenser={type(agent.condenser).__name__}")
133+
print()
134+
135+
cwd = os.getcwd()
136+
conversation = Conversation(agent=agent, workspace=cwd)
137+
conversation.send_message(
138+
"Create a file called hello_settings.txt containing "
139+
"'Agent settings work!' then confirm the file exists with ls."
140+
)
141+
conversation.run()
142+
143+
# Verify the agent actually wrote the file
144+
assert os.path.exists(os.path.join(cwd, "hello_settings.txt")), (
145+
"Agent should have created hello_settings.txt"
146+
)
147+
print("✓ Agent created hello_settings.txt — settings drove real behavior")
148+
print()
149+
150+
# ── 4. Different settings → different behavior ───────────────────────────
151+
# Now create settings with ONLY the terminal tool and condenser disabled.
152+
terminal_only_settings = AgentSettings(
153+
llm=settings.llm,
154+
tools=[Tool(name=TerminalTool.name)],
155+
condenser=CondenserSettings(enabled=False),
156+
)
157+
158+
terminal_agent = terminal_only_settings.create_agent()
159+
print(f"Terminal-only agent tools: {[t.name for t in terminal_agent.tools]}")
160+
assert len(terminal_agent.tools) == 1
161+
assert terminal_agent.condenser is None # condenser disabled in these settings
162+
print("✓ Different settings produce different agent configuration")
163+
print()
164+
165+
# ── Cleanup ──────────────────────────────────────────────────────────────
166+
os.remove(os.path.join(cwd, "hello_settings.txt"))
167+
168+
# Report cost
169+
cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost
170+
print(f"\nEXAMPLE_COST: {cost}")
171+
```
172+
173+
<RunExampleCode path_to_script="examples/01_standalone_sdk/46_agent_settings.py"/>
174+
175+
## Next Steps
176+
177+
- **[Getting Started](/sdk/getting-started)** - Start from a minimal agent and conversation setup
178+
- **[Context Condenser](/sdk/guides/context-condenser)** - Control conversation compaction behavior
179+
- **[Agent Delegation](/sdk/guides/agent-delegation)** - Compose specialized agents for larger tasks

sdk/guides/gpt5-preset.mdx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title: GPT-5 Preset (ApplyPatchTool)
3+
description: Use the GPT-5 preset to build an agent that swaps the standard FileEditorTool for ApplyPatchTool.
4+
---
5+
6+
import RunExampleCode from "/sdk/shared-snippets/how-to-run-example.mdx";
7+
8+
The GPT-5 preset is an opt-in agent preset for patch-based file editing. Calling `get_gpt5_agent(llm)` creates an agent that uses `ApplyPatchTool` instead of the standard `FileEditorTool`, while leaving the default preset unchanged for everything else.
9+
10+
## Ready-to-run Example
11+
12+
<Note>
13+
This example is available on GitHub: [examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py)
14+
</Note>
15+
16+
```python icon="python" expandable examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py
17+
"""Example: Using GPT-5 preset with ApplyPatchTool for file editing.
18+
19+
This example demonstrates how to enable the GPT-5 preset, which swaps the
20+
standard claude-style FileEditorTool for ApplyPatchTool.
21+
22+
Usage:
23+
export OPENAI_API_KEY=... # or set LLM_API_KEY
24+
# Optionally set a model (we recommend a mini variant if available):
25+
# export LLM_MODEL=(
26+
# "openai/gpt-5.2-mini" # or fallback: "openai/gpt-5.1-mini" or "openai/gpt-5.1"
27+
# )
28+
29+
uv run python examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py
30+
"""
31+
32+
import os
33+
34+
from openhands.sdk import LLM, Agent, Conversation
35+
from openhands.tools.preset.gpt5 import get_gpt5_agent
36+
37+
38+
# Resolve API key from env
39+
api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY")
40+
if not api_key:
41+
raise SystemExit("Please set OPENAI_API_KEY or LLM_API_KEY to run this example.")
42+
43+
model = os.getenv("LLM_MODEL", "openai/gpt-5.1")
44+
base_url = os.getenv("LLM_BASE_URL", None)
45+
46+
llm = LLM(model=model, api_key=api_key, base_url=base_url)
47+
48+
# Build an agent with the GPT-5 preset (ApplyPatchTool-based editing)
49+
agent: Agent = get_gpt5_agent(llm)
50+
51+
# Run in the current working directory
52+
cwd = os.getcwd()
53+
conversation = Conversation(agent=agent, workspace=cwd)
54+
55+
conversation.send_message(
56+
"Create (or update) a file named GPT5_DEMO.txt at the repo root with "
57+
"two short lines describing this repository."
58+
)
59+
conversation.run()
60+
61+
# Report cost
62+
cost = llm.metrics.accumulated_cost
63+
print(f"EXAMPLE_COST: {cost}")
64+
```
65+
66+
<RunExampleCode path_to_script="examples/04_llm_specific_tools/01_gpt5_apply_patch_preset.py"/>
67+
68+
<Tip>
69+
You can optionally set `LLM_MODEL` to a GPT-5 variant such as `openai/gpt-5.2-mini`, `openai/gpt-5.1-mini`, or `openai/gpt-5.1`.
70+
</Tip>
71+
72+
## What this preset changes
73+
74+
- Replaces the standard `FileEditorTool` with `ApplyPatchTool`
75+
- Keeps the GPT-5-specific configuration explicit via `get_gpt5_agent(llm)`
76+
- Leaves the default preset unchanged unless you opt into this one
77+
78+
## See Also
79+
80+
- **[LLM Reasoning](/sdk/guides/llm-reasoning)** - Learn more about newer OpenAI model behavior and the Responses API
81+
- **[LLM Subscriptions](/sdk/guides/llm-subscriptions)** - Use supported OpenAI subscription-backed models without API credits
82+
- **[Custom Tools](/sdk/guides/custom-tools)** - Understand the standard SDK tool system and presets

0 commit comments

Comments
 (0)