Skip to main content
Merits are the core building blocks of your AI system evaluations. Like pytest discovers test_* functions, Merit discovers and runs merit_* functions - each one checking how well your AI system performs using Merit’s APIs and components.

Basic Usage

The simplest merit function is a function whose name starts with merit_. Merit automatically discovers and executes these functions.
Run all merit functions in your project:
Merit discovers all merit_* functions, executes them, and generates a report - just like pytest but for AI system evaluation.

Merit Discovery

Merit follows pytest’s discovery patterns, finding merits in files, functions, and classes that follow naming conventions.

Files

Merit discovers Python files starting with merit_:
project
merit_chatbot.py ✓ Discovered
merit_agent.py ✓ Discovered
tests
merit_rag.py ✓ Discovered
helpers.py ✗ Not discovered
src
agent.py ✗ Not discovered

Functions

Within discovered files, Merit collects functions starting with merit_:

Classes

Classes starting with Merit are discovered, and their merit_* methods become merit cases:

Dependency Injection

Merit automatically injects dependencies by matching parameter names to registered resources, SUTs, and metrics.

Async Support

Merit automatically detects and runs async functions:
Resources can be async too:
Merit handles the async execution automatically - no asyncio.run() needed.

Iterate Merits

AI systems are inherently non-deterministic, making it essential to test them across multiple scenarios and runs. Merit provides three distinct approaches to iterate the same merit definition, each optimized for different use cases: quick parametrization for a few variations, structured cases for large datasets, and repeated execution to assess consistency.

Iterate with different parameters

When you need to run the same merit with a small set of different inputs, @merit.parametrize offers the most concise syntax. It’s ideal for testing a handful of variations without the overhead of defining structured case objects.
This creates 2 merit cases:
  • merit_geography_bot(city='Boston', state='Massachusetts')
  • merit_geography_bot(city='Austin', state='Texas')
Multiple parameters can be stacked:
Parametrization works best when you have a small number of input variations (typically fewer than 10) and don’t require strict type definitions.

Iterate with different cases

When evaluating against tens or hundreds of examples, or when you need consistent typing and structure, use @merit.iter_cases with Case objects. This approach provides type safety through Pydantic validation and enables loading test cases from external sources like JSON files or databases.
min_passes on iter_cases works like repeat: by default all case executions must pass, but you can require a lower threshold when evaluating large or noisy datasets.

Iterate with grouped cases

When your cases naturally fall into groups (e.g. topics, difficulty tiers, languages), use @merit.iter_case_groups with CaseGroup objects. Each group carries its own group-level references and a min_passes threshold, giving you hierarchical reporting (run → groups → cases) and per-group pass/fail semantics.
The merit passes only if every group meets its own min_passes. Inside the merit function, group and case are injected automatically — use group.references for group-level data and case.references for case-level data.
Use CaseGroup when you need per-group thresholds or group-level metadata. If all cases are flat and share the same threshold, stick with @merit.iter_cases(*cases, min_passes=k).

Repeat with same data

AI systems can produce different outputs for identical inputs due to their non-deterministic nature. Use @merit.repeat to run the same merit multiple times with the same data, measuring consistency and reliability of your AI component.
The min_passes parameter is sometimes referred to as “pass@k” in the AI evaluation community. For example, @merit.repeat(count=10, min_passes=8) checks if your system achieves the desired behavior in at least 8 out of 10 attempts (pass@8/10).

Organizing Merits with Tags

Running only specific Merits

Use @merit.tag to organize and filter merits:
Run specific tags from CLI:

Skipping Merits unconditionally

Skip merits with @merit.tag.skip:

Skipping Merits conditionally

You can also use merit.skip() inside resources to conditionally skip merits when dependencies aren’t available. This centralizes skip logic where the resource is defined rather than in every merit that uses it.

Expected Failures

Mark merits expected to fail with @merit.tag.xfail:
Use strict=True when the merit passing would be surprising and worth investigating.

Recommendations

1. Name functions descriptively

Merit function names become merit case identifiers in reports. Use descriptive names that explain what’s being evaluated. Don’t do this:
Do this:
Descriptive names make reports self-documenting and help team members understand merit failures.

2. Use dependency injection over global imports

Merit’s dependency injection system enables better resource management and merit isolation. Inject dependencies as parameters instead of importing globally. Don’t do this:
Do this:
This pattern enables:
  • Automatic setup and teardown
  • Resource scoping and reuse
  • Merit isolation
  • Better reporting and analytics
Group related merits in classes for better organization and shared tags/setup: Don’t do this:
Do this:
Classes provide:
  • Logical grouping in reports
  • Shared tags that cascade to methods
  • Better code organization
  • Easier navigation in IDEs