Skip to content

Commit 127f610

Browse files
authored
Merge pull request #57 from SentienceAPI/refactor_phase1.3_1.4
Phase 1.3 - 1.4: updated readme, SDK manual
2 parents 68ad42e + 7a8e4cf commit 127f610

File tree

3 files changed

+284
-1
lines changed

3 files changed

+284
-1
lines changed

CHANGELOG.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Changelog
2+
3+
All notable changes to the Sentience Python SDK will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.12.0] - 2025-12-26
9+
10+
### Added
11+
12+
#### Agent Tracing & Debugging
13+
- **New Module: `sentience.tracing`** - Built-in tracing infrastructure for debugging and analyzing agent behavior
14+
- `Tracer` class for recording agent execution
15+
- `TraceSink` abstract base class for custom trace storage
16+
- `JsonlTraceSink` for saving traces to JSONL files
17+
- `TraceEvent` dataclass for structured trace events
18+
- Trace events: `step_start`, `snapshot`, `llm_query`, `action`, `step_end`, `error`
19+
- **New Module: `sentience.agent_config`** - Centralized agent configuration
20+
- `AgentConfig` dataclass with defaults for snapshot limits, LLM settings, screenshot options
21+
- **New Module: `sentience.utils`** - Snapshot digest utilities
22+
- `compute_snapshot_digests()` - Generate SHA256 fingerprints for loop detection
23+
- `canonical_snapshot_strict()` - Digest including element text
24+
- `canonical_snapshot_loose()` - Digest excluding text (layout only)
25+
- `sha256_digest()` - Hash computation helper
26+
- **New Module: `sentience.formatting`** - LLM prompt formatting
27+
- `format_snapshot_for_llm()` - Convert snapshots to LLM-friendly text format
28+
- **Schema File: `sentience/schemas/trace_v1.json`** - JSON Schema for trace events, bundled with package
29+
30+
#### Enhanced SentienceAgent
31+
- Added optional `tracer` parameter to `SentienceAgent.__init__()` for execution tracking
32+
- Added optional `config` parameter to `SentienceAgent.__init__()` for advanced configuration
33+
- Automatic tracing throughout `act()` method when tracer is provided
34+
- All tracing is **opt-in** - backward compatible with existing code
35+
36+
### Changed
37+
- Bumped version from `0.11.0` to `0.12.0`
38+
- Updated `__init__.py` to export new modules: `AgentConfig`, `Tracer`, `TraceSink`, `JsonlTraceSink`, `TraceEvent`, and utility functions
39+
- Added `MANIFEST.in` to include JSON schema files in package distribution
40+
41+
### Fixed
42+
- Fixed linting errors across multiple files:
43+
- `sentience/cli.py` - Removed unused variable `code` (F841)
44+
- `sentience/inspector.py` - Removed unused imports (F401)
45+
- `tests/test_inspector.py` - Removed unused `pytest` import (F401)
46+
- `tests/test_recorder.py` - Removed unused imports (F401)
47+
- `tests/test_smart_selector.py` - Removed unused `pytest` import (F401)
48+
- `tests/test_stealth.py` - Added `noqa` comments for intentional violations (E402, C901, F541)
49+
- `tests/test_tracing.py` - Removed unused `TraceSink` import (F401)
50+
51+
### Documentation
52+
- Updated `README.md` with comprehensive "Advanced Features" section covering tracing and utilities
53+
- Updated `docs/SDK_MANUAL.md` to v0.12.0 with new "Agent Tracing & Debugging" section
54+
- Added examples for:
55+
- Basic tracing setup
56+
- AgentConfig usage
57+
- Snapshot digests for loop detection
58+
- LLM prompt formatting
59+
- Custom trace sinks
60+
61+
### Testing
62+
- Added comprehensive test suites for new modules:
63+
- `tests/test_tracing.py` - 10 tests for tracing infrastructure
64+
- `tests/test_utils.py` - 22 tests for digest utilities
65+
- `tests/test_formatting.py` - 9 tests for LLM formatting
66+
- `tests/test_agent_config.py` - 9 tests for configuration
67+
- All 50 new tests passing ✅
68+
69+
### Migration Guide
70+
71+
#### For Existing Users
72+
No breaking changes! All new features are opt-in:
73+
74+
```python
75+
# Old code continues to work exactly the same
76+
agent = SentienceAgent(browser, llm)
77+
agent.act("Click the button")
78+
79+
# New optional features
80+
tracer = Tracer(run_id="run-123", sink=JsonlTraceSink("trace.jsonl"))
81+
config = AgentConfig(snapshot_limit=100, temperature=0.5)
82+
agent = SentienceAgent(browser, llm, tracer=tracer, config=config)
83+
agent.act("Click the button") # Now traced!
84+
```
85+
86+
#### Importing New Modules
87+
88+
```python
89+
# Tracing
90+
from sentience.tracing import Tracer, JsonlTraceSink, TraceEvent, TraceSink
91+
92+
# Configuration
93+
from sentience.agent_config import AgentConfig
94+
95+
# Utilities
96+
from sentience.utils import (
97+
compute_snapshot_digests,
98+
canonical_snapshot_strict,
99+
canonical_snapshot_loose,
100+
sha256_digest
101+
)
102+
103+
# Formatting
104+
from sentience.formatting import format_snapshot_for_llm
105+
```
106+
107+
### Notes
108+
- This release focuses on developer experience and debugging capabilities
109+
- No changes to browser automation APIs
110+
- No changes to snapshot APIs
111+
- No changes to query/action APIs
112+
- Fully backward compatible with v0.11.0
113+
114+
---
115+
116+
## [0.11.0] - Previous Release
117+
118+
(Previous changelog entries would go here)

