Skip to content

Commit f4600c3

Browse files
author
SentienceDEV
committed
updated readme
1 parent a421c68 commit f4600c3

File tree

6 files changed

+332
-44
lines changed

6 files changed

+332
-44
lines changed

README.md

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ Use `AgentRuntime` to add Jest-style assertions to your agent loops. Verify brow
3131
```python
3232
import asyncio
3333
from sentience import AsyncSentienceBrowser, AgentRuntime
34-
from sentience.verification import url_contains, exists, all_of
34+
from sentience.verification import (
35+
url_contains,
36+
exists,
37+
all_of,
38+
is_enabled,
39+
is_checked,
40+
value_equals,
41+
)
3542
from sentience.tracing import Tracer, JsonlTraceSink
3643

3744
async def main():
@@ -52,14 +59,27 @@ async def main():
5259
runtime.begin_step("Verify page loaded")
5360
await runtime.snapshot()
5461

55-
# Run assertions (Jest-style)
62+
# v1: deterministic assertions (Jest-style)
5663
runtime.assert_(url_contains("example.com"), label="on_correct_domain")
5764
runtime.assert_(exists("role=heading"), label="has_heading")
5865
runtime.assert_(all_of([
5966
exists("role=button"),
6067
exists("role=link")
6168
]), label="has_interactive_elements")
6269

70+
# v1: state-aware assertions (when Gateway refinement is enabled)
71+
runtime.assert_(is_enabled("role=button"), label="button_enabled")
72+
runtime.assert_(is_checked("role=checkbox name~'subscribe'"), label="subscribe_checked_if_present")
73+
runtime.assert_(value_equals("role=textbox name~'email'", "user@example.com"), label="email_value_if_present")
74+
75+
# v2: retry loop with snapshot confidence gating + exhaustion
76+
ok = await runtime.check(
77+
exists("role=heading"),
78+
label="heading_eventually_visible",
79+
required=True,
80+
).eventually(timeout_s=10.0, poll_s=0.25, min_confidence=0.7, max_snapshot_attempts=3)
81+
print("eventually() result:", ok)
82+
6383
# Check task completion
6484
if runtime.assert_done(exists("text~'Example'"), label="task_complete"):
6585
print("✅ Task completed!")
@@ -69,7 +89,7 @@ async def main():
6989
asyncio.run(main())
7090
```
7191

72-
**See example:** [`examples/agent_runtime_verification.py`](examples/agent_runtime_verification.py)
92+
**See examples:** [`examples/asserts/`](examples/asserts/)
7393

7494
## 🚀 Quick Start: Choose Your Abstraction Level
7595

@@ -183,56 +203,35 @@ scroll_to(browser, button.id, behavior='instant', block='start')
183203
---
184204

185205
<details>
186-
<summary><h2>💼 Real-World Example: Amazon Shopping Bot</h2></summary>
206+
<summary><h2>💼 Real-World Example: Assertion-driven navigation</h2></summary>
187207

188-
This example demonstrates navigating Amazon, finding products, and adding items to cart:
208+
This example shows how to use **assertions + `.eventually()`** to make an agent loop resilient:
189209

