rag-scorer 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rag_scorer-0.1.0/.gitignore +219 -0
- rag_scorer-0.1.0/LICENSE +21 -0
- rag_scorer-0.1.0/PKG-INFO +218 -0
- rag_scorer-0.1.0/README.md +180 -0
- rag_scorer-0.1.0/configs/models.yaml +25 -0
- rag_scorer-0.1.0/configs/prompts.yaml +16 -0
- rag_scorer-0.1.0/configs/thresholds.yaml +16 -0
- rag_scorer-0.1.0/evalrag/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/cli.py +135 -0
- rag_scorer-0.1.0/evalrag/config.py +156 -0
- rag_scorer-0.1.0/evalrag/demo_adapter.py +34 -0
- rag_scorer-0.1.0/evalrag/gating.py +47 -0
- rag_scorer-0.1.0/evalrag/generator/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/generator/__main__.py +60 -0
- rag_scorer-0.1.0/evalrag/generator/synthesize.py +68 -0
- rag_scorer-0.1.0/evalrag/judge/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/judge/cache.py +42 -0
- rag_scorer-0.1.0/evalrag/judge/llm_client.py +58 -0
- rag_scorer-0.1.0/evalrag/judge/openrouter_provider.py +38 -0
- rag_scorer-0.1.0/evalrag/judge/parsing.py +62 -0
- rag_scorer-0.1.0/evalrag/judge/prompts.py +23 -0
- rag_scorer-0.1.0/evalrag/judge/provider.py +50 -0
- rag_scorer-0.1.0/evalrag/loaders.py +83 -0
- rag_scorer-0.1.0/evalrag/prompts/.gitkeep +0 -0
- rag_scorer-0.1.0/evalrag/prompts/answer_relevance_v1.md +32 -0
- rag_scorer-0.1.0/evalrag/prompts/context_precision_v1.md +31 -0
- rag_scorer-0.1.0/evalrag/prompts/faithfulness_v1.md +33 -0
- rag_scorer-0.1.0/evalrag/prompts/generation_v1.md +21 -0
- rag_scorer-0.1.0/evalrag/report/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/report/__main__.py +57 -0
- rag_scorer-0.1.0/evalrag/report/aggregate.py +56 -0
- rag_scorer-0.1.0/evalrag/report/html_report.py +105 -0
- rag_scorer-0.1.0/evalrag/report/json_report.py +44 -0
- rag_scorer-0.1.0/evalrag/runner/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/runner/__main__.py +25 -0
- rag_scorer-0.1.0/evalrag/runner/adapter.py +40 -0
- rag_scorer-0.1.0/evalrag/runner/loop.py +63 -0
- rag_scorer-0.1.0/evalrag/scorer/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/scorer/__main__.py +37 -0
- rag_scorer-0.1.0/evalrag/scorer/answer_relevance.py +50 -0
- rag_scorer-0.1.0/evalrag/scorer/context_precision.py +102 -0
- rag_scorer-0.1.0/evalrag/scorer/faithfulness.py +108 -0
- rag_scorer-0.1.0/evalrag/scorer/run_all.py +47 -0
- rag_scorer-0.1.0/evalrag/types.py +51 -0
- rag_scorer-0.1.0/evalrag/validation/__init__.py +0 -0
- rag_scorer-0.1.0/evalrag/validation/__main__.py +59 -0
- rag_scorer-0.1.0/evalrag/validation/agreement.py +89 -0
- rag_scorer-0.1.0/evalrag/validation/harness.py +101 -0
- rag_scorer-0.1.0/pyproject.toml +54 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
.idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
.DS_Store
|
rag_scorer-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Monteverdi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rag-scorer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A from-scratch CLI evaluation framework for RAG pipelines.
|
|
5
|
+
Project-URL: Homepage, https://github.com/lucasmonteverdi1/evalrag
|
|
6
|
+
Project-URL: Repository, https://github.com/lucasmonteverdi1/evalrag
|
|
7
|
+
Author: Lucas Monteverdi
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Lucas Monteverdi
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: evaluation,llm,llm-as-judge,rag,retrieval
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Requires-Dist: openai>=1.0
|
|
36
|
+
Requires-Dist: pyyaml>=6.0
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# EvalRAG
|
|
40
|
+
|
|
41
|
+
A from-scratch CLI + library for evaluating **RAG pipelines** — no RAGAS, TruLens, or
|
|
42
|
+
DeepEval wrappers.
|
|
43
|
+
|
|
44
|
+
Answers *"how do I know my RAG pipeline is actually working well?"* by producing
|
|
45
|
+
automated, reproducible quality scores across three metrics:
|
|
46
|
+
|
|
47
|
+
- **Faithfulness** — is every claim in the answer grounded in the retrieved chunks?
|
|
48
|
+
- **Answer relevance** — does the answer actually address the question?
|
|
49
|
+
- **Context precision** — did the retriever surface relevant chunks, ranked high?
|
|
50
|
+
|
|
51
|
+
Every score is traceable to an explicit, inspectable decision (the raw judge output is
|
|
52
|
+
kept). It exits non-zero when a metric falls below its threshold, so it works as a **CI
|
|
53
|
+
gate**.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# From PyPI (the import package is `evalrag`):
|
|
59
|
+
pip install rag-scorer
|
|
60
|
+
|
|
61
|
+
# Or straight from GitHub, no clone:
|
|
62
|
+
pip install git+https://github.com/lucasmonteverdi1/evalrag.git
|
|
63
|
+
|
|
64
|
+
# Or with uv, from a clone:
|
|
65
|
+
uv sync --all-groups
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You need an [OpenRouter](https://openrouter.ai) API key (one key, any vendor's models):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export OPENROUTER_API_KEY="sk-or-..."
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Evaluate your RAG pipeline
|
|
75
|
+
|
|
76
|
+
### 1. Write an adapter
|
|
77
|
+
|
|
78
|
+
EvalRAG treats your pipeline as a black box. Implement one method — `run(question)` that
|
|
79
|
+
returns `(retrieved_chunks, generated_answer)`:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# my_eval.py
|
|
83
|
+
from evalrag.types import Chunk
|
|
84
|
+
|
|
85
|
+
class MyRAGAdapter:
|
|
86
|
+
def run(self, question: str) -> tuple[list[Chunk], str]:
|
|
87
|
+
# Whatever your pipeline is (LangChain, LlamaIndex, raw code — doesn't matter):
|
|
88
|
+
docs = my_retriever.search(question)
|
|
89
|
+
answer = my_llm.generate(question, docs)
|
|
90
|
+
return [Chunk(id=d.id, text=d.text) for d in docs], answer
|
|
91
|
+
|
|
92
|
+
adapter = MyRAGAdapter() # module-level instance evalrag will import
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. Provide questions
|
|
96
|
+
|
|
97
|
+
A JSON list of questions to evaluate. `expected_answer` / `source_chunk_id` are optional
|
|
98
|
+
(the latter enables context precision's deterministic, no-LLM path):
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
[
|
|
102
|
+
{"question": "What is the capital of France?", "source_chunk_id": "doc-42"},
|
|
103
|
+
{"question": "What is the return policy?"}
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Run
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
evalrag --adapter my_eval:adapter --inputs questions.json --out-dir eval-out
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This runs your pipeline over each question, scores all three metrics, writes
|
|
114
|
+
`eval-out/report.json` + `eval-out/report.html`, and exits **0** if every metric is at
|
|
115
|
+
or above its threshold, **1** if any falls below, **2** on a config/usage error.
|
|
116
|
+
|
|
117
|
+
> **Note:** `--adapter` imports and runs the module you name, so only pass a spec you
|
|
118
|
+
> trust (like `pytest --plugin`). Don't derive it from untrusted input.
|
|
119
|
+
|
|
120
|
+
Don't have questions yet? Generate them from your documents instead of `--inputs`:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
evalrag --adapter my_eval:adapter --generate documents.json
|
|
124
|
+
# documents.json: [{"id": "doc-42", "text": "..."}, ...]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Try it with no code using the bundled demo adapter:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
echo '[{"question":"What is the capital of France?"}]' > q.json
|
|
131
|
+
evalrag --adapter evalrag.demo_adapter:demo_adapter --inputs q.json
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Reading the reports
|
|
135
|
+
|
|
136
|
+
Each run writes two files to `--out-dir`:
|
|
137
|
+
|
|
138
|
+
**`report.html`** — open in a browser. Top section is a **per-metric score table** plus
|
|
139
|
+
an *overall* number (informational only — gating is per-metric, not on the overall).
|
|
140
|
+
Below that, each evaluated case shows its question, the pipeline's answer, and every
|
|
141
|
+
metric's score + rationale. Expand **"raw judge output"** on any metric to see the
|
|
142
|
+
judge's verbatim reasoning — this is the traceability guarantee: no score is a black box.
|
|
143
|
+
|
|
144
|
+
**`report.json`** — the same data, machine-readable, for dashboards or diffing across
|
|
145
|
+
runs:
|
|
146
|
+
|
|
147
|
+
```jsonc
|
|
148
|
+
{
|
|
149
|
+
"summary": {
|
|
150
|
+
"n_cases": 2,
|
|
151
|
+
"per_metric": { "faithfulness": 1.0, "answer_relevance": 1.0, "context_precision": 1.0 },
|
|
152
|
+
"overall": 1.0 // informational, NOT used for gating
|
|
153
|
+
},
|
|
154
|
+
"cases": [
|
|
155
|
+
{
|
|
156
|
+
"question": "...",
|
|
157
|
+
"generated_answer": "...",
|
|
158
|
+
"metrics": [
|
|
159
|
+
{ "metric": "faithfulness", "score": 1.0, "rationale": "1/1 claims grounded",
|
|
160
|
+
"raw_judge_output": "..." } // the judge's full response, kept verbatim
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**How to read the numbers:** each metric is 0.0–1.0, higher is better. A metric **fails**
|
|
168
|
+
(and the run exits non-zero) when its mean score is below the threshold in
|
|
169
|
+
`configs/thresholds.yaml`. The terminal prints a `[PASS]`/`[FAIL]` line per metric and a
|
|
170
|
+
final `Result: PASS`/`FAIL`.
|
|
171
|
+
|
|
172
|
+
## Use in CI
|
|
173
|
+
|
|
174
|
+
`evalrag`'s exit code gates the build:
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
# .github/workflows/eval.yml
|
|
178
|
+
- run: pip install git+https://github.com/lucasmonteverdi1/evalrag.git
|
|
179
|
+
- run: evalrag --adapter my_eval:adapter --inputs questions.json
|
|
180
|
+
env:
|
|
181
|
+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Thresholds live in `configs/thresholds.yaml` (override with `--thresholds-config`).
|
|
185
|
+
|
|
186
|
+
## Use as a library
|
|
187
|
+
|
|
188
|
+
Every metric is importable and takes `(EvalCase, judge)`:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from evalrag.scorer.faithfulness import score_faithfulness
|
|
192
|
+
result = score_faithfulness(eval_case, judge) # -> MetricResult(score, rationale, ...)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Configuration
|
|
196
|
+
|
|
197
|
+
- `configs/models.yaml` — judge and system-under-test models (the judge **must** differ
|
|
198
|
+
from the model being evaluated, to avoid self-preference bias). Secrets come from env
|
|
199
|
+
vars named here (`api_key_env`), never stored in YAML.
|
|
200
|
+
- `configs/thresholds.yaml` — per-metric pass thresholds.
|
|
201
|
+
- `configs/prompts.yaml` — pinned judge-prompt versions.
|
|
202
|
+
|
|
203
|
+
## Troubleshooting
|
|
204
|
+
|
|
205
|
+
**`ModuleNotFoundError: No module named 'evalrag.cli'` during local development.**
|
|
206
|
+
Only affects the editable dev install (`uv sync`), never a `pip install` of the package.
|
|
207
|
+
It happens if you mix `uv pip install/uninstall` with `uv sync`. Reset the environment:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
rm -rf .venv && uv sync --all-groups
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Then use `uv run evalrag ...`. (Don't mix `uv pip` and `uv sync` in the same venv.)
|
|
214
|
+
|
|
215
|
+
## Design
|
|
216
|
+
|
|
217
|
+
See [AGENTS.md](AGENTS.md) for the full architecture, metric definitions, and the
|
|
218
|
+
scorer-first build order.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# EvalRAG
|
|
2
|
+
|
|
3
|
+
A from-scratch CLI + library for evaluating **RAG pipelines** — no RAGAS, TruLens, or
|
|
4
|
+
DeepEval wrappers.
|
|
5
|
+
|
|
6
|
+
Answers *"how do I know my RAG pipeline is actually working well?"* by producing
|
|
7
|
+
automated, reproducible quality scores across three metrics:
|
|
8
|
+
|
|
9
|
+
- **Faithfulness** — is every claim in the answer grounded in the retrieved chunks?
|
|
10
|
+
- **Answer relevance** — does the answer actually address the question?
|
|
11
|
+
- **Context precision** — did the retriever surface relevant chunks, ranked high?
|
|
12
|
+
|
|
13
|
+
Every score is traceable to an explicit, inspectable decision (the raw judge output is
|
|
14
|
+
kept). It exits non-zero when a metric falls below its threshold, so it works as a **CI
|
|
15
|
+
gate**.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# From PyPI (the import package is `evalrag`):
|
|
21
|
+
pip install rag-scorer
|
|
22
|
+
|
|
23
|
+
# Or straight from GitHub, no clone:
|
|
24
|
+
pip install git+https://github.com/lucasmonteverdi1/evalrag.git
|
|
25
|
+
|
|
26
|
+
# Or with uv, from a clone:
|
|
27
|
+
uv sync --all-groups
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You need an [OpenRouter](https://openrouter.ai) API key (one key, any vendor's models):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export OPENROUTER_API_KEY="sk-or-..."
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Evaluate your RAG pipeline
|
|
37
|
+
|
|
38
|
+
### 1. Write an adapter
|
|
39
|
+
|
|
40
|
+
EvalRAG treats your pipeline as a black box. Implement one method — `run(question)` that
|
|
41
|
+
returns `(retrieved_chunks, generated_answer)`:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# my_eval.py
|
|
45
|
+
from evalrag.types import Chunk
|
|
46
|
+
|
|
47
|
+
class MyRAGAdapter:
|
|
48
|
+
def run(self, question: str) -> tuple[list[Chunk], str]:
|
|
49
|
+
# Whatever your pipeline is (LangChain, LlamaIndex, raw code — doesn't matter):
|
|
50
|
+
docs = my_retriever.search(question)
|
|
51
|
+
answer = my_llm.generate(question, docs)
|
|
52
|
+
return [Chunk(id=d.id, text=d.text) for d in docs], answer
|
|
53
|
+
|
|
54
|
+
adapter = MyRAGAdapter() # module-level instance evalrag will import
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Provide questions
|
|
58
|
+
|
|
59
|
+
A JSON list of questions to evaluate. `expected_answer` / `source_chunk_id` are optional
|
|
60
|
+
(the latter enables context precision's deterministic, no-LLM path):
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
[
|
|
64
|
+
{"question": "What is the capital of France?", "source_chunk_id": "doc-42"},
|
|
65
|
+
{"question": "What is the return policy?"}
|
|
66
|
+
]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Run
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
evalrag --adapter my_eval:adapter --inputs questions.json --out-dir eval-out
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This runs your pipeline over each question, scores all three metrics, writes
|
|
76
|
+
`eval-out/report.json` + `eval-out/report.html`, and exits **0** if every metric is at
|
|
77
|
+
or above its threshold, **1** if any falls below, **2** on a config/usage error.
|
|
78
|
+
|
|
79
|
+
> **Note:** `--adapter` imports and runs the module you name, so only pass a spec you
|
|
80
|
+
> trust (like `pytest --plugin`). Don't derive it from untrusted input.
|
|
81
|
+
|
|
82
|
+
Don't have questions yet? Generate them from your documents instead of `--inputs`:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
evalrag --adapter my_eval:adapter --generate documents.json
|
|
86
|
+
# documents.json: [{"id": "doc-42", "text": "..."}, ...]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Try it with no code using the bundled demo adapter:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
echo '[{"question":"What is the capital of France?"}]' > q.json
|
|
93
|
+
evalrag --adapter evalrag.demo_adapter:demo_adapter --inputs q.json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Reading the reports
|
|
97
|
+
|
|
98
|
+
Each run writes two files to `--out-dir`:
|
|
99
|
+
|
|
100
|
+
**`report.html`** — open in a browser. Top section is a **per-metric score table** plus
|
|
101
|
+
an *overall* number (informational only — gating is per-metric, not on the overall).
|
|
102
|
+
Below that, each evaluated case shows its question, the pipeline's answer, and every
|
|
103
|
+
metric's score + rationale. Expand **"raw judge output"** on any metric to see the
|
|
104
|
+
judge's verbatim reasoning — this is the traceability guarantee: no score is a black box.
|
|
105
|
+
|
|
106
|
+
**`report.json`** — the same data, machine-readable, for dashboards or diffing across
|
|
107
|
+
runs:
|
|
108
|
+
|
|
109
|
+
```jsonc
|
|
110
|
+
{
|
|
111
|
+
"summary": {
|
|
112
|
+
"n_cases": 2,
|
|
113
|
+
"per_metric": { "faithfulness": 1.0, "answer_relevance": 1.0, "context_precision": 1.0 },
|
|
114
|
+
"overall": 1.0 // informational, NOT used for gating
|
|
115
|
+
},
|
|
116
|
+
"cases": [
|
|
117
|
+
{
|
|
118
|
+
"question": "...",
|
|
119
|
+
"generated_answer": "...",
|
|
120
|
+
"metrics": [
|
|
121
|
+
{ "metric": "faithfulness", "score": 1.0, "rationale": "1/1 claims grounded",
|
|
122
|
+
"raw_judge_output": "..." } // the judge's full response, kept verbatim
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**How to read the numbers:** each metric is 0.0–1.0, higher is better. A metric **fails**
|
|
130
|
+
(and the run exits non-zero) when its mean score is below the threshold in
|
|
131
|
+
`configs/thresholds.yaml`. The terminal prints a `[PASS]`/`[FAIL]` line per metric and a
|
|
132
|
+
final `Result: PASS`/`FAIL`.
|
|
133
|
+
|
|
134
|
+
## Use in CI
|
|
135
|
+
|
|
136
|
+
`evalrag`'s exit code gates the build:
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
# .github/workflows/eval.yml
|
|
140
|
+
- run: pip install git+https://github.com/lucasmonteverdi1/evalrag.git
|
|
141
|
+
- run: evalrag --adapter my_eval:adapter --inputs questions.json
|
|
142
|
+
env:
|
|
143
|
+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Thresholds live in `configs/thresholds.yaml` (override with `--thresholds-config`).
|
|
147
|
+
|
|
148
|
+
## Use as a library
|
|
149
|
+
|
|
150
|
+
Every metric is importable and takes `(EvalCase, judge)`:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from evalrag.scorer.faithfulness import score_faithfulness
|
|
154
|
+
result = score_faithfulness(eval_case, judge) # -> MetricResult(score, rationale, ...)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
- `configs/models.yaml` — judge and system-under-test models (the judge **must** differ
|
|
160
|
+
from the model being evaluated, to avoid self-preference bias). Secrets come from env
|
|
161
|
+
vars named here (`api_key_env`), never stored in YAML.
|
|
162
|
+
- `configs/thresholds.yaml` — per-metric pass thresholds.
|
|
163
|
+
- `configs/prompts.yaml` — pinned judge-prompt versions.
|
|
164
|
+
|
|
165
|
+
## Troubleshooting
|
|
166
|
+
|
|
167
|
+
**`ModuleNotFoundError: No module named 'evalrag.cli'` during local development.**
|
|
168
|
+
Only affects the editable dev install (`uv sync`), never a `pip install` of the package.
|
|
169
|
+
It happens if you mix `uv pip install/uninstall` with `uv sync`. Reset the environment:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
rm -rf .venv && uv sync --all-groups
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Then use `uv run evalrag ...`. (Don't mix `uv pip` and `uv sync` in the same venv.)
|
|
176
|
+
|
|
177
|
+
## Design
|
|
178
|
+
|
|
179
|
+
See [AGENTS.md](AGENTS.md) for the full architecture, metric definitions, and the
|
|
180
|
+
scorer-first build order.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
judge:
|
|
2
|
+
provider: openrouter
|
|
3
|
+
base_url: https://openrouter.ai/api/v1
|
|
4
|
+
api_key_env: OPENROUTER_API_KEY # names WHICH env var holds the key; value never in YAML
|
|
5
|
+
model: google/gemini-2.5-flash
|
|
6
|
+
temperature: 0 # Reproducibility
|
|
7
|
+
max_tokens: 1024
|
|
8
|
+
timeout_seconds: 30
|
|
9
|
+
max_retries: 3
|
|
10
|
+
|
|
11
|
+
# The system under test: the RAG pipeline's answer-generating model — the one being
|
|
12
|
+
# evaluated. MUST differ from the judge (self-preference bias). Not to be confused with
|
|
13
|
+
# evalrag/generator/, which synthesizes evaluation questions.
|
|
14
|
+
system_under_test:
|
|
15
|
+
provider: openrouter
|
|
16
|
+
base_url: https://openrouter.ai/api/v1
|
|
17
|
+
api_key_env: OPENROUTER_API_KEY
|
|
18
|
+
model: openai/gpt-4o
|
|
19
|
+
temperature: 0
|
|
20
|
+
max_tokens: 512
|
|
21
|
+
|
|
22
|
+
# Response cache — all LLM calls keyed by (provider, model, prompt_version, inputs).
|
|
23
|
+
cache:
|
|
24
|
+
enabled: true
|
|
25
|
+
dir: .cache/llm_responses
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Pinned prompt versions.
|
|
2
|
+
# The judge client loads prompts from prompts/<metric>_<version>.md.
|
|
3
|
+
# Bump the version string here to switch prompts for all future runs;
|
|
4
|
+
# old runs remain reproducible because the cache key includes the version.
|
|
5
|
+
|
|
6
|
+
# MVP metrics
|
|
7
|
+
faithfulness: v1
|
|
8
|
+
answer_relevance: v1
|
|
9
|
+
context_precision: v1
|
|
10
|
+
|
|
11
|
+
# Synthetic data generation (used by the generator, not a metric)
|
|
12
|
+
generation: v1
|
|
13
|
+
|
|
14
|
+
# Optional metrics (uncomment when implemented)
|
|
15
|
+
# answer_correctness: v1
|
|
16
|
+
# context_recall: v1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Per-metric pass thresholds (0.0–1.0).
|
|
2
|
+
# The CLI exits non-zero when any enabled metric's aggregate score falls below
|
|
3
|
+
# its threshold. Set a threshold to null to disable gating for that metric.
|
|
4
|
+
|
|
5
|
+
# MVP metrics
|
|
6
|
+
faithfulness: 0.8
|
|
7
|
+
answer_relevance: 0.7
|
|
8
|
+
context_precision: 0.7
|
|
9
|
+
|
|
10
|
+
# Optional metrics (not yet implemented; null = not gated)
|
|
11
|
+
answer_correctness: null
|
|
12
|
+
context_recall: null
|
|
13
|
+
|
|
14
|
+
# When strict_mode is true, ANY individual EvalCase below threshold fails the
|
|
15
|
+
# run (not just the aggregate). Useful for catching catastrophic regressions.
|
|
16
|
+
strict_mode: false
|
|
File without changes
|