README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,86 @@ cd sentience-chrome
456456
- Check visibility: `element.in_viewport and not element.is_occluded`
457457
- Scroll to element: `browser.page.evaluate(f"window.sentience_registry[{element.id}].scrollIntoView()")`
458458

459+
## Advanced Features (v0.12.0+)
460+
461+
### Agent Tracing & Debugging
462+
463+
The SDK now includes built-in tracing infrastructure for debugging and analyzing agent behavior:
464+
465+
```python
466+
from sentience import SentienceBrowser, SentienceAgent
467+
from sentience.llm_provider import OpenAIProvider
468+
from sentience.tracing import Tracer, JsonlTraceSink
469+
from sentience.agent_config import AgentConfig
470+
471+
# Create tracer to record agent execution
472+
tracer = Tracer(
473+
run_id="my-agent-run-123",
474+
sink=JsonlTraceSink("trace.jsonl")
475+
)
476+
477+
# Configure agent behavior
478+
config = AgentConfig(
479+
snapshot_limit=50,
480+
temperature=0.0,
481+
max_retries=1,
482+
capture_screenshots=True
483+
)
484+
485+
browser = SentienceBrowser()
486+
llm = OpenAIProvider(api_key="your-key", model="gpt-4o")
487+
488+
# Pass tracer and config to agent
489+
agent = SentienceAgent(browser, llm, tracer=tracer, config=config)
490+
491+
with browser:
492+
browser.page.goto("https://example.com")
493+
494+
# All actions are automatically traced
495+
agent.act("Click the sign in button")
496+
agent.act("Type 'user@example.com' into email field")
497+
498+
# Trace events saved to trace.jsonl
499+
# Events: step_start, snapshot, llm_query, action, step_end, error
500+
```
501+
502+
**Trace Events Captured:**
503+
- `step_start` - Agent begins executing a goal
504+
- `snapshot` - Page state captured
505+
- `llm_query` - LLM decision made (includes tokens, model, response)
506+
- `action` - Action executed (click, type, press)
507+
- `step_end` - Step completed successfully
508+
- `error` - Error occurred during execution
509+
510+
**Use Cases:**
511+
- Debug why agent failed or got stuck
512+
- Analyze token usage and costs
513+
- Replay agent sessions
514+
- Train custom models from successful runs
515+
- Monitor production agents
516+
517+
### Snapshot Utilities
518+
519+
New utility functions for working with snapshots:
520+
521+
```python
522+
from sentience import snapshot
523+
from sentience.utils import compute_snapshot_digests, canonical_snapshot_strict
524+
from sentience.formatting import format_snapshot_for_llm
525+
526+
snap = snapshot(browser)
527+
528+
# Compute snapshot fingerprints (detect page changes)
529+
digests = compute_snapshot_digests(snap.elements)
530+
print(f"Strict digest: {digests['strict']}") # Changes when text changes
531+
print(f"Loose digest: {digests['loose']}") # Only changes when layout changes
532+
533+
# Format snapshot for LLM prompts
534+
llm_context = format_snapshot_for_llm(snap, limit=50)
535+
print(llm_context)
536+
# Output: [1] <button> "Sign In" {PRIMARY,CLICKABLE} @ (100,50) (Imp:10)
537+
```
538+
459539
## Documentation
460540

461541
- **📖 [Amazon Shopping Guide](../docs/AMAZON_SHOPPING_GUIDE.md)** - Complete tutorial with real-world example

sentience/agent.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import re
77
import time
8-
from typing import Any, Dict, List, Optional, Union
8+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
99

1010
from .actions import click, press, type_text
1111
from .base_agent import BaseAgent
@@ -23,6 +23,10 @@
2323
)
2424
from .snapshot import snapshot
2525

26+
if TYPE_CHECKING:
27+
from .agent_config import AgentConfig
28+
from .tracing import Tracer
29+
2630