190210
```python
191-
from sentience import SentienceBrowser, snapshot, find, click
192-
import time
193-
194-
with SentienceBrowser(headless=False) as browser:
195-
# Navigate to Amazon Best Sellers
196-
browser.goto("https://www.amazon.com/gp/bestsellers/", wait_until="domcontentloaded")
197-
time.sleep(2) # Wait for dynamic content
198-
199-
# Take snapshot and find products
200-
snap = snapshot(browser)
201-
print(f"Found {len(snap.elements)} elements")
202-
203-
# Find first product in viewport using spatial filtering
204-
products = [
205-
el for el in snap.elements
206-
if el.role == "link"
207-
and el.visual_cues.is_clickable
208-
and el.in_viewport
209-
and not el.is_occluded
210-
and el.bbox.y < 600 # First row
211-
]
212-
213-
if products:
214-
# Sort by position (left to right, top to bottom)
215-
products.sort(key=lambda e: (e.bbox.y, e.bbox.x))
216-
first_product = products[0]
211+
import asyncio
212+
import os
213+
from sentience import AsyncSentienceBrowser, AgentRuntime
214+
from sentience.tracing import Tracer, JsonlTraceSink
215+
from sentience.verification import url_contains, exists
217216

218-
print(f"Clicking: {first_product.text}")
219-
result = click(browser, first_product.id)
217+
async def main():
218+
tracer = Tracer(run_id="verified-run", sink=JsonlTraceSink("trace_verified.jsonl"))
219+
async with AsyncSentienceBrowser(headless=True) as browser:
220+
page = await browser.new_page()
221+
runtime = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)
222+
runtime.sentience_api_key = os.getenv("SENTIENCE_API_KEY") # optional, enables Gateway diagnostics
220223

221-
# Wait for product page
222-
browser.page.wait_for_load_state("networkidle")
223-
time.sleep(2)
224+
await page.goto("https://example.com")
225+
runtime.begin_step("Verify we're on the right page")
224226

225-
# Find and click "Add to Cart" button
226-
product_snap = snapshot(browser)
227-
add_to_cart = find(product_snap, "role=button text~'add to cart'")
227+
await runtime.check(url_contains("example.com"), label="on_domain", required=True).eventually(
228+
timeout_s=10.0, poll_s=0.25, min_confidence=0.7, max_snapshot_attempts=3
229+
)
230+
runtime.assert_(exists("role=heading"), label="heading_present")
228231

229-
if add_to_cart:
230-
cart_result = click(browser, add_to_cart.id)
231-
print(f"Added to cart: {cart_result.success}")
232+
asyncio.run(main())
232233
```
233234

234-
**📖 See the complete tutorial:** [Amazon Shopping Guide](../docs/AMAZON_SHOPPING_GUIDE.md)
235-
236235
</details>
237236

238237
---

