Skip to main content

A lightweight OSS library to evaluate RAG systems with LLM-as-a-Judge.

Project description

evalf

License Python versions

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_integrity
    • accuracy_consistency
    • safety_sovereignty_tone
    • completeness_coverage

It supports single-attempt and multi-attempt evaluation through pass@k and pass^k.

Getting Started

  1. 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"
  1. 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
  1. 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.

  1. 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-reason to request a synthesized overall reason
  • --no-c4-include-reason to omit criterion-level reasoning from the final report
  • --c4-strict-mode to clamp below-threshold C4 scores to 0.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:

  • id
  • question
  • retrieved_contexts
  • reference_contexts
  • actual_output
  • expected_output
  • attempts

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 cleanup
  • notebooks/02_metrics.ipynb: all built-in metrics with ReplayJudge, including C4 composite metric
  • notebooks/03_evaluate_file.ipynb: load a dataset, run evaluation, inspect the report, and export JSON/Markdown
  • notebooks/04_custom_metrics.ipynb: create, register, and run a custom metric alongside built-in ones
  • notebooks/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:

Notes

  • Cost is estimated in USD from the model pricing registry when token usage is available.
  • If a response does not include usage, cost_usd remains null.
  • 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, and context_recall return 1.0 when no material claims are extracted; evalf treats those cases as vacuous success rather than hard failure.
  • context_coverage supports strict_mode: when enabled and the score falls below the threshold, the score is clamped to 0.0.
  • faithfulness penalizes contradicted claims more heavily than unsupported claims by subtracting an extra 0.5 weight per contradicted claim before normalizing by total claims.
  • k is capped at 5.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

evalf-1.0.0.tar.gz (65.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

evalf-1.0.0-py3-none-any.whl (65.6 kB view details)

Uploaded Python 3

File details

Details for the file evalf-1.0.0.tar.gz.

File metadata

  • Download URL: evalf-1.0.0.tar.gz
  • Upload date:
  • Size: 65.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for evalf-1.0.0.tar.gz
Algorithm Hash digest
SHA256 1dcfe8542eaa82661934a7adefa9a209549c4b1150e5f1a8168afdbc2ef9678c
MD5 12c96bef39cd157139a6cb37a1ab1e16
BLAKE2b-256 18b4ebb2351a0b5952d2590e95f9f86f8f9e8f03cb3201421dc617df970dbd83

See more details on using hashes here.

Provenance

The following attestation bundles were made for evalf-1.0.0.tar.gz:

Publisher: release.yml on hnhoangdz/evalf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file evalf-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: evalf-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 65.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for evalf-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4b3b949241cbe55f64c427227fda988ffc203e7289f6067d61af81e591e875be
MD5 04d9195ceb0bae306268efefb9835707
BLAKE2b-256 993ffd67c5daea9f88396853e6a4b7c07ef125406a47087b1ed34f5ee2a650f2

See more details on using hashes here.

Provenance

The following attestation bundles were made for evalf-1.0.0-py3-none-any.whl:

Publisher: release.yml on hnhoangdz/evalf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page