Skip to content

feat(evaluators): allow custom tools on judge-based evaluators (Trajectory, Output, Multimodal) - #324

Merged
poshinchen merged 2 commits into
strands-agents:mainfrom
pdebjyot:feat/evaluator-custom-tools
Jul 28, 2026
Merged

feat(evaluators): allow custom tools on judge-based evaluators (Trajectory, Output, Multimodal)#324
poshinchen merged 2 commits into
strands-agents:mainfrom
pdebjyot:feat/evaluator-custom-tools

Conversation

@pdebjyot

@pdebjyot pdebjyot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes #322

Description

Adds an optional tools= constructor parameter so the judge agent can call domain-specific verification functions during evaluation. Follows the same opt-in pattern accepted for ToolSimulator in #208 / #209.

Covered evaluators: TrajectoryEvaluator, OutputEvaluator, MultimodalOutputEvaluator, and its specialized subclasses (MultimodalCorrectnessEvaluator, MultimodalFaithfulnessEvaluator, MultimodalInstructionFollowingEvaluator, MultimodalOverallQualityEvaluator).

  • TrajectoryEvaluator: user tools are appended after the default trajectory scoring tools (exact/in-order/any-order match), which are always retained.
  • OutputEvaluator: tools defaults to None, preserving the current no-tools judge (Agent accepts tools=None).
  • Multimodal evaluators: MultimodalOutputEvaluator accepts tools and forwards it to OutputEvaluator; the four specialized subclasses accept and forward it likewise, so tool-augmented judging works for multimodal rubrics too.

Non-breaking: tools is appended after name in every signature, so existing positional and keyword callers are unaffected; omitting it keeps current behavior exactly.

Example

from strands import tool
from strands_evals.evaluators import TrajectoryEvaluator

@tool
def verify_ticket_id(ticket_id: str) -> str:
    """Check whether a ticket ID matches the support system's format."""
    return "valid" if ticket_id.startswith("TKT-") else "invalid format"

evaluator = TrajectoryEvaluator(
    rubric="Verify the agent referenced only valid ticket IDs.",
    tools=[verify_ticket_id],
)

Testing

  • New unit tests: constructor defaults preserved, merged tools list asserted explicitly (trajectory), tools passed through to the judge agent (output, sync + async), and tools=None default verified.
  • Full evaluator + experiment unit suites pass; ruff check and ruff format clean.

Checklist

  • Non-breaking change
  • Unit tests added
  • Conventional commit
  • Lint/format clean

…tEvaluator

Adds an optional tools= constructor parameter so the judge agent can call
domain-specific verification functions during evaluation.

- TrajectoryEvaluator: user tools are merged ahead of the default
  trajectory scoring tools (exact/in-order/any-order match), which are
  always retained.
- OutputEvaluator: tools default to None, preserving the current
  no-tools judge. When provided, they are passed to the evaluator Agent
  in both sync and async paths (and inherited by MultimodalOutputEvaluator
  subclasses via _create_evaluator_agent).

Non-breaking: omitting tools= keeps existing behavior exactly.
@pdebjyot
pdebjyot requested a review from a team as a code owner July 23, 2026 16:04
@pdebjyot
pdebjyot requested a review from chaynabors July 23, 2026 16:04
@pdebjyot
pdebjyot temporarily deployed to manual-approval July 23, 2026 16:06 — with GitHub Actions Inactive
@github-actions github-actions Bot added area-evaluators Evaluators: output, trajectory, tool use, interactions, and LLM-as-judge quality metrics enhancement New feature or request strands-running labels Jul 23, 2026
Comment thread tests/strands_evals/evaluators/test_trajectory_evaluator.py Outdated
Comment thread src/strands_evals/evaluators/output_evaluator.py Outdated
@github-actions

Copy link
Copy Markdown

Assessment: Comment

Clean, well-tested, opt-in addition that follows the established ToolSimulator pattern — tests pass locally (36) and ruff is clean. One correctness concern about parameter ordering worth addressing before merge; the rest are minor.

Review Categories
  • API / Compatibility: New tools param is inserted before the existing name param in both evaluators, which shifts name's positional index and can silently break positional callers — this makes the change technically breaking despite the "Non-breaking" note. Appending it after name or making trailing params keyword-only resolves it.
  • Consistency: OutputEvaluator extracted a _create_evaluator_agent() helper, but TrajectoryEvaluator still duplicates the Agent(...) construction across sync/async paths.
  • Testing: Good coverage of the new behavior; one redundant/circular assertion could be tightened to an explicit expected list.
  • Scope: The multimodal subclasses inherit the helper but don't expose/forward tools, so the description slightly overstates coverage there.

Nice use of the existing opt-in convention and thorough backward-compatibility tests.

Comment thread src/strands_evals/evaluators/output_evaluator.py Outdated
Comment thread src/strands_evals/evaluators/output_evaluator.py Outdated
@poshinchen

Copy link
Copy Markdown
Contributor

In the description:

via a new _create_evaluator_agent() helper (inherited by MultimodalOutputEvaluator subclasses).

This is incorrect.

Comment thread src/strands_evals/evaluators/trajectory_evaluator.py Outdated
- Move tools after name in both signatures so existing positional
  callers are unaffected
- OutputEvaluator: use public self.tools and pass tools=self.tools
  directly to Agent (Agent accepts None); drop the
  _create_evaluator_agent helper
- TrajectoryEvaluator: build the merged list with defaults first and
  custom tools spread after
- Forward tools through MultimodalOutputEvaluator and its specialized
  subclasses so the param is available there too
- Tests: assert the exact expected merged tools list instead of the
  circular evaluator._tools reference; drop the redundant membership
  assertion
@github-actions

Copy link
Copy Markdown

Assessment: Approve (pending a doc tidy-up)

Re-reviewed at 36a4cee. All feedback from the previous round is addressed cleanly:

  • tools now appended after name in OutputEvaluator, TrajectoryEvaluator, and MultimodalOutputEvaluator — no positional-ordering break.
  • ✅ Dropped the _create_evaluator_agent() helper in favor of passing tools=self.tools directly (relying on Agent(tools=None) being valid) — consistent across sync/async.
  • MultimodalOutputEvaluator now accepts and forwards tools.
  • ✅ Test tightened to assert the explicit merged tools list instead of the redundant/circular membership check.

Verified locally: 52 evaluator tests pass and ruff check is clean.

Two minor, non-blocking follow-ups:

  1. Stale PR description: it still says user tools are "merged ahead of the default trajectory scoring tools" and references the now-removed _create_evaluator_agent() helper. The code now appends user tools after the defaults ([*defaults, *(tools or [])]), so the description should be updated to match.
  2. Attribute naming consistency (optional): OutputEvaluator exposes self.tools (public) while TrajectoryEvaluator keeps self._tools (private). Reasonable given _tools also holds the internal default scorers, but worth a quick thought on whether the two should align.

Nice, responsive iteration — this is in good shape.

@poshinchen

Copy link
Copy Markdown
Contributor

I see that you've updated other evaluators too, could you update the description / title?

@pdebjyot pdebjyot changed the title feat(evaluators): allow custom tools on TrajectoryEvaluator and OutputEvaluator feat(evaluators): allow custom tools on judge-based evaluators (Trajectory, Output, Multimodal) Jul 27, 2026

@pdebjyot pdebjyot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the title and description to cover all touched evaluators — the multimodal family (MultimodalOutputEvaluator and its four specialized subclasses) now accepts and forwards tools as well, which their to_dict/from_dict round-trip tests require once the attribute is public. Thanks for the catch.

@poshinchen
poshinchen merged commit c5461d0 into strands-agents:main Jul 28, 2026
23 checks passed
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] Allow custom tools on TrajectoryEvaluator and OutputEvaluator

2 participants