Skip to content
Merged
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
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,94 @@ if __name__ == "__main__":
description = data.get("description") if isinstance(data, dict) else data.description
```

## Actions caching

You can cache actions in Stagehand to avoid redundant LLM calls. This is particularly useful for actions that are expensive to run or when the underlying DOM structure is not expected to change.

### Using `observe` to preview an action

`observe` lets you preview an action before taking it. If you are satisfied with the action preview, you can run it in `page.act` with no further LLM calls.

```python
# Get the action preview
action_preview = await page.observe("Click the quickstart link")

# action_preview is a JSON-ified version of a Playwright action:
# {
# "description": "The quickstart link",
# "action": "click",
# "selector": "/html/body/div[1]/div[1]/a",
# "arguments": []
# }

# NO LLM INFERENCE when calling act on the preview
await page.act(action_preview[0])
```

### Simple caching

Here's an example of implementing a simple file-based cache:

```python
import json
from pathlib import Path
from typing import Optional, Dict, Any

# Get the cached value (None if it doesn't exist)
async def get_cache(key: str) -> Optional[Dict[str, Any]]:
try:
cache_path = Path("cache.json")
if not cache_path.exists():
return None
with open(cache_path) as f:
cache = json.load(f)
return cache.get(key)
except Exception:
return None

# Set the cache value
async def set_cache(key: str, value: Dict[str, Any]) -> None:
cache_path = Path("cache.json")
cache = {}
if cache_path.exists():
with open(cache_path) as f:
cache = json.load(f)
cache[key] = value
with open(cache_path, "w") as f:
json.dump(cache, f)
```

### Act with cache

Here's a function that checks the cache, gets the action, and runs it:

```python
async def act_with_cache(page, key: str, prompt: str):
# Check if we have a cached action
cached_action = await get_cache(key)

if cached_action:
# Use the cached action
action = cached_action
else:
# Get the observe result (the action)
action = await page.observe(prompt)
# Cache the action
await set_cache(key, action[0])

# Run the action (no LLM inference)
await page.act(action[0])
```

You can now use `act_with_cache` to run an action with caching:

```python
prompt = "Click the quickstart link"
key = prompt # Simple cache key
await act_with_cache(page, key, prompt)
```


## Why?
**Stagehand adds determinism to otherwise unpredictable agents.**

Expand Down
7 changes: 5 additions & 2 deletions examples/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ async def main():
)

# Initialize - this creates a new session automatically.
console.print("Launch on server: ", os.getenv("STAGEHAND_SERVER_URL"))
console.print("\n🚀 [info]Initializing Stagehand...[/]")
await stagehand.init()
page = stagehand.page
Expand Down Expand Up @@ -101,11 +102,13 @@ async def main():
await asyncio.sleep(2)

console.print("\n▶️ [highlight] Observing page[/] for news button")
observed = await page.observe("find the news button on the page")
observed = await page.observe("find all articles")

if len(observed) > 0:
element = observed[0]
console.print("✅ [success]Found element:[/] News button")
console.print("\n▶️ [highlight] Performing action on observed element")
console.print("\n▶️ [highlight] Performing action on observed element:")
console.print(element)
await page.act(element)
console.print("✅ [success]Performing Action:[/] Action completed successfully")

Expand Down