ragdiag 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.
- ragdiag-0.1.0/.github/workflows/publish.yml +166 -0
- ragdiag-0.1.0/.github/workflows/test.yml +42 -0
- ragdiag-0.1.0/.gitignore +51 -0
- ragdiag-0.1.0/LICENSE +21 -0
- ragdiag-0.1.0/PKG-INFO +407 -0
- ragdiag-0.1.0/README.md +378 -0
- ragdiag-0.1.0/examples/README.md +107 -0
- ragdiag-0.1.0/examples/basic_dataset.json +41 -0
- ragdiag-0.1.0/examples/basic_pipeline.py +89 -0
- ragdiag-0.1.0/examples/demo_dataset.json +55 -0
- ragdiag-0.1.0/examples/dense_pipeline.py +83 -0
- ragdiag-0.1.0/examples/hybrid_pipeline.py +93 -0
- ragdiag-0.1.0/pyproject.toml +72 -0
- ragdiag-0.1.0/src/ragdiag/__init__.py +48 -0
- ragdiag-0.1.0/src/ragdiag/cli/__init__.py +7 -0
- ragdiag-0.1.0/src/ragdiag/cli/main.py +342 -0
- ragdiag-0.1.0/src/ragdiag/comparison/__init__.py +20 -0
- ragdiag-0.1.0/src/ragdiag/comparison/comparator.py +548 -0
- ragdiag-0.1.0/src/ragdiag/comparison/models.py +124 -0
- ragdiag-0.1.0/src/ragdiag/comparison/terminal.py +217 -0
- ragdiag-0.1.0/src/ragdiag/dataset/__init__.py +23 -0
- ragdiag-0.1.0/src/ragdiag/dataset/exceptions.py +17 -0
- ragdiag-0.1.0/src/ragdiag/dataset/loader.py +72 -0
- ragdiag-0.1.0/src/ragdiag/dataset/validator.py +80 -0
- ragdiag-0.1.0/src/ragdiag/diagnosis/__init__.py +26 -0
- ragdiag-0.1.0/src/ragdiag/diagnosis/classifier.py +169 -0
- ragdiag-0.1.0/src/ragdiag/diagnosis/models.py +48 -0
- ragdiag-0.1.0/src/ragdiag/diagnosis/rules.py +208 -0
- ragdiag-0.1.0/src/ragdiag/judges/__init__.py +22 -0
- ragdiag-0.1.0/src/ragdiag/judges/base.py +61 -0
- ragdiag-0.1.0/src/ragdiag/judges/exceptions.py +17 -0
- ragdiag-0.1.0/src/ragdiag/judges/models.py +35 -0
- ragdiag-0.1.0/src/ragdiag/judges/openai.py +157 -0
- ragdiag-0.1.0/src/ragdiag/metrics/__init__.py +29 -0
- ragdiag-0.1.0/src/ragdiag/metrics/aggregation.py +175 -0
- ragdiag-0.1.0/src/ragdiag/metrics/latency.py +83 -0
- ragdiag-0.1.0/src/ragdiag/metrics/models.py +74 -0
- ragdiag-0.1.0/src/ragdiag/metrics/retrieval.py +132 -0
- ragdiag-0.1.0/src/ragdiag/models/__init__.py +14 -0
- ragdiag-0.1.0/src/ragdiag/models/chunk.py +19 -0
- ragdiag-0.1.0/src/ragdiag/models/dataset.py +41 -0
- ragdiag-0.1.0/src/ragdiag/models/result.py +37 -0
- ragdiag-0.1.0/src/ragdiag/models/sample.py +59 -0
- ragdiag-0.1.0/src/ragdiag/pipeline/__init__.py +12 -0
- ragdiag-0.1.0/src/ragdiag/pipeline/base.py +57 -0
- ragdiag-0.1.0/src/ragdiag/pipeline/exceptions.py +11 -0
- ragdiag-0.1.0/src/ragdiag/pipeline/loader.py +65 -0
- ragdiag-0.1.0/src/ragdiag/reporting/__init__.py +23 -0
- ragdiag-0.1.0/src/ragdiag/reporting/aggregator.py +335 -0
- ragdiag-0.1.0/src/ragdiag/reporting/insights.py +149 -0
- ragdiag-0.1.0/src/ragdiag/reporting/models.py +131 -0
- ragdiag-0.1.0/src/ragdiag/reporting/terminal.py +142 -0
- ragdiag-0.1.0/src/ragdiag/runner/__init__.py +7 -0
- ragdiag-0.1.0/src/ragdiag/runner/evaluator.py +234 -0
- ragdiag-0.1.0/tests/__init__.py +1 -0
- ragdiag-0.1.0/tests/test_comparison.py +723 -0
- ragdiag-0.1.0/tests/test_dataset.py +456 -0
- ragdiag-0.1.0/tests/test_diagnosis.py +644 -0
- ragdiag-0.1.0/tests/test_judges.py +509 -0
- ragdiag-0.1.0/tests/test_metrics.py +413 -0
- ragdiag-0.1.0/tests/test_models.py +261 -0
- ragdiag-0.1.0/tests/test_pipeline.py +69 -0
- ragdiag-0.1.0/tests/test_pipeline_loader.py +90 -0
- ragdiag-0.1.0/tests/test_reporting.py +601 -0
- ragdiag-0.1.0/tests/test_runner.py +305 -0
- ragdiag-0.1.0/uv.lock +489 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: "Target registry environment"
|
|
10
|
+
required: true
|
|
11
|
+
type: choice
|
|
12
|
+
default: "testpypi"
|
|
13
|
+
options:
|
|
14
|
+
- "testpypi"
|
|
15
|
+
- "pypi"
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
name: Build distribution packages
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
steps:
|
|
27
|
+
- name: Check out repository
|
|
28
|
+
uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- name: Set up Python 3.12
|
|
31
|
+
uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
|
|
35
|
+
- name: Install PyPA build tooling
|
|
36
|
+
run: |
|
|
37
|
+
python -m pip install --upgrade pip build twine
|
|
38
|
+
|
|
39
|
+
- name: Verify package version safety
|
|
40
|
+
env:
|
|
41
|
+
EVENT_NAME: ${{ github.event_name }}
|
|
42
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
43
|
+
REF_NAME: ${{ github.ref_name }}
|
|
44
|
+
REF_TYPE: ${{ github.ref_type }}
|
|
45
|
+
run: |
|
|
46
|
+
python - << 'EOF'
|
|
47
|
+
import os, sys, tomllib, re
|
|
48
|
+
|
|
49
|
+
# 1. Read declared pyproject.toml version
|
|
50
|
+
with open("pyproject.toml", "rb") as f:
|
|
51
|
+
pyproject_data = tomllib.load(f)
|
|
52
|
+
pkg_version = pyproject_data.get("project", {}).get("version")
|
|
53
|
+
if not pkg_version:
|
|
54
|
+
sys.exit("ERROR: Could not find [project].version in pyproject.toml")
|
|
55
|
+
|
|
56
|
+
# 2. Read declared src/ragdiag/__init__.py __version__
|
|
57
|
+
with open("src/ragdiag/__init__.py", "r", encoding="utf-8") as f:
|
|
58
|
+
init_content = f.read()
|
|
59
|
+
init_match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', init_content)
|
|
60
|
+
init_version = init_match.group(1) if init_match else None
|
|
61
|
+
|
|
62
|
+
print(f"pyproject.toml version: {pkg_version}")
|
|
63
|
+
print(f"src/ragdiag/__init__.py version: {init_version}")
|
|
64
|
+
|
|
65
|
+
if pkg_version != init_version:
|
|
66
|
+
sys.exit(
|
|
67
|
+
f"ERROR: Version mismatch inside repository!\n"
|
|
68
|
+
f" pyproject.toml: {pkg_version}\n"
|
|
69
|
+
f" src/ragdiag/__init__.py: {init_version}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 3. If triggered by a release or git tag, strictly verify tag match
|
|
73
|
+
event_name = os.environ.get("EVENT_NAME", "")
|
|
74
|
+
release_tag = os.environ.get("RELEASE_TAG", "").strip()
|
|
75
|
+
ref_name = os.environ.get("REF_NAME", "").strip()
|
|
76
|
+
ref_type = os.environ.get("REF_TYPE", "").strip()
|
|
77
|
+
|
|
78
|
+
tag = release_tag or (ref_name if ref_type == "tag" else "")
|
|
79
|
+
|
|
80
|
+
if tag:
|
|
81
|
+
normalized_tag = tag[1:] if tag.startswith("v") else tag
|
|
82
|
+
if normalized_tag != pkg_version:
|
|
83
|
+
sys.exit(
|
|
84
|
+
f"ERROR: Release tag does not match package version!\n"
|
|
85
|
+
f" Git Tag: {tag} (normalized: {normalized_tag})\n"
|
|
86
|
+
f" Package Version: {pkg_version}\n"
|
|
87
|
+
f"Publishing aborted to prevent mismatched artifact release."
|
|
88
|
+
)
|
|
89
|
+
print(f"SUCCESS: Git tag '{tag}' matches package version '{pkg_version}'.")
|
|
90
|
+
else:
|
|
91
|
+
print(f"INFO: Workflow run without git tag (event: {event_name}). Internal version consistency verified.")
|
|
92
|
+
EOF
|
|
93
|
+
|
|
94
|
+
- name: Build sdist and wheel
|
|
95
|
+
run: python -m build
|
|
96
|
+
|
|
97
|
+
- name: Verify build artifacts
|
|
98
|
+
run: |
|
|
99
|
+
python - << 'EOF'
|
|
100
|
+
import os, sys
|
|
101
|
+
|
|
102
|
+
dist_files = sorted(os.listdir("dist"))
|
|
103
|
+
print("Artifacts generated in dist/:", dist_files)
|
|
104
|
+
|
|
105
|
+
has_wheel = any(f.endswith(".whl") for f in dist_files)
|
|
106
|
+
has_sdist = any(f.endswith(".tar.gz") for f in dist_files)
|
|
107
|
+
|
|
108
|
+
if not has_wheel:
|
|
109
|
+
sys.exit("ERROR: No .whl wheel package found in dist/")
|
|
110
|
+
if not has_sdist:
|
|
111
|
+
sys.exit("ERROR: No .tar.gz source distribution found in dist/")
|
|
112
|
+
|
|
113
|
+
print("SUCCESS: Both wheel and source distribution artifacts exist.")
|
|
114
|
+
EOF
|
|
115
|
+
python -m twine check --strict dist/*
|
|
116
|
+
ls -la dist/
|
|
117
|
+
|
|
118
|
+
- name: Upload build artifacts
|
|
119
|
+
uses: actions/upload-artifact@v4
|
|
120
|
+
with:
|
|
121
|
+
name: dist-artifacts
|
|
122
|
+
path: dist/
|
|
123
|
+
retention-days: 1
|
|
124
|
+
|
|
125
|
+
publish-testpypi:
|
|
126
|
+
name: Publish to TestPyPI
|
|
127
|
+
needs: build
|
|
128
|
+
if: (github.event_name == 'release' && github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi')
|
|
129
|
+
runs-on: ubuntu-latest
|
|
130
|
+
environment:
|
|
131
|
+
name: testpypi
|
|
132
|
+
url: https://test.pypi.org/p/ragdiag
|
|
133
|
+
permissions:
|
|
134
|
+
id-token: write
|
|
135
|
+
steps:
|
|
136
|
+
- name: Download build artifacts
|
|
137
|
+
uses: actions/download-artifact@v4
|
|
138
|
+
with:
|
|
139
|
+
name: dist-artifacts
|
|
140
|
+
path: dist/
|
|
141
|
+
|
|
142
|
+
- name: Publish package distributions to TestPyPI
|
|
143
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
144
|
+
with:
|
|
145
|
+
repository-url: https://test.pypi.org/legacy/
|
|
146
|
+
skip-existing: true
|
|
147
|
+
|
|
148
|
+
publish-pypi:
|
|
149
|
+
name: Publish to Production PyPI
|
|
150
|
+
needs: build
|
|
151
|
+
if: (github.event_name == 'release' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
152
|
+
runs-on: ubuntu-latest
|
|
153
|
+
environment:
|
|
154
|
+
name: pypi
|
|
155
|
+
url: https://pypi.org/p/ragdiag
|
|
156
|
+
permissions:
|
|
157
|
+
id-token: write
|
|
158
|
+
steps:
|
|
159
|
+
- name: Download build artifacts
|
|
160
|
+
uses: actions/download-artifact@v4
|
|
161
|
+
with:
|
|
162
|
+
name: dist-artifacts
|
|
163
|
+
path: dist/
|
|
164
|
+
|
|
165
|
+
- name: Publish package distributions to PyPI
|
|
166
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check out repository
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v3
|
|
24
|
+
with:
|
|
25
|
+
version: "latest"
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
uv venv
|
|
30
|
+
uv pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Run Ruff Lint
|
|
33
|
+
run: |
|
|
34
|
+
uv run ruff check .
|
|
35
|
+
|
|
36
|
+
- name: Run Ruff Format Check
|
|
37
|
+
run: |
|
|
38
|
+
uv run ruff format --check .
|
|
39
|
+
|
|
40
|
+
- name: Run Pytest
|
|
41
|
+
run: |
|
|
42
|
+
uv run pytest
|
ragdiag-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
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
|
+
# Virtual environments
|
|
30
|
+
.venv/
|
|
31
|
+
env/
|
|
32
|
+
venv/
|
|
33
|
+
ENV/
|
|
34
|
+
env.bak/
|
|
35
|
+
venv.bak/
|
|
36
|
+
|
|
37
|
+
# Testing & linting
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
.ruff_cache/
|
|
40
|
+
.coverage
|
|
41
|
+
htmlcov/
|
|
42
|
+
.mypy_cache/
|
|
43
|
+
|
|
44
|
+
# IDE & OS files
|
|
45
|
+
.vscode/
|
|
46
|
+
.idea/
|
|
47
|
+
*.swp
|
|
48
|
+
*.swo
|
|
49
|
+
*~
|
|
50
|
+
.DS_Store
|
|
51
|
+
Thumbs.db
|
ragdiag-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RAGDiag Contributors
|
|
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.
|
ragdiag-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ragdiag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RAG evaluation and root-cause diagnosis SDK/CLI
|
|
5
|
+
Project-URL: Homepage, https://github.com/SayanBhattacharjee2006/ragdiag
|
|
6
|
+
Project-URL: Repository, https://github.com/SayanBhattacharjee2006/ragdiag
|
|
7
|
+
Project-URL: Issues, https://github.com/SayanBhattacharjee2006/ragdiag/issues
|
|
8
|
+
Author: RAGDiag Contributors
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: diagnosis,evaluation,hallucination,llm,metrics,rag
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: openai>=1.40.0
|
|
22
|
+
Requires-Dist: pydantic>=2.7.0
|
|
23
|
+
Requires-Dist: rich>=13.7.0
|
|
24
|
+
Requires-Dist: typer>=0.12.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# RAGDiag
|
|
31
|
+
|
|
32
|
+
> **Developer tool for evaluating RAG pipelines and identifying evidence-backed primary failure categories.** Built for the Razorpay Buildathon.
|
|
33
|
+
|
|
34
|
+
[](https://www.python.org/)
|
|
35
|
+
[](https://opensource.org/licenses/MIT)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Problem
|
|
40
|
+
|
|
41
|
+
Traditional RAG evaluation frameworks calculate scalar scores (e.g. an aggregate score of 0.72) but leave developers guessing when outputs degrade:
|
|
42
|
+
- Did the vector retriever miss the relevant context chunks entirely?
|
|
43
|
+
- Were the right chunks retrieved but buried beneath distracting irrelevant chunks?
|
|
44
|
+
- Was only partial context retrieved for multi-part questions?
|
|
45
|
+
- Did the LLM hallucinate unsupported claims despite having the context?
|
|
46
|
+
- Or did retrieval latency spike beyond acceptable production thresholds?
|
|
47
|
+
|
|
48
|
+
When engineers test an architectural change—such as switching from dense semantic search to hybrid search—isolated numbers cannot explain whether higher recall justifies the additional latency or which specific queries improved or regressed.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## What RAGDiag Does
|
|
53
|
+
|
|
54
|
+
RAGDiag inspects the raw evidence captured during execution across retrieval and generation to classify why individual queries fail, generate system-level diagnostic intelligence, and perform evidence-based comparisons between pipeline architectures:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
58
|
+
│ RAG Pipeline │ ───> │ Evaluation │ ───> │ Root-Cause │ ───> │ Multi-Pipeline │
|
|
59
|
+
│ (Adapter) │ │ Harness │ │ Diagnosis │ │ Comparison │
|
|
60
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
61
|
+
Custom retriever Precision@K, MRR, 8-Category Decision Directional deltas,
|
|
62
|
+
and generator Latency, LLM Judge Precedence Hierarchy Transitions, Winners
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Key Capabilities
|
|
68
|
+
|
|
69
|
+
- **Framework-Agnostic Adapter**: Wrap any RAG stack (custom vector stores, LangChain, LlamaIndex, BM25) in standard Python methods.
|
|
70
|
+
- **Golden Dataset Schema**: Validated ground-truth dataset format with categorized query types (`factual`, `reasoning`, `multi-hop`).
|
|
71
|
+
- **Deterministic Retrieval Metrics**: Exact calculations for Precision@K, Recall@K, Reciprocal Rank, and MRR.
|
|
72
|
+
- **Latency Distribution Analysis**: Non-parametric percentile statistics (Mean, P50, P95, P99, Min, Max) for retrieval and generation stages.
|
|
73
|
+
- **Isolated LLM Judge**: Evaluates answer correctness against ground truth and context groundedness against retrieved chunks via schema-enforced structured outputs.
|
|
74
|
+
- **Evidence-Based Root-Cause Diagnosis**: Classifies failures into a deterministic 8-category taxonomy without asking an unconstrained LLM to guess.
|
|
75
|
+
- **Diagnostic System Reports**: Aggregates query-type breakdowns, deterministically ranked top failures, and rule-based insights.
|
|
76
|
+
- **Multi-Pipeline A/B Comparison**: Compares two pipeline configurations side-by-side on the same dataset, calculating metric deltas, failure shifts, query transitions (`improved`, `regressed`, `unchanged`), and deterministic winner/trade-off decisions.
|
|
77
|
+
- **CLI & Typed JSON Exports**: Formatted Rich terminal reports and complete Pydantic JSON serialization.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quickstart
|
|
82
|
+
|
|
83
|
+
### Installation
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install ragdiag
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or when developing with [`uv`](https://docs.astral.sh/uv/):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/SayanBhattacharjee2006/ragdiag.git
|
|
93
|
+
cd ragdiag
|
|
94
|
+
uv venv
|
|
95
|
+
uv pip install -e ".[dev]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Pipeline Adapter Interface
|
|
101
|
+
|
|
102
|
+
Developers adapt their existing RAG pipeline by subclassing `ragdiag.Pipeline` and exposing a top-level instance named `pipeline`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# my_pipeline.py
|
|
106
|
+
from ragdiag import Pipeline, RetrievedChunk
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class MyCustomPipeline(Pipeline):
|
|
110
|
+
name = "payment_faq_rag"
|
|
111
|
+
|
|
112
|
+
def retrieve(self, query: str) -> list[RetrievedChunk]:
|
|
113
|
+
# Connect to your vector DB, dense index, or hybrid retriever
|
|
114
|
+
return [
|
|
115
|
+
RetrievedChunk(
|
|
116
|
+
id="doc_refund_policy_01",
|
|
117
|
+
text="Standard card refunds settle within 5 to 7 business days.",
|
|
118
|
+
score=0.92,
|
|
119
|
+
)
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def generate(self, query: str, chunks: list[RetrievedChunk]) -> str:
|
|
123
|
+
# Pass context to your LLM generator
|
|
124
|
+
return "Card refund settlements typically take 5 to 7 business days."
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
pipeline = MyCustomPipeline()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Golden Dataset System
|
|
133
|
+
|
|
134
|
+
Datasets are JSON files matching the `GoldenDataset` schema with ground truth and categorized query types:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"name": "payment_gateway_eval",
|
|
139
|
+
"version": "1.0",
|
|
140
|
+
"samples": [
|
|
141
|
+
{
|
|
142
|
+
"id": "q001",
|
|
143
|
+
"query": "What is the standard turnaround time for card refund settlements?",
|
|
144
|
+
"expected_answer": "Standard card refund settlements are credited within 5 to 7 business days.",
|
|
145
|
+
"relevant_chunk_ids": ["doc_refund_policy_01"],
|
|
146
|
+
"query_type": "factual"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "q002",
|
|
150
|
+
"query": "Why was the customer's recurring auto-debit declined?",
|
|
151
|
+
"expected_answer": "The auto-debit was declined because the e-mandate registration expired.",
|
|
152
|
+
"relevant_chunk_ids": ["doc_subscriptions_03", "doc_mandates_05"],
|
|
153
|
+
"query_type": "reasoning"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": "q003",
|
|
157
|
+
"query": "What is the effective net settlement fee considering base interchange and GST?",
|
|
158
|
+
"expected_answer": "The effective fee is 2.5% base fee plus 18% GST on the fee, totaling 2.95%.",
|
|
159
|
+
"relevant_chunk_ids": ["doc_pricing_tier_01", "doc_tax_regulations_03"],
|
|
160
|
+
"query_type": "multi-hop"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Validate a Dataset
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
ragdiag validate --dataset examples/demo_dataset.json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Run Evaluation
|
|
175
|
+
|
|
176
|
+
Evaluate a single pipeline configuration and print the diagnostic terminal report:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
ragdiag run --pipeline examples/basic_pipeline.py --dataset examples/basic_dataset.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Enable Semantic LLM Judge
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
export OPENAI_API_KEY="sk-..."
|
|
186
|
+
ragdiag run \
|
|
187
|
+
--pipeline examples/basic_pipeline.py \
|
|
188
|
+
--dataset examples/basic_dataset.json \
|
|
189
|
+
--judge openai \
|
|
190
|
+
--model gpt-4o-mini
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Export JSON Report
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
ragdiag run \
|
|
197
|
+
--pipeline examples/basic_pipeline.py \
|
|
198
|
+
--dataset examples/basic_dataset.json \
|
|
199
|
+
--output evaluation_report.json
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Compare Two Pipelines
|
|
205
|
+
|
|
206
|
+
Compare a baseline pipeline (Pipeline A) against a candidate architecture (Pipeline B) on the same dataset:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
ragdiag compare \
|
|
210
|
+
--pipeline-a examples/dense_pipeline.py \
|
|
211
|
+
--pipeline-b examples/hybrid_pipeline.py \
|
|
212
|
+
--dataset examples/demo_dataset.json
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Real Deterministic Comparison Output
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
Running multi-pipeline comparison...
|
|
219
|
+
|
|
220
|
+
RAGDiag Comparison
|
|
221
|
+
==================================================
|
|
222
|
+
Dataset: payment_gateway_demo_eval (v1.0)
|
|
223
|
+
Pipeline A: dense_pipeline
|
|
224
|
+
Pipeline B: hybrid_pipeline
|
|
225
|
+
|
|
226
|
+
OVERALL METRICS
|
|
227
|
+
--------------------------------------------------
|
|
228
|
+
Metric dense_pipeline hybrid_pipeline Delta (B-A)
|
|
229
|
+
--------------------------------------------------------------
|
|
230
|
+
Precision@5 0.87 0.90 +0.03
|
|
231
|
+
Recall@5 0.80 1.00 +0.20
|
|
232
|
+
MRR 0.87 0.87 0.00
|
|
233
|
+
Mean Retrieval 5.2ms 25.5ms +20.25ms
|
|
234
|
+
P95 Retrieval 5.3ms 25.7ms +20.35ms
|
|
235
|
+
|
|
236
|
+
FAILURE COUNTS
|
|
237
|
+
--------------------------------------------------
|
|
238
|
+
Category dense_pipeline hybrid_pipeline Delta
|
|
239
|
+
----------------------------------------------------------
|
|
240
|
+
PASS 3 5 +2
|
|
241
|
+
WRONG_CHUNK_RETRIEVED 0 0 0
|
|
242
|
+
WRONG_CHUNK_RANK 0 0 0
|
|
243
|
+
INSUFFICIENT_CONTEXT 2 0 -2
|
|
244
|
+
RETRIEVED_BUT_NOT_GROUNDED 0 0 0
|
|
245
|
+
ANSWER_INCORRECT 0 0 0
|
|
246
|
+
LATENCY_OUTLIER 0 0 0
|
|
247
|
+
UNKNOWN 0 0 0
|
|
248
|
+
|
|
249
|
+
QUERY TYPES
|
|
250
|
+
--------------------------------------------------
|
|
251
|
+
Factual
|
|
252
|
+
Recall@5: 1.00 -> 1.00 (0.00) MRR: 1.00 -> 1.00 (0.00)
|
|
253
|
+
|
|
254
|
+
Reasoning
|
|
255
|
+
Recall@5: 0.75 -> 1.00 (+0.25) MRR: 1.00 -> 1.00 (0.00)
|
|
256
|
+
Failure count delta: -1
|
|
257
|
+
|
|
258
|
+
Multi-hop
|
|
259
|
+
Recall@5: 0.50 -> 1.00 (+0.50) MRR: 0.33 -> 0.33 (0.00)
|
|
260
|
+
Failure count delta: -1
|
|
261
|
+
|
|
262
|
+
DECISION
|
|
263
|
+
--------------------------------------------------
|
|
264
|
+
Overall winner: hybrid_pipeline
|
|
265
|
+
|
|
266
|
+
Why:
|
|
267
|
+
hybrid_pipeline improves Recall@5 by 20 percentage points and MRR by 0 points,
|
|
268
|
+
while increasing mean retrieval latency by 20.3 ms.
|
|
269
|
+
|
|
270
|
+
Trade-off:
|
|
271
|
+
Higher quality <-> higher latency
|
|
272
|
+
|
|
273
|
+
QUERY OUTCOMES
|
|
274
|
+
--------------------------------------------------
|
|
275
|
+
Improved: 2
|
|
276
|
+
Regressed: 0
|
|
277
|
+
Unchanged: 3
|
|
278
|
+
|
|
279
|
+
Total comparison time: 0.16s
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Export comparison report to JSON:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
ragdiag compare \
|
|
286
|
+
--pipeline-a examples/dense_pipeline.py \
|
|
287
|
+
--pipeline-b examples/hybrid_pipeline.py \
|
|
288
|
+
--dataset examples/demo_dataset.json \
|
|
289
|
+
--output comparison.json
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Root-Cause Failure Taxonomy
|
|
295
|
+
|
|
296
|
+
RAGDiag classifies every completed query into an explainable 8-category hierarchy:
|
|
297
|
+
|
|
298
|
+
| Category | Severity | Description |
|
|
299
|
+
| :--- | :--- | :--- |
|
|
300
|
+
| **`PASS`** | `info` | Query succeeded across retrieval, context completeness, semantic, and latency checks. |
|
|
301
|
+
| **`WRONG_CHUNK_RETRIEVED`** | `major` | Complete retrieval miss; none of the required context chunks were retrieved in top-$K$. |
|
|
302
|
+
| **`INSUFFICIENT_CONTEXT`** | `warning` | Partial context retrieval; query required multiple chunks but only a subset was retrieved. |
|
|
303
|
+
| **`WRONG_CHUNK_RANK`** | `warning` | All required context was retrieved, but the first relevant chunk ranked lower than threshold (rank > 3). |
|
|
304
|
+
| **`RETRIEVED_BUT_NOT_GROUNDED`** | `major` | Hallucination; context was retrieved, but the LLM made claims unsupported by the chunks. |
|
|
305
|
+
| **`ANSWER_INCORRECT`** | `major` | Context was retrieved and answer was grounded, but contradicted or failed the ground truth. |
|
|
306
|
+
| **`LATENCY_OUTLIER`** | `warning` | Quality passed, but retrieval latency exceeded threshold (default: 1000ms). |
|
|
307
|
+
| **`UNKNOWN`** | `major` | Pipeline crash or unclassifiable execution exception. |
|
|
308
|
+
|
|
309
|
+
### Decision Precedence
|
|
310
|
+
|
|
311
|
+
1. **Pipeline Execution Failure** (`status != 'completed'`) $\to$ `UNKNOWN`
|
|
312
|
+
2. **Total Retrieval Miss** (0 overlap with expected chunks) $\to$ `WRONG_CHUNK_RETRIEVED`
|
|
313
|
+
3. **Partial Context Retrieval** (subset retrieved) $\to$ `INSUFFICIENT_CONTEXT`
|
|
314
|
+
4. **All Context Retrieved but Ranked Late** (rank > 3) $\to$ `WRONG_CHUNK_RANK`
|
|
315
|
+
5. **Hallucination** (`grounded == False`) $\to$ `RETRIEVED_BUT_NOT_GROUNDED`
|
|
316
|
+
6. **Incorrect Answer** (`answer_correct == False`) $\to$ `ANSWER_INCORRECT`
|
|
317
|
+
7. **Latency Outlier** (`retrieval_ms > threshold`) $\to$ `LATENCY_OUTLIER`
|
|
318
|
+
8. **Pass** (all checks passed) $\to$ `PASS`
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Comparison Methodology
|
|
323
|
+
|
|
324
|
+
### Directional Deltas ($\Delta = \text{Pipeline B} - \text{Pipeline A}$)
|
|
325
|
+
- **Quality Metrics**: Positive delta means Pipeline B achieved higher quality.
|
|
326
|
+
- **Latency Metrics**: Positive delta means Pipeline B is slower; negative delta means Pipeline B is faster.
|
|
327
|
+
- **Failure Counts**: Negative delta means Pipeline B reduced failures in that category (improvement).
|
|
328
|
+
|
|
329
|
+
### Winner Strategy & Trade-Off Detection
|
|
330
|
+
1. **Quality Priority**: Primary signals are evaluated in priority order:
|
|
331
|
+
$$\text{Recall@}K \longrightarrow \text{MRR} \longrightarrow \text{Groundedness} \longrightarrow \text{Answer Correctness}$$
|
|
332
|
+
Using configurable tolerance $\epsilon = 0.02$.
|
|
333
|
+
2. **Latency Trade-Off**: Evaluates latency delta against tolerance $\epsilon_{\text{lat}} = 10.0\text{ ms}$.
|
|
334
|
+
3. **Synthesis**:
|
|
335
|
+
- Quality improves + latency increases $\to$ Declares winner with `"Higher quality <-> higher latency"`.
|
|
336
|
+
- Quality improves + latency improves $\to$ Declares winner with `"Higher quality with improved latency"`.
|
|
337
|
+
- Quality decreases + latency improves $\to$ Declares winner with `"Faster latency at the expense of lower quality"`.
|
|
338
|
+
- Both roughly equal $\to$ Declares `"TIE"`.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Python SDK API
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
from ragdiag import Comparator, Evaluator, OpenAIJudge, build_report
|
|
346
|
+
from ragdiag.dataset import load_dataset
|
|
347
|
+
from ragdiag.pipeline import load_pipeline
|
|
348
|
+
|
|
349
|
+
# 1. Load pipeline and dataset
|
|
350
|
+
pipeline_a = load_pipeline("examples/dense_pipeline.py")
|
|
351
|
+
pipeline_b = load_pipeline("examples/hybrid_pipeline.py")
|
|
352
|
+
dataset = load_dataset("examples/demo_dataset.json")
|
|
353
|
+
|
|
354
|
+
# 2. Compare two pipelines
|
|
355
|
+
comparator = Comparator(k=5)
|
|
356
|
+
comparison = comparator.compare(pipeline_a, pipeline_b, dataset)
|
|
357
|
+
|
|
358
|
+
print(f"Overall Winner: {comparison.overall_winner}")
|
|
359
|
+
print(f"Trade-off: {comparison.trade_off}")
|
|
360
|
+
print(f"Recall Delta: {comparison.metric_deltas.recall_at_k:+.2f}")
|
|
361
|
+
print(f"Improved: {comparison.queries_improved} queries")
|
|
362
|
+
|
|
363
|
+
# 3. Export structured JSON
|
|
364
|
+
json_output = comparison.model_dump_json(indent=2)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Metrics Reference
|
|
370
|
+
|
|
371
|
+
- **Precision@K**: Fraction of top-$K$ retrieved chunks that are relevant:
|
|
372
|
+
$$\text{Precision@}K = \frac{|\text{Relevant Chunks in Top-}K|}{\min(K, |\text{Retrieved Chunks}|)}$$
|
|
373
|
+
- **Recall@K**: Proportion of all ground-truth relevant chunks retrieved in top-$K$:
|
|
374
|
+
$$\text{Recall@}K = \frac{|\text{Relevant Chunks in Top-}K|}{|\text{Total Relevant Chunks}|}$$
|
|
375
|
+
- **Mean Reciprocal Rank (MRR)**: Mean reciprocal rank of the first relevant chunk across queries:
|
|
376
|
+
$$\text{MRR} = \frac{1}{|Q|} \sum_{i=1}^{|Q|} \frac{1}{\text{rank}_i}$$
|
|
377
|
+
- **Answer Correctness**: Semantic equivalence of synthesized answer against expected ground truth.
|
|
378
|
+
- **Groundedness**: Evaluates whether synthesized claims are strictly supported by retrieved context chunks (the expected answer is never used as evidence for groundedness).
|
|
379
|
+
- **Latency Percentiles**: Linear interpolation percentiles (Mean, P50, P95, P99) for retrieval and generation execution.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Limitations
|
|
384
|
+
|
|
385
|
+
- **Python-First**: Pipelines and adapters are authored in Python.
|
|
386
|
+
- **Two-Pipeline Comparison**: MVP currently supports comparing exactly two pipeline configurations (A vs B).
|
|
387
|
+
- **JSON Golden Datasets**: Evaluation datasets are currently loaded from structured JSON files.
|
|
388
|
+
- **LLM Judge Providers**: OpenAI structured outputs are currently supported out of the box; additional model providers can implement the extensible `Judge` interface.
|
|
389
|
+
- **Rule-Based Diagnosis**: The failure diagnosis taxonomy is derived deterministically from captured evidence rather than statistical model inference.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Roadmap
|
|
394
|
+
|
|
395
|
+
Planned for future releases:
|
|
396
|
+
- [ ] Multi-configuration matrix comparison (>2 pipelines)
|
|
397
|
+
- [ ] Automated synthetic golden dataset generation
|
|
398
|
+
- [ ] Additional LLM judge providers (Anthropic, Gemini, local Ollama)
|
|
399
|
+
- [ ] Web dashboard and visual trace inspector
|
|
400
|
+
- [ ] CI/CD automation action for regression gating
|
|
401
|
+
- [ ] Docker containerized runner
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## License
|
|
406
|
+
|
|
407
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|