Skip to content

feat(tools): add KnowledgeIndex for progressive disclosure over reference knowledge - #396

Open
pdebjyot wants to merge 8 commits into
strands-agents:mainfrom
pdebjyot:feat/knowledge-index
Open

pdebjyot wants to merge 8 commits into
strands-agents:mainfrom
pdebjyot:feat/knowledge-index

Conversation

@pdebjyot

@pdebjyot pdebjyot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What & why

Closes #395. Extends the progressive-disclosure work from #343 to a second kind
of oversized input: reference knowledge that lives outside the trace — the
catalog of tools/skills the agent could have used, API specs, policy documents.

That corpus overflows the judge for the same reason a large trajectory does
(#342), and inlining all of it degrades accuracy even when it fits
(lost-in-the-middle, position bias). A Session also can't answer capability
questions — it only holds what a run actually invoked, so a rubric like "was
there a tool that would have satisfied this request?"
needs the full catalog,
which is a standalone knowledge source rather than a slice of the trace.

What's in the PR

1. refactor(tools) — extract shared helpers (_progressive.py).
TraceIndex's paging, windowing, and budget-bounded search move into a shared
module so the new index reuses identical behavior and the two can't drift. No
observable change to TraceIndex (overview()/get_span/search_spans now
delegate; local _window/_preview/constants removed). Existing TraceIndex
tests pass unchanged.

2. feat(tools)KnowledgeIndex. Same list / get / search shape over an
arbitrary keyed corpus ({key: text}, KnowledgeEntry iterable, or pairs), with
two modes:

from strands_evals.tools.knowledge_index import KnowledgeIndex

index = KnowledgeIndex(tool_catalog)

# Mode A — retrieve-then-inject (default; deterministic, one LLM call)
knowledge_block = index.render(keys=plan_tool_names)   # bounded <Knowledge> block
judged_output = f"{agent_answer}\n{knowledge_block}"

# Mode B — agentic discovery (reserve for open-ended lookup)
prompt_section, tools = index.for_judge()              # overview + list/get/search
  • Mode A (render) — the metric selects the trace-relevant keys and injects
    only that bounded slice. Use it whenever the keys are derivable from the trace.
  • Mode B (for_judge) — overview + list_entries/get_entry/search_entries
    tools; reserve for when the needed entry can't be predetermined.

Both honor the same max_read_chars bound and case-insensitive literal-or-regex
search (line-anchored ^/$) as TraceIndex. Composes with OutputEvaluator
(caller-controlled output), not TrajectoryEvaluator.

Show it works

Offline, on a 201-entry tool catalog (~85 KB) with the one decisive tool buried
mid-catalog:

path chars into the prompt decisive tool available?
inline full catalog 84,730 yes, but overflows / judge skims
Mode B overview 8,054 (10.5× smaller, paged) found via search_entries("escalate")
Mode A render(keys) 318 (266× smaller) yes, exact slice

tests_integ/test_knowledge_index_judge_reliability.py is a Bedrock A/B (skips
without creds): the agent declines a request claiming no capability exists;
inline vs render vs explore are scored on a grounded case (no such tool →
declining is correct) and a fabricated case (tool exists mid-catalog → declining
is wrong). Both progressive modes must separate the two.

Tests / checks

  • 23 new unit tests for KnowledgeIndex (paging, get/missing key, windowing,
    literal/regex/anchored/budget search, render slice/dedupe/bounds, input
    coercion, validation). Full suite: 1793 passed.
  • ruff check and mypy -p src clean.
  • No behavior change to TraceIndex — its tests pass as-is.

Builds on #343; independent of the #342 could-not-evaluate status work.

… 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.
…, expose system_prompt/tools, tz-safe sort, add for_judge()
…gex anchors

Addresses the adversarial review's three residual yellows on TraceIndex, plus
the pre-existing mypy failures on this branch (CI runs ruff + mypy; only ruff
was green before).

- search_spans now caps its accumulated output at max_read_chars (not
  max_matches alone), so a generous max_matches on a long trace can no longer
  return 13x the per-call budget. Emits a 'budget reached' marker distinct from
  the max_matches marker.
- Inference-span search rendered messages via model_dump(), leaking 'error': None
  into the haystack (false positive on every inference-span 'error' search) while
  a genuinely failed tool inside an inference span was unfindable. Messages are
  now rendered from their fields (role + text + tool name/args/result), and tool
  errors carry a searchable 'error:' token in both the tool and inference
  branches, matching the overview's [ERROR] vocabulary.
- Regex search compiles with re.MULTILINE so ^/$ anchor to line boundaries in
  the newline-joined haystack instead of silently never matching.
- mypy: annotate hits/lines empty-list locals and rename the regex exception
  variable so it no longer collides with the match-position unpacking.

README and search_spans docstring updated to state search is bounded too.
Pull TraceIndex's paging, windowing, and budget-bounded search into a
_progressive module so a sibling index can reuse identical behavior. No
observable change to TraceIndex: overview(), get_span, and search_spans now
delegate to paged_listing/window/search_matches, and the local _window,
_preview, and constants are removed.
…ence knowledge

Some rubrics must check the trace against knowledge that lives outside it (the
catalog of tools/skills the agent could have used, API specs, policies). That
corpus overflows the judge for the same reason a large trace does, and a Session
can't answer capability questions since it only holds what a run invoked.

KnowledgeIndex applies TraceIndex's list/get/search shape to an arbitrary keyed
corpus, on the shared _progressive core, with two modes:
- render(keys): deterministic retrieve-then-inject as a bounded <Knowledge>
  block (one LLM call) for when the relevant keys are derivable from the trace;
- for_judge(): overview + discovery tools for open-ended lookup.

Adds unit tests (paging, get/missing key, literal/regex/anchored/budget search,
render slicing/dedupe/bounds, coercion, validation) and a Bedrock A/B integ test
showing inline-vs-render-vs-explore judge reliability on an oversized catalog.
@pdebjyot
pdebjyot requested a review from a team as a code owner September 9, 2026 08:34
@github-actions github-actions Bot added enhancement New feature or request area-evaluators Evaluators: output, trajectory, tool use, interactions, and LLM-as-judge quality metrics labels Sep 9, 2026
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 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Progressive disclosure for large reference knowledge, not just traces (extend TraceIndex with a KnowledgeIndex)

1 participant