Skip to content

Commit 64f6282

Browse files
authored
Merge pull request #230 from PredicateSystems/agent_arch
Planner + executor agent arch
2 parents 05c43de + 6b1ed21 commit 64f6282

File tree

7 files changed

+2194
-0
lines changed

7 files changed

+2194
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Planner + Executor Agent Examples
2+
3+
This directory contains examples for the `PlannerExecutorAgent`, a two-tier agent
4+
architecture with separate Planner (7B+) and Executor (3B-7B) models.
5+
6+
## Examples
7+
8+
| File | Description |
9+
|------|-------------|
10+
| `minimal_example.py` | Basic usage with OpenAI models |
11+
| `local_models_example.py` | Using local HuggingFace/MLX models |
12+
| `custom_config_example.py` | Custom configuration (escalation, retry, vision) |
13+
| `tracing_example.py` | Full tracing integration for Predicate Studio |
14+
15+
## Architecture
16+
17+
```
18+
┌─────────────────────────────────────────────────────────────┐
19+
│ PlannerExecutorAgent │
20+
├─────────────────────────────────────────────────────────────┤
21+
│ Planner (7B+) │ Executor (3B-7B) │
22+
│ ───────────── │ ──────────────── │
23+
│ • Generates JSON plan │ • Executes each step │
24+
│ • Includes predicates │ • Snapshot-first approach │
25+
│ • Handles replanning │ • Vision fallback │
26+
└─────────────────────────────────────────────────────────────┘
27+
28+
29+
┌─────────────────────────────────────────────────────────────┐
30+
│ AgentRuntime │
31+
│ • Snapshots with limit escalation │
32+
│ • Predicate verification │
33+
│ • Tracing for Studio visualization │
34+
└─────────────────────────────────────────────────────────────┘
35+
```
36+
37+
## Quick Start
38+
39+
```python
40+
from predicate.agents import PlannerExecutorAgent, PlannerExecutorConfig
41+
from predicate.llm_provider import OpenAIProvider
42+
from predicate import AsyncPredicateBrowser
43+
from predicate.agent_runtime import AgentRuntime
44+
45+
# Create LLM providers
46+
planner = OpenAIProvider(model="gpt-4o")
47+
executor = OpenAIProvider(model="gpt-4o-mini")
48+
49+
# Create agent
50+
agent = PlannerExecutorAgent(
51+
planner=planner,
52+
executor=executor,
53+
)
54+
55+
# Run task
56+
async with AsyncPredicateBrowser() as browser:
57+
page = await browser.new_page()
58+
await page.goto("https://example.com")
59+
60+
runtime = AgentRuntime.from_page(page)
61+
result = await agent.run(
62+
runtime=runtime,
63+
task="Find the main heading on this page",
64+
)
65+
print(f"Success: {result.success}")
66+
```
67+
68+
## Configuration
69+
70+
### Snapshot Escalation
71+
72+
Control how the agent increases snapshot limits when elements are missing:
73+
74+
```python
75+
from predicate.agents import SnapshotEscalationConfig
76+
77+
# Default: 60 -> 90 -> 120 -> 150 -> 180 -> 200
78+
config = PlannerExecutorConfig()
79+
80+
# Disable escalation (always use 60)
81+
config = PlannerExecutorConfig(
82+
snapshot=SnapshotEscalationConfig(enabled=False)
83+
)
84+
85+
# Custom step size: 60 -> 110 -> 160 -> 200
86+
config = PlannerExecutorConfig(
87+
snapshot=SnapshotEscalationConfig(limit_step=50)
88+
)
89+
```
90+
91+
### Retry Configuration
92+
93+
```python
94+
from predicate.agents import RetryConfig
95+
96+
config = PlannerExecutorConfig(
97+
retry=RetryConfig(
98+
verify_timeout_s=15.0, # Verification timeout
99+
verify_max_attempts=8, # Max verification attempts
100+
max_replans=2, # Max replanning attempts
101+
)
102+
)
103+
```
104+
105+
### Vision Fallback
106+
107+
```python
108+
from predicate.agents.browser_agent import VisionFallbackConfig
109+
110+
config = PlannerExecutorConfig(
111+
vision=VisionFallbackConfig(
112+
enabled=True,
113+
max_vision_calls=5,
114+
)
115+
)
116+
```
117+
118+
## Tracing for Predicate Studio
119+
120+
To visualize agent runs in Predicate Studio:
121+
122+
```python
123+
from predicate.tracer_factory import create_tracer
124+
125+
tracer = create_tracer(
126+
api_key="sk_...",
127+
upload_trace=True,
128+
goal="Search and add to cart",
129+
agent_type="PlannerExecutorAgent",
130+
)
131+
132+
agent = PlannerExecutorAgent(
133+
planner=planner,
134+
executor=executor,
135+
tracer=tracer, # Pass tracer for visualization
136+
)
137+
138+
# ... run agent ...
139+
140+
tracer.close() # Upload trace to Studio
141+
```
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env python3
2+
"""
3+
PlannerExecutorAgent example with custom configuration.
4+
5+
This example demonstrates various configuration options:
6+
- Snapshot escalation (enable/disable, custom step sizes)
7+
- Retry configuration (timeouts, max attempts)
8+
- Vision fallback settings
9+
10+
Usage:
11+
export OPENAI_API_KEY="sk-..."
12+
python custom_config_example.py
13+
"""
14+
15+
from __future__ import annotations
16+
17+
import asyncio
18+
import os
19+
20+
from predicate import AsyncPredicateBrowser
21+
from predicate.agent_runtime import AgentRuntime
22+
from predicate.agents import (
23+
PlannerExecutorAgent,
24+
PlannerExecutorConfig,
25+
RetryConfig,
26+
SnapshotEscalationConfig,
27+
)
28+
from predicate.agents.browser_agent import VisionFallbackConfig
29+
from predicate.backends.playwright_backend import PlaywrightBackend
30+
from predicate.llm_provider import OpenAIProvider
31+
32+
33+
async def example_default_config() -> None:
34+
"""Default configuration: escalation enabled, step=30."""
35+
print("\n--- Example 1: Default Config ---")
36+
print("Escalation: 60 -> 90 -> 120 -> 150 -> 180 -> 200")
37+
38+
config = PlannerExecutorConfig()
39+
40+
print(f" snapshot.enabled: {config.snapshot.enabled}")
41+
print(f" snapshot.limit_base: {config.snapshot.limit_base}")
42+
print(f" snapshot.limit_step: {config.snapshot.limit_step}")
43+
print(f" snapshot.limit_max: {config.snapshot.limit_max}")
44+
45+
46+
async def example_disabled_escalation() -> None:
47+
"""Disable escalation: always use limit_base."""
48+
print("\n--- Example 2: Disabled Escalation ---")
49+
print("Escalation: disabled (always 60)")
50+
51+
config = PlannerExecutorConfig(
52+
snapshot=SnapshotEscalationConfig(enabled=False),
53+
)
54+
55+
print(f" snapshot.enabled: {config.snapshot.enabled}")
56+
print(f" snapshot.limit_base: {config.snapshot.limit_base}")
57+
58+
59+
async def example_custom_step_size() -> None:
60+
"""Custom step size for faster escalation."""
61+
print("\n--- Example 3: Custom Step Size ---")
62+
print("Escalation: 60 -> 110 -> 160 -> 200 (step=50)")
63+
64+
config = PlannerExecutorConfig(
65+
snapshot=SnapshotEscalationConfig(
66+
limit_step=50, # Larger steps = fewer iterations
67+
),
68+
)
69+
70+
print(f" snapshot.limit_step: {config.snapshot.limit_step}")
71+
72+
73+
async def example_custom_limits() -> None:
74+
"""Custom base and max limits."""
75+
print("\n--- Example 4: Custom Limits ---")
76+
print("Escalation: 100 -> 125 -> 150 -> 175 -> 200 -> 225 -> 250")
77+
78+
config = PlannerExecutorConfig(
79+
snapshot=SnapshotEscalationConfig(
80+
limit_base=100, # Start higher
81+
limit_step=25, # Smaller increments
82+
limit_max=250, # Higher maximum
83+
),
84+
)
85+
86+
print(f" snapshot.limit_base: {config.snapshot.limit_base}")
87+
print(f" snapshot.limit_step: {config.snapshot.limit_step}")
88+
print(f" snapshot.limit_max: {config.snapshot.limit_max}")
89+
90+
91+
async def example_retry_config() -> None:
92+
"""Custom retry configuration."""
93+
print("\n--- Example 5: Retry Config ---")
94+
95+
config = PlannerExecutorConfig(
96+
retry=RetryConfig(
97+
verify_timeout_s=15.0, # Longer timeout for slow pages
98+
verify_poll_s=0.3, # Faster polling
99+
verify_max_attempts=10, # More verification attempts
100+
executor_repair_attempts=3, # More repair attempts
101+
max_replans=2, # Allow 2 replans on failure
102+
),
103+
)
104+
105+
print(f" retry.verify_timeout_s: {config.retry.verify_timeout_s}")
106+
print(f" retry.verify_max_attempts: {config.retry.verify_max_attempts}")
107+
print(f" retry.max_replans: {config.retry.max_replans}")
108+
109+
110+
async def example_vision_fallback() -> None:
111+
"""Vision fallback configuration."""
112+
print("\n--- Example 6: Vision Fallback ---")
113+
114+
config = PlannerExecutorConfig(
115+
vision=VisionFallbackConfig(
116+
enabled=True,
117+
max_vision_calls=5, # Up to 5 vision calls per run
118+
trigger_requires_vision=True, # Trigger on require_vision status
119+
trigger_canvas_or_low_actionables=True, # Trigger on canvas pages
120+
),
121+
)
122+
123+
print(f" vision.enabled: {config.vision.enabled}")
124+
print(f" vision.max_vision_calls: {config.vision.max_vision_calls}")
125+
126+
127+
async def example_full_custom() -> None:
128+
"""Full custom configuration with all options."""
129+
print("\n--- Example 7: Full Custom Config ---")
130+
131+
config = PlannerExecutorConfig(
132+
# Snapshot escalation
133+
snapshot=SnapshotEscalationConfig(
134+
enabled=True,
135+
limit_base=80,
136+
limit_step=40,
137+
limit_max=240,
138+
),
139+
# Retry settings
140+
retry=RetryConfig(
141+
verify_timeout_s=12.0,
142+
verify_poll_s=0.4,
143+
verify_max_attempts=6,
144+
max_replans=2,
145+
),
146+
# Vision fallback
147+
vision=VisionFallbackConfig(
148+
enabled=True,
149+
max_vision_calls=3,
150+
),
151+
# Planner settings
152+
planner_max_tokens=3000,
153+
planner_temperature=0.0,
154+
# Executor settings
155+
executor_max_tokens=128,
156+
executor_temperature=0.0,
157+
# Tracing
158+
trace_screenshots=True,
159+
trace_screenshot_format="jpeg",
160+
trace_screenshot_quality=85,
161+
)
162+
163+
print(" Full config created successfully!")
164+
print(f" Escalation: {config.snapshot.limit_base} -> ... -> {config.snapshot.limit_max}")
165+
print(f" Max replans: {config.retry.max_replans}")
166+
print(f" Vision enabled: {config.vision.enabled}")
167+
168+
169+
async def example_run_with_config() -> None:
170+
"""Run agent with custom config."""
171+
print("\n--- Example 8: Run Agent with Custom Config ---")
172+
173+
openai_key = os.getenv("OPENAI_API_KEY")
174+
if not openai_key:
175+
print(" Skipping (no OPENAI_API_KEY)")
176+
return
177+
178+
predicate_api_key = os.getenv("PREDICATE_API_KEY")
179+
180+
# Create config optimized for reliability
181+
config = PlannerExecutorConfig(
182+
snapshot=SnapshotEscalationConfig(
183+
enabled=True,
184+
limit_base=60,
185+
limit_step=30,
186+
limit_max=180,
187+
),
188+
retry=RetryConfig(
189+
verify_timeout_s=10.0,
190+
max_replans=1,
191+
),
192+
)
193+
194+
planner = OpenAIProvider(model="gpt-4o")
195+
executor = OpenAIProvider(model="gpt-4o-mini")
196+
197+
agent = PlannerExecutorAgent(
198+
planner=planner,
199+
executor=executor,
200+
config=config,
201+
)
202+
203+
async with AsyncPredicateBrowser(
204+
api_key=predicate_api_key,
205+
headless=True,
206+
) as browser:
207+
page = await browser.new_page()
208+
await page.goto("https://example.com")
209+
210+
backend = PlaywrightBackend(page)
211+
runtime = AgentRuntime(backend=backend)
212+
213+
result = await agent.run(
214+
runtime=runtime,
215+
task="Verify example.com is loaded",
216+
)
217+
218+
print(f" Success: {result.success}")
219+
print(f" Steps: {result.steps_completed}/{result.steps_total}")
220+
221+
222+
async def main() -> None:
223+
print("PlannerExecutorAgent Configuration Examples")
224+
print("=" * 50)
225+
226+
await example_default_config()
227+
await example_disabled_escalation()
228+
await example_custom_step_size()
229+
await example_custom_limits()
230+
await example_retry_config()
231+
await example_vision_fallback()
232+
await example_full_custom()
233+
await example_run_with_config()
234+
235+
print("\n" + "=" * 50)
236+
print("Done!")
237+
238+
239+
if __name__ == "__main__":
240+
asyncio.run(main())

0 commit comments

Comments
 (0)