Getting started¶
Installation¶
Python 3.11 or later is required. The package is on PyPI as
llm-as-judge-platform; it
installs the llm_judge library and the llm-judge command:
pip install llm-as-judge-platform # library and CLI
pip install "llm-as-judge-platform[all]" # plus dashboard, REST API, YAML, PostgreSQL
To run the dashboard or work on the code, clone the repository:
git clone https://github.com/syed-waleed-ahmed/LLM-as-Judge.git
cd LLM-as-Judge
python -m venv .venv
# Windows: .venv\Scripts\activate
# Unix: source .venv/bin/activate
# Library + UI + dev tooling
pip install -e ".[ui,dev]"
# Or everything (UI, REST API, YAML, PostgreSQL)
pip install -e ".[all]"
Every GitHub Release
also has the wheel and sdist attached. The dashboard lives in app.py and
dashboard/, so it runs from a clone.
Upgrading an environment from before 2.3.0? The distribution was renamed
from llm-as-judge, so remove the old one first with
pip uninstall llm-as-judge. Otherwise its files can shadow the new install.
Then configure your key:
cp .env.example .env # then edit .env and set GROQ_API_KEY
Get a free key at https://console.groq.com/keys. No key yet? Every
command works offline with --provider mock, a deterministic heuristic judge
(clearly labelled, not an LLM), or locally with --provider ollama.
Usage¶
1. Web app (Streamlit)¶
streamlit run app.py
A multi-page dashboard over the same engine:
| Page | What it does |
|---|---|
| Overview | Which judge is active, session and stored-run statistics, recent runs |
| Playground | Compare two outputs, grade one, check against a reference, or rank candidates, with optional judge instructions, charts, explanations and warnings |
| Datasets | Upload CSV/JSON/JSONL/YAML (or pick a sample), validate, evaluate, chart and download results |
| Benchmarks | Run a bundled sample and see per-system statistics |
| RAG evaluation | Faithfulness, answer relevance, context precision and recall, with claim-level verdicts |
| Agent evaluation | Paste or upload a trace: deterministic checks plus a judged grade |
| Experiments | Compare two recorded runs: verdict, confidence interval, per-row changes; download Markdown reports |
| Analytics | Across recorded runs: score trends, score distributions, per-criterion comparison, bias signals and operational reliability |
| Reliability | Repeated-judgment consistency and agreement between judges |
| Settings | Judge temperature and tie margin for your session, provider status and health checks, limits in effect, storage, cache and privacy controls |
| About | How the results stay honest, privacy, how to run it yourself |
The sidebar switches between the live judge (when a key is configured) and the offline mock judge. The mock judge works without any key, and its results are always labelled as not coming from an LLM. Charts use a colorblind-validated palette with separate light and dark versions.
For a public deployment, set JUDGE_DEMO_MODE=true. That adds per-session
and daily live-call budgets and input caps, and keeps each visitor's data
session-only. See DEPLOYMENT.md for free Streamlit
Community Cloud hosting.
2. Command line¶
# List built-in criteria (no API key required)
llm-judge list-criteria
# Judge a single pair
llm-judge evaluate \
--task "Write a friendly tweet announcing our new AI tool" \
--output-a "Meet our new AI assistant - it saves you hours every week. Try it free!" \
--output-b "We have released a new artificial intelligence product." \
--criteria "Creativity,Brand Tone Adherence" \
--mitigate-bias
# Weight criteria and record which models produced each output
llm-judge evaluate --task-file task.txt \
--output-a-file a.txt --output-b-file b.txt \
--criteria "Helpfulness,Conciseness" --weights "Helpfulness=2" \
--model-a gpt-4o --model-b llama-3.1-8b-instant
# Read long inputs from files and emit raw JSON
llm-judge evaluate --task-file task.txt \
--output-a-file a.txt --output-b-file b.txt --json
# Give the judge your own guidance (also: grade, rank, batch, benchmark)
llm-judge evaluate --task-file task.txt \
--output-a-file a.txt --output-b-file b.txt \
--instructions "Penalise answers that state facts without a source."
# Grade a whole dataset concurrently (checkpointed; resumable with --resume)
llm-judge batch --input examples/sample_batch.csv \
--output results.csv --criteria Helpfulness --workers 8
# Run a bundled benchmark sample and print per-model statistics
llm-judge benchmark summarization
# Grade one output on its own, or against an expected answer
llm-judge grade --task "Explain DNS" --output-file answer.txt
llm-judge grade --task "When did Apollo 11 land?" \
--output "In 1969." --reference "Apollo 11 landed on the Moon on 20 July 1969."
# Rank several candidates (round robin, or --strategy pointwise for fewer calls)
llm-judge rank --task "Write a tagline" --candidates-file candidates.json
candidates.json is either {"id": "text", ...} or a list of
{"id": ..., "output": ..., "model": ...} objects.
3. Python library¶
from llm_judge import Criterion, Judge, resolve_criterion
judge = Judge() # reads config from environment / .env
result = judge.evaluate(
task_description="Summarise the text in one sentence.",
output_a="A concise, accurate one-line summary.",
output_b="A rambling three-paragraph response.",
criteria=["Helpfulness", "Conciseness"],
candidate_a_model="gpt-4o", # optional; recorded in metadata
)
print(result.overall_winner) # Winner.A
print(result.overall_score_a) # weighted, scale-normalised score (0-1)
for warning in result.warnings: # reliability signals worth a second look
print(warning.code, warning.message)
# Custom criteria can set their own scale and weight.
coverage = Criterion(
name="Coverage", description="Covers every key point.", scale_min=0, scale_max=100, weight=2
)
judge.evaluate("...", "...", "...", [resolve_criterion("Helpfulness"), coverage])
# Optional guidance for the judge: how strictly to score, what to penalise.
# It's recorded in result.metadata["instructions"] and in experiment runs.
judge.evaluate(
"...",
"...",
"...",
"Accuracy",
instructions="Penalise answers that state facts without a source.",
)
The same Judge also grades single outputs and ranks candidates:
# Pointwise: grade one output on its own merits.
grade = judge.evaluate_pointwise("Explain DNS.", answer, ["Helpfulness", "Conciseness"])
print(grade.overall_score, grade.feedback)
# Reference-based: compare with an expected answer (defaults to Correctness + Completeness).
check = judge.evaluate_reference("When did Apollo 11 land?", "In 1968.", reference="20 July 1969.")
print(check.unsupported_claims) # hallucination indicators
print(check.missing_information) # key points the output omits
print(check.reference_metrics) # exact_match, token_f1, optional semantic_similarity
# Ranking: round robin (every pair head to head) or pointwise (cheaper).
ranking = judge.rank("Write a tagline", {"v1": "...", "v2": "...", "v3": "..."})
for row in ranking.leaderboard:
print(row.rank, row.candidate, row.score, (row.wins, row.losses, row.ties))
Every method has an async twin (aevaluate, aevaluate_pointwise,
aevaluate_reference, arank, aevaluate_rag, aevaluate_agent). Each one
runs the evaluation in a worker thread, so you can await it from async code
without blocking the event loop, and run several concurrently:
import asyncio
results = await asyncio.gather(
judge.aevaluate(task, a, b, "Helpfulness"),
judge.aevaluate_pointwise(task, a, "Conciseness"),
)
Set JUDGE_CACHE_SIZE (for example 256) to reuse results of identical
evaluations: same inputs, criteria, instructions, judge and settings. The cache
is opt-in, in memory only, and used only at temperature 0. Hits are marked
metadata["cache_hit"], and they don't spend provider quota. Consistency
analysis never uses it.
Every pairwise evaluation returns a validated, versioned result:
{
"schema_version": "1.3",
"criteria": [
{
"criterion": "Helpfulness",
"output_a_score": 8.0,
"output_b_score": 3.0,
"output_a_explanation": "Directly and accurately answers the task.",
"output_b_explanation": "Vague and largely off-topic.",
"winner": "A"
}
],
"overall_winner": "A",
"overall_comment": "Output A is clearly more helpful.",
"overall_score_a": 0.7778,
"overall_score_b": 0.2222,
"warnings": [],
"metadata": {
"model": "openai/gpt-oss-120b",
"judge": { "provider": "groq", "model": "openai/gpt-oss-120b" },
"candidates": { "A": { "model": "gpt-4o" }, "B": { "model": null } },
"prompt_version": "2.1",
"position_bias_mitigated": false,
"latency_seconds": 0.93
}
}
4. REST API¶
pip install -e ".[api]" # from a clone; included in ".[all]"
llm-judge serve # http://127.0.0.1:8000, docs at /docs
curl -s http://127.0.0.1:8000/api/v1/evaluate/pairwise \
-H "Content-Type: application/json" \
-d '{"task_description": "Explain DNS", "output_a": "...", "output_b": "...",
"criteria": ["Helpfulness", {"name": "Accuracy", "weight": 2}]}'
| Endpoint | Purpose |
|---|---|
POST /api/v1/evaluate/{pairwise,pointwise,reference,rank} |
Single evaluations |
POST /api/v1/evaluate/{rag,agent} |
RAG and agent-trajectory evaluation |
POST /api/v1/analysis/consistency, GET /api/v1/runs/agreement |
Judge reliability |
POST /api/v1/evaluate/batch |
Small synchronous batches (JUDGE_API_MAX_BATCH_ROWS) |
POST /api/v1/jobs, GET /api/v1/jobs/{id}[/rows], DELETE /api/v1/jobs/{id} |
Background dataset jobs (bounded queue, cancellable, idempotent with Idempotency-Key) |
POST /api/v1/datasets/validate |
Validate rows or CSV/JSONL/JSON/YAML content |
GET /api/v1/benchmarks, POST /api/v1/benchmarks/{name}/jobs |
Bundled sample benchmarks |
GET /api/v1/experiments, /runs, /runs/{id}[/rows], /runs/compare |
Experiment history and regression checks |
GET /api/v1/system/status, /providers, /criteria, /statistics, GET /health |
Service information |
Protections, all configurable:
- API keys. JUDGE_API_KEYS (comma-separated), sent as X-API-Key or Authorization: Bearer.
- Admin routes. Deleting runs and listing all jobs are disabled unless JUDGE_API_ADMIN_KEY is set.
- Rate limiting. A per-client limit, with Retry-After on 429.
- Size limit. Request bodies over the limit get a 413.
- Concurrency cap. Excess load gets a 503, so free-tier quotas aren't exhausted silently.
Every error uses one JSON envelope with a request id, and request logs are
structured JSON without bodies. serve binds to localhost by default and
warns when exposed without API keys.
No always-free host is assumed for the API. Run it locally or on your own infrastructure. The public demo (Streamlit) doesn't depend on it.