A lightweight OSS library to evaluate RAG systems with LLM-as-a-Judge.
Project description
evalf
Evaluate RAG systematically with LLM-as-a-Judge from the CLI or Python.
evalf scores answers against expected outputs and contexts, aggregates results across samples, and writes JSON or Markdown reports with scores, pass/fail status, token usage, latency, and estimated cost.
How it works
evalf takes one sample or a dataset of samples, sends structured judging prompts to an OpenAI-compatible model, and computes metric scores such as:
- answer correctness
- answer relevance
- faithfulness
- context coverage
- context relevance
- context precision
- context recall
c4, a one-call composite metric that scores:alignment_integrityaccuracy_consistencysafety_sovereignty_tonecompleteness_coverage
It supports single-attempt and multi-attempt evaluation through pass@k and pass^k.
Getting Started
- Install uv if you do not already have it.
macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows:
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
- Install
evalf:
uv tool install evalf
# or
pip install evalf
# or inside an application
uv add evalf
If you prefer to work from a source checkout instead of installing from PyPI:
git clone https://github.com/hnhoangdz/evalf.git
cd evalf
uv sync --extra dev
- Configure your judge model:
cp .env.example .env.local
Example:
EVALF_PROVIDER=openai
EVALF_MODEL=gpt-4.1-mini
EVALF_BASE_URL=https://api.openai.com/v1
EVALF_API_KEY=your-api-key-here
EVALF_CONCURRENCY=4
EVALF_REQUEST_TIMEOUT_SECONDS=60
EVALF_PER_SAMPLE_TIMEOUT_SECONDS=120
EVALF_MAX_RETRIES=3
evalf loads .env.local first, then .env.
- Check available metrics:
evalf list-metrics
Usage
Evaluate a single sample
evalf run \
--provider openai \
--model gpt-4.1-mini \
--request-timeout-seconds 60 \
--per-sample-timeout-seconds 120 \
--question "Under FERPA, when do rights transfer from parents to a student?" \
--retrieved-context "When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student." \
--actual-output "Under FERPA, rights transfer when a student turns 18 or enters a postsecondary institution at any age." \
--expected-output "Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age." \
--metrics faithfulness,answer_correctness,answer_relevance \
--threshold 0.7
Evaluate inline JSON
evalf run \
--sample-json '{"id":"case-1","question":"Under FERPA, when do rights transfer from parents to a student?","retrieved_contexts":["When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."],"reference_contexts":["When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."],"actual_output":"Under FERPA, rights transfer when a student turns 18 or enters a postsecondary institution at any age.","expected_output":"Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age."}'
Evaluate a file
evalf run \
--input examples/rag_eval.jsonl \
--metrics faithfulness,answer_correctness,context_precision,context_recall \
--threshold 0.8 \
--output .evalf/report.json
If --output has no suffix, evalf writes <path>.json.
Evaluate with c4
evalf run \
--input examples/rag_eval.jsonl \
--metrics c4 \
--threshold 0.7
Optional C4 flags:
--c4-summary-reasonto request a synthesized overall reason--no-c4-include-reasonto omit criterion-level reasoning from the final report--c4-strict-modeto clamp below-threshold C4 scores to0.0
Evaluate multi-attempt samples
Use pass@k when a sample passes if any attempt passes. Use pass^k when all evaluated attempts must pass.
evalf run \
--input examples/rag_eval_attempts.json \
--metrics faithfulness,answer_correctness \
--metric-mode pass@k \
--k 3
Python API
from evalf import EvalCase, Evaluator
from evalf.metrics import AnswerCorrectnessMetric, FaithfulnessMetric
from evalf.llms import build_llm
judge = build_llm()
evaluator = Evaluator(judge=judge, concurrency=4)
report = evaluator.evaluate(
cases=[
EvalCase(
id="case-1",
question="Under FERPA, when do rights transfer from parents to a student?",
retrieved_contexts=[
"When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."
],
reference_contexts=[
"When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."
],
actual_output="Under FERPA, rights transfer when a student turns 18 or enters a postsecondary institution at any age.",
expected_output="Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age.",
)
],
metrics=[
FaithfulnessMetric(threshold=0.8),
AnswerCorrectnessMetric(threshold=0.8),
],
)
print(report.summary.total_cost_usd)
print(report.samples[0].status)
If your environment is already configured, you can skip build_llm():
from evalf import EvalCase, Evaluator
from evalf.metrics import AnswerCorrectnessMetric, FaithfulnessMetric
evaluator = Evaluator(concurrency=4)
If you pass a custom judge into Evaluator, you own that client's lifecycle. evalf only auto-closes judges that it creates itself.
You can also register project-specific metrics without monkey-patching internals:
from evalf.metrics import BaseMetric, build_metrics, register_metric
class MyMetric(BaseMetric):
...
register_metric("my_metric", MyMetric)
metrics = build_metrics(["faithfulness", "my_metric"])
Input format
Each sample can include:
idquestionretrieved_contextsreference_contextsactual_outputexpected_outputattempts
Example examples/rag_eval.jsonl:
{"id":"case-1","question":"Under FERPA, when do rights transfer from parents to a student?","retrieved_contexts":["When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."],"reference_contexts":["When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."],"actual_output":"Under FERPA, rights transfer when a student turns 18 or enters a postsecondary institution at any age.","expected_output":"Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age."}
{"id":"case-2","question":"Under FERPA, when do rights transfer from parents to a student?","retrieved_contexts":["When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."],"reference_contexts":["When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."],"actual_output":"Under FERPA, rights transfer only when a student turns 21 years old.","expected_output":"Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age."}
Example examples/rag_eval_attempts.json:
[
{
"id": "case-3",
"question": "Under FERPA, when do rights transfer from parents to a student?",
"retrieved_contexts": [
"When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."
],
"reference_contexts": [
"When a student turns 18 years old, or enters a postsecondary institution at any age, the rights under FERPA transfer from the parents to the student."
],
"expected_output": "Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age.",
"attempts": [
{"actual_output": "Under FERPA, rights transfer only when a student turns 21 years old."},
{"actual_output": "Under FERPA, rights transfer when a student turns 18 years old or enters a postsecondary institution at any age."},
{"actual_output": "FERPA rights move from the parent to the student at age 18 or when the student enters a postsecondary institution at any age."}
]
}
]
Output
evalf writes either JSON or Markdown reports.
Example:
{
"run_id": "run_123456789abc",
"summary": {
"total_samples": 2,
"passed_samples": 1,
"failed_samples": 1,
"skipped_samples": 0,
"total_input_tokens": 1864,
"total_output_tokens": 622,
"total_tokens": 2486,
"total_cost_usd": 0.00174,
"avg_latency_ms_per_sample": 1421.6,
"metric_pass_rates": {
"answer_correctness": 0.5,
"faithfulness": 0.5
}
},
"samples": [
{
"sample_id": "case-1",
"status": "passed"
},
{
"sample_id": "case-2",
"status": "failed"
}
]
}
Notebooks
notebooks/01_llms.ipynb: runtime settings,build_llm, structured output, and client cleanupnotebooks/02_metrics.ipynb: all built-in metrics withReplayJudge, including C4 composite metricnotebooks/03_evaluate_file.ipynb: load a dataset, run evaluation, inspect the report, and export JSON/Markdownnotebooks/04_custom_metrics.ipynb: create, register, and run a custom metric alongside built-in onesnotebooks/05_cli_guide.ipynb: CLI quick reference with examples for every major feature
Example Sources
The example facts in this README and in the built-in prompt examples are based on official sources:
- FERPA: https://studentprivacy.ed.gov/faq/what-ferpa
- CDC flu vaccine safety: https://www.cdc.gov/vaccine-safety/vaccines/flu.html
- ADA service animals: https://www.ada.gov/resources/service-animals-2010-requirements/
- EEOC filing deadlines: https://www.eeoc.gov/time-limits-filing-charge
Notes
- Cost is estimated in USD from the model pricing registry when token usage is available.
- If a response does not include
usage,cost_usdremainsnull. - Gemini uses a built-in OpenAI-compatible base URL.
- Claude requires an explicit OpenAI-compatible
base_url; the native Anthropic API endpoint is not supported by this transport. faithfulness,context_precision, andcontext_recallreturn1.0when no material claims are extracted;evalftreats those cases as vacuous success rather than hard failure.context_coveragesupportsstrict_mode: when enabled and the score falls below the threshold, the score is clamped to0.0.faithfulnesspenalizes contradicted claims more heavily than unsupported claims by subtracting an extra0.5weight per contradicted claim before normalizing by total claims.kis capped at5.
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 evalf-1.0.1.tar.gz.
File metadata
- Download URL: evalf-1.0.1.tar.gz
- Upload date:
- Size: 66.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4d97e82edfdc1d5b9dfe573a0b983866a60c52dc05ac98b6454985574ca5335
|
|
| MD5 |
bad8d71db95b2c0b70d7bcb77795b121
|
|
| BLAKE2b-256 |
f5b9a3bb55660c9aade3bb7a8e32ecf56d9dd55ec254fcc4eab0ea974e392d6b
|
Provenance
The following attestation bundles were made for evalf-1.0.1.tar.gz:
Publisher:
release.yml on hnhoangdz/evalf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
evalf-1.0.1.tar.gz -
Subject digest:
f4d97e82edfdc1d5b9dfe573a0b983866a60c52dc05ac98b6454985574ca5335 - Sigstore transparency entry: 1342937123
- Sigstore integration time:
-
Permalink:
hnhoangdz/evalf@287bfc714f39ed130cc0a0f83629fd7e07e8e64b -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/hnhoangdz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@287bfc714f39ed130cc0a0f83629fd7e07e8e64b -
Trigger Event:
release
-
Statement type:
File details
Details for the file evalf-1.0.1-py3-none-any.whl.
File metadata
- Download URL: evalf-1.0.1-py3-none-any.whl
- Upload date:
- Size: 66.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0363d5fe8789d525987463ac53b718d75b52d79f23df703748556dde85b66bf
|
|
| MD5 |
6a7c186f24443b561494a0d8bcccd679
|
|
| BLAKE2b-256 |
cb016bedf893ac78c58a5c34356dea99af4700e161a116b8b442cd4d3c73e5d3
|
Provenance
The following attestation bundles were made for evalf-1.0.1-py3-none-any.whl:
Publisher:
release.yml on hnhoangdz/evalf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
evalf-1.0.1-py3-none-any.whl -
Subject digest:
f0363d5fe8789d525987463ac53b718d75b52d79f23df703748556dde85b66bf - Sigstore transparency entry: 1342937128
- Sigstore integration time:
-
Permalink:
hnhoangdz/evalf@287bfc714f39ed130cc0a0f83629fd7e07e8e64b -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/hnhoangdz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@287bfc714f39ed130cc0a0f83629fd7e07e8e64b -
Trigger Event:
release
-
Statement type: