sentienceapi 0.95.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sentienceapi might be problematic. Click here for more details.
- sentience/__init__.py +253 -0
- sentience/_extension_loader.py +195 -0
- sentience/action_executor.py +215 -0
- sentience/actions.py +1020 -0
- sentience/agent.py +1181 -0
- sentience/agent_config.py +46 -0
- sentience/agent_runtime.py +424 -0
- sentience/asserts/__init__.py +70 -0
- sentience/asserts/expect.py +621 -0
- sentience/asserts/query.py +383 -0
- sentience/async_api.py +108 -0
- sentience/backends/__init__.py +137 -0
- sentience/backends/actions.py +343 -0
- sentience/backends/browser_use_adapter.py +241 -0
- sentience/backends/cdp_backend.py +393 -0
- sentience/backends/exceptions.py +211 -0
- sentience/backends/playwright_backend.py +194 -0
- sentience/backends/protocol.py +216 -0
- sentience/backends/sentience_context.py +469 -0
- sentience/backends/snapshot.py +427 -0
- sentience/base_agent.py +196 -0
- sentience/browser.py +1215 -0
- sentience/browser_evaluator.py +299 -0
- sentience/canonicalization.py +207 -0
- sentience/cli.py +130 -0
- sentience/cloud_tracing.py +807 -0
- sentience/constants.py +6 -0
- sentience/conversational_agent.py +543 -0
- sentience/element_filter.py +136 -0
- sentience/expect.py +188 -0
- sentience/extension/background.js +104 -0
- sentience/extension/content.js +161 -0
- sentience/extension/injected_api.js +914 -0
- sentience/extension/manifest.json +36 -0
- sentience/extension/pkg/sentience_core.d.ts +51 -0
- sentience/extension/pkg/sentience_core.js +323 -0
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
- sentience/extension/release.json +115 -0
- sentience/formatting.py +15 -0
- sentience/generator.py +202 -0
- sentience/inspector.py +367 -0
- sentience/llm_interaction_handler.py +191 -0
- sentience/llm_provider.py +875 -0
- sentience/llm_provider_utils.py +120 -0
- sentience/llm_response_builder.py +153 -0
- sentience/models.py +846 -0
- sentience/ordinal.py +280 -0
- sentience/overlay.py +222 -0
- sentience/protocols.py +228 -0
- sentience/query.py +303 -0
- sentience/read.py +188 -0
- sentience/recorder.py +589 -0
- sentience/schemas/trace_v1.json +335 -0
- sentience/screenshot.py +100 -0
- sentience/sentience_methods.py +86 -0
- sentience/snapshot.py +706 -0
- sentience/snapshot_diff.py +126 -0
- sentience/text_search.py +262 -0
- sentience/trace_event_builder.py +148 -0
- sentience/trace_file_manager.py +197 -0
- sentience/trace_indexing/__init__.py +27 -0
- sentience/trace_indexing/index_schema.py +199 -0
- sentience/trace_indexing/indexer.py +414 -0
- sentience/tracer_factory.py +322 -0
- sentience/tracing.py +449 -0
- sentience/utils/__init__.py +40 -0
- sentience/utils/browser.py +46 -0
- sentience/utils/element.py +257 -0
- sentience/utils/formatting.py +59 -0
- sentience/utils.py +296 -0
- sentience/verification.py +380 -0
- sentience/visual_agent.py +2058 -0
- sentience/wait.py +139 -0
- sentienceapi-0.95.0.dist-info/METADATA +984 -0
- sentienceapi-0.95.0.dist-info/RECORD +82 -0
- sentienceapi-0.95.0.dist-info/WHEEL +5 -0
- sentienceapi-0.95.0.dist-info/entry_points.txt +2 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE +24 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE-APACHE +201 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE-MIT +21 -0
- sentienceapi-0.95.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration classes for Sentience agents.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AgentConfig:
|
|
10
|
+
"""
|
|
11
|
+
Configuration for Sentience Agent execution.
|
|
12
|
+
|
|
13
|
+
This dataclass provides centralized configuration for agent behavior,
|
|
14
|
+
including snapshot limits, retry logic, verification, and screenshot capture.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
snapshot_limit: Maximum elements to include in LLM context (default: 50)
|
|
18
|
+
temperature: LLM temperature 0.0-1.0 for response generation (default: 0.0)
|
|
19
|
+
max_retries: Number of retries on action failure (default: 1)
|
|
20
|
+
verify: Whether to run verification step after actions (default: True)
|
|
21
|
+
capture_screenshots: Whether to capture screenshots during execution (default: True)
|
|
22
|
+
screenshot_format: Screenshot format 'png' or 'jpeg' (default: 'jpeg')
|
|
23
|
+
screenshot_quality: JPEG quality 1-100, ignored for PNG (default: 80)
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> from sentience import AgentConfig, SentienceAgent
|
|
27
|
+
>>> config = AgentConfig(
|
|
28
|
+
... snapshot_limit=100,
|
|
29
|
+
... max_retries=2,
|
|
30
|
+
... verify=True
|
|
31
|
+
... )
|
|
32
|
+
>>> agent = SentienceAgent(browser, llm, config=config)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
snapshot_limit: int = 50
|
|
36
|
+
temperature: float = 0.0
|
|
37
|
+
max_retries: int = 1
|
|
38
|
+
verify: bool = True
|
|
39
|
+
|
|
40
|
+
# Screenshot options
|
|
41
|
+
capture_screenshots: bool = True
|
|
42
|
+
screenshot_format: str = "jpeg" # "png" or "jpeg"
|
|
43
|
+
screenshot_quality: int = 80 # 1-100 (for JPEG only)
|
|
44
|
+
|
|
45
|
+
# Visual overlay options
|
|
46
|
+
show_overlay: bool = False # Show green bbox overlay in browser
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent runtime for verification loop support.
|
|
3
|
+
|
|
4
|
+
This module provides a thin runtime wrapper that combines:
|
|
5
|
+
1. Browser session management (via BrowserBackend protocol)
|
|
6
|
+
2. Snapshot/query helpers
|
|
7
|
+
3. Tracer for event emission
|
|
8
|
+
4. Assertion/verification methods
|
|
9
|
+
|
|
10
|
+
The AgentRuntime is designed to be used in agent verification loops where
|
|
11
|
+
you need to repeatedly take snapshots, execute actions, and verify results.
|
|
12
|
+
|
|
13
|
+
Example usage with browser-use:
|
|
14
|
+
from browser_use import BrowserSession, BrowserProfile
|
|
15
|
+
from sentience import get_extension_dir
|
|
16
|
+
from sentience.backends import BrowserUseAdapter
|
|
17
|
+
from sentience.agent_runtime import AgentRuntime
|
|
18
|
+
from sentience.verification import url_matches, exists
|
|
19
|
+
from sentience.tracing import Tracer, JsonlTraceSink
|
|
20
|
+
|
|
21
|
+
# Setup browser-use with Sentience extension
|
|
22
|
+
profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"])
|
|
23
|
+
session = BrowserSession(browser_profile=profile)
|
|
24
|
+
await session.start()
|
|
25
|
+
|
|
26
|
+
# Create adapter and backend
|
|
27
|
+
adapter = BrowserUseAdapter(session)
|
|
28
|
+
backend = await adapter.create_backend()
|
|
29
|
+
|
|
30
|
+
# Navigate using browser-use
|
|
31
|
+
page = await session.get_current_page()
|
|
32
|
+
await page.goto("https://example.com")
|
|
33
|
+
|
|
34
|
+
# Create runtime with backend
|
|
35
|
+
sink = JsonlTraceSink("trace.jsonl")
|
|
36
|
+
tracer = Tracer(run_id="test-run", sink=sink)
|
|
37
|
+
runtime = AgentRuntime(backend=backend, tracer=tracer)
|
|
38
|
+
|
|
39
|
+
# Take snapshot and run assertions
|
|
40
|
+
await runtime.snapshot()
|
|
41
|
+
runtime.assert_(url_matches(r"example\\.com"), label="on_homepage")
|
|
42
|
+
runtime.assert_(exists("role=button"), label="has_buttons")
|
|
43
|
+
|
|
44
|
+
# Check if task is done
|
|
45
|
+
if runtime.assert_done(exists("text~'Success'"), label="task_complete"):
|
|
46
|
+
print("Task completed!")
|
|
47
|
+
|
|
48
|
+
Example usage with AsyncSentienceBrowser (backward compatible):
|
|
49
|
+
from sentience import AsyncSentienceBrowser
|
|
50
|
+
from sentience.agent_runtime import AgentRuntime
|
|
51
|
+
|
|
52
|
+
async with AsyncSentienceBrowser() as browser:
|
|
53
|
+
page = await browser.new_page()
|
|
54
|
+
await page.goto("https://example.com")
|
|
55
|
+
|
|
56
|
+
runtime = await AgentRuntime.from_sentience_browser(
|
|
57
|
+
browser=browser,
|
|
58
|
+
page=page,
|
|
59
|
+
tracer=tracer,
|
|
60
|
+
)
|
|
61
|
+
await runtime.snapshot()
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
from __future__ import annotations
|
|
65
|
+
|
|
66
|
+
import uuid
|
|
67
|
+
from typing import TYPE_CHECKING, Any
|
|
68
|
+
|
|
69
|
+
from .models import Snapshot, SnapshotOptions
|
|
70
|
+
from .verification import AssertContext, Predicate
|
|
71
|
+
|
|
72
|
+
if TYPE_CHECKING:
|
|
73
|
+
from playwright.async_api import Page
|
|
74
|
+
|
|
75
|
+
from .backends.protocol import BrowserBackend
|
|
76
|
+
from .browser import AsyncSentienceBrowser
|
|
77
|
+
from .tracing import Tracer
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AgentRuntime:
|
|
81
|
+
"""
|
|
82
|
+
Runtime wrapper for agent verification loops.
|
|
83
|
+
|
|
84
|
+
Provides ergonomic methods for:
|
|
85
|
+
- snapshot(): Take page snapshot
|
|
86
|
+
- assert_(): Evaluate assertion predicates
|
|
87
|
+
- assert_done(): Assert task completion (required assertion)
|
|
88
|
+
|
|
89
|
+
The runtime manages assertion state per step and emits verification events
|
|
90
|
+
to the tracer for Studio timeline display.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
backend: BrowserBackend instance for browser operations
|
|
94
|
+
tracer: Tracer for event emission
|
|
95
|
+
step_id: Current step identifier
|
|
96
|
+
step_index: Current step index (0-based)
|
|
97
|
+
last_snapshot: Most recent snapshot (for assertion context)
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
backend: BrowserBackend,
|
|
103
|
+
tracer: Tracer,
|
|
104
|
+
snapshot_options: SnapshotOptions | None = None,
|
|
105
|
+
sentience_api_key: str | None = None,
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Initialize agent runtime with any BrowserBackend-compatible browser.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
backend: Any browser implementing BrowserBackend protocol.
|
|
112
|
+
Examples:
|
|
113
|
+
- CDPBackendV0 (for browser-use via BrowserUseAdapter)
|
|
114
|
+
- PlaywrightBackend (future, for direct Playwright)
|
|
115
|
+
tracer: Tracer for emitting verification events
|
|
116
|
+
snapshot_options: Default options for snapshots
|
|
117
|
+
sentience_api_key: API key for Pro/Enterprise tier (enables Gateway refinement)
|
|
118
|
+
"""
|
|
119
|
+
self.backend = backend
|
|
120
|
+
self.tracer = tracer
|
|
121
|
+
|
|
122
|
+
# Build default snapshot options with API key if provided
|
|
123
|
+
default_opts = snapshot_options or SnapshotOptions()
|
|
124
|
+
if sentience_api_key:
|
|
125
|
+
default_opts.sentience_api_key = sentience_api_key
|
|
126
|
+
if default_opts.use_api is None:
|
|
127
|
+
default_opts.use_api = True
|
|
128
|
+
self._snapshot_options = default_opts
|
|
129
|
+
|
|
130
|
+
# Step tracking
|
|
131
|
+
self.step_id: str | None = None
|
|
132
|
+
self.step_index: int = 0
|
|
133
|
+
|
|
134
|
+
# Snapshot state
|
|
135
|
+
self.last_snapshot: Snapshot | None = None
|
|
136
|
+
|
|
137
|
+
# Cached URL (updated on snapshot or explicit get_url call)
|
|
138
|
+
self._cached_url: str | None = None
|
|
139
|
+
|
|
140
|
+
# Assertions accumulated during current step
|
|
141
|
+
self._assertions_this_step: list[dict[str, Any]] = []
|
|
142
|
+
|
|
143
|
+
# Task completion tracking
|
|
144
|
+
self._task_done: bool = False
|
|
145
|
+
self._task_done_label: str | None = None
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
async def from_sentience_browser(
|
|
149
|
+
cls,
|
|
150
|
+
browser: AsyncSentienceBrowser,
|
|
151
|
+
page: Page,
|
|
152
|
+
tracer: Tracer,
|
|
153
|
+
snapshot_options: SnapshotOptions | None = None,
|
|
154
|
+
sentience_api_key: str | None = None,
|
|
155
|
+
) -> AgentRuntime:
|
|
156
|
+
"""
|
|
157
|
+
Create AgentRuntime from AsyncSentienceBrowser (backward compatibility).
|
|
158
|
+
|
|
159
|
+
This factory method wraps an AsyncSentienceBrowser + Page combination
|
|
160
|
+
into the new BrowserBackend-based AgentRuntime.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
browser: AsyncSentienceBrowser instance
|
|
164
|
+
page: Playwright Page for browser interaction
|
|
165
|
+
tracer: Tracer for emitting verification events
|
|
166
|
+
snapshot_options: Default options for snapshots
|
|
167
|
+
sentience_api_key: API key for Pro/Enterprise tier
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
AgentRuntime instance
|
|
171
|
+
"""
|
|
172
|
+
from .backends.playwright_backend import PlaywrightBackend
|
|
173
|
+
|
|
174
|
+
backend = PlaywrightBackend(page)
|
|
175
|
+
runtime = cls(
|
|
176
|
+
backend=backend,
|
|
177
|
+
tracer=tracer,
|
|
178
|
+
snapshot_options=snapshot_options,
|
|
179
|
+
sentience_api_key=sentience_api_key,
|
|
180
|
+
)
|
|
181
|
+
# Store browser reference for snapshot() to use
|
|
182
|
+
runtime._legacy_browser = browser
|
|
183
|
+
runtime._legacy_page = page
|
|
184
|
+
return runtime
|
|
185
|
+
|
|
186
|
+
def _ctx(self) -> AssertContext:
|
|
187
|
+
"""
|
|
188
|
+
Build assertion context from current state.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
AssertContext with current snapshot and URL
|
|
192
|
+
"""
|
|
193
|
+
url = None
|
|
194
|
+
if self.last_snapshot is not None:
|
|
195
|
+
url = self.last_snapshot.url
|
|
196
|
+
elif self._cached_url:
|
|
197
|
+
url = self._cached_url
|
|
198
|
+
|
|
199
|
+
return AssertContext(
|
|
200
|
+
snapshot=self.last_snapshot,
|
|
201
|
+
url=url,
|
|
202
|
+
step_id=self.step_id,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
async def get_url(self) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Get current page URL.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Current page URL
|
|
211
|
+
"""
|
|
212
|
+
url = await self.backend.get_url()
|
|
213
|
+
self._cached_url = url
|
|
214
|
+
return url
|
|
215
|
+
|
|
216
|
+
async def snapshot(self, **kwargs: Any) -> Snapshot:
|
|
217
|
+
"""
|
|
218
|
+
Take a snapshot of the current page state.
|
|
219
|
+
|
|
220
|
+
This updates last_snapshot which is used as context for assertions.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
**kwargs: Override default snapshot options for this call.
|
|
224
|
+
Common options:
|
|
225
|
+
- limit: Maximum elements to return
|
|
226
|
+
- goal: Task goal for ordinal support
|
|
227
|
+
- screenshot: Include screenshot
|
|
228
|
+
- show_overlay: Show visual overlay
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Snapshot of current page state
|
|
232
|
+
"""
|
|
233
|
+
# Check if using legacy browser (backward compat)
|
|
234
|
+
if hasattr(self, "_legacy_browser") and hasattr(self, "_legacy_page"):
|
|
235
|
+
self.last_snapshot = await self._legacy_browser.snapshot(self._legacy_page, **kwargs)
|
|
236
|
+
return self.last_snapshot
|
|
237
|
+
|
|
238
|
+
# Use backend-agnostic snapshot
|
|
239
|
+
from .backends.snapshot import snapshot as backend_snapshot
|
|
240
|
+
|
|
241
|
+
# Merge default options with call-specific kwargs
|
|
242
|
+
options_dict = self._snapshot_options.model_dump(exclude_none=True)
|
|
243
|
+
options_dict.update(kwargs)
|
|
244
|
+
options = SnapshotOptions(**options_dict)
|
|
245
|
+
|
|
246
|
+
self.last_snapshot = await backend_snapshot(self.backend, options=options)
|
|
247
|
+
return self.last_snapshot
|
|
248
|
+
|
|
249
|
+
def begin_step(self, goal: str, step_index: int | None = None) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Begin a new step in the verification loop.
|
|
252
|
+
|
|
253
|
+
This:
|
|
254
|
+
- Generates a new step_id
|
|
255
|
+
- Clears assertions from previous step
|
|
256
|
+
- Increments step_index (or uses provided value)
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
goal: Description of what this step aims to achieve
|
|
260
|
+
step_index: Optional explicit step index (otherwise auto-increments)
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Generated step_id
|
|
264
|
+
"""
|
|
265
|
+
# Clear previous step state
|
|
266
|
+
self._assertions_this_step = []
|
|
267
|
+
|
|
268
|
+
# Generate new step_id
|
|
269
|
+
self.step_id = str(uuid.uuid4())
|
|
270
|
+
|
|
271
|
+
# Update step index
|
|
272
|
+
if step_index is not None:
|
|
273
|
+
self.step_index = step_index
|
|
274
|
+
else:
|
|
275
|
+
self.step_index += 1
|
|
276
|
+
|
|
277
|
+
return self.step_id
|
|
278
|
+
|
|
279
|
+
def assert_(
|
|
280
|
+
self,
|
|
281
|
+
predicate: Predicate,
|
|
282
|
+
label: str,
|
|
283
|
+
required: bool = False,
|
|
284
|
+
) -> bool:
|
|
285
|
+
"""
|
|
286
|
+
Evaluate an assertion against current snapshot state.
|
|
287
|
+
|
|
288
|
+
The assertion result is:
|
|
289
|
+
1. Accumulated for inclusion in step_end.data.verify.signals.assertions
|
|
290
|
+
2. Emitted as a dedicated 'verification' event for Studio timeline
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
predicate: Predicate function to evaluate
|
|
294
|
+
label: Human-readable label for this assertion
|
|
295
|
+
required: If True, this assertion gates step success (default: False)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
True if assertion passed, False otherwise
|
|
299
|
+
"""
|
|
300
|
+
outcome = predicate(self._ctx())
|
|
301
|
+
|
|
302
|
+
record = {
|
|
303
|
+
"label": label,
|
|
304
|
+
"passed": outcome.passed,
|
|
305
|
+
"required": required,
|
|
306
|
+
"reason": outcome.reason,
|
|
307
|
+
"details": outcome.details,
|
|
308
|
+
}
|
|
309
|
+
self._assertions_this_step.append(record)
|
|
310
|
+
|
|
311
|
+
# Emit dedicated verification event (Option B from design doc)
|
|
312
|
+
# This makes assertions visible in Studio timeline
|
|
313
|
+
self.tracer.emit(
|
|
314
|
+
"verification",
|
|
315
|
+
data={
|
|
316
|
+
"kind": "assert",
|
|
317
|
+
"passed": outcome.passed,
|
|
318
|
+
**record,
|
|
319
|
+
},
|
|
320
|
+
step_id=self.step_id,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return outcome.passed
|
|
324
|
+
|
|
325
|
+
def assert_done(
|
|
326
|
+
self,
|
|
327
|
+
predicate: Predicate,
|
|
328
|
+
label: str,
|
|
329
|
+
) -> bool:
|
|
330
|
+
"""
|
|
331
|
+
Assert task completion (required assertion).
|
|
332
|
+
|
|
333
|
+
This is a convenience wrapper for assert_() with required=True.
|
|
334
|
+
When the assertion passes, it marks the task as done.
|
|
335
|
+
|
|
336
|
+
Use this for final verification that the agent's goal is complete.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
predicate: Predicate function to evaluate
|
|
340
|
+
label: Human-readable label for this assertion
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
True if task is complete (assertion passed), False otherwise
|
|
344
|
+
"""
|
|
345
|
+
ok = self.assert_(predicate, label=label, required=True)
|
|
346
|
+
if ok:
|
|
347
|
+
self._task_done = True
|
|
348
|
+
self._task_done_label = label
|
|
349
|
+
|
|
350
|
+
# Emit task_done verification event
|
|
351
|
+
self.tracer.emit(
|
|
352
|
+
"verification",
|
|
353
|
+
data={
|
|
354
|
+
"kind": "task_done",
|
|
355
|
+
"passed": True,
|
|
356
|
+
"label": label,
|
|
357
|
+
},
|
|
358
|
+
step_id=self.step_id,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return ok
|
|
362
|
+
|
|
363
|
+
def get_assertions_for_step_end(self) -> dict[str, Any]:
|
|
364
|
+
"""
|
|
365
|
+
Get assertions data for inclusion in step_end.data.verify.signals.
|
|
366
|
+
|
|
367
|
+
This is called when building the step_end event to include
|
|
368
|
+
assertion results in the trace.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Dictionary with 'assertions', 'task_done', 'task_done_label' keys
|
|
372
|
+
"""
|
|
373
|
+
result: dict[str, Any] = {
|
|
374
|
+
"assertions": self._assertions_this_step.copy(),
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if self._task_done:
|
|
378
|
+
result["task_done"] = True
|
|
379
|
+
result["task_done_label"] = self._task_done_label
|
|
380
|
+
|
|
381
|
+
return result
|
|
382
|
+
|
|
383
|
+
def flush_assertions(self) -> list[dict[str, Any]]:
|
|
384
|
+
"""
|
|
385
|
+
Get and clear assertions for current step.
|
|
386
|
+
|
|
387
|
+
Call this at step end to get accumulated assertions
|
|
388
|
+
for the step_end event, then clear for next step.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
List of assertion records from this step
|
|
392
|
+
"""
|
|
393
|
+
assertions = self._assertions_this_step.copy()
|
|
394
|
+
self._assertions_this_step = []
|
|
395
|
+
return assertions
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def is_task_done(self) -> bool:
|
|
399
|
+
"""Check if task has been marked as done via assert_done()."""
|
|
400
|
+
return self._task_done
|
|
401
|
+
|
|
402
|
+
def reset_task_done(self) -> None:
|
|
403
|
+
"""Reset task_done state (for multi-task runs)."""
|
|
404
|
+
self._task_done = False
|
|
405
|
+
self._task_done_label = None
|
|
406
|
+
|
|
407
|
+
def all_assertions_passed(self) -> bool:
|
|
408
|
+
"""
|
|
409
|
+
Check if all assertions in current step passed.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
True if all assertions passed (or no assertions made)
|
|
413
|
+
"""
|
|
414
|
+
return all(a["passed"] for a in self._assertions_this_step)
|
|
415
|
+
|
|
416
|
+
def required_assertions_passed(self) -> bool:
|
|
417
|
+
"""
|
|
418
|
+
Check if all required assertions in current step passed.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
True if all required assertions passed (or no required assertions)
|
|
422
|
+
"""
|
|
423
|
+
required = [a for a in self._assertions_this_step if a.get("required")]
|
|
424
|
+
return all(a["passed"] for a in required)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Assertion DSL for Sentience SDK.
|
|
3
|
+
|
|
4
|
+
This module provides a Playwright/Cypress-like assertion API for verifying
|
|
5
|
+
browser state in agent verification loops.
|
|
6
|
+
|
|
7
|
+
Main exports:
|
|
8
|
+
- E: Element query builder (filters elements by role, text, href, etc.)
|
|
9
|
+
- expect: Expectation builder (creates predicates from queries)
|
|
10
|
+
- in_dominant_list: Query over dominant group elements (ordinal access)
|
|
11
|
+
|
|
12
|
+
Example usage:
|
|
13
|
+
from sentience.asserts import E, expect, in_dominant_list
|
|
14
|
+
|
|
15
|
+
# Basic presence assertions
|
|
16
|
+
runtime.assert_(
|
|
17
|
+
expect(E(role="button", text_contains="Save")).to_exist(),
|
|
18
|
+
label="save_button_visible"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Visibility assertions
|
|
22
|
+
runtime.assert_(
|
|
23
|
+
expect(E(text_contains="Checkout")).to_be_visible(),
|
|
24
|
+
label="checkout_visible"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Global text assertions
|
|
28
|
+
runtime.assert_(
|
|
29
|
+
expect.text_present("Welcome back"),
|
|
30
|
+
label="user_logged_in"
|
|
31
|
+
)
|
|
32
|
+
runtime.assert_(
|
|
33
|
+
expect.no_text("Error"),
|
|
34
|
+
label="no_error_message"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Ordinal assertions on dominant group
|
|
38
|
+
runtime.assert_(
|
|
39
|
+
expect(in_dominant_list().nth(0)).to_have_text_contains("Show HN"),
|
|
40
|
+
label="first_item_is_show_hn"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Task completion
|
|
44
|
+
runtime.assert_done(
|
|
45
|
+
expect.text_present("Order confirmed"),
|
|
46
|
+
label="checkout_complete"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
The DSL compiles to existing Predicate functions, so it works seamlessly
|
|
50
|
+
with AgentRuntime.assert_() and assert_done().
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from .expect import EventuallyConfig, EventuallyWrapper, ExpectBuilder, expect, with_eventually
|
|
54
|
+
from .query import E, ElementQuery, ListQuery, MultiQuery, in_dominant_list
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
# Query builders
|
|
58
|
+
"E",
|
|
59
|
+
"ElementQuery",
|
|
60
|
+
"ListQuery",
|
|
61
|
+
"MultiQuery",
|
|
62
|
+
"in_dominant_list",
|
|
63
|
+
# Expectation builders
|
|
64
|
+
"expect",
|
|
65
|
+
"ExpectBuilder",
|
|
66
|
+
# Eventually helpers
|
|
67
|
+
"with_eventually",
|
|
68
|
+
"EventuallyWrapper",
|
|
69
|
+
"EventuallyConfig",
|
|
70
|
+
]
|