Skip to content

Commit 17e8b40

Browse files
telemetry + debug logging (#569)
* telemetry * empty catch block for now * prettier * stagehand.metrics instead of stagehand.getMetrics() * add inferenceLogUtils file * collect tokens from anthropic * changeset * single updateMetrics function * rm import
1 parent 96fd035 commit 17e8b40

12 files changed

+804
-157
lines changed

Diff for: .changeset/fifty-crabs-arrive.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
you can now call stagehand.metrics to get token usage metrics. you can also set logInferenceToFile in stagehand config to log the entire call/response history from stagehand & the LLM.

Diff for: lib/StagehandPage.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class StagehandPage {
8989

9090
if (this.llmClient) {
9191
this.actHandler = new StagehandActHandler({
92+
stagehand: this.stagehand,
9293
verbose: this.stagehand.verbose,
9394
llmProvider: this.stagehand.llmProvider,
9495
enableCaching: this.stagehand.enableCaching,

Diff for: lib/handlers/actHandler.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,22 @@ import {
1616
ObserveResult,
1717
ActOptions,
1818
ObserveOptions,
19+
StagehandFunctionName,
1920
} from "@/types/stagehand";
2021
import { MethodHandlerContext, SupportedPlaywrightAction } from "@/types/act";
2122
import { buildActObservePrompt } from "../prompt";
2223
import {
2324
methodHandlerMap,
2425
fallbackLocatorMethod,
2526
} from "./handlerUtils/actHandlerUtils";
26-
27+
import { Stagehand } from "@/lib";
2728
/**
2829
* NOTE: Vision support has been removed from this version of Stagehand.
2930
* If useVision or verifierUseVision is set to true, a warning is logged and
3031
* the flow continues as if vision = false.
3132
*/
3233
export class StagehandActHandler {
34+
private readonly stagehand: Stagehand;
3335
private readonly stagehandPage: StagehandPage;
3436
private readonly verbose: 0 | 1 | 2;
3537
private readonly llmProvider: LLMProvider;
@@ -44,6 +46,7 @@ export class StagehandActHandler {
4446
private readonly waitForCaptchaSolves: boolean;
4547

4648
constructor({
49+
stagehand,
4750
verbose,
4851
llmProvider,
4952
enableCaching,
@@ -53,6 +56,7 @@ export class StagehandActHandler {
5356
selfHeal,
5457
waitForCaptchaSolves,
5558
}: {
59+
stagehand: Stagehand;
5660
verbose: 0 | 1 | 2;
5761
llmProvider: LLMProvider;
5862
enableCaching: boolean;
@@ -64,6 +68,7 @@ export class StagehandActHandler {
6468
selfHeal: boolean;
6569
waitForCaptchaSolves: boolean;
6670
}) {
71+
this.stagehand = stagehand;
6772
this.verbose = verbose;
6873
this.llmProvider = llmProvider;
6974
this.enableCaching = enableCaching;
@@ -337,15 +342,17 @@ export class StagehandActHandler {
337342
});
338343

339344
// Always use text-based DOM verification (no vision).
340-
actionCompleted = await verifyActCompletion({
345+
const verifyResult = await verifyActCompletion({
341346
goal: action,
342347
steps,
343348
llmProvider: this.llmProvider,
344349
llmClient: verifyLLmClient,
345350
domElements,
346351
logger: this.logger,
347352
requestId,
353+
logInferenceToFile: this.stagehand.logInferenceToFile,
348354
});
355+
actionCompleted = verifyResult.completed;
349356

350357
this.logger({
351358
category: "action",
@@ -362,6 +369,12 @@ export class StagehandActHandler {
362369
},
363370
},
364371
});
372+
this.stagehand.updateMetrics(
373+
StagehandFunctionName.ACT,
374+
verifyResult.prompt_tokens,
375+
verifyResult.completion_tokens,
376+
verifyResult.inference_time_ms,
377+
);
365378
}
366379

367380
return actionCompleted;
@@ -681,6 +694,15 @@ export class StagehandActHandler {
681694
requestId,
682695
variables,
683696
userProvidedInstructions: this.userProvidedInstructions,
697+
onActMetrics: (promptTokens, completionTokens, inferenceTimeMs) => {
698+
this.stagehand.updateMetrics(
699+
StagehandFunctionName.ACT,
700+
promptTokens,
701+
completionTokens,
702+
inferenceTimeMs,
703+
);
704+
},
705+
logInferenceToFile: this.stagehand.logInferenceToFile,
684706
});
685707

686708
this.logger({

Diff for: lib/handlers/extractHandler.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { extract } from "../inference";
55
import { LLMClient } from "../llm/LLMClient";
66
import { formatText } from "../utils";
77
import { StagehandPage } from "../StagehandPage";
8-
import { Stagehand } from "../index";
8+
import { Stagehand, StagehandFunctionName } from "../index";
99
import { pageTextSchema } from "../../types/page";
1010

1111
const PROXIMITY_THRESHOLD = 15;
@@ -353,13 +353,24 @@ export class StagehandExtractHandler {
353353
requestId,
354354
userProvidedInstructions: this.userProvidedInstructions,
355355
logger: this.logger,
356+
logInferenceToFile: this.stagehand.logInferenceToFile,
356357
});
357358

358359
const {
359360
metadata: { completed },
361+
prompt_tokens: promptTokens,
362+
completion_tokens: completionTokens,
363+
inference_time_ms: inferenceTimeMs,
360364
...output
361365
} = extractionResponse;
362366

367+
this.stagehand.updateMetrics(
368+
StagehandFunctionName.EXTRACT,
369+
promptTokens,
370+
completionTokens,
371+
inferenceTimeMs,
372+
);
373+
363374
// **11:** Handle the extraction response and log the results
364375
this.logger({
365376
category: "extraction",
@@ -481,13 +492,24 @@ export class StagehandExtractHandler {
481492
isUsingTextExtract: false,
482493
userProvidedInstructions: this.userProvidedInstructions,
483494
logger: this.logger,
495+
logInferenceToFile: this.stagehand.logInferenceToFile,
484496
});
485497

486498
const {
487499
metadata: { completed },
500+
prompt_tokens: promptTokens,
501+
completion_tokens: completionTokens,
502+
inference_time_ms: inferenceTimeMs,
488503
...output
489504
} = extractionResponse;
490505

506+
this.stagehand.updateMetrics(
507+
StagehandFunctionName.EXTRACT,
508+
promptTokens,
509+
completionTokens,
510+
inferenceTimeMs,
511+
);
512+
491513
this.logger({
492514
category: "extraction",
493515
message: "received extraction response",

Diff for: lib/handlers/observeHandler.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { LogLine } from "../../types/log";
2-
import { Stagehand } from "../index";
2+
import { Stagehand, StagehandFunctionName } from "../index";
33
import { observe } from "../inference";
44
import { LLMClient } from "../llm/LLMClient";
55
import { StagehandPage } from "../StagehandPage";
@@ -113,8 +113,22 @@ export class StagehandObserveHandler {
113113
logger: this.logger,
114114
isUsingAccessibilityTree: useAccessibilityTree,
115115
returnAction,
116+
logInferenceToFile: this.stagehand.logInferenceToFile,
116117
});
117118

119+
const {
120+
prompt_tokens = 0,
121+
completion_tokens = 0,
122+
inference_time_ms = 0,
123+
} = observationResponse;
124+
125+
this.stagehand.updateMetrics(
126+
StagehandFunctionName.OBSERVE,
127+
prompt_tokens,
128+
completion_tokens,
129+
inference_time_ms,
130+
);
131+
118132
//Add iframes to the observation response if there are any on the page
119133
if (iframes.length > 0) {
120134
iframes.forEach((iframe) => {

Diff for: lib/index.ts

+62-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
ObserveOptions,
2727
ObserveResult,
2828
AgentConfig,
29+
StagehandMetrics,
30+
StagehandFunctionName,
2931
} from "../types/stagehand";
3032
import { StagehandContext } from "./StagehandContext";
3133
import { StagehandPage } from "./StagehandPage";
@@ -382,7 +384,7 @@ export class Stagehand {
382384
public readonly selfHeal: boolean;
383385
private cleanupCalled = false;
384386
public readonly actTimeoutMs: number;
385-
387+
public readonly logInferenceToFile?: boolean;
386388
protected setActivePage(page: StagehandPage): void {
387389
this.stagehandPage = page;
388390
}
@@ -396,6 +398,63 @@ export class Stagehand {
396398
return this.stagehandPage.page;
397399
}
398400

401+
public stagehandMetrics: StagehandMetrics = {
402+
actPromptTokens: 0,
403+
actCompletionTokens: 0,
404+
actInferenceTimeMs: 0,
405+
extractPromptTokens: 0,
406+
extractCompletionTokens: 0,
407+
extractInferenceTimeMs: 0,
408+
observePromptTokens: 0,
409+
observeCompletionTokens: 0,
410+
observeInferenceTimeMs: 0,
411+
totalPromptTokens: 0,
412+
totalCompletionTokens: 0,
413+
totalInferenceTimeMs: 0,
414+
};
415+
416+
public get metrics(): StagehandMetrics {
417+
return this.stagehandMetrics;
418+
}
419+
420+
public updateMetrics(
421+
functionName: StagehandFunctionName,
422+
promptTokens: number,
423+
completionTokens: number,
424+
inferenceTimeMs: number,
425+
): void {
426+
switch (functionName) {
427+
case StagehandFunctionName.ACT:
428+
this.stagehandMetrics.actPromptTokens += promptTokens;
429+
this.stagehandMetrics.actCompletionTokens += completionTokens;
430+
this.stagehandMetrics.actInferenceTimeMs += inferenceTimeMs;
431+
break;
432+
433+
case StagehandFunctionName.EXTRACT:
434+
this.stagehandMetrics.extractPromptTokens += promptTokens;
435+
this.stagehandMetrics.extractCompletionTokens += completionTokens;
436+
this.stagehandMetrics.extractInferenceTimeMs += inferenceTimeMs;
437+
break;
438+
439+
case StagehandFunctionName.OBSERVE:
440+
this.stagehandMetrics.observePromptTokens += promptTokens;
441+
this.stagehandMetrics.observeCompletionTokens += completionTokens;
442+
this.stagehandMetrics.observeInferenceTimeMs += inferenceTimeMs;
443+
break;
444+
}
445+
this.updateTotalMetrics(promptTokens, completionTokens, inferenceTimeMs);
446+
}
447+
448+
private updateTotalMetrics(
449+
promptTokens: number,
450+
completionTokens: number,
451+
inferenceTimeMs: number,
452+
): void {
453+
this.stagehandMetrics.totalPromptTokens += promptTokens;
454+
this.stagehandMetrics.totalCompletionTokens += completionTokens;
455+
this.stagehandMetrics.totalInferenceTimeMs += inferenceTimeMs;
456+
}
457+
399458
constructor(
400459
{
401460
env,
@@ -419,6 +478,7 @@ export class Stagehand {
419478
selfHeal = true,
420479
waitForCaptchaSolves = false,
421480
actTimeoutMs = 60_000,
481+
logInferenceToFile = false,
422482
}: ConstructorParams = {
423483
env: "BROWSERBASE",
424484
},
@@ -473,6 +533,7 @@ export class Stagehand {
473533
if (this.usingAPI) {
474534
this.registerSignalHandlers();
475535
}
536+
this.logInferenceToFile = logInferenceToFile;
476537
}
477538

478539
private registerSignalHandlers() {

0 commit comments

Comments
 (0)