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
7 changes: 6 additions & 1 deletion packages/docs/tests/sdk-reference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,12 @@ describe("SDK reference surface", () => {
for (const field of projectedResultFields(method, language, protocol)) {
const actual = documented.get(field.key);
if (!actual) continue;
const expectedType = canonicalSchemaType(field.schema, language, protocol);
const expectedType =
method.operationName === "stagehand.extract" && field.key === "result.data"
? language === "TypeScript"
? "z.output<Schema>"
: "ResultModel"
: canonicalSchemaType(field.schema, language, protocol);
if (actual.type !== expectedType || actual.optional !== field.optional) {
differences.push(
`${methodKey(method)} ${language} ${field.key}: expected type=${expectedType} optional=${field.optional}, received type=${actual.type ?? "<missing>"} optional=${actual.optional}`,
Expand Down
68 changes: 39 additions & 29 deletions packages/docs/v4/basics/act.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,30 @@ await stagehand.act("Open filters, choose 4-star rating, and click apply");

### Return value

`act()` resolves to an `ActResultData` object.
`act()` resolves to an `ActResult` with typed `data` and shared `metadata`.

```typescript
const result = await stagehand.act("Click the sign in button");
// {
// success: true,
// message: "Successfully clicked the sign in button",
// actionDescription: "Clicked button with text 'Sign in'",
// actions: [
// { selector: "xpath=/html/body/...", description: "Sign in", method: "click", arguments: [] }
// ]
// data: {
// success: true,
// message: "Successfully clicked the sign in button",
// actionDescription: "Clicked button with text 'Sign in'",
// actions: [...]
// },
// metadata: { cacheStatus: "MISS" }
// }

if (!result.success) {
throw new Error(`act() failed: ${result.message}`);
if (!result.data.success) {
throw new Error(`act() failed: ${result.data.message}`);
}
```

Always check `success` before assuming the action ran. See the full field list in the
Always check `data.success` before assuming the action ran. See the full field list in the
[`stagehand` reference](/v4/reference/stagehand).

<Card title="Complete act() reference" icon="book" href="/v4/reference/stagehand">
Full parameters, per-call options, and the `ActResultData` shape.
Full parameters, per-call options, and the `ActResult` shape.
</Card>

## Advanced configuration
Expand Down Expand Up @@ -159,7 +160,9 @@ await stagehand.act("Click the login button");
exists or to preview what `act()` will do.

```typescript
const [candidate] = await stagehand.observe("Find the login button");
const {
data: [candidate],
} = await stagehand.observe("Find the login button");
if (candidate) {
await stagehand.act("Click the login button");
}
Expand All @@ -180,7 +183,8 @@ On Browserbase, turn on caching
so repeated, identical actions are served from a cache instead of calling the model again.

```typescript
await stagehand.act("Click the sign in button", { cache: true });
const result = await stagehand.act("Click the sign in button", { cache: true });
console.log(result.metadata.cacheStatus);
```

## Troubleshooting
Expand All @@ -194,7 +198,9 @@ the target element exists before acting.
```typescript
await page.waitForLoadState("domcontentloaded");

const [candidate] = await stagehand.observe("Find the submit button");
const {
data: [candidate],
} = await stagehand.observe("Find the submit button");
if (candidate) {
await stagehand.act("Click the submit button", { timeout: 30_000 });
}
Expand All @@ -214,7 +220,9 @@ await stagehand.act("Click the button");
await stagehand.act("Click the red 'Delete' button next to the user John Smith");

// Or preview with observe() and confirm the description before acting:
const [candidate] = await stagehand.observe("Find the submit button in the checkout form");
const {
data: [candidate],
} = await stagehand.observe("Find the submit button in the checkout form");
if (candidate?.description.toLowerCase().includes("checkout")) {
await stagehand.act("Click the submit button in the checkout form");
}
Expand Down Expand Up @@ -302,24 +310,25 @@ await stagehand.act("Open filters, choose 4-star rating, and click apply")

### Return value

`act()` resolves to an `ActResultData` object.
`act()` resolves to an `ActResult` with typed `data` and shared `metadata`.

```python
result = await stagehand.act("Click the sign in button")
# result.success -> bool
# result.message -> str
# result.action_description -> str
# result.actions -> list of executed actions

if not result.success:
raise RuntimeError(f"act() failed: {result.message}")
# result.data.success -> bool
# result.data.message -> str
# result.data.action_description -> str
# result.data.actions -> list of executed actions
# result.metadata.cache_status -> "HIT", "MISS", or None

if not result.data.success:
raise RuntimeError(f"act() failed: {result.data.message}")
```

Always check `success` before assuming the action ran. See the full field list in the
Always check `data.success` before assuming the action ran. See the full field list in the
[`stagehand` reference](/v4/reference/stagehand).

<Card title="Complete act() reference" icon="book" href="/v4/reference/stagehand">
Full parameters, per-call options, and the `ActResultData` shape.
Full parameters, per-call options, and the `ActResult` shape.
</Card>

## Advanced configuration
Expand Down Expand Up @@ -387,7 +396,7 @@ exists or to preview what `act()` will do.

```python
candidates = await stagehand.observe(instruction="Find the login button")
if candidates:
if candidates.data:
await stagehand.act("Click the login button")
```

Expand All @@ -406,7 +415,8 @@ On Browserbase, turn on caching
so repeated, identical actions are served from a cache instead of calling the model again.

```python
await stagehand.act("Click the sign in button", cache=True)
result = await stagehand.act("Click the sign in button", cache=True)
print(result.metadata.cache_status)
```

## Troubleshooting
Expand All @@ -421,7 +431,7 @@ the target element exists before acting.
await page.wait_for_load_state("domcontentloaded")

candidates = await stagehand.observe(instruction="Find the submit button")
if candidates:
if candidates.data:
await stagehand.act("Click the submit button", timeout=30_000)
```

Expand All @@ -440,7 +450,7 @@ await stagehand.act("Click the red 'Delete' button next to the user John Smith")

# Or preview with observe() and confirm the description before acting:
candidates = await stagehand.observe(instruction="Find the submit button in the checkout form")
if candidates and "checkout" in candidates[0].description.lower():
if candidates.data and "checkout" in candidates.data[0].description.lower():
await stagehand.act("Click the submit button in the checkout form")
```

Expand Down
56 changes: 32 additions & 24 deletions packages/docs/v4/basics/extract.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Define the schema as a [Zod](https://zod.dev) object:
```typescript
import { z } from "zod";

const product = await stagehand.extract(
const { data: product } = await stagehand.extract(
"Extract the product name and price",
z.object({
name: z.string(),
Expand Down Expand Up @@ -50,7 +50,7 @@ values, and use field descriptions to guide the model.
```typescript
import { z } from "zod";

const results = await stagehand.extract(
const { data: results } = await stagehand.extract(
"Extract each search result",
z.object({
results: z.array(
Expand All @@ -66,25 +66,26 @@ const results = await stagehand.extract(

### Return value

`extract()` resolves to data that matches your schema, with no wrapper object. In
TypeScript the type is inferred from the Zod schema; in Python it's an instance of your
Pydantic model. When the schema is an array or list, you get an array or list back.
`extract()` resolves to an object with `data` and `metadata`. `data` matches your schema:
in TypeScript its type is inferred from Zod, and in Python it is an instance of your
Pydantic model. `metadata` contains Stagehand-owned information such as cache status and
an optional action ID.

```typescript
const product = await stagehand.extract(
const extraction = await stagehand.extract(
"Extract the product details",
z.object({ name: z.string(), price: z.number(), inStock: z.boolean() }),
);
// product is typed as { name: string; price: number; inStock: boolean }
// e.g. { name: "Wireless Mouse", price: 29.99, inStock: true }
// extraction.data is typed as { name: string; price: number; inStock: boolean }

console.log(product.name, product.price);
console.log(extraction.data.name, extraction.data.price);
console.log(extraction.metadata.cacheStatus);
```

## Advanced configuration

```typescript
const data = await stagehand.extract("Extract the article body", schema, {
const extraction = await stagehand.extract("Extract the article body", schema, {
selector: "#main-content",
ignoreSelectors: ["nav", ".cookie-banner"],
screenshot: true,
Expand All @@ -108,7 +109,9 @@ with `ignoreSelectors`) is faster, cheaper, and more accurate than extracting fr
whole document.

```typescript
const { price } = await stagehand.extract(
const {
data: { price },
} = await stagehand.extract(
"Extract the sale price",
z.object({ price: z.number() }),
{ selector: "#product-summary", ignoreSelectors: [".related-products"] },
Expand All @@ -126,11 +129,12 @@ On Browserbase, turn on caching
so repeated, identical extractions are served from a cache instead of calling the model again.

```typescript
const product = await stagehand.extract(
const extraction = await stagehand.extract(
"Extract the product name and price",
z.object({ name: z.string(), price: z.number() }),
{ cache: true },
);
console.log(extraction.data, extraction.metadata.cacheStatus);
```

## Best practices
Expand All @@ -155,7 +159,9 @@ Narrow with `selector`, exclude distractors with `ignoreSelectors`, or enable `s
when the correct value is clear visually but ambiguous in the DOM.

```typescript
const { price } = await stagehand.extract(
const {
data: { price },
} = await stagehand.extract(
"Extract the sale price, not the original price",
z.object({ price: z.number() }),
{ selector: "#product-summary", ignoreSelectors: [".related-products"] },
Expand All @@ -180,11 +186,11 @@ class Product(BaseModel):
price: float


product = await stagehand.extract(
extraction = await stagehand.extract(
instruction="Extract the product name and price",
schema=Product,
)
# product.name -> str, product.price -> float
# extraction.data.name -> str, extraction.data.price -> float
```

## Why use `extract()`?
Expand Down Expand Up @@ -223,17 +229,17 @@ class SearchResults(BaseModel):
results: list[Result]


results = await stagehand.extract(
extraction = await stagehand.extract(
instruction="Extract each search result",
schema=SearchResults,
)
```

### Return value

`extract()` resolves to data that matches your schema, with no wrapper object. In
TypeScript the type is inferred from the Zod schema; in Python it's an instance of your
Pydantic model. When the schema is an array or list, you get an array or list back.
`extract()` resolves to an object with `data` and `metadata`. `data` is an instance of
your Pydantic model, while `metadata` contains Stagehand-owned information such as cache
status and an optional action ID.

```python
class Product(BaseModel):
Expand All @@ -242,13 +248,14 @@ class Product(BaseModel):
in_stock: bool


product = await stagehand.extract(
extraction = await stagehand.extract(
instruction="Extract the product details",
schema=Product,
)
# product is a Product instance, e.g. Product(name="Wireless Mouse", price=29.99, in_stock=True)
# extraction.data is a Product instance

print(product.name, product.price)
print(extraction.data.name, extraction.data.price)
print(extraction.metadata.cache_status)
```

## Advanced configuration
Expand All @@ -257,7 +264,7 @@ print(product.name, product.price)
from stagehand import ModelConfig


data = await stagehand.extract(
extraction = await stagehand.extract(
instruction="Extract the article body",
schema=schema,
selector="#main-content",
Expand Down Expand Up @@ -302,11 +309,12 @@ On Browserbase, turn on caching
so repeated, identical extractions are served from a cache instead of calling the model again.

```python
product = await stagehand.extract(
extraction = await stagehand.extract(
instruction="Extract the product name and price",
schema=Product,
cache=True,
)
print(extraction.data, extraction.metadata.cache_status)
```

## Best practices
Expand Down
Loading
Loading