metrics
Core metric architecture for evaLLM.
This package provides the metric contract, composition model, registration/discovery, and configuration-schema machinery used by evaLLM's evaluation pipeline.
Abstract
evallm.metrics defines the metric subsystem end-to-end: base specifies the
shared metric contract and traversal helpers, composite provides recursive
multi-metric aggregation, config handles runtime schema definition and
validation, registry provides discovery and registration, and builtin
contains reference implementations shipped with evaLLM.
Design Decisions
- Decision: Use a Composite tree for multi-metric execution.
- Motivation: evaLLM must run all configured metrics and merge their outputs for the same input record, not short-circuit through handlers.
- Decision: Enforce tree invariants with identity-based membership.
- Motivation:
CompositeMetric.add/removeoperates on node identity (is), not class equivalence, so duplicate classes are allowed while preserving strict parent ownership and cycle-free structure. - Decision: Validate metric value type at class-definition time.
- Motivation:
BaseMetric[T]capturesTon subclass creation, verifies it is serializable, and validatesevaluatesignature, failing fast for invalid metric implementations. - Decision: Keep metric configuration declarative and strict.
- Motivation:
config_schemadefines runtime parameters; validation happens inBaseMetric.__init__/from_config, andDefaultSchemaforbids parameters when none are declared. - Decision: Discover metrics through an import-time catalog.
- Motivation:
@register_metricwrites classes into aCatalogimplementation (MetricsCatalogby default), enabling pluggability without hard-coded metric lists. - Decision: Distinguish class identity from result-field identity.
- Motivation:
nameidentifies the metric class in the registry, whileresult_keynames one metric instance's output field. This allows multiple differently-configured instances of the same class.
Composite Invariants
The composite implementation enforces the following invariants:
- Tree shape: add/remove operations do not create cycles.
- Single parent: each metric instance has at most one parent at a time.
- Identity-based membership: node identity, not class equality, determines whether two metrics are "the same node" in the tree.
Extension Path for Custom Metrics
- Subclass
BaseMetric[T]with a serializable value type. - Implement
evaluate(text) -> dict[str, T]. - Optionally define
config_schema(or use@config_schema) for runtime parameters. - Optionally register with
@register_metricto make the metric discoverable.
Example
A custom metric that computes token lengths.
from evallm.metrics.base import BaseMetric
from evallm.metrics.registry import register_metric
@register_metric(name="token_lengths") # (1)!
class CustomMetric(
BaseMetric[list[int]] # (2)!
):
def evaluate(self, text: str) -> dict[str, list[int]]:
res: list[int] # (3)!
if text is None or text.strip() == "": # (4)!
res = []
else: # (5)!
res = [len(tok) for tok in text.split()]
return self.result(res) # (6)!
- or simply
@register_metricto use the metric class' name as the metric's name in the catalog. - parametrize
BaseMetricwith the desired value type (in this case,list[int]). - Ensure that the computed value's type matches the metric's declared
value_type. - Make sure to handle edge cases!
- Implement the evaluation logic
- Return the computed value in a dictionary, keyed by the metric's
result_keyusing theresult(value)helper method.
1. Parametrizing BaseMetric with a value type
When creating a custom metric, the first question you need to answer is: "What type of value do I want my metric to return?"
This could be any JSON-serializable type, such as a string, number, boolean, list, or
dictionary (see
SerializableValueType
for the full list of supported types).
The metric's value type is then specified by parametrizing the BaseMetric[T] generic
class with the desired type (i.e. binding your desired type to T).
2. Implementing the evaluate method
In order for your metric to be used in an evaluation pipeline, you need to tell evaLLM how to compute its value from an input string. At the least, this involves:
- handling edge cases (how should an empty or
Noneinput be evaluated?), - defining the core evaluation logic,
- ensuring that the computed value's type matches with the type you declared in step 1, and
- returning it in a dictionary, keyed by the metric's
result_key(theresult(value)helper does this for you).
3. Registering your metric
Finally, if you want your metric to be discoverable by evaLLM's evaluation pipelines,
decorate your metric class with the
@register_metric decorator.
By default, this will register your metric in the
MetricsCatalog under the name
of your metric class (use the name argument of the decorator to specify a custom name
).
| MODULE | DESCRIPTION |
|---|---|
base |
Abstract generic base class |
builtin |
Concrete implementations of built-in metrics |
composite |
|
config |
Configuration schema & runtime parameters for metrics |
registry |
Global registry for automatic metrics discovery |
types |
Types and serialization helper functions |