Skip to content

feat(tools): TraceIndex — progressive trace disclosure for judge-based evaluators - #343

Open
pdebjyot wants to merge 4 commits into
strands-agents:mainfrom
pdebjyot:feat/trace-index
Open

feat(tools): TraceIndex — progressive trace disclosure for judge-based evaluators#343
pdebjyot wants to merge 4 commits into
strands-agents:mainfrom
pdebjyot:feat/trace-index

Conversation

@pdebjyot

@pdebjyot pdebjyot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

feat(tools): TraceIndex — progressive trace disclosure for judge-based evaluators

Branch: feat/trace-index
Builds on: #324 (custom tools= on OutputEvaluator / TrajectoryEvaluator)
Addresses: #342

Problem

When the trajectory handed to a judge-based evaluator is larger than the judge
model's context window, the judge call raises ContextWindowOverflowException.
Experiment._run_evaluator catches it under error isolation and records the case
as score: 0, test_pass: False — indistinguishable from a genuine quality
failure. A correct agent response gets a false-negative failing score purely
because its trace was too big for the judge to read.

This isn't a theoretical edge. We observe it on real production agent traces,
where routine multi-step sessions serialize past a 200K-token judge window and
the largest run into the millions of tokens — so under a default Sonnet-class
judge, correct agents are being silently scored as failures today. The problem
is stack-agnostic: we reproduced identical overflow behavior across
strands-evals, DeepEval, and a Langfuse-style managed judge on a shared Bedrock
judge model (three distinct error signatures, same trace-size cliff).

The measured evidence below is from a synthetic, deterministic, offline
benchmark — no real data — so it's independently reproducible.

What this PR adds

TraceIndex — an in-memory index over a Session that lets a judge read a large
trace without inlining it. It is an established pattern: MLflow's
Agent-as-a-Judge trace scorers hand the judge ListSpans / GetSpan /
SearchTraceRegex, and Zhuge et al.'s Agent-as-a-Judge
(arXiv:2410.10934) uses retrieve/read/
locate modules to pull only the relevant segments.

from strands_evals.evaluators import OutputEvaluator
from strands_evals.tools.trace_index import TraceIndex

index = TraceIndex(session)  # session: a Session from any provider/mapper

evaluator = OutputEvaluator(
    rubric="Every factual claim must be supported by tool-result evidence in the trace.",
    tools=index.tools,  # list_spans, get_span, search_spans
)

# Compact overview in the prompt instead of the full trajectory:
output = f"{agent_answer}\n\n<TraceOverview>\n{index.overview()}\n</TraceOverview>"
evaluator.evaluate(EvaluationData(input="(from trace)", actual_output=output))
  • overview() — one compact line per span (index, type, tool name, sizes,
    preview). Always fits the judge context; substituted for the full trajectory.
  • list_spans / get_span / search_spans — discovery tools the judge
    calls on demand (mirrors the MLflow triad). get_span pages oversized spans
    via max_read_chars (default 8000) + offset, so no single tool return can
    itself overflow the judge.
  • Backend-agnostic — consumes any Session a provider/mapper produces.

Two disclosure strategies compose from these pieces: index (substitute
overview() into the prompt — portable, works with any judge) and explore
(index + discovery tools via #324's tools= — Strands-native, best accuracy).

Evidence

Cross-framework matrix (1344 cells). 200-trace labeled corpus × 3 frameworks
(strands-evals, DeepEval, Langfuse-style) × 4 metrics (groundedness, accuracy,
trajectory, tool_use) × inline/index/explore, all bound to one shared Bedrock
judge. Each metric scored as a binary classifier against planted ground truth
(overflow ⇒ wrong prediction):

  • The overflow cliff is stack-agnostic and lands at the same trace size for
    all three frameworks.
  • index and explore hold accuracy on traces that fit and recover it on
    traces that overflow
    (inline is unusable in the overflow bucket; index/explore
    score correctly).
  • The judge-side explore tools beat a bare index when the decisive fact is
    buried in a large tool result
    : on wrong_tool traces the deciding refund
    amount sits inside a large search result that overview() elides, so the index
    judge false-fails groundedness (0.83); get_span/search_spans let the judge
    retrieve it → groundedness 1.00.
  • No degradation on traces that fit: index/explore ≈ inline everywhere inline
    still works.

Ground-truth A/B (grounded / fabricated claims, evidence buried mid-trace):
the index-equipped judge separates grounded from fabricated where the inline
judge overflows — captured as the integ test test_judge_reliability_inline_vs_explore.

Known limitation (called out honestly)

TrajectoryEvaluator inlines actual_trajectory unconditionally
(case_prompt_template.py:51), so the trajectory metric still overflows on large
traces even with the index — the substitution only reaches evaluators that route
through the caller-controlled actual_output (the output-family metrics, fixed
today). Making the trajectory metric disclosure-aware needs a template change and
is proposed as a follow-up; this PR does not change that path.

Separately, this PR adds the capability but does not change
_run_evaluator's error handling — distinguishing harness overflow from a
quality score: 0 is tracked in #342 as an independent change.

Testing

  • 34 unit tests (overview formatting; list/get/search behavior; offset paging on
    oversized spans; cross-pattern; end-to-end through OutputEvaluator with
    tools=index.tools) — all pass.
  • 1 integ A/B test (skips without live Bedrock credentials).
  • ruff check / ruff format clean.

Checklist

… agents

Large agent trajectories overflow a judge model's context window when inlined
into the evaluation prompt, forcing Experiment error-isolation to record the
case as score:0 / test_pass:False — a false failure for a correct agent.

TraceIndex builds an in-memory index over a Session and exposes:
  - overview(): one compact line per span (index, type, tool, sizes, preview)
    that always fits the judge context, substituted for the full trajectory.
  - three discovery tools the judge calls on demand: list_spans / get_span /
    search_spans (mirrors MLflow's ListSpans/GetSpan/SearchTraceRegex).

get_span pages oversized spans via max_read_chars + offset so no single tool
return can itself overflow the judge. Backend-agnostic: consumes any Session
produced by a provider/mapper.
… TraceIndex

Covers overview() formatting, list_spans/get_span/search_spans behavior,
offset paging on oversized spans, and end-to-end use through OutputEvaluator
with tools=index.tools.
Compares OutputEvaluator judging grounded vs fabricated claims two ways —
full trajectory inlined vs overview + discovery tools — asserting the
index-equipped judge separates grounded from fabricated where inline overflows.
Skips without live Bedrock credentials.
@pdebjyot
pdebjyot requested a review from a team as a code owner August 3, 2026 18:42
@pdebjyot
pdebjyot requested a review from mehtarac August 3, 2026 18:42
@github-actions github-actions Bot added area-evaluators Evaluators: output, trajectory, tool use, interactions, and LLM-as-judge quality metrics area-tracing Trace/session ingestion: providers, session mappers, extractors, telemetry/OTEL enhancement New feature or request labels Aug 3, 2026
@mehtarac

Copy link
Copy Markdown
Member

/strands review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-evaluators Evaluators: output, trajectory, tool use, interactions, and LLM-as-judge quality metrics area-tracing Trace/session ingestion: providers, session mappers, extractors, telemetry/OTEL enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants