[STG-2515] feat(evals): vercel_ai_sdk bare-loop harness adapter#2338
Open
shrey150 wants to merge 5 commits into
Open
[STG-2515] feat(evals): vercel_ai_sdk bare-loop harness adapter#2338shrey150 wants to merge 5 commits into
shrey150 wants to merge 5 commits into
Conversation
|
225e69a to
1fabf2b
Compare
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Confidence score: 5/5
- Safe to merge after the addressed issues were fixed.
Architecture diagram
sequenceDiagram
participant CLI as eval CLI
participant Registry as Harness Registry
participant Runner as VercelAiSdkRunner
participant AISDK as AI SDK (ai pkg)
participant Stagehand as @stagehand/getAISDKLanguageModel
participant Adapter as ExternalHarnessToolAdapter
participant Browse as browse CLI (temp bin)
participant Recorder as BareLoopToolRecorder
participant Verifier as Verifier (gradeExternalTrajectory)
Note over CLI,Verifier: NEW: vercel_ai_sdk bare-loop harness flow
CLI->>CLI: Parse --harness vercel_ai_sdk
CLI->>Registry: getBenchHarness("vercel_ai_sdk")
Registry-->>CLI: vercelAiSdkHarness adapter
CLI->>Adapter: prepareExternalHarnessAdapter(plan, model)
Adapter->>Adapter: Build browse CLI path, env, system prompt
Adapter-->>CLI: PreparedExternalHarnessAdapter
CLI->>Runner: runVercelAiSdkAgent(input)
Note over Runner: Resolve model provider
Runner->>Runner: Check model includes "/"
alt Missing provider prefix
Runner->>Runner: Throw EvalsError("provider-prefixed model required")
end
Runner->>Stagehand: getAISDKLanguageModel(provider, modelName)
Stagehand-->>Runner: LanguageModel instance
Note over Runner: Read step cap config
Runner->>Runner: readBareLoopMaxSteps(MAX_STEPS_ENV)
Note right of Runner: Default 40, override with EVAL_VERCEL_AI_SDK_MAX_STEPS
Note over Runner: Build tool recorder
Runner->>Recorder: createBareLoopToolRecorder(adapter, logger)
Recorder-->>Runner: recorder instance
Note over Runner: Execute generateText loop
Runner->>AISDK: import { generateText, tool, stepCountIs }
Runner->>AISDK: generateText(options)
Note right of Runner: model, system prompt, user prompt, stopWhen, tools
AISDK->>AISDK: Run multi-step loop (stepCountIs(N) max)
loop Per step (up to N)
alt Model calls browse tool
AISDK->>Runner: Execute tools.browse.execute({ args })
Runner->>Recorder: recorder.execute(args)
Recorder->>Browse: browse <args>
Browse-->>Recorder: stdout result
Recorder->>Recorder: Record normalized tool call
Recorder-->>AISDK: tool output string
else Model produces final text
AISDK->>AISDK: finishReason = "stop"
end
end
Note over AISDK: stopWhen cuts loop mid-tool-call
AISDK-->>Runner: { text, steps, totalUsage, finishReason }
Note over Runner: Detect step cap truncation
alt stepsUsed >= maxSteps AND finishReason === "tool-calls"
Runner->>Runner: cappedOut = true (step cap reached)
else
Runner->>Runner: Normal completion or clean stop
end
alt Loop threw error
Runner->>Runner: capture error, log warn
Runner->>Runner: status = "error", stopReason = error string
end
Note over Runner: Finalize result
Runner->>Runner: finalizeBareLoopResult(...)
Runner->>Recorder: recorder.calls (normalized tool calls)
Runner->>Verifier: gradeExternalTrajectory(toolCalls)
Verifier-->>Runner: verifier scores
Runner-->>CLI: TaskResult with metrics
Note right of Runner: vercelAiSdkStatus, vercelAiSdkStopReason, usage tokens
CLI->>CLI: Write trajectory + scores
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
baf8b91 to
1fe1fb5
Compare
shrey150
added a commit
that referenced
this pull request
Jul 14, 2026
…otals, and propagate tool-call success through BareLoopToolRecorder
finalizeBareLoopResult collapsed TrajectoryStatus ("complete" | "aborted" |
"stalled" | "error") down to just completed/error, so a capped or aborted
run reported Status: "error" alongside a contradictory StopReason like
"step cap reached" -- indistinguishable from a genuine provider failure.
Preserve the real status instead.
Added an optional providerTotalTokens override so the `_total_tokens`
metric can use a provider's real reported total (e.g. reasoning tokens can
make it differ from input+output) instead of always recomputing -- left
unset here; the per-SDK runners (#2338/#2339/#2340) will pass a real value.
BareLoopToolRecorder.execute() returned a bare string, discarding the `ok`
flag from runBareBrowseCommand at the return-value level (it was still
recorded internally, just not surfaced to callers). Changed it to return
{ ok, output } so per-SDK runners can propagate a real tool-error signal
into their own tool-result protocol instead of the model only learning
about a failure by reading error text. Also threads skillMode through to
runBareBrowseCommand via the adapter's own skillMode field (no new
parameter needed at call sites).
75d9810 to
3bfbf31
Compare
Registers --harness vercel_ai_sdk: a bare loop via the ai package (generateText + stopWhen: stepCountIs(N)) with a single gated browse tool. The AI SDK's own multi-step loop IS the harness — zero additional scaffolding, mirroring the JS-default bare loop found in the wild (and the Modal sandbox template used in the 2026-07-09 smoke). Model resolution goes through stagehand's getAISDKLanguageModel provider map, so -m provider/model means the same thing as on the stagehand harness. Step cap defaults to the shared 40 (EVAL_VERCEL_AI_SDK_MAX_STEPS override). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…runner generateText returns normally whether stopWhen: stepCountIs(maxSteps) cut the loop off or the model actually finished -- finishReason was declared but never read, so a cap-truncated run and a clean stop were indistinguishable. Read finishReason: "tool-calls" at the step cap means the model still wanted another tool call when the cap cut it off; report the same `step cap reached (N)` stopReason shape anthropic_sdk already uses so every bare-loop harness is comparable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drops the dated-event rationale from the module docblock in favor of a present-tense description of the loop shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… totalTokens
- status now becomes "aborted" (not "complete") when the loop hits the step
cap while the model still wanted to call a tool -- matches the real
TrajectoryStatus contract bareLoopRunner now preserves.
- pass the AI SDK's totalUsage.totalTokens through as providerTotalTokens so
the total-tokens metric doesn't undercount when it differs from input+output.
- update the tool execute callback for BareLoopToolRecorder.execute()'s new
{ ok, output } return shape.
1fe1fb5 to
aaed51e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Part 2 of 5 — stacked on #2337 (base:
shrey/evals-bareloop-harnesses; GitHub shows only this PR's delta). When #2337 merges, retarget this PR's base tomainand rebase.Registers
--harness vercel_ai_sdk: a bare loop via theaipackage —generateText+stopWhen: stepCountIs(N)with a single gatedbrowsetool. The AI SDK's own multi-step loop IS the harness; zero additional scaffolding. This mirrors the JS-default bare loop found in the wild (15.1M npm dl/wk) and the Modal sandbox template used in the 2026-07-09 smoke that surfaced 5 SKILL.md issues.getAISDKLanguageModelprovider map, so-m provider/modelmeans the same thing as on the stagehand harness.EVAL_VERCEL_AI_SDK_MAX_STEPSoverride.NormalizedToolCalls →gradeExternalTrajectoryunchanged.generateTextFninjection seam — unit tests drive the loop and invoke the real tool wiring against a tempbrowsebinary.External harnesses design + usage:
packages/evals/README.md#external-harnesses(in #2337). Linear: STG-2515.Comparability fix (2026-07-09 audit)
generateTextreturns normally whetherstopWhen: stepCountIs(maxSteps)cut the loop off mid tool-call or the model actually finished —finishReasonwas declared on the result type but never read, so a cap-truncated run and a clean stop were indistinguishable invercelAiSdkStatus/vercelAiSdkStopReason. Now readsfinishReason === "tool-calls"atstepsUsed >= maxStepsas the step-cap signal and reports the samestep cap reached (N)stopReason shapeanthropic_sdk(#2339) already uses, so every bare-loop harness surfaces the cap identically. Two new tests cover both the capped case and a clean stop that happens to land exactly at the max step count (to prove the fix keys offfinishReason, not just the step count).E2E Test Matrix
Live smoke ran on the pre-split combined branch (content identical to this stack's tip — verified by diff); results transplanted, not rerun.
evals run b:webtailbench --harness vercel_ai_sdk -m anthropic/claude-haiku-4-5-20251001 -l 1 -t 1 -c 1(local build, real ANTHROPIC + GEMINI keys, local Chrome via browse CLI)browsetool calls recorded (first action:open https://www.google.com); trajectory persisted; verifier graded:outcomeSuccess=false,processScore=0.79, 5 rubric criteria, no verifierError. Usage: 440k in / 1.3k out tokenspnpm exec vitest run(this branch)Test Files 50 passed, Tests 371 passed— addsvercelAiSdkRunner.test.ts(system prompt = verbatim bare one-liner, tool wiring against a real temp binary, step/token metrics, error-to-TaskResult folding) + the registry testtsc --noEmit+ prettier + eslintpnpm exec vitest run(this branch, post-fix)Test Files 50 passed, Tests 373 passed— adds the two step-cap tests (capped-with-tool-calls-finishReason vs. clean-stop-at-max-steps)pnpm -w exec tsc --noEmit+ prettier + eslint (post-fix)The 440k cumulative input tokens in the smoke is what motivated the 20k-char tool-output clip that ships in the base PR (
EVAL_BARE_LOOP_TOOL_OUTPUT_LIMIT) — the smoke ran pre-clip and still completed, but largersnapshotoutputs would have overflowed context.Tier 1 fixes (post-review audit, 2026-07-14)
An independent audit (on top of the 07-09 review round) found the step-cap status still reported
"complete"and the token total still ignored the AI SDK's owntotalUsage.totalTokens. Fixed here:"complete", not"aborted".cappedOutwas already detected correctly but never fed intostatus-- nowcappedOut ? "aborted" : "complete".total_tokensrecomputed input+output, ignoring the SDK's own total.VercelAiSdkGenerateTextResult.totalUsagenow declarestotalTokens, passed through via the base PR's newproviderTotalTokensoverride instead of being silently recomputed.executecallback forBareLoopToolRecorder.execute()'s new{ ok, output }return shape (base PR).Verified:
pnpm --filter @browserbasehq/stagehand-evals build && lint && test:unit--Test Files 55 passed, Tests 420 passed(2 new tests for the totalTokens preference/fallback). Live: a real Browserbase local trial withEVAL_VERCEL_AI_SDK_MAX_STEPS=2hit the step cap and the persisted trajectory (task_data.json) recorded"status": "aborted", not"complete".No changeset:
packages/evalsis private.🤖 Generated with Claude Code
Summary by cubic
Adds a
vercel_ai_sdkbare-loop harness that runsai.generateTextwithstopWhen: stepCountIs(N)and a single gatedbrowsetool. Registers it as an executable bench harness; aligns model mapping with@browserbasehq/stagehandand reports step-cap truncation consistently with anabortedstatus and providertotalTokenspassthrough (STG-2515).New Features
--harness vercel_ai_sdkas executable (agent/suite).generateTextwithstopWhen: stepCountIs(N); default max steps 40 viaEVAL_VERCEL_AI_SDK_MAX_STEPS.@browserbasehq/stagehandgetAISDKLanguageModel; requiresprovider/model.browsetool; records tool calls; returns usage metrics (input/output, optionalcached_input/reasoning, and providertotalTokens).generateTextFnfor tests.Bug Fixes
finishReason === "tool-calls"at the cap; sets status toabortedand stopReasonstep cap reached (N), matching other bare-loop harnesses.Written for commit aaed51e. Summary will update on new commits.