⚡ RagArena
Evaluate & benchmark every RAG strategy × LLM × embedding model — with one unified API
18 strategies · 100+ models · 20+ providers · 10 metrics · Next.js playground · Docker/PyPI/GitHub
📐 Architecture & full feature reference · 🐳 Docker guide · 📝 Changelog
One unified API across every RAG strategy, LLM and embedding model:
from ragarena import evaluate
evaluate(questions=[...], documents=[...],
strategy="hybrid", # any of 18 strategies
model="openai/gpt-4o-mini", # swap with claude/gemini/llama/...
embedding_model="voyage/voyage-3", # swap with openai/cohere/jina/...
metrics="quality").print_summary()
Why RagArena?
Choosing a RAG stack is guesswork today. "Is hybrid retrieval actually better than naive for my data? Is GPT-4o worth 17× the price of GPT-4o-mini for answer faithfulness? Do Voyage-3 embeddings beat OpenAI's on my legal corpus?"
RagArena turns those guesses into a leaderboard.
| Ragas | DeepEval | TruLens | RagArena | |
|---|---|---|---|---|
| Score your existing pipeline | ✅ | ✅ | ✅ | ✅ |
| Run the pipelines themselves (18 strategies) | ❌ | ❌ | ❌ | ✅ |
Swap LLM/embedding providers per run (provider/model syntax) |
partial | partial | partial | ✅ 100+ models |
| Built-in cost + latency accounting per strategy | ❌ | ❌ | partial | ✅ |
| Head-to-head leaderboard w/ shared index | ❌ | ❌ | ❌ | ✅ |
| Zero-config web dashboard | ❌ | ❌ | ❌ | ✅ |
vs. RAG-Anything (LightRAG-based multimodal pipelines)
RAG-Anything is a great single-pipeline multimodal document processor (MinerU parsing, VLM captioning, multimodal knowledge graph). RagArena attacks a different — and complementary — problem: which RAG design should you ship?
| Capability | RAG-Anything | RagArena |
|---|---|---|
| Retrieval strategies you can choose from | 1 (fixed LightRAG pipeline) | 18 (naive → hybrid → HyDE → CRAG → Self-RAG → agentic → graph ×4 → multimodal) |
| Benchmarking / evaluation harness | ❌ none | ✅ metrics, cost & latency accounting, leaderboards, strategy recommendation |
| LLM / embedding provider freedom | OpenAI-shaped funcs you wire yourself | ✅ provider/model IDs across 25+ providers, swap per run |
| Structured data (SQL, SQLite, Excel, CSV/TSV, JSON/JSONL, XML, YAML) | via LibreOffice conversion only | ✅ native parsers — row-aware chunking, key-path flattening, schema-context chunks |
| Heavy system dependencies | MinerU + LibreOffice + model downloads | ✅ pure-Python stdlib-first; every parser degrades gracefully |
| UI playground for non-engineers | ❌ | ✅ Next.js playground + one-command Docker image |
Use both together: parse gnarly scanned PDFs with RAG-Anything/MinerU, feed the extracted
content lists into RagArena's evaluate() to pick the best strategy/model combo before you commit.
Install
pip install ragarena # core
pip install "ragarena[all]" # + all provider SDKs
export OPENAI_API_KEY=sk-... # only the providers you use
60-second quickstart
from ragarena import evaluate
docs = [
{"text": "Retrieval-Augmented Generation (RAG) grounds LLM answers in your documents."},
{"text": "Hybrid retrieval combines dense vector search with BM25 keyword search."},
{"text": "Cross-encoder rerankers like bge-reranker significantly improve precision."},
]
report = evaluate(
questions=["What is RAG?", "How does hybrid retrieval work?"],
documents=docs,
reference_answers=["RAG grounds LLMs in docs", "It fuses dense + BM25 search"],
strategy="hybrid", # naive|hybrid|multi_query|hyde|rerank|
# rag_fusion|compression|crag|self_rag|
# decomposition|step_back|agentic|flare
model="openai/gpt-4o-mini",
embedding_model="openai/text-embedding-3-small",
metrics="quality", # quick|quality|full|production
)
report.print_summary()
report.save("report.json")
╭─ RagArena · hybrid · openai/gpt-4o-mini
├─ embedding : openai/text-embedding-3-small
├─ samples : 2 wall time 6.4s
├──────────────────────────────────────────────────────────
│ faithfulness : 0.92 answer relevance : 0.95
│ context precision : 0.83 context recall : 0.88
│ hit rate : 1.0 mrr : 0.75
│ avg latency : 3.1s total cost : $0.00214
╰──────────────────────────────────────────────────────────
🔌 Drop it into any app as your RAG layer
RagArena isn't only an offline evaluator — answer() uses it to actually answer questions,
with strategy="auto" picking the best strategy for YOUR documents automatically:
from ragarena import answer
# strategy="auto" (default): evaluates candidate strategies against a few real sample
# questions the first time it sees this document set, caches the winner, reuses it after
result = answer(
query="What is RAG?",
documents=docs,
auto_eval_questions=["What is RAG?", "How does hybrid retrieval work?"],
)
print(result) # -> the answer string
# or pin a specific strategy directly, same as evaluate()
answer(query="What is RAG?", documents=docs, strategy="hybrid")
No eval questions available yet? Auto-generate them from your own documents:
from ragarena import generate_testset, evaluate
questions, references = generate_testset(docs, n=20, model="openai/gpt-4o-mini")
report = evaluate(questions=questions, reference_answers=references, documents=docs)
🏆 Find the best strategy/model in one call
from ragarena import compare
result = compare(
questions=my_questions,
documents=my_docs,
reference_answers=ground_truth,
configs=[
{"strategy": "naive", "model": "openai/gpt-4o-mini"},
{"strategy": "hybrid", "model": "openai/gpt-4o-mini"},
{"strategy": "hyde", "model": "openai/gpt-4o-mini"},
{"strategy": "agentic", "model": "groq/openai/gpt-oss-120b"},
{"strategy": "hybrid", "model": "anthropic/claude-3-haiku-20240307"},
],
)
result.print_leaderboard(sort_by="faithfulness")
print("WINNER:", result.best("faithfulness"))
The document index is embedded once and shared across all configs — comparisons are fast and cheap.
🎯 "Which strategy is best for MY data?"
Don't want to hand-pick configs? recommend_strategy() runs every strategy (or a
chosen subset) against your corpus and questions, then ranks them by a
quality/cost/latency-weighted composite score:
from ragarena import recommend_strategy
rec = recommend_strategy(
questions=my_questions,
documents=my_docs,
reference_answers=ground_truth,
model="groq/openai/gpt-oss-20b",
embedding_model="google/gemini-embedding-001",
quality_weight=0.7, cost_weight=0.15, latency_weight=0.15, # tune for your priorities
)
rec.print_summary()
print(rec.best, rec.reasoning)
print(rec.code_snippet()) # ready-to-paste answer() call using the winning strategy
Also available as ragarena recommend --documents ... --questions ... (CLI) and
POST /api/recommend (used by the Recommend tab in the playground UI).
📄 Bring your own data — any format, any database
from ragarena import parse_file, parse_dir, from_sql
docs = parse_file("report.pdf") # pdf, docx, pptx, html
docs += parse_file("notes.docx") # csv/tsv, json/jsonl, xml, yaml/yml,
docs += parse_file("sheet.xlsx") # xlsx (all sheets), md, txt, images,
docs += parse_dir("./knowledge_base/") # .sql dumps, sqlite/sqlite3/db files — mixed dirs OK
docs += from_sql("postgresql://user:pass@host/db", # any SQLAlchemy-supported DB
"SELECT id, title, body FROM articles")
Structured data is parsed retrieval-aware: table rows are chunked in groups that repeat the
column headers ("col: value" pairs), JSON/YAML are flattened to full key-path facts
(offices[0].city: Berlin), and XML keeps tag-path + attribute context — so embeddings stay
self-contained and answers cite the right field. pip install "ragarena[ingest,sql]" for the
optional parser/DB-driver dependencies (SQLite works with zero extras).
🖥 Web dashboard
RagArena serve # → http://localhost:4000
- Overview — run history & framework stats
- New Evaluation — pick strategy/models from dropdowns, paste corpus, score instantly
- Compare — build config matrices, get leaderboards + Chart.js visualizations
- Runs — drill into every sample: answer, chunks, metric reasoning
- Catalog — browse all 100+ models with pricing/context windows
🔀 Every popular provider, one syntax
Models are addressed as provider/name:
from ragarena import completion
completion(model="openai/gpt-4o-mini", ...) # OpenAI
completion(model="anthropic/claude-3-5-sonnet-20240620", ...)
completion(model="google/gemini-1.5-flash", ...)
completion(model="deepseek/deepseek-chat", ...) # 97% cheaper than gpt-4o
completion(model="groq/openai/gpt-oss-20b", ...) # sub-second inference
completion(model="ollama/llama3.1", ...) # local & free
completion(model="bedrock/meta.llama3-1-405b-instruct-v1:0", ...)
📋 Full provider support matrix (click to expand)
LLM Providers — provider/ prefix:
| Provider | Example models | Notes |
|---|---|---|
openai/ |
gpt-4o, gpt-4o-mini, o1-preview | flagship quality |
anthropic/ |
claude-3-5-sonnet, opus, haiku | best coding/agentic |
google/, vertex/ |
gemini-1.5-pro (2M ctx), flash | huge context |
azure/ |
any OpenAI model on Azure | enterprise compliance |
bedrock/ |
claude/llama/titan on AWS | VPC deployments |
cohere/ |
command-r-plus | native RAG features |
mistral/ |
large, nemo, codestral | EU-hosted options |
xai/ |
grok-beta | real-time knowledge |
deepseek/ |
deepseek-chat, coder | extreme $/quality |
groq/ |
gpt-oss-120b @300tok/s | fastest hosted |
together/, fireworks/, deepinfra/ |
llama, qwen, mixtral | open-model hosts |
perplexity/ |
sonar-online | search-grounded |
openrouter/ |
100+ gateway models | one key, all models |
nvidia_nim/, anyscale/, ai21/, databricks/ |
… | … |
ollama/, vllm/, lmstudio/ |
local llama/qwen/gemma/phi | free, private |
Embedding Providers:
openai/text-embedding-3-* · cohere/embed-* · voyage/voyage-3(-large|-code|-law|-finance) ·
jina/jina-embeddings-v3 · mistral/mistral-embed · google/text-embedding-004 ·
bedrock/amazon.titan-embed-text-v2 · huggingface/BAAI/bge-m3 (+MiniLM, E5, GTE) · ollama/nomic-embed-text
Rerankers: cohere/rerank-v3.5 · voyage/rerank-2 · huggingface/BAAI/bge-reranker-v2-m3
Vector stores: FAISS (built-in) · Chroma · Pinecone · Qdrant · Weaviate · Milvus · LanceDB · pgvector · Elasticsearch · Redis · OpenSearch · MongoDB
Browse everything from the CLI:
RagArena models list # all 100+
RagArena models list --modality embedding # embeddings only
RagArena models providers # provider summary
RagArena strategies # the 18 strategies
Already using LangChain, or your own model wrapper? Bring it as-is
model= and embedding_model= don't have to be a provider/name string — pass any
LangChain chat model / Embeddings object, or a plain Python callable, and RagArena
will use it directly:
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from ragarena import evaluate
evaluate(
questions=[...], documents=[...],
model=ChatOpenAI(model="gpt-4o-mini"), # any LangChain BaseChatModel/Runnable
embedding_model=OpenAIEmbeddings(), # any LangChain Embeddings object
judge_model="openai/gpt-4o-mini", # mix and match freely
)
# or just a plain callable — no LangChain required
evaluate(model=lambda messages: my_llm_call(messages), ...)
🧪 The 18 built-in strategies
| Strategy | What it does | Best for |
|---|---|---|
naive |
dense top-k → generate | baseline / simple corpora |
hybrid |
dense + BM25 weighted fusion (α-tunable) | keyword-heavy domains |
multi_query |
LLM rewrites N queries, merges results | vague questions |
rag_fusion |
multi-query + Reciprocal Rank Fusion | robustness over multi_query |
hyde |
retrieve using an imagined answer's embedding | vocabulary mismatch |
rerank |
wide recall → cross-encoder refine | precision-critical |
compression |
LLM strips irrelevant spans pre-generation | long noisy chunks |
crag |
grades retrieval; rewrites query if weak | production guardrails |
self_rag |
model decides if retrieval is needed | mixed easy/hard traffic |
decomposition |
splits complex Q → sub-Qs → synthesis | multi-hop questions |
step_back |
abstract principle question first | conceptual/domain Qs |
agentic |
iterative search→reflect→search loop | hard research tasks |
flare |
flags uncertain draft claims → re-retrieves | hallucination-prone domains |
graph_local |
entity-precise retrieval over a knowledge graph | "who/what" factual lookups |
graph_global |
macro-theme retrieval across entity communities | "how/why" analytical Qs |
graph_hybrid |
combines local entities + global themes | general-purpose graph RAG |
graph_mix |
local + global fused in one synthesis pass | best-of-both retrieval |
multimodal |
retrieves typed chunks (text/table/image/equation) | mixed-content documents |
🕸️ Graph RAG (dual-level retrieval)
graph_* strategies layer a lightweight knowledge graph over your index — entities
are extracted per chunk, chunks that share entities form communities, and queries
are answered at two levels:
- local (
graph_local) — match the query's entities to graph nodes and pull the connected chunks. Best for precise "who/what" factual lookups. - global (
graph_global) — summarise each community, rank communities by relevance to the query, then synthesise a cross-document answer. Best for "how/why" analysis. - hybrid / mix (
graph_hybrid,graph_mix) — combine both levels.
The graph is built lazily and cached on a shared index, so compare() only builds it
once. Entity extraction falls back to a deterministic keyword extractor if the LLM is
unavailable.
from ragarena import compare
result = compare(
questions=["Who builds Pinecone?", "How do retrieval systems relate?"],
documents=my_docs,
configs=[
{"strategy": "graph_local", "model": "openai/gpt-4o-mini"},
{"strategy": "graph_global", "model": "openai/gpt-4o-mini"},
{"strategy": "graph_hybrid", "model": "anthropic/claude-3-haiku-20240307"},
],
)
Build a graph index directly for inspection:
from ragarena import VectorIndex, GraphIndex
vi = VectorIndex(embedding_model="openai/text-embedding-3-small")
vi.add_documents(my_docs)
g = GraphIndex(vi).build("openai/gpt-4o-mini") # cache on the index
local = g.local_search("What is Pinecone?", k=5, llm_model="openai/gpt-4o-mini")
chunks, theme = g.global_search("How do vector DBs compare?", k=5, llm_model="openai/gpt-4o-mini")
🖼️ Multimodal RAG
Tables, images and equations are kept intact (not sentence-split) and tagged with a
doc_type so retrieval and generation can treat them differently:
from ragarena import MultimodalDocument, evaluate
docs = [
MultimodalDocument(content="| model | params |", doc_type="table"),
MultimodalDocument(content="E = mc^2", doc_type="equation"),
{"text": "RAG grounds LLMs in retrieved context.", "metadata": {"doc_type": "text"}},
]
evaluate(questions=["..."], documents=docs, strategy="multimodal",
model="openai/gpt-4o-mini")
📐 Metrics
Presets: quick · quality · full · production — or cherry-pick:
metrics=["context_precision", "context_recall", "hit_rate", "mrr", # retrieval
"faithfulness", "answer_relevance", "answer_correctness", # generation (LLM-judge)
"latency_s", "cost_usd", "total_tokens"] # operational
Any model can be the judge: judge_model="anthropic/claude-3-haiku-20240307".
Retrieval metrics use real embedding similarity, not keyword matching. When an
embedding_model is set, context_precision/context_recall/hit_rate/mrr score
chunk relevance via cosine similarity — so a paraphrased-but-relevant chunk scores
correctly even with zero shared keywords. (Falls back to lexical overlap if no embedding
model is configured.) Tune the semantic threshold with RAGARENA_RELEVANCE_THRESHOLD
(default 0.55).
Reduce LLM-judge variance with multi-sampling. A single judge call can be noisy —
pass judge_samples=3 to average 3 independent judge calls (with temperature jitter)
instead of trusting one sample:
evaluate(questions=[...], documents=docs, judge_samples=3)
# report includes score_stdev and all_scores per metric
🗄️ Benchmark the vector DB itself
The vector store is swappable, so you can hold the corpus, embeddings and strategy fixed and measure what the DB choice alone costs you:
from ragarena import VectorIndex, evaluate, list_backends
print(list_backends()) # which backends are installed & importable here
for backend in ["numpy", "faiss", "qdrant", "lancedb", "chroma"]:
index = VectorIndex(embedding_model="openai/text-embedding-3-small", backend=backend)
index.add_documents(docs)
report = evaluate(questions=my_questions, index=index, strategy="hybrid")
print(backend, report.summary())
Nine backends, all verified against live servers. Runnable with no server:
numpy (default, exact cosine), faiss, chroma, qdrant (in-memory), lancedb.
Server-backed: qdrant, chroma, weaviate, elasticsearch, redis, pgvector —
pass connection details as kwargs:
VectorIndex(embedding_model=..., backend="pgvector", dsn="postgresql://user:pw@host/db")
VectorIndex(embedding_model=..., backend="weaviate", url="http://localhost:8080")
VectorIndex(embedding_model=..., backend="elasticsearch", url="http://localhost:9200")
VectorIndex(embedding_model=..., backend="redis", url="redis://localhost:6379")
VectorIndex(embedding_model=..., backend="qdrant", url="http://localhost:6333")
VectorIndex(embedding_model=..., backend="chroma", host="localhost", port=8000)
All nine return identical similarity scores on the same corpus, so a comparison measures the store, not an accidental difference in how it was configured.
A note on Elasticsearch defaults. Since 8.12 an indexed
dense_vectordefaults toint8_hnsw, which quantizes vectors to 8 bits and shifts similarities by ~0.4% — enough to reorder documents that are genuinely close, silently scoring a different retrieval than the one you meant to measure. RagArena therefore defaults to exact search (index_type="flat"); passindex_type="hnsw"or"int8_hnsw"explicitly when you actually want to benchmark ANN behaviour at scale.
register_backend() adds your own. RagArena backends lists them from the CLI.
✅ Regression-check runs, gate CI on quality
from ragarena import evaluate, diff_runs, EvaluationReport
from ragarena.testing import assert_metric, assert_no_regression
report = evaluate(questions=Q, documents=DOCS, reference_answers=REFS, strategy="hybrid")
assert_metric(report, "faithfulness", gte=0.8) # fails like a normal assert if it drops below
report.save("latest.json")
old = EvaluationReport.load("baseline.json")
assert_no_regression(diff_runs(old, report)) # fails if any metric got worse
RagArena diff --a baseline.json --b latest.json does the same check from the CLI.
📦 Use inside your existing pipeline
Already have a RAG system? Score it directly:
from ragarena import VectorIndex
from ragarena.engine import EvalSample, MetricContext
from ragarena.metrics import resolve_metrics
# ...run YOUR pipeline to get answer + chunks...
sample = EvalSample(question=q, reference_answer=gt,
generated_answer=your_answer,
retrieved_chunks=[{"text": c} for c in your_chunks],
context="...", usage={...}, latency_s=..., intermediate={})
for m in resolve_metrics("full"):
print(m.name, m.compute(sample, MetricContext(judge_model="openai/gpt-4o-mini")).score)
HTTP API
curl -X POST localhost:4000/api/evaluate -H 'Content-Type: application/json' -d '{
"questions": ["What is RAG?"],
"documents": [{"text": "RAG grounds LLM answers in documents."}],
"strategy": "hybrid",
"model": "openai/gpt-4o-mini",
"embedding_model": "voyage/voyage-3"
}'
Also available: POST /api/compare · GET /api/catalog · GET /api/runs/{id} · GET /health
Roadmap
- Async batch runner + checkpoint/resume
- Statistical significance tests (paired bootstrap) between configs
- Chroma/Pinecone/Qdrant backends for
VectorIndex - Graph RAG incremental updates + community-aware re-indexing
- Cloud/blob storage ingestion (S3, Azure Blob, GCS) alongside local
parse_dir() - Prompt-optimization loop (DSPy-style)
- Team features: API keys, budgets, RBAC
- CI mode:
ragarena ci --threshold faithfulness>=0.8(fails PRs on regressions)
Contributing
PRs welcome! pip install -e ".[dev]" && pytest. Please read CONTRIBUTING.md.
License
MIT © RagArena contributors
Metadata
Release files for ragarena 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ragarena-0.6.0.tar.gz | 341.3 kB | Without provenance | Details for ragarena-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ragarena-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for ragarena-0.6.0-py3-none-any.whl |
Total release size: 723.4 kB
Release files / ragarena-0.6.0.tar.gz
| Download URL | ragarena-0.6.0.tar.gz |
|---|---|
| Size | 341.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f42a41edc23676fb888f4a8c425cf78fa6924f6134930d1862e5a06857e8b369
|
|
BLAKE2b-256 checksum How to use checksums |
2d2728daa23f5a2f667a1ade3de53e9568f19018cb3ef8b597b26bf9bf70f549
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.11
|
Release files / ragarena-0.6.0-py3-none-any.whl
| Download URL | ragarena-0.6.0-py3-none-any.whl |
|---|---|
| Size | 382.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
08519de9054f15e85fa7572aaae0da71d75e206b29897745c1d8e7a3ba2adb12
|
|
BLAKE2b-256 checksum How to use checksums |
00dcf62fe522f42c9c31cf425e2332172e1152a8e92c1625dd32df908beea692
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.11
|
Security for ragarena 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.