Skip to content

Commit 935bcae

Browse files
author
SentienceDEV
committed
Merge pull request #53 from SentienceAPI/tracer_support
phase 1 and 2 for Tracing
2 parents 4ce13c9 + 3de0ea0 commit 935bcae

19 files changed

+2068
-7
lines changed

README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,136 @@ await browser.close();
123123

124124
---
125125

126+
## Agent Execution Tracing (NEW in v0.3.1)
127+
128+
Record complete agent execution traces for debugging, analysis, and replay. Traces capture every step, snapshot, LLM decision, and action in a structured JSONL format.
129+
130+
### Quick Start: Agent with Tracing
131+
132+
```typescript
133+
import {
134+
SentienceBrowser,
135+
SentienceAgent,
136+
OpenAIProvider,
137+
Tracer,
138+
JsonlTraceSink
139+
} from 'sentience-ts';
140+
import { randomUUID } from 'crypto';
141+
142+
const browser = await SentienceBrowser.create({ apiKey: process.env.SENTIENCE_API_KEY });
143+
const llm = new OpenAIProvider(process.env.OPENAI_API_KEY!, 'gpt-4o');
144+
145+
// Create a tracer
146+
const runId = randomUUID();
147+
const sink = new JsonlTraceSink(`traces/${runId}.jsonl`);
148+
const tracer = new Tracer(runId, sink);
149+
150+
// Create agent with tracer
151+
const agent = new SentienceAgent(browser, llm, 50, true, tracer);
152+
153+
// Emit run_start
154+
tracer.emitRunStart('SentienceAgent', 'gpt-4o');
155+
156+
try {
157+
await browser.getPage().goto('https://google.com');
158+
159+
// Every action is automatically traced!
160+
await agent.act('Click the search box');
161+
await agent.act("Type 'sentience ai' into the search field");
162+
await agent.act('Press Enter');
163+
164+
tracer.emitRunEnd(3);
165+
} finally {
166+
// Flush trace to disk
167+
await agent.closeTracer();
168+
await browser.close();
169+
}
170+
171+
console.log(`✅ Trace saved to: traces/${runId}.jsonl`);
172+
```
173+
174+
### What Gets Traced
175+
176+
Each agent action generates multiple events:
177+
178+
1. **step_start** - Before action execution (goal, URL, attempt)
179+
2. **snapshot** - Page state with all interactive elements
180+
3. **llm_response** - LLM decision (model, tokens, response)
181+
4. **action** - Executed action (type, element ID, success)
182+
5. **error** - Any failures (error message, retry attempt)
183+
184+
**Example trace output:**
185+
```jsonl
186+
{"v":1,"type":"run_start","ts":"2025-12-26T10:00:00.000Z","run_id":"abc-123","seq":1,"data":{"agent":"SentienceAgent","llm_model":"gpt-4o"}}
187+
{"v":1,"type":"step_start","ts":"2025-12-26T10:00:01.000Z","run_id":"abc-123","seq":2,"step_id":"step-1","data":{"step_index":1,"goal":"Click the search box","attempt":0,"url":"https://google.com"}}
188+
{"v":1,"type":"snapshot","ts":"2025-12-26T10:00:01.500Z","run_id":"abc-123","seq":3,"step_id":"step-1","data":{"url":"https://google.com","elements":[...]}}
189+
{"v":1,"type":"llm_response","ts":"2025-12-26T10:00:02.000Z","run_id":"abc-123","seq":4,"step_id":"step-1","data":{"model":"gpt-4o","prompt_tokens":250,"completion_tokens":10,"response_text":"CLICK(42)"}}
190+
{"v":1,"type":"action","ts":"2025-12-26T10:00:02.500Z","run_id":"abc-123","seq":5,"step_id":"step-1","data":{"action_type":"click","element_id":42,"success":true}}
191+
{"v":1,"type":"run_end","ts":"2025-12-26T10:00:03.000Z","run_id":"abc-123","seq":6,"data":{"steps":1}}
192+
```
193+
194+
### Reading and Analyzing Traces
195+
196+
```typescript
197+
import * as fs from 'fs';
198+
199+
// Read trace file
200+
const content = fs.readFileSync(`traces/${runId}.jsonl`, 'utf-8');
201+
const events = content.trim().split('\n').map(JSON.parse);
202+
203+
console.log(`Total events: ${events.length}`);
204+
205+
// Analyze events
206+
events.forEach(event => {
207+
console.log(`[${event.seq}] ${event.type} - ${event.ts}`);
208+
});
209+
210+
// Filter by type
211+
const actions = events.filter(e => e.type === 'action');
212+
console.log(`Actions taken: ${actions.length}`);
213+
214+
// Get token usage
215+
const llmEvents = events.filter(e => e.type === 'llm_response');
216+
const totalTokens = llmEvents.reduce((sum, e) => sum + (e.data.prompt_tokens || 0) + (e.data.completion_tokens || 0), 0);
217+
console.log(`Total tokens: ${totalTokens}`);
218+
```
219+
220+
### Tracing Without Agent (Manual)
221+
222+
You can also use the tracer directly for custom workflows:
223+
224+
```typescript
225+
import { Tracer, JsonlTraceSink } from 'sentience-ts';
226+
import { randomUUID } from 'crypto';
227+
228+
const runId = randomUUID();
229+
const sink = new JsonlTraceSink(`traces/${runId}.jsonl`);
230+
const tracer = new Tracer(runId, sink);
231+
232+
// Emit custom events
233+
tracer.emit('custom_event', {
234+
message: 'Something happened',
235+
details: { foo: 'bar' }
236+
});
237+
238+
// Use convenience methods
239+
tracer.emitRunStart('MyAgent', 'gpt-4o');
240+
tracer.emitStepStart('step-1', 1, 'Do something');
241+
tracer.emitError('step-1', 'Something went wrong');
242+
tracer.emitRunEnd(1);
243+
244+
// Flush to disk
245+
await tracer.close();
246+
```
247+
248+
### Schema Compatibility
249+
250+
Traces are **100% compatible** with Python SDK traces - use the same tools to analyze traces from both TypeScript and Python agents!
251+
252+
**See full example:** [examples/agent-with-tracing.ts](examples/agent-with-tracing.ts)
253+
254+
---
255+
126256
## Agent Layer Examples
127257