2731
class SentienceAgent(BaseAgent):
2832
"""
@@ -54,6 +58,8 @@ def __init__(
5458
llm: LLMProvider,
5559
default_snapshot_limit: int = 50,
5660
verbose: bool = True,
61+
tracer: Optional["Tracer"] = None,
62+
config: Optional["AgentConfig"] = None,
5763
):
5864
"""
5965
Initialize Sentience Agent
@@ -63,11 +69,15 @@ def __init__(
6369
llm: LLM provider (OpenAIProvider, AnthropicProvider, etc.)
6470
default_snapshot_limit: Default maximum elements to include in context (default: 50)
6571
verbose: Print execution logs (default: True)
72+
tracer: Optional Tracer instance for execution tracking (default: None)
73+
config: Optional AgentConfig for advanced configuration (default: None)
6674
"""
6775
self.browser = browser
6876
self.llm = llm
6977
self.default_snapshot_limit = default_snapshot_limit
7078
self.verbose = verbose
79+
self.tracer = tracer
80+
self.config = config
7181

7282
# Execution history
7383
self.history: list[dict[str, Any]] = []
@@ -80,6 +90,9 @@ def __init__(
8090
"by_action": [],
8191
}
8292

93+
# Step counter for tracing
94+
self._step_count = 0
95+
8396
def act(
8497
self, goal: str, max_retries: int = 2, snapshot_options: SnapshotOptions | None = None
8598
) -> AgentActionResult:
@@ -107,6 +120,21 @@ def act(
107120
print(f"🤖 Agent Goal: {goal}")
108121
print(f"{'='*70}")
109122

123+
# Generate step ID for tracing
124+
self._step_count += 1
125+
step_id = f"step-{self._step_count}"
126+
127+
# Emit step_start trace event if tracer is enabled
128+
if self.tracer:
129+
pre_url = self.browser.page.url if self.browser.page else None
130+
self.tracer.emit_step_start(
131+
step_id=step_id,
132+
step_index=self._step_count,
133+
goal=goal,
134+
attempt=0,
135+
pre_url=pre_url,
136+
)
137+
110138
for attempt in range(max_retries + 1):
111139
try:
112140
# 1. OBSERVE: Get refined semantic snapshot
@@ -135,6 +163,18 @@ def act(
135163
if snap.status != "success":
136164
raise RuntimeError(f"Snapshot failed: {snap.error}")
137165

166+
# Emit snapshot trace event if tracer is enabled
167+
if self.tracer:
168+
self.tracer.emit(
169+
"snapshot",
170+
{
171+
"url": snap.url,
172+
"element_count": len(snap.elements),
173+
"timestamp": snap.timestamp,
174+
},
175+
step_id=step_id,
176+
)
177+
138178
# Apply element filtering based on goal
139179
filtered_elements = self.filter_elements(snap, goal)
140180

@@ -156,6 +196,19 @@ def act(
156196
# 3. THINK: Query LLM for next action
157197
llm_response = self._query_llm(context, goal)
158198

199+
# Emit LLM query trace event if tracer is enabled
200+
if self.tracer:
201+
self.tracer.emit(
202+
"llm_query",
203+
{
204+
"prompt_tokens": llm_response.prompt_tokens,
205+
"completion_tokens": llm_response.completion_tokens,
206+
"model": llm_response.model,
207+
"response": llm_response.content[:200], # Truncate for brevity
208+
},
209+
step_id=step_id,
210+
)
211+
159212
if self.verbose:
160213
print(f"🧠 LLM Decision: {llm_response.content}")
161214

@@ -186,6 +239,22 @@ def act(
186239
message=result_dict.get("message"),
187240
)
188241

242+
# Emit action execution trace event if tracer is enabled
243+
if self.tracer:
244+
post_url = self.browser.page.url if self.browser.page else None
245+
self.tracer.emit(
246+
"action",
247+
{
248+
"action": result.action,
249+
"element_id": result.element_id,
250+
"success": result.success,
251+
"outcome": result.outcome,
252+
"duration_ms": duration_ms,
253+
"post_url": post_url,
254+
},
255+
step_id=step_id,
256+
)
257+
189258
# 5. RECORD: Track history
190259
self.history.append(
191260
{
@@ -202,9 +271,25 @@ def act(
202271
status = "✅" if result.success else "❌"
203272
print(f"{status} Completed in {duration_ms}ms")
204273

274+
# Emit step completion trace event if tracer is enabled
275+
if self.tracer:
276+
self.tracer.emit(
277+
"step_end",
278+
{
279+
"success": result.success,
280+
"duration_ms": duration_ms,
281+
"action": result.action,
282+
},
283+
step_id=step_id,
284+
)
285+
205286
return result
206287

207288
except Exception as e:
289+
# Emit error trace event if tracer is enabled
290+
if self.tracer:
291+
self.tracer.emit_error(step_id=step_id, error=str(e), attempt=attempt)
292+
208293
if attempt < max_retries:
209294
if self.verbose:
210295
print(f"⚠️ Retry {attempt + 1}/{max_retries}: {e}")

0 commit comments

Comments
 (0)