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,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tracer factory with automatic tier detection.
|
|
3
|
+
|
|
4
|
+
Provides convenient factory function for creating tracers with cloud upload support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import gzip
|
|
8
|
+
import os
|
|
9
|
+
import uuid
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
from sentience.cloud_tracing import CloudTraceSink, SentienceLogger
|
|
17
|
+
from sentience.constants import SENTIENCE_API_URL
|
|
18
|
+
from sentience.tracing import JsonlTraceSink, Tracer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_tracer(
|
|
22
|
+
api_key: str | None = None,
|
|
23
|
+
run_id: str | None = None,
|
|
24
|
+
api_url: str | None = None,
|
|
25
|
+
logger: SentienceLogger | None = None,
|
|
26
|
+
upload_trace: bool = False,
|
|
27
|
+
goal: str | None = None,
|
|
28
|
+
agent_type: str | None = None,
|
|
29
|
+
llm_model: str | None = None,
|
|
30
|
+
start_url: str | None = None,
|
|
31
|
+
screenshot_processor: Callable[[str], str] | None = None,
|
|
32
|
+
) -> Tracer:
|
|
33
|
+
"""
|
|
34
|
+
Create tracer with automatic tier detection.
|
|
35
|
+
|
|
36
|
+
Tier Detection:
|
|
37
|
+
- If api_key is provided: Try to initialize CloudTraceSink (Pro/Enterprise)
|
|
38
|
+
- If cloud init fails or no api_key: Fall back to JsonlTraceSink (Free tier)
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
api_key: Sentience API key (e.g., "sk_pro_xxxxx")
|
|
42
|
+
- Free tier: None or empty
|
|
43
|
+
- Pro/Enterprise: Valid API key
|
|
44
|
+
run_id: Unique identifier for this agent run. If not provided, generates UUID.
|
|
45
|
+
api_url: Sentience API base URL (default: https://api.sentienceapi.com)
|
|
46
|
+
logger: Optional logger instance for logging file sizes and errors
|
|
47
|
+
upload_trace: Enable cloud trace upload (default: False). When True and api_key
|
|
48
|
+
is provided, traces will be uploaded to cloud. When False, traces
|
|
49
|
+
are saved locally only.
|
|
50
|
+
goal: User's goal/objective for this trace run. This will be displayed as the
|
|
51
|
+
trace name in the frontend. Should be descriptive and action-oriented.
|
|
52
|
+
Example: "Add wireless headphones to cart on Amazon"
|
|
53
|
+
agent_type: Type of agent running (e.g., "SentienceAgent", "CustomAgent")
|
|
54
|
+
llm_model: LLM model used (e.g., "gpt-4-turbo", "claude-3-5-sonnet")
|
|
55
|
+
start_url: Starting URL of the agent run (e.g., "https://amazon.com")
|
|
56
|
+
screenshot_processor: Optional function to process screenshots before upload.
|
|
57
|
+
Takes base64 string, returns processed base64 string.
|
|
58
|
+
Useful for PII redaction or custom image processing.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Tracer configured with appropriate sink
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> # Pro tier user with goal
|
|
65
|
+
>>> tracer = create_tracer(
|
|
66
|
+
... api_key="sk_pro_xyz",
|
|
67
|
+
... run_id="demo",
|
|
68
|
+
... goal="Add headphones to cart",
|
|
69
|
+
... agent_type="SentienceAgent",
|
|
70
|
+
... llm_model="gpt-4-turbo",
|
|
71
|
+
... start_url="https://amazon.com"
|
|
72
|
+
... )
|
|
73
|
+
>>> # Returns: Tracer with CloudTraceSink
|
|
74
|
+
>>>
|
|
75
|
+
>>> # With screenshot processor for PII redaction
|
|
76
|
+
>>> def redact_pii(screenshot_base64: str) -> str:
|
|
77
|
+
... # Your custom redaction logic
|
|
78
|
+
... return redacted_screenshot
|
|
79
|
+
>>>
|
|
80
|
+
>>> tracer = create_tracer(
|
|
81
|
+
... api_key="sk_pro_xyz",
|
|
82
|
+
... screenshot_processor=redact_pii
|
|
83
|
+
... )
|
|
84
|
+
>>> # Screenshots will be processed before upload
|
|
85
|
+
>>>
|
|
86
|
+
>>> # Free tier user
|
|
87
|
+
>>> tracer = create_tracer(run_id="demo")
|
|
88
|
+
>>> # Returns: Tracer with JsonlTraceSink (local-only)
|
|
89
|
+
>>>
|
|
90
|
+
>>> # Use with agent
|
|
91
|
+
>>> agent = SentienceAgent(browser, llm, tracer=tracer)
|
|
92
|
+
>>> agent.act("Click search")
|
|
93
|
+
>>> tracer.close() # Uploads to cloud if Pro tier
|
|
94
|
+
"""
|
|
95
|
+
if run_id is None:
|
|
96
|
+
run_id = str(uuid.uuid4())
|
|
97
|
+
|
|
98
|
+
if api_url is None:
|
|
99
|
+
api_url = SENTIENCE_API_URL
|
|
100
|
+
|
|
101
|
+
# 0. Check for orphaned traces from previous crashes (if api_key provided and upload enabled)
|
|
102
|
+
if api_key and upload_trace:
|
|
103
|
+
_recover_orphaned_traces(api_key, api_url)
|
|
104
|
+
|
|
105
|
+
# 1. Try to initialize Cloud Sink (Pro/Enterprise tier) if upload enabled
|
|
106
|
+
if api_key and upload_trace:
|
|
107
|
+
try:
|
|
108
|
+
# Build metadata object for trace initialization
|
|
109
|
+
# Only include non-empty fields to avoid sending empty strings
|
|
110
|
+
metadata: dict[str, str] = {}
|
|
111
|
+
if goal and goal.strip():
|
|
112
|
+
metadata["goal"] = goal.strip()
|
|
113
|
+
if agent_type and agent_type.strip():
|
|
114
|
+
metadata["agent_type"] = agent_type.strip()
|
|
115
|
+
if llm_model and llm_model.strip():
|
|
116
|
+
metadata["llm_model"] = llm_model.strip()
|
|
117
|
+
if start_url and start_url.strip():
|
|
118
|
+
metadata["start_url"] = start_url.strip()
|
|
119
|
+
|
|
120
|
+
# Build request payload
|
|
121
|
+
payload: dict[str, Any] = {"run_id": run_id}
|
|
122
|
+
if metadata:
|
|
123
|
+
payload["metadata"] = metadata
|
|
124
|
+
|
|
125
|
+
# Request pre-signed upload URL from backend
|
|
126
|
+
response = requests.post(
|
|
127
|
+
f"{api_url}/v1/traces/init",
|
|
128
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
129
|
+
json=payload,
|
|
130
|
+
timeout=10,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if response.status_code == 200:
|
|
134
|
+
data = response.json()
|
|
135
|
+
upload_url = data.get("upload_url")
|
|
136
|
+
|
|
137
|
+
if upload_url:
|
|
138
|
+
print("☁️ [Sentience] Cloud tracing enabled (Pro tier)")
|
|
139
|
+
return Tracer(
|
|
140
|
+
run_id=run_id,
|
|
141
|
+
sink=CloudTraceSink(
|
|
142
|
+
upload_url=upload_url,
|
|
143
|
+
run_id=run_id,
|
|
144
|
+
api_key=api_key,
|
|
145
|
+
api_url=api_url,
|
|
146
|
+
logger=logger,
|
|
147
|
+
),
|
|
148
|
+
screenshot_processor=screenshot_processor,
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
print("⚠️ [Sentience] Cloud init response missing upload_url")
|
|
152
|
+
print(f" Response data: {data}")
|
|
153
|
+
print(" Falling back to local-only tracing")
|
|
154
|
+
|
|
155
|
+
elif response.status_code == 403:
|
|
156
|
+
print("⚠️ [Sentience] Cloud tracing requires Pro tier")
|
|
157
|
+
try:
|
|
158
|
+
error_data = response.json()
|
|
159
|
+
error_msg = error_data.get("error") or error_data.get("message", "")
|
|
160
|
+
if error_msg:
|
|
161
|
+
print(f" API Error: {error_msg}")
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
print(" Falling back to local-only tracing")
|
|
165
|
+
elif response.status_code == 401:
|
|
166
|
+
print("⚠️ [Sentience] Cloud init failed: HTTP 401 Unauthorized")
|
|
167
|
+
print(" API key is invalid or expired")
|
|
168
|
+
try:
|
|
169
|
+
error_data = response.json()
|
|
170
|
+
error_msg = error_data.get("error") or error_data.get("message", "")
|
|
171
|
+
if error_msg:
|
|
172
|
+
print(f" API Error: {error_msg}")
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
print(" Falling back to local-only tracing")
|
|
176
|
+
else:
|
|
177
|
+
print(f"⚠️ [Sentience] Cloud init failed: HTTP {response.status_code}")
|
|
178
|
+
try:
|
|
179
|
+
error_data = response.json()
|
|
180
|
+
error_msg = error_data.get("error") or error_data.get(
|
|
181
|
+
"message", "Unknown error"
|
|
182
|
+
)
|
|
183
|
+
print(f" Error: {error_msg}")
|
|
184
|
+
if "tier" in error_msg.lower() or "subscription" in error_msg.lower():
|
|
185
|
+
print(f" 💡 This may be a tier/subscription issue")
|
|
186
|
+
except Exception:
|
|
187
|
+
print(f" Response: {response.text[:200]}")
|
|
188
|
+
print(" Falling back to local-only tracing")
|
|
189
|
+
|
|
190
|
+
except requests.exceptions.Timeout:
|
|
191
|
+
print("⚠️ [Sentience] Cloud init timeout")
|
|
192
|
+
print(" Falling back to local-only tracing")
|
|
193
|
+
except requests.exceptions.ConnectionError:
|
|
194
|
+
print("⚠️ [Sentience] Cloud init connection error")
|
|
195
|
+
print(" Falling back to local-only tracing")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
print(f"⚠️ [Sentience] Cloud init error: {e}")
|
|
198
|
+
print(" Falling back to local-only tracing")
|
|
199
|
+
|
|
200
|
+
# 2. Fallback to Local Sink (Free tier / Offline mode)
|
|
201
|
+
traces_dir = Path("traces")
|
|
202
|
+
traces_dir.mkdir(exist_ok=True)
|
|
203
|
+
|
|
204
|
+
local_path = traces_dir / f"{run_id}.jsonl"
|
|
205
|
+
print(f"💾 [Sentience] Local tracing: {local_path}")
|
|
206
|
+
|
|
207
|
+
return Tracer(
|
|
208
|
+
run_id=run_id,
|
|
209
|
+
sink=JsonlTraceSink(str(local_path)),
|
|
210
|
+
screenshot_processor=screenshot_processor,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _recover_orphaned_traces(api_key: str, api_url: str = SENTIENCE_API_URL) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Attempt to upload orphaned traces from previous crashed runs.
|
|
217
|
+
|
|
218
|
+
Scans ~/.sentience/traces/pending/ for un-uploaded trace files and
|
|
219
|
+
attempts to upload them using the provided API key.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
api_key: Sentience API key for authentication
|
|
223
|
+
api_url: Sentience API base URL (defaults to SENTIENCE_API_URL)
|
|
224
|
+
"""
|
|
225
|
+
pending_dir = Path.home() / ".sentience" / "traces" / "pending"
|
|
226
|
+
|
|
227
|
+
if not pending_dir.exists():
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
orphaned = list(pending_dir.glob("*.jsonl"))
|
|
231
|
+
|
|
232
|
+
if not orphaned:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
# Filter out test files (run_ids that start with "test-" or are clearly test data)
|
|
236
|
+
# These are likely from local testing and shouldn't be uploaded
|
|
237
|
+
test_patterns = ["test-", "test_", "test."]
|
|
238
|
+
valid_orphaned = [
|
|
239
|
+
f
|
|
240
|
+
for f in orphaned
|
|
241
|
+
if not any(f.stem.startswith(pattern) for pattern in test_patterns)
|
|
242
|
+
and not f.stem.startswith("test")
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
if not valid_orphaned:
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
print(f"⚠️ [Sentience] Found {len(valid_orphaned)} un-uploaded trace(s) from previous runs")
|
|
249
|
+
print(" Attempting to upload now...")
|
|
250
|
+
|
|
251
|
+
for trace_file in valid_orphaned:
|
|
252
|
+
try:
|
|
253
|
+
# Extract run_id from filename (format: {run_id}.jsonl)
|
|
254
|
+
run_id = trace_file.stem
|
|
255
|
+
|
|
256
|
+
# Request new upload URL for this run_id
|
|
257
|
+
response = requests.post(
|
|
258
|
+
f"{api_url}/v1/traces/init",
|
|
259
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
260
|
+
json={"run_id": run_id},
|
|
261
|
+
timeout=10,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if response.status_code != 200:
|
|
265
|
+
# HTTP 409 means trace already exists (already uploaded)
|
|
266
|
+
# Treat as success and delete local file
|
|
267
|
+
if response.status_code == 409:
|
|
268
|
+
print(f"✅ Trace {run_id} already exists in cloud (skipping re-upload)")
|
|
269
|
+
# Delete local file since it's already in cloud
|
|
270
|
+
try:
|
|
271
|
+
os.remove(trace_file)
|
|
272
|
+
except Exception:
|
|
273
|
+
pass # Ignore cleanup errors
|
|
274
|
+
continue
|
|
275
|
+
# HTTP 422 typically means invalid run_id (e.g., test files)
|
|
276
|
+
# Skip silently for 422, but log other errors
|
|
277
|
+
if response.status_code == 422:
|
|
278
|
+
# Likely a test file or invalid run_id, skip silently
|
|
279
|
+
continue
|
|
280
|
+
print(f"❌ Failed to get upload URL for {run_id}: HTTP {response.status_code}")
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
data = response.json()
|
|
284
|
+
upload_url = data.get("upload_url")
|
|
285
|
+
|
|
286
|
+
if not upload_url:
|
|
287
|
+
print(f"❌ Upload URL missing for {run_id}")
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Read and compress trace file
|
|
291
|
+
with open(trace_file, "rb") as f:
|
|
292
|
+
trace_data = f.read()
|
|
293
|
+
|
|
294
|
+
compressed_data = gzip.compress(trace_data)
|
|
295
|
+
|
|
296
|
+
# Upload to cloud
|
|
297
|
+
upload_response = requests.put(
|
|
298
|
+
upload_url,
|
|
299
|
+
data=compressed_data,
|
|
300
|
+
headers={
|
|
301
|
+
"Content-Type": "application/x-gzip",
|
|
302
|
+
"Content-Encoding": "gzip",
|
|
303
|
+
},
|
|
304
|
+
timeout=60,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if upload_response.status_code == 200:
|
|
308
|
+
print(f"✅ Uploaded orphaned trace: {run_id}")
|
|
309
|
+
# Delete file on successful upload
|
|
310
|
+
try:
|
|
311
|
+
os.remove(trace_file)
|
|
312
|
+
except Exception:
|
|
313
|
+
pass # Ignore cleanup errors
|
|
314
|
+
else:
|
|
315
|
+
print(f"❌ Failed to upload {run_id}: HTTP {upload_response.status_code}")
|
|
316
|
+
|
|
317
|
+
except requests.exceptions.Timeout:
|
|
318
|
+
print(f"❌ Timeout uploading {trace_file.name}")
|
|
319
|
+
except requests.exceptions.ConnectionError:
|
|
320
|
+
print(f"❌ Connection error uploading {trace_file.name}")
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"❌ Error uploading {trace_file.name}: {e}")
|