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,380 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Verification primitives for agent assertion loops.
|
|
3
|
+
|
|
4
|
+
This module provides assertion predicates and outcome types for runtime verification
|
|
5
|
+
in agent loops. Assertions evaluate against the current browser state (snapshot/url)
|
|
6
|
+
and record results into the trace.
|
|
7
|
+
|
|
8
|
+
Key concepts:
|
|
9
|
+
- AssertOutcome: Result of evaluating an assertion
|
|
10
|
+
- AssertContext: Context provided to assertion predicates (snapshot, url, step_id)
|
|
11
|
+
- Predicate: Callable that takes context and returns outcome
|
|
12
|
+
|
|
13
|
+
Example usage:
|
|
14
|
+
from sentience.verification import url_matches, exists, AssertContext
|
|
15
|
+
|
|
16
|
+
# Create predicates
|
|
17
|
+
on_search_page = url_matches(r"/s\\?k=")
|
|
18
|
+
results_loaded = exists("text~'Results'")
|
|
19
|
+
|
|
20
|
+
# Evaluate against context
|
|
21
|
+
ctx = AssertContext(snapshot=snapshot, url="https://example.com/s?k=shoes")
|
|
22
|
+
outcome = on_search_page(ctx)
|
|
23
|
+
print(outcome.passed) # True
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import re
|
|
29
|
+
from collections.abc import Callable
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from typing import TYPE_CHECKING, Any
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from .models import Snapshot
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class AssertOutcome:
|
|
39
|
+
"""
|
|
40
|
+
Result of evaluating an assertion predicate.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
passed: Whether the assertion passed
|
|
44
|
+
reason: Human-readable explanation (especially useful when failed)
|
|
45
|
+
details: Additional structured data for debugging/display
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
passed: bool
|
|
49
|
+
reason: str = ""
|
|
50
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class AssertContext:
|
|
55
|
+
"""
|
|
56
|
+
Context provided to assertion predicates.
|
|
57
|
+
|
|
58
|
+
Provides access to current browser state without requiring
|
|
59
|
+
the predicate to know about browser internals.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
snapshot: Current page snapshot (may be None if not taken)
|
|
63
|
+
url: Current page URL
|
|
64
|
+
step_id: Current step identifier (for trace correlation)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
snapshot: Snapshot | None = None
|
|
68
|
+
url: str | None = None
|
|
69
|
+
step_id: str | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Type alias for assertion predicates
|
|
73
|
+
Predicate = Callable[[AssertContext], AssertOutcome]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def url_matches(pattern: str) -> Predicate:
|
|
77
|
+
"""
|
|
78
|
+
Create a predicate that checks if current URL matches a regex pattern.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
pattern: Regular expression pattern to match against URL
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Predicate function that evaluates URL matching
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
>>> pred = url_matches(r"/search\\?q=")
|
|
88
|
+
>>> ctx = AssertContext(url="https://example.com/search?q=shoes")
|
|
89
|
+
>>> outcome = pred(ctx)
|
|
90
|
+
>>> outcome.passed
|
|
91
|
+
True
|
|
92
|
+
"""
|
|
93
|
+
rx = re.compile(pattern)
|
|
94
|
+
|
|
95
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
96
|
+
url = ctx.url or ""
|
|
97
|
+
ok = rx.search(url) is not None
|
|
98
|
+
return AssertOutcome(
|
|
99
|
+
passed=ok,
|
|
100
|
+
reason="" if ok else f"url did not match pattern: {pattern}",
|
|
101
|
+
details={"pattern": pattern, "url": url[:200]},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return _pred
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def url_contains(substring: str) -> Predicate:
|
|
108
|
+
"""
|
|
109
|
+
Create a predicate that checks if current URL contains a substring.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
substring: String to search for in URL
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Predicate function that evaluates URL containment
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
>>> pred = url_contains("/cart")
|
|
119
|
+
>>> ctx = AssertContext(url="https://example.com/cart/checkout")
|
|
120
|
+
>>> outcome = pred(ctx)
|
|
121
|
+
>>> outcome.passed
|
|
122
|
+
True
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
126
|
+
url = ctx.url or ""
|
|
127
|
+
ok = substring in url
|
|
128
|
+
return AssertOutcome(
|
|
129
|
+
passed=ok,
|
|
130
|
+
reason="" if ok else f"url does not contain: {substring}",
|
|
131
|
+
details={"substring": substring, "url": url[:200]},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return _pred
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def exists(selector: str) -> Predicate:
|
|
138
|
+
"""
|
|
139
|
+
Create a predicate that checks if elements matching selector exist.
|
|
140
|
+
|
|
141
|
+
Uses the SDK's query engine to find matching elements.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
selector: Semantic selector string (e.g., "role=button text~'Sign in'")
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Predicate function that evaluates element existence
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
>>> pred = exists("text~'Results'")
|
|
151
|
+
>>> # Will check if snapshot contains elements with "Results" in text
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
155
|
+
snap = ctx.snapshot
|
|
156
|
+
if snap is None:
|
|
157
|
+
return AssertOutcome(
|
|
158
|
+
passed=False,
|
|
159
|
+
reason="no snapshot available",
|
|
160
|
+
details={"selector": selector},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Import here to avoid circular imports
|
|
164
|
+
from .query import query
|
|
165
|
+
|
|
166
|
+
matches = query(snap, selector)
|
|
167
|
+
ok = len(matches) > 0
|
|
168
|
+
return AssertOutcome(
|
|
169
|
+
passed=ok,
|
|
170
|
+
reason="" if ok else f"no elements matched selector: {selector}",
|
|
171
|
+
details={"selector": selector, "matched": len(matches)},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return _pred
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def not_exists(selector: str) -> Predicate:
|
|
178
|
+
"""
|
|
179
|
+
Create a predicate that checks that NO elements match the selector.
|
|
180
|
+
|
|
181
|
+
Useful for asserting that error messages, loading spinners, etc. are gone.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
selector: Semantic selector string
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Predicate function that evaluates element non-existence
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> pred = not_exists("text~'Loading'")
|
|
191
|
+
>>> # Will pass if no elements contain "Loading" text
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
195
|
+
snap = ctx.snapshot
|
|
196
|
+
if snap is None:
|
|
197
|
+
return AssertOutcome(
|
|
198
|
+
passed=False,
|
|
199
|
+
reason="no snapshot available",
|
|
200
|
+
details={"selector": selector},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
from .query import query
|
|
204
|
+
|
|
205
|
+
matches = query(snap, selector)
|
|
206
|
+
ok = len(matches) == 0
|
|
207
|
+
return AssertOutcome(
|
|
208
|
+
passed=ok,
|
|
209
|
+
reason="" if ok else f"found {len(matches)} elements matching: {selector}",
|
|
210
|
+
details={"selector": selector, "matched": len(matches)},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return _pred
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def element_count(selector: str, *, min_count: int = 0, max_count: int | None = None) -> Predicate:
|
|
217
|
+
"""
|
|
218
|
+
Create a predicate that checks the number of matching elements.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
selector: Semantic selector string
|
|
222
|
+
min_count: Minimum number of matches required (inclusive)
|
|
223
|
+
max_count: Maximum number of matches allowed (inclusive, None = no limit)
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Predicate function that evaluates element count
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
>>> pred = element_count("role=button", min_count=1, max_count=5)
|
|
230
|
+
>>> # Will pass if 1-5 buttons found
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
234
|
+
snap = ctx.snapshot
|
|
235
|
+
if snap is None:
|
|
236
|
+
return AssertOutcome(
|
|
237
|
+
passed=False,
|
|
238
|
+
reason="no snapshot available",
|
|
239
|
+
details={"selector": selector, "min_count": min_count, "max_count": max_count},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
from .query import query
|
|
243
|
+
|
|
244
|
+
matches = query(snap, selector)
|
|
245
|
+
count = len(matches)
|
|
246
|
+
|
|
247
|
+
ok = count >= min_count
|
|
248
|
+
if max_count is not None:
|
|
249
|
+
ok = ok and count <= max_count
|
|
250
|
+
|
|
251
|
+
if ok:
|
|
252
|
+
reason = ""
|
|
253
|
+
else:
|
|
254
|
+
if max_count is not None:
|
|
255
|
+
reason = f"expected {min_count}-{max_count} elements, found {count}"
|
|
256
|
+
else:
|
|
257
|
+
reason = f"expected at least {min_count} elements, found {count}"
|
|
258
|
+
|
|
259
|
+
return AssertOutcome(
|
|
260
|
+
passed=ok,
|
|
261
|
+
reason=reason,
|
|
262
|
+
details={
|
|
263
|
+
"selector": selector,
|
|
264
|
+
"matched": count,
|
|
265
|
+
"min_count": min_count,
|
|
266
|
+
"max_count": max_count,
|
|
267
|
+
},
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return _pred
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def all_of(*predicates: Predicate) -> Predicate:
|
|
274
|
+
"""
|
|
275
|
+
Create a predicate that passes only if ALL sub-predicates pass.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
*predicates: Predicate functions to combine with AND logic
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Combined predicate
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
>>> pred = all_of(url_contains("/cart"), exists("text~'Checkout'"))
|
|
285
|
+
>>> # Will pass only if both conditions are true
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
289
|
+
failed_reasons = []
|
|
290
|
+
all_details: list[dict[str, Any]] = []
|
|
291
|
+
|
|
292
|
+
for p in predicates:
|
|
293
|
+
outcome = p(ctx)
|
|
294
|
+
all_details.append(outcome.details)
|
|
295
|
+
if not outcome.passed:
|
|
296
|
+
failed_reasons.append(outcome.reason)
|
|
297
|
+
|
|
298
|
+
ok = len(failed_reasons) == 0
|
|
299
|
+
return AssertOutcome(
|
|
300
|
+
passed=ok,
|
|
301
|
+
reason="; ".join(failed_reasons) if failed_reasons else "",
|
|
302
|
+
details={"sub_predicates": all_details, "failed_count": len(failed_reasons)},
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return _pred
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def any_of(*predicates: Predicate) -> Predicate:
|
|
309
|
+
"""
|
|
310
|
+
Create a predicate that passes if ANY sub-predicate passes.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
*predicates: Predicate functions to combine with OR logic
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Combined predicate
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
>>> pred = any_of(exists("text~'Success'"), exists("text~'Complete'"))
|
|
320
|
+
>>> # Will pass if either condition is true
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
324
|
+
all_reasons = []
|
|
325
|
+
all_details: list[dict[str, Any]] = []
|
|
326
|
+
|
|
327
|
+
for p in predicates:
|
|
328
|
+
outcome = p(ctx)
|
|
329
|
+
all_details.append(outcome.details)
|
|
330
|
+
if outcome.passed:
|
|
331
|
+
return AssertOutcome(
|
|
332
|
+
passed=True,
|
|
333
|
+
reason="",
|
|
334
|
+
details={
|
|
335
|
+
"sub_predicates": all_details,
|
|
336
|
+
"matched_at_index": len(all_details) - 1,
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
all_reasons.append(outcome.reason)
|
|
340
|
+
|
|
341
|
+
return AssertOutcome(
|
|
342
|
+
passed=False,
|
|
343
|
+
reason=f"none of {len(predicates)} predicates passed: " + "; ".join(all_reasons),
|
|
344
|
+
details={"sub_predicates": all_details},
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
return _pred
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def custom(check_fn: Callable[[AssertContext], bool], label: str = "custom") -> Predicate:
|
|
351
|
+
"""
|
|
352
|
+
Create a predicate from a custom function.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
check_fn: Function that takes AssertContext and returns bool
|
|
356
|
+
label: Label for debugging/display
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Predicate wrapping the custom function
|
|
360
|
+
|
|
361
|
+
Example:
|
|
362
|
+
>>> pred = custom(lambda ctx: ctx.snapshot and len(ctx.snapshot.elements) > 10, "has_many_elements")
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
def _pred(ctx: AssertContext) -> AssertOutcome:
|
|
366
|
+
try:
|
|
367
|
+
ok = check_fn(ctx)
|
|
368
|
+
return AssertOutcome(
|
|
369
|
+
passed=ok,
|
|
370
|
+
reason="" if ok else f"custom check '{label}' returned False",
|
|
371
|
+
details={"label": label},
|
|
372
|
+
)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
return AssertOutcome(
|
|
375
|
+
passed=False,
|
|
376
|
+
reason=f"custom check '{label}' raised exception: {e}",
|
|
377
|
+
details={"label": label, "error": str(e)},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
return _pred
|