examples/asserts/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Assertions examples (v1 + v2)
2+
3+
These examples focus on **AgentRuntime assertions**:
4+
5+
- **v1**: deterministic, state-aware assertions (enabled/checked/value/expanded) + failure intelligence
6+
- **v2**: `.eventually()` retry loops with `min_confidence` gating + snapshot exhaustion, plus optional Python vision fallback
7+
8+
Run examples:
9+
10+
```bash
11+
cd sdk-python
12+
python examples/asserts/v1_state_assertions.py
13+
python examples/asserts/v2_eventually_min_confidence.py
14+
python examples/asserts/v2_vision_fallback.py
15+
```
16+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
v2: `.check(...).eventually(...)` with snapshot confidence gating + exhaustion.
3+
4+
This example shows:
5+
- retry loop semantics
6+
- `min_confidence` gating (snapshot_low_confidence -> snapshot_exhausted)
7+
- structured assertion records in traces
8+
"""
9+
10+
import asyncio
11+
import os
12+
13+
from sentience import AsyncSentienceBrowser, AgentRuntime
14+
from sentience.tracing import JsonlTraceSink, Tracer
15+
from sentience.verification import exists
16+
17+
18+
async def main() -> None:
19+
tracer = Tracer(run_id="asserts-v2", sink=JsonlTraceSink("trace_asserts_v2.jsonl"))
20+
sentience_api_key = os.getenv("SENTIENCE_API_KEY")
21+
22+
async with AsyncSentienceBrowser(headless=True) as browser:
23+
page = await browser.new_page()
24+
runtime = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)
25+
if sentience_api_key:
26+
runtime.sentience_api_key = sentience_api_key
27+
28+
await page.goto("https://example.com")
29+
runtime.begin_step("Assert v2 eventually")
30+
31+
ok = await runtime.check(
32+
exists("role=heading"),
33+
label="heading_eventually_visible",
34+
required=True,
35+
).eventually(
36+
timeout_s=10.0,
37+
poll_s=0.25,
38+
# If the Gateway reports snapshot.diagnostics.confidence, gate on it:
39+
min_confidence=0.7,
40+
max_snapshot_attempts=3,
41+
)
42+
43+
print("eventually() result:", ok)
44+
print("Final assertion:", runtime.get_assertions_for_step_end()["assertions"])
45+
46+
47+
if __name__ == "__main__":
48+
asyncio.run(main())
49+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
v1: State-aware assertions with AgentRuntime.
3+
4+
This example is meant to be run with a Pro/Enterprise API key so the Gateway
5+
can refine raw elements into SmartElements with state fields (enabled/checked/value/etc).
6+
7+
Env vars:
8+
- SENTIENCE_API_KEY (optional but recommended for v1 state assertions)
9+
"""
10+
11+
import asyncio
12+
import os
13+
14+
from sentience import AsyncSentienceBrowser, AgentRuntime
15+
from sentience.tracing import JsonlTraceSink, Tracer
16+
from sentience.verification import (
17+
exists,
18+
is_checked,
19+
is_disabled,
20+
is_enabled,
21+
is_expanded,
22+
value_contains,
23+
)
24+
25+
26+
async def main() -> None:
27+
tracer = Tracer(run_id="asserts-v1", sink=JsonlTraceSink("trace_asserts_v1.jsonl"))
28+
29+
sentience_api_key = os.getenv("SENTIENCE_API_KEY")
30+
31+
async with AsyncSentienceBrowser(headless=True) as browser:
32+
page = await browser.new_page()
33+
runtime = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)
34+
35+
# If you have a Pro/Enterprise key, set it on the runtime so snapshots use the Gateway.
36+
# (This improves selector quality and unlocks state-aware fields for assertions.)
37+
if sentience_api_key:
38+
runtime.sentience_api_key = sentience_api_key
39+
40+
await page.goto("https://example.com")
41+
runtime.begin_step("Assert v1 state")
42+
await runtime.snapshot()
43+
44+
# v1: state-aware assertions (examples)
45+
runtime.assert_(exists("role=heading"), label="has_heading")
46+
runtime.assert_(is_enabled("role=link"), label="some_link_enabled")
47+
runtime.assert_(is_disabled("role=button text~'continue'"), label="continue_disabled_if_present")
48+
runtime.assert_(is_checked("role=checkbox name~'subscribe'"), label="subscribe_checked_if_present")
49+
runtime.assert_(is_expanded("role=button name~'more'"), label="more_is_expanded_if_present")
50+
runtime.assert_(value_contains("role=textbox name~'email'", "@"), label="email_has_at_if_present")
51+
52+
# Failure intelligence: if something fails you’ll see:
53+
# - details.reason_code
54+
# - details.nearest_matches (suggestions)
55+
56+
print("Assertions recorded:", runtime.get_assertions_for_step_end()["assertions"])
57+
58+
59+
if __name__ == "__main__":
60+
asyncio.run(main())
61+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
v2 (Python-only): vision fallback after snapshot exhaustion.
3+
4+
When `min_confidence` gating keeps failing (snapshot_exhausted), you can pass a
5+
vision-capable LLMProvider to `eventually()` and ask it for a strict YES/NO
6+
verification using a screenshot.
7+
8+
Env vars:
9+
- OPENAI_API_KEY (if using OpenAIProvider)
10+
- SENTIENCE_API_KEY (optional, recommended so diagnostics/confidence is present)
11+
"""
12+
13+
import asyncio
14+
import os
15+
16+
from sentience import AsyncSentienceBrowser, AgentRuntime
17+
from sentience.llm_provider import OpenAIProvider
18+
from sentience.tracing import JsonlTraceSink, Tracer
19+
from sentience.verification import exists
20+
21+
22+
async def main() -> None:
23+
tracer = Tracer(run_id="asserts-v2-vision", sink=JsonlTraceSink("trace_asserts_v2_vision.jsonl"))
24+
sentience_api_key = os.getenv("SENTIENCE_API_KEY")
25+
26+
# Any provider implementing supports_vision() + generate_with_image() works.
27+
vision = OpenAIProvider(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
28+
29+
async with AsyncSentienceBrowser(headless=True) as browser:
30+
page = await browser.new_page()
31+
runtime = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)
32+
if sentience_api_key:
33+
runtime.sentience_api_key = sentience_api_key
34+
35+
await page.goto("https://example.com")
36+
runtime.begin_step("Assert v2 vision fallback")
37+
38+
ok = await runtime.check(exists("text~'Example Domain'"), label="example_domain_text").eventually(
39+
timeout_s=10.0,
40+
poll_s=0.25,
41+
min_confidence=0.7,
42+
max_snapshot_attempts=2,
43+
vision_provider=vision,
44+
vision_system_prompt="You are a strict visual verifier. Answer only YES or NO.",
45+
vision_user_prompt="In the screenshot, is the phrase 'Example Domain' visible? Answer YES or NO.",
46+
)
47+
48+
print("eventually() w/ vision result:", ok)
49+
50+
51+
if __name__ == "__main__":
52+
asyncio.run(main())
53+

0 commit comments

Comments
 (0)