QA framework for evaluating LLM outputs based on user-defined metrics
Project description
evaLLM
evaLLM is a lightweight QA framework for evaluating LLM outputs with composable, schema-validated metrics.
It is designed for two workflows:
- batch evaluation through a CLI
- embeddable evaluation pipelines through a Python API
Quick links
- Documentation: https://psandhaas.github.io/evaLLM/
- Repository: https://github.com/psandhaas/evaLLM
Installation
Install from PyPI
pip install evallm-qa
Install optional extras
Use this if you want encoding-based metrics (for example count_encodings):
pip install "evallm-qa[hf-tokenizers]"
Install from source (secondary option)
Use this if you want to work from a local checkout:
git clone https://github.com/psandhaas/evaLLM.git
cd evaLLM
pip install .
Source install with extras:
pip install .[hf-tokenizers]
What evaLLM evaluates
Current built-in metrics include:
CountsMetric: character, token, or model-encoding countsJsonFormatMetric: field-level JSON format validation against a JSON Schema
Results are emitted per input record as JSON objects, which makes them easy to pipe, persist, and analyze downstream.
Library structure
evaLLM follows a layered architecture to keep concerns separated:
evallm.application- orchestration layer
- DTOs (
EvaluationRequest,MetricSpec, dataset specs) - request resolution and evaluator execution
evallm.metrics- base metric interface (
BaseMetric[T]) - built-in metrics and registration catalog
- metric composition (
CompositeMetric)
- base metric interface (
evallm.readers- dataset access layer (currently JSONL reader)
evallm.cli- Typer-based command-line interface
Further details on architectural choices can be found in the documentation.
Composite metrics
evaLLM intentionally models multi-metric evaluation using the Composite design pattern.
Composite is a good fit because each metric is an independent computation over the same input text, and the framework must aggregate all metric outputs into one result object. This creates a tree-shaped execution model:
- a parent metric container evaluates children
- each child contributes a namespaced result
- multiple instances of the same metric class can coexist with different configs
This gives you two concrete benefits:
- as a user you can rely on the fact that calling
evalute(text)does exactly that; your input is evaluated against however many metrics, with whichever configuration you chose, and - as a developer you only need to implement
evaluate(text), without having to consider how or when your metric is called.
Core concepts
Evaluation request
An evaluation is defined by:
- one dataset spec (
dataset) - one or more metric specs (
metrics)
Metric spec
Each metric spec supports:
name: registered metric identifierconfig(optional): runtime metric configresult_key(optional): output field name override
Dataset support
Current support:
- JSONL datasets (
JsonlDatasetSpec)
Each JSONL record must contain a text field and can optionally provide a stable record id field.
Extend BaseReader and BaseDatasetSpec for further formats.
Python API examples
1) Build and run an evaluation with helper presets
from evallm.application import (
JsonlEvaluationBuilder,
count_characters,
count_tokens,
json_format,
)
builder = JsonlEvaluationBuilder(
"./data/dataset.jsonl",
text_key="text",
record_id_key="id",
)
builder.use(count_characters(result_key="chars"))
builder.use(count_tokens(segmentation="wordpunct", result_key="tokens_wordpunct"))
builder.use(
json_format(
{
"type": "object",
"properties": {
"answer": {"type": "string"},
"confidence": {"type": "number", "minimum": 0.0, "maximum": 1.0},
},
"required": ["answer"],
},
result_key="format_ok",
)
)
for result in builder.run():
print(result.model_dump())
2) Build a request explicitly
from evallm.application import EvaluationRequest
request = EvaluationRequest.create(
obj={
"dataset": {
"input_file": "./data/dataset.jsonl",
"text_key": "text",
"record_id_key": "id",
},
"metrics": [
{"name": "CountsMetric", "result_key": "chars"},
{
"name": "CountsMetric",
"config": {"segments": "tokens", "segmentation": "whitespace"},
"result_key": "tokens",
},
],
}
)
CLI examples
Evaluate via flags
evallm run \
--input-file ./data/dataset.jsonl \
--text-key text \
--record-id-key id \
--metric CountsMetric
Evaluate via config file
evallm run --config ./run.yaml
Discover available metrics and presets
evallm metrics list
evallm metrics info CountsMetric --format yaml
evallm metrics presets
Write JSONL results to a file
evallm run \
-f ./data/dataset.jsonl \
-t text \
-i id \
-m CountsMetric \
--output ./results.jsonl
Config file reference
evallm run expects two top-level sections:
datasetmetrics
Minimal YAML example:
dataset:
input_file: ./data/dataset.jsonl
text_key: text
record_id_key: id
metrics:
- name: CountsMetric
result_key: chars
- name: CountsMetric
config:
segments: tokens
segmentation: whitespace
result_key: tokens
- name: JsonFormatMetric
config:
expected_schema:
type: object
properties:
answer:
type: string
confidence:
type: number
minimum: 0.0
maximum: 1.0
required:
- answer
check_formats: true
result_key: structured_output
Important merge rule:
- if
--configand inline flags are both provided, inline dataset flags override config fields - if any
--metricflags are provided, they replaceconfig.metrics
Extending evaLLM with custom metrics
Implement BaseMetric[T] and register with @register_metric.
from evallm.metrics.base import BaseMetric
from evallm.metrics.registry import register_metric
@register_metric(name="token_lengths")
class TokenLengthsMetric(BaseMetric[list[int]]):
def evaluate(self, text: str) -> dict[str, list[int]]:
if text.strip() == "":
return self.result([])
return self.result([len(tok) for tok in text.split()])
Development
Run tests:
uv run pytest
Without uv:
python -m pytest
Run docs locally:
uv run mkdocs serve
Without uv:
mkdocs serve
If you want to use uv, installation instructions are available at
https://docs.astral.sh/uv/getting-started/installation/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file evallm_qa-0.1.1.tar.gz.
File metadata
- Download URL: evallm_qa-0.1.1.tar.gz
- Upload date:
- Size: 60.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16d220654e5d6bc223fa12030e190f1b959092f7e842d72dc6750e3d91acbcfd
|
|
| MD5 |
0f7aff61047a19ec67ac17085d594329
|
|
| BLAKE2b-256 |
6ef0d03158cdabb3ee68979e39215cb84371f2452fc771b2daab717b82b861c5
|
File details
Details for the file evallm_qa-0.1.1-py3-none-any.whl.
File metadata
- Download URL: evallm_qa-0.1.1-py3-none-any.whl
- Upload date:
- Size: 70.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea2a106ac12fce26ce74d30be6ede34528c0b9c79fd79b2a830825848cf79b2f
|
|
| MD5 |
b21af79a115e76391f581276e9de9500
|
|
| BLAKE2b-256 |
225646e827b8d59372fde64128d0991b5e5e6f374bbc2ea61a0192ba49c5d745
|