diff --git a/packages/docs/v4/basics/act.mdx b/packages/docs/v4/basics/act.mdx
index 30dd339c4..b05596a78 100644
--- a/packages/docs/v4/basics/act.mdx
+++ b/packages/docs/v4/basics/act.mdx
@@ -520,7 +520,7 @@ if (action?.method === "click") {
```python
-result = await stagehand.observe(instruction="click the login button")
+result = await stagehand.observe("click the login button")
if result.data and result.data[0].method == "click":
# No inference: Stagehand replays the observed action
@@ -764,7 +764,7 @@ except Exception as error:
raise
# Observe the same prompt to get the planned action
- observed = await stagehand.observe(instruction=prompt)
+ observed = await stagehand.observe(prompt)
action = observed.data[0] if observed.data else None
if action is not None and action.method == expected_method:
@@ -930,7 +930,7 @@ except Exception:
await page.wait_for_load_state("domcontentloaded")
# Use observe to check element state
- observed = await stagehand.observe(instruction="find the submit button")
+ observed = await stagehand.observe("find the submit button")
if observed.data:
print("Element found, trying more specific instruction")
@@ -1013,7 +1013,7 @@ await stagehand.act("click the red 'Delete' button next to the user John Smith")
# Or preview with observe first:
observed = await stagehand.observe(
- instruction="click the submit button in the checkout form"
+ "click the submit button in the checkout form"
)
if observed.data and "checkout" in observed.data[0].description:
await stagehand.act(observed.data[0])
diff --git a/packages/docs/v4/basics/extract.mdx b/packages/docs/v4/basics/extract.mdx
index 6347354a2..30f08aa67 100644
--- a/packages/docs/v4/basics/extract.mdx
+++ b/packages/docs/v4/basics/extract.mdx
@@ -21,8 +21,8 @@ class Repository(BaseModel):
await stagehand.extract(
- instruction="extract the name of the repository",
- schema=Repository,
+ "extract the name of the repository",
+ Repository,
)
```
@@ -93,8 +93,8 @@ class Product(BaseModel):
result = await stagehand.extract(
- instruction="extract product details",
- schema=Product,
+ "extract product details",
+ Product,
)
```
@@ -188,8 +188,8 @@ class Apartments(BaseModel):
result = await stagehand.extract(
- instruction="extract all apartment listings",
- schema=Apartments,
+ "extract all apartment listings",
+ Apartments,
)
```
@@ -298,8 +298,8 @@ class Price(BaseModel):
result = await stagehand.extract(
- instruction="extract the price",
- schema=Price,
+ "extract the price",
+ Price,
)
```
@@ -371,8 +371,8 @@ class ContactLink(BaseModel):
result = await stagehand.extract(
- instruction="extract the contact page link",
- schema=ContactLink,
+ "extract the contact page link",
+ ContactLink,
)
```
@@ -429,8 +429,8 @@ const result = await stagehand.extract(
from stagehand import ModelConfig
result = await stagehand.extract(
- instruction="extract the repository name",
- schema=Repository,
+ "extract the repository name",
+ Repository,
model=ModelConfig.model_validate({
"model_name": "anthropic/claude-sonnet-4-6",
"api_key": os.environ["ANTHROPIC_API_KEY"],
@@ -516,8 +516,8 @@ stagehand = Stagehand(
# Or disable it for a single call
result = await stagehand.extract(
- instruction="extract the repository name",
- schema=Repository,
+ "extract the repository name",
+ Repository,
cache=False,
)
@@ -589,8 +589,8 @@ class TableRow(BaseModel):
table_data = (await stagehand.extract(
- instruction="Extract the values of the third row",
- schema=TableRow,
+ "Extract the values of the third row",
+ TableRow,
# xPath or CSS selector
selector="xpath=/html/body/div/table/",
)).data
@@ -650,8 +650,8 @@ class Article(BaseModel):
article = (await stagehand.extract(
- instruction="extract the article title and body",
- schema=Article,
+ "extract the article title and body",
+ Article,
ignore_selectors=[".ad", ".newsletter-modal", "nav.related-posts"],
)).data
```
@@ -717,8 +717,8 @@ class SaleBadge(BaseModel):
sale_badge = (await stagehand.extract(
- instruction="extract the text shown in the visible sale badge",
- schema=SaleBadge,
+ "extract the text shown in the visible sale badge",
+ SaleBadge,
screenshot=True,
)).data
```
@@ -796,11 +796,11 @@ class Apartments(BaseModel):
result = await stagehand.extract(
- instruction=(
+ (
"Extract ALL the apartment listings and their details, "
"including address, price, and square feet."
),
- schema=Apartments,
+ Apartments,
)
```
@@ -879,8 +879,8 @@ class ContactLink(BaseModel):
result = await stagehand.extract(
- instruction="extract the link to the 'contact us' page",
- schema=ContactLink,
+ "extract the link to the 'contact us' page",
+ ContactLink,
)
print("the link to the contact us page is: ", result.data.contact_link)
@@ -973,8 +973,8 @@ class Products(BaseModel):
result = await stagehand.extract(
- instruction="extract all product names and prices",
- schema=Products,
+ "extract all product names and prices",
+ Products,
)
```
@@ -1120,7 +1120,7 @@ const { data } = await stagehand.extract(
```python
# First observe to understand the page structure
-observed = await stagehand.observe(instruction="find all product listings")
+observed = await stagehand.observe("find all product listings")
print("Found elements:", [element.description for element in observed.data])
@@ -1135,8 +1135,8 @@ class Products(BaseModel):
result = await stagehand.extract(
- instruction="extract name and price from each product listing shown on the page",
- schema=Products,
+ "extract name and price from each product listing shown on the page",
+ Products,
)
```
@@ -1247,8 +1247,8 @@ for page_num in page_numbers:
await stagehand.act(f"navigate to page {page_num}")
result = await stagehand.extract(
- instruction="extract product data from the current page only",
- schema=Products,
+ "extract product data from the current page only",
+ Products,
timeout=60000, # 60 second timeout
)
diff --git a/packages/docs/v4/basics/observe.mdx b/packages/docs/v4/basics/observe.mdx
index 743fc62e9..d127bd17c 100644
--- a/packages/docs/v4/basics/observe.mdx
+++ b/packages/docs/v4/basics/observe.mdx
@@ -13,7 +13,7 @@ await stagehand.observe("find the login button");
```python
-await stagehand.observe(instruction="find the login button")
+await stagehand.observe("find the login button")
```
@@ -66,7 +66,7 @@ page = await stagehand.context.active_page()
if page is None:
raise RuntimeError("Stagehand initialized without an active page")
await page.goto("https://example.com")
-result = await stagehand.observe(instruction="find the learn more button")
+result = await stagehand.observe("find the learn more button")
```
@@ -173,10 +173,10 @@ await stagehand.observe("find the delete account button in settings");
```python
# Clear and specific
await stagehand.observe(
- instruction="find the primary call-to-action button in the hero section"
+ "find the primary call-to-action button in the hero section"
)
-await stagehand.observe(instruction="find all input fields in the checkout form")
-await stagehand.observe(instruction="find the delete account button in settings")
+await stagehand.observe("find all input fields in the checkout form")
+await stagehand.observe("find the delete account button in settings")
```
@@ -217,10 +217,10 @@ await stagehand.observe("what is the page title?");
```python
# Too vague
-await stagehand.observe(instruction="find buttons")
+await stagehand.observe("find buttons")
# Use extract() for data instead
-await stagehand.observe(instruction="what is the page title?")
+await stagehand.observe("what is the page title?")
```
@@ -266,7 +266,7 @@ from stagehand import ModelConfig
# Custom model configuration
result = await stagehand.observe(
- instruction="find navigation links",
+ "find navigation links",
model=ModelConfig.model_validate({
"model_name": "openai/gpt-5.4-mini",
"api_key": os.environ["OPENAI_API_KEY"],
@@ -320,7 +320,7 @@ const { data: actions } = await stagehand.observe("find the main call-to-action
```python
result = await stagehand.observe(
- instruction="find the main call-to-action buttons",
+ "find the main call-to-action buttons",
ignore_selectors=[
"//aside[contains(@class, 'promo-rail')]",
"//div[@id='floating-chat-launcher']",
@@ -380,7 +380,7 @@ if (emailField && passwordField) {
```python
observed = await stagehand.observe(
- instruction="find the login form fields",
+ "find the login form fields",
variables={
"username": {"value": "user@example.com", "description": "The login email"},
"password": {
@@ -503,7 +503,7 @@ stagehand = Stagehand(
)
# Or disable it for a single call
-result = await stagehand.observe(instruction="find the login button", cache=False)
+result = await stagehand.observe("find the login button", cache=False)
```
@@ -575,7 +575,7 @@ await products_page.goto("https://www.example.com/products")
# Use observe with that specific page
result = await stagehand.observe(
- instruction="find all product cards",
+ "find all product cards",
page=products_page,
)
```
@@ -647,7 +647,7 @@ for (const field of formFields) {
```python
-observed = await stagehand.observe(instruction="find all form input fields")
+observed = await stagehand.observe("find all form input fields")
for field in observed.data:
# No LLM call: Stagehand replays the observed action
@@ -717,11 +717,11 @@ class Pricing(BaseModel):
tiers: list[PricingTier]
-observed = await stagehand.observe(instruction="find the pricing table")
+observed = await stagehand.observe("find the pricing table")
pricing = (await stagehand.extract(
- instruction="extract all pricing tiers",
- schema=Pricing,
+ "extract all pricing tiers",
+ Pricing,
selector=observed.data[0].selector,
)).data
@@ -813,7 +813,7 @@ if (deleteButton?.method === "click") {
```python
-observed = await stagehand.observe(instruction="find the delete account button")
+observed = await stagehand.observe("find the delete account button")
delete_button = observed.data[0] if observed.data else None
if delete_button is not None and delete_button.method == "click":
@@ -874,7 +874,7 @@ async def cached_observe(instruction: str) -> list[Action]:
if instruction in action_cache:
return action_cache[instruction]
- observed = await stagehand.observe(instruction=instruction)
+ observed = await stagehand.observe(instruction)
action_cache[instruction] = observed.data
return observed.data
```
@@ -942,12 +942,12 @@ if page is None:
raise RuntimeError("Stagehand initialized without an active page")
await page.wait_for_load_state("domcontentloaded")
-observed = await stagehand.observe(instruction="find the submit button")
+observed = await stagehand.observe("find the submit button")
if not observed.data:
print("No elements found, trying alternative instruction")
alt = await stagehand.observe(
- instruction="find the button at the bottom of the form"
+ "find the button at the bottom of the form"
)
```
@@ -1006,11 +1006,11 @@ await stagehand.observe("find the red 'Delete' button in the user settings panel
```python
# More specific instructions improve accuracy
# Instead of:
-await stagehand.observe(instruction="find the button")
+await stagehand.observe("find the button")
# Use context:
await stagehand.observe(
- instruction="find the red 'Delete' button in the user settings panel"
+ "find the red 'Delete' button in the user settings panel"
)
```
@@ -1058,7 +1058,7 @@ if (action && validMethods.includes(action.method || "")) {
```python
-observed = await stagehand.observe(instruction="find the submit button")
+observed = await stagehand.observe("find the submit button")
action = observed.data[0] if observed.data else None
# Validate method before acting
diff --git a/packages/docs/v4/configuration/models.mdx b/packages/docs/v4/configuration/models.mdx
index cea4b1d92..f6f89c883 100644
--- a/packages/docs/v4/configuration/models.mdx
+++ b/packages/docs/v4/configuration/models.mdx
@@ -1312,8 +1312,8 @@ await stagehand.act("click the login button")
# Uses a stronger model for one hard extraction
data = (await stagehand.extract(
- instruction="summarize the pricing table",
- schema=Pricing,
+ "summarize the pricing table",
+ Pricing,
model=ModelConfig.model_validate({
"model_name": "anthropic/claude-sonnet-4-6",
"api_key": os.environ["ANTHROPIC_API_KEY"],
diff --git a/packages/docs/v4/configuration/observability.mdx b/packages/docs/v4/configuration/observability.mdx
index 225078797..ff64ba8e5 100644
--- a/packages/docs/v4/configuration/observability.mdx
+++ b/packages/docs/v4/configuration/observability.mdx
@@ -354,7 +354,7 @@ if page is None:
raise RuntimeError("Stagehand initialized without an active page")
await page.goto("https://example.com")
await stagehand.act("click button")
-await stagehand.extract(instruction="get data", schema=Data)
+await stagehand.extract("get data", Data)
final_metrics = await stagehand.metrics()
execution_time = (perf_counter() - start_time) * 1000
@@ -703,7 +703,7 @@ if page is None:
raise RuntimeError("Stagehand initialized without an active page")
await page.goto("https://example.com")
await stagehand.act("click the login button")
-data = (await stagehand.extract(instruction="extract user info", schema=User)).data
+data = (await stagehand.extract("extract user info", User)).data
print(f"Extracted {data.name} <{data.email}>")
final_metrics = await stagehand.metrics()
diff --git a/packages/docs/v4/first-steps/ai-rules.mdx b/packages/docs/v4/first-steps/ai-rules.mdx
index ba08930cc..ddaee8026 100644
--- a/packages/docs/v4/first-steps/ai-rules.mdx
+++ b/packages/docs/v4/first-steps/ai-rules.mdx
@@ -399,7 +399,8 @@ The main class can be imported as `Stagehand` from `stagehand`.
There is no `agent` API in v4. Compose `observe`, `act`, and `extract` in your own control flow instead.
-All Stagehand methods are async. `observe` and `extract` take keyword-only arguments.
+All Stagehand methods are async. `act`, `observe`, and `extract` take `instruction` (and, for
+`extract`, `schema`) positionally; every other argument is keyword-only.
## Initialize
@@ -471,7 +472,7 @@ await stagehand.act(
`act` accepts either a string instruction or an `Action` returned by `observe`. Use `observe` to inspect the candidate action, then pass it back to `act` for deterministic replay with no inference:
```python
-result = await stagehand.observe(instruction="Click the sign in button")
+result = await stagehand.observe("Click the sign in button")
action = result.data[0] if result.data else None
if action is not None and action.method == "click":
@@ -482,7 +483,7 @@ To target a specific page:
```python
result = await stagehand.observe(
- instruction="select blue as the favorite color",
+ "select blue as the favorite color",
page=page2,
)
await stagehand.act(result.data[0], page=page2)
@@ -510,8 +511,8 @@ class Listings(BaseModel):
result = await stagehand.extract(
- instruction="extract all apartment listings with prices and addresses",
- schema=Listings,
+ "extract all apartment listings with prices and addresses",
+ Listings,
)
print(result.data.listings)
@@ -527,8 +528,8 @@ class ButtonText(BaseModel):
result = await stagehand.extract(
- instruction="extract the sign in button text",
- schema=ButtonText,
+ "extract the sign in button text",
+ ButtonText,
)
print(result.data.button_text) # "Sign in"
@@ -544,8 +545,8 @@ class Reason(BaseModel):
result = await stagehand.extract(
- instruction="extract the reason why script injection fails",
- schema=Reason,
+ "extract the reason why script injection fails",
+ Reason,
selector="#main-content",
ignore_selectors=["nav", ".cookie-banner"],
)
@@ -564,8 +565,8 @@ class Links(BaseModel):
result = await stagehand.extract(
- instruction="extract all navigation links",
- schema=Links,
+ "extract all navigation links",
+ Links,
)
```
@@ -577,8 +578,8 @@ class Placeholder(BaseModel):
result = await stagehand.extract(
- instruction="extract the placeholder text on the name field",
- schema=Placeholder,
+ "extract the placeholder text on the name field",
+ Placeholder,
page=page2,
)
```
@@ -591,8 +592,8 @@ class Title(BaseModel):
result = await stagehand.extract(
- instruction="extract the page title",
- schema=Title,
+ "extract the page title",
+ Title,
)
print(result.data.title)
@@ -605,7 +606,7 @@ print(result.metadata.cache_status) # "HIT", "MISS", or None
Plan actions before executing them. Candidate actions are returned on `data`:
```python
-result = await stagehand.observe(instruction="Click the sign in button")
+result = await stagehand.observe("Click the sign in button")
action = result.data[0] if result.data else None
if action is not None:
@@ -616,7 +617,7 @@ Observing on a specific page:
```python
result = await stagehand.observe(
- instruction="find the next page button",
+ "find the next page button",
page=page2,
)
await stagehand.act(result.data[0], page=page2)
@@ -646,7 +647,7 @@ await page2.goto("https://example2.com")
# Act/extract/observe operate on the current active page by default
# Pass page= to target a specific page
await stagehand.act("click button", page=page1)
-await stagehand.extract(instruction="get title", schema=Title, page=page2)
+await stagehand.extract("get title", Title, page=page2)
```
### Caching
diff --git a/packages/docs/v4/first-steps/installation.mdx b/packages/docs/v4/first-steps/installation.mdx
index 614c723dc..e2ee5d940 100644
--- a/packages/docs/v4/first-steps/installation.mdx
+++ b/packages/docs/v4/first-steps/installation.mdx
@@ -202,8 +202,8 @@ async def main() -> None:
# Extract structured data
result = await stagehand.extract(
- instruction="extract the description",
- schema=Description,
+ "extract the description",
+ Description,
)
print(result.data.description)
diff --git a/packages/docs/v4/first-steps/introduction.mdx b/packages/docs/v4/first-steps/introduction.mdx
index c706899a6..08b4da917 100644
--- a/packages/docs/v4/first-steps/introduction.mdx
+++ b/packages/docs/v4/first-steps/introduction.mdx
@@ -56,13 +56,13 @@ class Price(BaseModel):
price: float
result = await stagehand.extract(
- instruction="extract the price",
- schema=Price,
+ "extract the price",
+ Price,
)
price = result.data.price
# Observe: discover available actions
-actions = (await stagehand.observe(instruction="find submit buttons")).data
+actions = (await stagehand.observe("find submit buttons")).data
```
diff --git a/packages/docs/v4/first-steps/quickstart.mdx b/packages/docs/v4/first-steps/quickstart.mdx
index e4155e1e2..473502307 100644
--- a/packages/docs/v4/first-steps/quickstart.mdx
+++ b/packages/docs/v4/first-steps/quickstart.mdx
@@ -120,14 +120,14 @@ async def main() -> None:
await page.goto("https://stagehand.dev")
extract_result = await stagehand.extract(
- instruction="Extract the value proposition from the page.",
- schema=ValueProposition,
+ "Extract the value proposition from the page.",
+ ValueProposition,
)
print("Extract result:\n", extract_result.data)
await stagehand.act("Click the 'Evals' button.")
- observe_result = await stagehand.observe(instruction="What can I click on this page?")
+ observe_result = await stagehand.observe("What can I click on this page?")
print("Observe result:\n", observe_result.data)
finally:
# Always release the session, even when a step above raises
diff --git a/packages/docs/v4/reference/stagehand.mdx b/packages/docs/v4/reference/stagehand.mdx
index afd42357c..a88ac5ee5 100644
--- a/packages/docs/v4/reference/stagehand.mdx
+++ b/packages/docs/v4/reference/stagehand.mdx
@@ -829,7 +829,7 @@ result = await stagehand.act("Click the sign in button")
Find candidate actions on the page from an optional instruction.
```python
-actions = await stagehand.observe(instruction="Find the sign in button")
+actions = await stagehand.observe("Find the sign in button")
```
@@ -957,7 +957,7 @@ actions = await stagehand.observe(instruction="Find the sign in button")
Extract structured data from the page.
```python
-product = await stagehand.extract(instruction="Extract the product", schema=Product)
+product = await stagehand.extract("Extract the product", Product)
print(product.data, product.metadata.cache_status)
```
diff --git a/packages/sdk-python/README.md b/packages/sdk-python/README.md
index 34b73f6c8..d32d9a94d 100644
--- a/packages/sdk-python/README.md
+++ b/packages/sdk-python/README.md
@@ -17,7 +17,7 @@ async def main() -> None:
if page is None:
raise RuntimeError("Stagehand initialized without an active page")
await page.goto("https://example.com")
- await stagehand.observe(instruction="Find the more information link")
+ await stagehand.observe("Find the more information link")
print(await page.title())
finally:
await stagehand.close()
diff --git a/packages/sdk-python/examples/caching.py b/packages/sdk-python/examples/caching.py
index 0a4c590a2..77c9382b4 100644
--- a/packages/sdk-python/examples/caching.py
+++ b/packages/sdk-python/examples/caching.py
@@ -42,11 +42,11 @@ async def main() -> None:
async def extract_companies() -> tuple[ExtractResult[Companies], int]:
start = perf_counter()
result = await stagehand.extract(
- instruction=(
+ (
"Extract the names and descriptions of the first five companies "
"listed on the page"
),
- schema=Companies,
+ Companies,
page=page,
cache=True,
)
diff --git a/packages/sdk-python/examples/custom_llm.py b/packages/sdk-python/examples/custom_llm.py
index e007f55f4..f63fd129c 100644
--- a/packages/sdk-python/examples/custom_llm.py
+++ b/packages/sdk-python/examples/custom_llm.py
@@ -46,11 +46,11 @@ async def main() -> None:
await page.goto("https://example.com")
page_info = await stagehand.extract(
- instruction="Extract the page heading and description",
- schema=PageInfo,
+ "Extract the page heading and description",
+ PageInfo,
)
actions = await stagehand.observe(
- instruction="Find the link that provides more information about Example Domain",
+ "Find the link that provides more information about Example Domain",
)
action_result = await stagehand.act(
"Click the link that provides more information about Example Domain"
diff --git a/packages/sdk-python/examples/custom_logging.py b/packages/sdk-python/examples/custom_logging.py
index b03ad07e3..114b1fb77 100644
--- a/packages/sdk-python/examples/custom_logging.py
+++ b/packages/sdk-python/examples/custom_logging.py
@@ -28,7 +28,7 @@ async def main() -> None:
raise RuntimeError
await page.goto("https://example.com")
- print(await stagehand.observe(instruction="Find the Learn more link"))
+ print(await stagehand.observe("Find the Learn more link"))
finally:
await stagehand.close()
finally:
diff --git a/packages/sdk-python/examples/extract.py b/packages/sdk-python/examples/extract.py
index 5ce135403..4c61c957c 100644
--- a/packages/sdk-python/examples/extract.py
+++ b/packages/sdk-python/examples/extract.py
@@ -31,8 +31,8 @@ async def main() -> None:
await page.goto("https://example.com")
page_info = await stagehand.extract(
- instruction="Extract the page heading and description",
- schema=PageInfo,
+ "Extract the page heading and description",
+ PageInfo,
)
print(json.dumps(page_info.model_dump(mode="json"), indent=2))
diff --git a/packages/sdk-python/examples/model_gateway.py b/packages/sdk-python/examples/model_gateway.py
index b9ba73f47..2ba874b99 100644
--- a/packages/sdk-python/examples/model_gateway.py
+++ b/packages/sdk-python/examples/model_gateway.py
@@ -32,8 +32,8 @@ async def main() -> None:
await page.goto("https://example.com")
page_info = await stagehand.extract(
- instruction="Extract the page heading and the domain this page says it is for",
- schema=PageInfo,
+ "Extract the page heading and the domain this page says it is for",
+ PageInfo,
)
print(json.dumps(page_info.model_dump(mode="json"), indent=2))
diff --git a/packages/sdk-python/examples/observe.py b/packages/sdk-python/examples/observe.py
index fbf8c727e..3e46d61a9 100644
--- a/packages/sdk-python/examples/observe.py
+++ b/packages/sdk-python/examples/observe.py
@@ -24,7 +24,7 @@ async def main() -> None:
await page.goto("https://example.com")
actions = await stagehand.observe(
- instruction="Find the link that provides more information about Example Domain",
+ "Find the link that provides more information about Example Domain",
)
print(
diff --git a/packages/sdk-python/src/stagehand/stagehand.py b/packages/sdk-python/src/stagehand/stagehand.py
index 4823a41bd..d46b8770b 100644
--- a/packages/sdk-python/src/stagehand/stagehand.py
+++ b/packages/sdk-python/src/stagehand/stagehand.py
@@ -239,8 +239,8 @@ async def act(
async def observe(
self,
- *,
instruction: str | None = None,
+ *,
page: Page | None = None,
model: ModelConfig | None = None,
variables: Variables | None = None,
@@ -274,9 +274,9 @@ async def observe(
async def extract(
self,
- *,
instruction: str,
schema: builtins.type[ResultModel],
+ *,
page: Page | None = None,
model: ModelConfig | None = None,
timeout: float | None = None,
diff --git a/packages/sdk-python/tests/test_stagehand.py b/packages/sdk-python/tests/test_stagehand.py
index e5cc1c54a..0faab0ef7 100644
--- a/packages/sdk-python/tests/test_stagehand.py
+++ b/packages/sdk-python/tests/test_stagehand.py
@@ -2,6 +2,7 @@
import asyncio
import importlib
+import inspect
import json
from collections.abc import Awaitable, Callable
from typing import TypeVar, cast, overload
@@ -569,10 +570,10 @@ async def test_stagehand_routes_metrics_and_ai_methods(
locator=locator,
cache=CacheOptions(threshold=1),
)
- observed = await stagehand.observe(instruction="Find the link", model=model, locator=locator)
+ observed = await stagehand.observe("Find the link", model=model, locator=locator)
extracted = await stagehand.extract(
- instruction="Extract the heading",
- schema=PageInfo,
+ "Extract the heading",
+ PageInfo,
page=page,
model=model,
screenshot=True,
@@ -620,3 +621,25 @@ async def test_stagehand_ai_methods_require_an_active_page(
with pytest.raises(RuntimeError, match="no active page"):
await stagehand.act("Click the link")
+
+
+@pytest.mark.parametrize(
+ ("method", "positional"),
+ [
+ ("act", ["instruction"]),
+ ("observe", ["instruction"]),
+ ("extract", ["instruction", "schema"]),
+ ],
+)
+def test_semantic_arguments_stay_positional(method: str, positional: list[str]) -> None:
+ """TS and Go take these positionally; Python must match, with options keyword-only."""
+ parameters = list(inspect.signature(getattr(Stagehand, method)).parameters.values())
+ assert [
+ parameter.name
+ for parameter in parameters[1:]
+ if parameter.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
+ ] == positional
+ assert all(
+ parameter.kind is inspect.Parameter.KEYWORD_ONLY
+ for parameter in parameters[1 + len(positional) :]
+ )