feat: add evaluator metadata method and types - #361
Conversation
Add a metadata() instance method to the Evaluator base class that lets evaluators declare what they check, how they work, and their tier in the evaluation hierarchy. New types in strands_evals.types: - EvaluatorMetadata TypedDict (checks, method, threshold, tier, description) - MethodInfo TypedDict (category, summary) - MethodCategory Literal type (7 categories) - Tier Literal type (guardrail, quality, diagnostic) - validate_metadata() function for runtime validation The base class returns None by default so existing evaluators are not broken. Built-in evaluators that declare metadata: - Contains, Equals, StartsWith (deterministic_string) - ToolCalled, StateEquals (deterministic_extraction) - FaithfulnessEvaluator, HarmfulnessEvaluator (llm_judge_output, guardrail) - CorrectnessEvaluator (llm_judge_output, quality) - ToolSelectionAccuracyEvaluator, ToolParameterAccuracyEvaluator (llm_judge_trajectory) - GoalSuccessRateEvaluator (llm_judge_trajectory)
| - diagnostic: Surfaced in reports but does not gate pass/fail. | ||
| """ | ||
|
|
||
| VALID_METHOD_CATEGORIES: set[str] = { |
There was a problem hiding this comment.
Issue: VALID_METHOD_CATEGORIES (and VALID_TIERS below) duplicate the exact string values already declared in the MethodCategory and Tier Literals. Any future addition has to be made in two places, and they can silently drift.
Suggestion: Derive the runtime sets from the Literals so there's a single source of truth:
from typing_extensions import get_args
VALID_METHOD_CATEGORIES: set[str] = set(get_args(MethodCategory))
VALID_TIERS: set[str] = set(get_args(Tier))The test_valid_method_categories_match_literal / test_valid_tiers_match_literal tests catch drift but only after it happens — single-sourcing prevents it entirely.
There was a problem hiding this comment.
Resolved in 74ae442 — VALID_METHOD_CATEGORIES and VALID_TIERS are now derived via set(get_args(MethodCategory)) / set(get_args(Tier)), single-sourcing them from the Literals. 👍
| assert meta is not None | ||
| assert "Case-insensitive" in meta["method"]["summary"] | ||
|
|
||
| def test_equals_metadata(self): |
There was a problem hiding this comment.
Issue: The per-evaluator metadata tests assert individual fields one at a time (meta["checks"], meta["method"]["category"], meta["tier"], ...). Since each evaluator's metadata is fully deterministic (or fully determined by constructor args), per-field assertions silently miss regressions in fields that aren't checked — e.g. a changed method.summary, a wrong threshold string, or an unexpectedly added key.
Suggestion: Assert the whole dict in a single equality check, e.g.:
assert meta == {
"checks": "Whether actual_output exactly equals an expected value",
"method": {"category": "deterministic_string", "summary": "Exact equality comparison between actual_output and expected value."},
"threshold": "exact match",
"tier": "quality",
}This applies to Equals, Contains, StartsWith, ToolCalled, StateEquals, and the LLM-judge tests. For the case-sensitivity variants, you can still build the expected dict from case_sensitive and compare the full shape. Keep validate_metadata(meta, ...) as an additional check.
There was a problem hiding this comment.
Resolved in 74ae442 — the per-evaluator tests now assert the full metadata dict in a single equality check (including the case_sensitive variants) while keeping validate_metadata(meta, ...) as an additional check, exactly as suggested. Thanks!
| REQUIRED_METADATA_KEYS: set[str] = {"checks", "method", "threshold"} | ||
|
|
||
|
|
||
| def validate_metadata(metadata: EvaluatorMetadata, evaluator_name: str) -> None: |
There was a problem hiding this comment.
Issue: validate_metadata is typed to accept EvaluatorMetadata, but the documented producer — Evaluator.metadata() — returns EvaluatorMetadata | None. If a caller passes the result of an evaluator that hasn't declared metadata (returns None), this raises an opaque AttributeError on metadata.keys() rather than a clear message.
Suggestion: Either guard for None early with an explicit error (e.g. "Evaluator '<name>' did not declare metadata"), or document that callers must null-check before calling. A small guard keeps the failure mode consistent with the other clear ValueErrors in this function.
There was a problem hiding this comment.
Thanks for addressing this — the current code now types the parameter as EvaluatorMetadata | None and returns early when None, with the docstring updated to match. Resolves the concern. 👍
|
Issue: Suggestion: This is a reasonable incremental first step and doesn't need to block the PR. Please open a tracking issue for adding metadata to the remaining evaluators so the intent (and which evaluators are guardrail vs quality vs diagnostic) is captured. Non-blocking. |
|
Assessment: Comment (approve after minor changes) Clean, well-scoped addition. The type definitions are clear, Review themes
Nice work keeping the metadata declarations consistent with real evaluator behavior — that's the part most likely to rot, and it's accurate here. |
|
Issue (scope / acceptance criteria):
...and frames the Suggestion: This is fine as an incremental first PR, but please make the scope explicit so reviewers align: either (a) wire |
Metadata is now validated at the start of run_evaluations for all evaluators that declare it. Evaluators returning None from metadata() are silently skipped. Adds three tests covering rejection, acceptance, and the None-skip path.
|
Assessment: Approve ✅ Re-reviewed after Resolution status
Verified locally: One non-blocking follow-up still stands: metadata is on 11 of ~25 evaluators, and tier-based aggregation / report rendering remain future work — worth a tracking issue. Nice, responsive iteration on this. 🚀 |
Summary
Adds a
metadata()instance method to theEvaluatorbase class, allowing evaluators to declare what they check, how they work, and their tier in the evaluation hierarchy.This enables downstream systems to:
Usage
What's new
src/strands_evals/types/evaluator_metadata.py- New module withEvaluatorMetadata,MethodInfo,MethodCategory,Tiertypes andvalidate_metadata()functionmetadata()method on baseEvaluatorclass (returnsNoneby default)Contains,Equals,StartsWith,ToolCalled,StateEqualsFaithfulnessEvaluator,HarmfulnessEvaluator,CorrectnessEvaluator,ToolSelectionAccuracyEvaluator,ToolParameterAccuracyEvaluator,GoalSuccessRateEvaluatorWhat's tested
validate_metadata()with all valid/invalid input combinationsRelated to #350