128258
### Google Search (6 lines of code)

examples/agent-with-tracing.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* Agent with Tracing Example
3+
*
4+
* Demonstrates how to record agent execution traces for debugging and analysis
5+
*
6+
* Usage:
7+
* ts-node examples/agent-with-tracing.ts
8+
* or
9+
* npm run example:tracing
10+
*/
11+
12+
import { SentienceBrowser } from '../src/browser';
13+
import { SentienceAgent } from '../src/agent';
14+
import { OpenAIProvider } from '../src/llm-provider';
15+
import { Tracer } from '../src/tracing/tracer';
16+
import { JsonlTraceSink } from '../src/tracing/jsonl-sink';
17+
import { randomUUID } from 'crypto';
18+
import * as fs from 'fs';
19+
import * as path from 'path';
20+
21+
async function main() {
22+
// Get API keys from environment
23+
const sentienceKey = process.env.SENTIENCE_API_KEY;
24+
const openaiKey = process.env.OPENAI_API_KEY;
25+
26+
if (!sentienceKey || !openaiKey) {
27+
console.error('Error: Missing API keys');
28+
console.error('Please set SENTIENCE_API_KEY and OPENAI_API_KEY environment variables');
29+
process.exit(1);
30+
}
31+
32+
console.log('🚀 Starting Agent with Tracing Demo\n');
33+
34+
// Create browser and LLM
35+
const browser = await SentienceBrowser.create({ apiKey: sentienceKey });
36+
const llm = new OpenAIProvider(openaiKey, 'gpt-4o-mini');
37+
38+
// Create traces directory
39+
const tracesDir = path.join(__dirname, '..', 'traces');
40+
if (!fs.existsSync(tracesDir)) {
41+
fs.mkdirSync(tracesDir, { recursive: true });
42+
}
43+
44+
// Create tracer
45+
const runId = randomUUID();
46+
const traceFile = path.join(tracesDir, `${runId}.jsonl`);
47+
const sink = new JsonlTraceSink(traceFile);
48+
const tracer = new Tracer(runId, sink);
49+
50+
console.log(`📝 Trace file: ${traceFile}`);
51+
console.log(`🆔 Run ID: ${runId}\n`);
52+
53+
// Create agent with tracer
54+
const agent = new SentienceAgent(browser, llm, 50, true, tracer);
55+
56+
// Emit run_start event
57+
tracer.emitRunStart('SentienceAgent', 'gpt-4o-mini', {
58+
example: 'agent-with-tracing',
59+
timestamp: new Date().toISOString(),
60+
});
61+
62+
try {
63+
// Navigate to Google
64+
console.log('🌐 Navigating to Google...\n');
65+
const page = browser.getPage();
66+
await page.goto('https://www.google.com');
67+
await page.waitForLoadState('networkidle');
68+
await new Promise(resolve => setTimeout(resolve, 1000));
69+
70+
// Execute agent actions (automatically traced!)
71+
console.log('🤖 Executing agent actions...\n');
72+
73+
await agent.act('Click the search box');
74+
await agent.act("Type 'artificial intelligence' into the search field");
75+
await agent.act('Press Enter key');
76+
77+
// Wait for results
78+
await new Promise(resolve => setTimeout(resolve, 2000));
79+
80+
// Emit run_end event
81+
tracer.emitRunEnd(3);
82+
83+
console.log('\n✅ Agent execution completed successfully!\n');
84+
85+
// Display token usage
86+
const stats = agent.getTokenStats();
87+
console.log('📊 Token Usage:');
88+
console.log(` Total Prompt Tokens: ${stats.totalPromptTokens}`);
89+
console.log(` Total Completion Tokens: ${stats.totalCompletionTokens}`);
90+
console.log(` Total Tokens: ${stats.totalTokens}\n`);
91+
92+
} catch (error: any) {
93+
console.error('❌ Error during execution:', error.message);
94+
tracer.emitError('main', error.message, 0);
95+
} finally {
96+
// Flush trace to disk
97+
console.log('💾 Flushing trace to disk...');
98+
await agent.closeTracer();
99+
await browser.close();
100+
}
101+
102+
// Read and analyze the trace
103+
console.log('\n📖 Trace Analysis:\n');
104+
105+
const content = fs.readFileSync(traceFile, 'utf-8');
106+
const events = content.trim().split('\n').map(line => JSON.parse(line));
107+
108+
console.log(` Total events: ${events.length}`);
109+
110+
// Count by type
111+
const eventTypes = events.reduce((acc: Record<string, number>, e) => {
112+
acc[e.type] = (acc[e.type] || 0) + 1;
113+
return acc;
114+
}, {});
115+
116+
console.log(' Event breakdown:');
117+
Object.entries(eventTypes).forEach(([type, count]) => {
118+
console.log(` - ${type}: ${count}`);
119+
});
120+
121+
// Show event sequence
122+
console.log('\n Event sequence:');
123+
events.forEach((event, i) => {
124+
const stepInfo = event.step_id ? ` [step: ${event.step_id.substring(0, 8)}]` : '';
125+
console.log(` [${event.seq}] ${event.type}${stepInfo} - ${event.ts}`);
126+
});
127+
128+
// Calculate total tokens from trace
129+
const llmEvents = events.filter(e => e.type === 'llm_response');
130+
const totalTokensFromTrace = llmEvents.reduce(
131+
(sum, e) => sum + (e.data.prompt_tokens || 0) + (e.data.completion_tokens || 0),
132+
0
133+
);
134+
135+
console.log(`\n Total tokens (from trace): ${totalTokensFromTrace}`);
136+
137+
console.log(`\n✨ Trace saved to: ${traceFile}`);
138+
console.log(' You can analyze this file with any JSONL parser!\n');
139+
}
140+
141+
main().catch(error => {
142+
console.error('Fatal error:', error);
143+
process.exit(1);
144+
});

0 commit comments

Comments
 (0)