ragdoc 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.
- ragdoc-0.1.0/LICENSE +21 -0
- ragdoc-0.1.0/PKG-INFO +191 -0
- ragdoc-0.1.0/README.md +163 -0
- ragdoc-0.1.0/pyproject.toml +42 -0
- ragdoc-0.1.0/setup.cfg +4 -0
- ragdoc-0.1.0/src/ragdoc/__init__.py +33 -0
- ragdoc-0.1.0/src/ragdoc/artifacts.py +256 -0
- ragdoc-0.1.0/src/ragdoc/attribution/__init__.py +40 -0
- ragdoc-0.1.0/src/ragdoc/attribution/checks.py +420 -0
- ragdoc-0.1.0/src/ragdoc/attribution/engine.py +269 -0
- ragdoc-0.1.0/src/ragdoc/attribution/verdict.py +338 -0
- ragdoc-0.1.0/src/ragdoc/batch/__init__.py +245 -0
- ragdoc-0.1.0/src/ragdoc/chunker/__init__.py +248 -0
- ragdoc-0.1.0/src/ragdoc/cli.py +397 -0
- ragdoc-0.1.0/src/ragdoc/compare/__init__.py +140 -0
- ragdoc-0.1.0/src/ragdoc/config.py +54 -0
- ragdoc-0.1.0/src/ragdoc/indexer/__init__.py +130 -0
- ragdoc-0.1.0/src/ragdoc/indexer/local_embeddings.py +199 -0
- ragdoc-0.1.0/src/ragdoc/loader/__init__.py +95 -0
- ragdoc-0.1.0/src/ragdoc/loader/pdfplumber_loader.py +70 -0
- ragdoc-0.1.0/src/ragdoc/loader/pymupdf_loader.py +84 -0
- ragdoc-0.1.0/src/ragdoc/loader/pypdf_loader.py +66 -0
- ragdoc-0.1.0/src/ragdoc/loader/registry.py +73 -0
- ragdoc-0.1.0/src/ragdoc/report/__init__.py +186 -0
- ragdoc-0.1.0/src/ragdoc/report/_table.py +45 -0
- ragdoc-0.1.0/src/ragdoc/report/batch.py +170 -0
- ragdoc-0.1.0/src/ragdoc/report/compare.py +111 -0
- ragdoc-0.1.0/src/ragdoc/report/retrieved.py +101 -0
- ragdoc-0.1.0/src/ragdoc/report/sweep.py +155 -0
- ragdoc-0.1.0/src/ragdoc/report/symbols.py +76 -0
- ragdoc-0.1.0/src/ragdoc/retriever/__init__.py +145 -0
- ragdoc-0.1.0/src/ragdoc/sweep/__init__.py +151 -0
- ragdoc-0.1.0/src/ragdoc.egg-info/PKG-INFO +191 -0
- ragdoc-0.1.0/src/ragdoc.egg-info/SOURCES.txt +66 -0
- ragdoc-0.1.0/src/ragdoc.egg-info/dependency_links.txt +1 -0
- ragdoc-0.1.0/src/ragdoc.egg-info/entry_points.txt +2 -0
- ragdoc-0.1.0/src/ragdoc.egg-info/requires.txt +5 -0
- ragdoc-0.1.0/src/ragdoc.egg-info/top_level.txt +1 -0
- ragdoc-0.1.0/tests/test_ascii_rendering.py +134 -0
- ragdoc-0.1.0/tests/test_attribution_fixtures.py +91 -0
- ragdoc-0.1.0/tests/test_batch.py +319 -0
- ragdoc-0.1.0/tests/test_batch_input.py +35 -0
- ragdoc-0.1.0/tests/test_batch_runner.py +84 -0
- ragdoc-0.1.0/tests/test_checks.py +203 -0
- ragdoc-0.1.0/tests/test_cli_batch.py +101 -0
- ragdoc-0.1.0/tests/test_cli_input.py +237 -0
- ragdoc-0.1.0/tests/test_cli_loader.py +162 -0
- ragdoc-0.1.0/tests/test_cli_show_retrieved.py +103 -0
- ragdoc-0.1.0/tests/test_cli_sweep.py +158 -0
- ragdoc-0.1.0/tests/test_cli_verbose.py +74 -0
- ragdoc-0.1.0/tests/test_compare.py +268 -0
- ragdoc-0.1.0/tests/test_config.py +59 -0
- ragdoc-0.1.0/tests/test_empty_pages.py +47 -0
- ragdoc-0.1.0/tests/test_indexer_errors.py +74 -0
- ragdoc-0.1.0/tests/test_indexer_quiet.py +130 -0
- ragdoc-0.1.0/tests/test_loader_errors.py +117 -0
- ragdoc-0.1.0/tests/test_loader_registry.py +57 -0
- ragdoc-0.1.0/tests/test_loaders.py +65 -0
- ragdoc-0.1.0/tests/test_pdfplumber_loader.py +47 -0
- ragdoc-0.1.0/tests/test_pypdf_loader.py +50 -0
- ragdoc-0.1.0/tests/test_report.py +157 -0
- ragdoc-0.1.0/tests/test_report_batch.py +97 -0
- ragdoc-0.1.0/tests/test_report_compare.py +130 -0
- ragdoc-0.1.0/tests/test_report_retrieved.py +85 -0
- ragdoc-0.1.0/tests/test_report_sweep.py +133 -0
- ragdoc-0.1.0/tests/test_sweep.py +101 -0
- ragdoc-0.1.0/tests/test_symbols.py +76 -0
- ragdoc-0.1.0/tests/test_table.py +46 -0
ragdoc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmed Imran
|
|
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.
|
ragdoc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ragdoc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Headless diagnostic for PDF RAG pipelines: attributes a lost answer to the pipeline stage that lost it.
|
|
5
|
+
Author: Ahmed Imran
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://ahmedimran.dev
|
|
8
|
+
Project-URL: Source, https://github.com/Sheikh-Ahmed-Imran/rag-doc
|
|
9
|
+
Project-URL: Issues, https://github.com/Sheikh-Ahmed-Imran/rag-doc/issues
|
|
10
|
+
Keywords: rag,pdf,retrieval-augmented-generation,embeddings,llm,diagnostics,attribution
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Text Processing :: General
|
|
19
|
+
Requires-Python: <3.13,>=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: PyMuPDF>=1.24
|
|
23
|
+
Requires-Dist: pdfplumber>=0.10
|
|
24
|
+
Requires-Dist: pypdf>=6.21.2
|
|
25
|
+
Requires-Dist: sentence-transformers>=2.2
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# ragdoc
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/ragdoc/)
|
|
32
|
+
[](https://pypi.org/project/ragdoc/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
**Find out which stage of your PDF RAG pipeline lost the answer.**
|
|
36
|
+
|
|
37
|
+
When a retrieval-augmented pipeline fails to answer a question you *know* the
|
|
38
|
+
document contains, the hard part is rarely the fix — it is working out **where** it
|
|
39
|
+
broke. A bad chunk size and a bad retriever look identical from the outside: in both
|
|
40
|
+
cases the answer just doesn't come back. `ragdoc` ends that guesswork. Give it a PDF,
|
|
41
|
+
a question, and the answer you know is in the document, and it tells you the single
|
|
42
|
+
stage that lost it — **extraction**, **chunking**, or **retrieval** — with the
|
|
43
|
+
evidence behind the verdict.
|
|
44
|
+
|
|
45
|
+
It is a headless, offline, deterministic CLI. No API keys, no LLM calls, no telemetry.
|
|
46
|
+
|
|
47
|
+
## Project status
|
|
48
|
+
|
|
49
|
+
ragdoc is in **beta** (`0.x`). Its attribution engine and CLI are covered by an
|
|
50
|
+
extensive automated test suite — unit tests plus integration tests against fixture
|
|
51
|
+
PDFs that exercise each stage and every failure mode. What is still maturing is
|
|
52
|
+
*breadth*: validation across a wide range of real-world PDFs is ongoing, so on unusual
|
|
53
|
+
layouts or heavily structured documents a verdict may warrant a careful read. Treat the
|
|
54
|
+
output as strong, evidence-backed signal rather than an oracle, and expect the occasional
|
|
55
|
+
rough edge before `1.0`. Reports of PDFs it gets wrong are especially welcome.
|
|
56
|
+
|
|
57
|
+
## How it works
|
|
58
|
+
|
|
59
|
+
`ragdoc` runs your document through the forward pipeline once, then runs a chain of
|
|
60
|
+
backward checks that each consume one stage's output:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
load → chunk → index → retrieve (the forward pipeline)
|
|
64
|
+
│ │ │ │
|
|
65
|
+
locate → survive ───────→ rank (the backward checks)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **locate** — is the expected answer actually in the extracted text? If not, the
|
|
69
|
+
loader lost it → **extraction**.
|
|
70
|
+
- **survive** — does the answer survive chunking intact inside one chunk, with the
|
|
71
|
+
section context it needs to be findable? If a boundary cut it, or its heading was
|
|
72
|
+
never embedded → **chunking**.
|
|
73
|
+
- **rank** — does the chunk holding the answer rank within `top_k`? If it ranks too
|
|
74
|
+
low → **retrieval**.
|
|
75
|
+
|
|
76
|
+
The chain stops at the first failing stage; every later stage is reported as *not
|
|
77
|
+
reached*, never as a false pass. The answer is supplied by you — nothing is inferred
|
|
78
|
+
from the model's own scores — so the verdict is grounded, not another guess.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install ragdoc
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Requires Python 3.12. The first run downloads a small local embedding model
|
|
87
|
+
(`all-MiniLM-L6-v2`); every run after that is fully offline.
|
|
88
|
+
|
|
89
|
+
## Quick start
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ragdoc diagnose paper.pdf "What was the sample size?" "384 participants"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
extraction ✓
|
|
97
|
+
chunking ✗ section heading not embedded in chunk text
|
|
98
|
+
retrieval ✗ target chunk ranked 23 of 400; precision@5 = 0.00 (max 0.20); RR = 0.04
|
|
99
|
+
generation — not reached
|
|
100
|
+
|
|
101
|
+
VERDICT: chunking — the answer's chunk sits under a heading that is not embedded
|
|
102
|
+
FIX: prepend section path to chunk text before embedding
|
|
103
|
+
|
|
104
|
+
Run configuration:
|
|
105
|
+
loader: pymupdf
|
|
106
|
+
chunk size: 500
|
|
107
|
+
overlap: 50
|
|
108
|
+
embedding model: all-MiniLM-L6-v2
|
|
109
|
+
top_k: 5
|
|
110
|
+
prepend_section_context: False
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The answer can also be read from a file (handy for long or multi-line answers):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
ragdoc diagnose paper.pdf "What was the sample size?" --answer-file answer.txt
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Add `--verbose` for the full evidence behind each check, `--show-retrieved` to see the
|
|
120
|
+
retrieved chunks with the target marked, or `--ascii` for terminals that can't render
|
|
121
|
+
Unicode.
|
|
122
|
+
|
|
123
|
+
## Commands
|
|
124
|
+
|
|
125
|
+
### `diagnose` — one question, one verdict
|
|
126
|
+
```bash
|
|
127
|
+
ragdoc diagnose paper.pdf "What was the sample size?" "384 participants"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `compare` — how different loaders extract the same answer
|
|
131
|
+
Silent extraction failures are often a loader problem. `compare` runs every PDF loader
|
|
132
|
+
against the same answer so you can see which one actually preserves it.
|
|
133
|
+
```bash
|
|
134
|
+
ragdoc compare paper.pdf "384 participants"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `batch` — a failure distribution across a question set
|
|
138
|
+
One verdict tells you about one question; a *distribution* tells you where a pipeline
|
|
139
|
+
systematically breaks. Point `batch` at a JSON question set and it reports how the
|
|
140
|
+
failures split across the stages.
|
|
141
|
+
```bash
|
|
142
|
+
ragdoc batch paper.pdf questions.json --json results.json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The question set is a JSON array:
|
|
146
|
+
```json
|
|
147
|
+
[
|
|
148
|
+
{ "question": "What was the sample size?", "expected_answer": "384 participants" },
|
|
149
|
+
{ "question": "Which model performed best?", "expected_answer": "the gradient-boosted ensemble" }
|
|
150
|
+
]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `sweep` — find the best configuration
|
|
154
|
+
Chunk size is a balance: too small splits answers apart (chunking failures), too big
|
|
155
|
+
dilutes them (retrieval failures). `sweep` runs the whole question set across a grid of
|
|
156
|
+
configurations and names the one that survives the most answers.
|
|
157
|
+
```bash
|
|
158
|
+
ragdoc sweep paper.pdf questions.json --chunk-size 500,800,1200 --overlap 0,50
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
It prints a table — one row per configuration, with the varied knobs, the no-failure
|
|
162
|
+
count, and the retrieval metrics — and highlights the winner. Use `--objective` to rank
|
|
163
|
+
by `no-failure` (default), `hit-rate`, or `mrr`.
|
|
164
|
+
|
|
165
|
+
## Configuration
|
|
166
|
+
|
|
167
|
+
Every knob has a sensible default and can be overridden on `diagnose` and `batch` (and
|
|
168
|
+
swept as a comma-separated list on `sweep`):
|
|
169
|
+
|
|
170
|
+
| Flag | Meaning | Default |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `--chunk-size` | characters per chunk | `500` |
|
|
173
|
+
| `--overlap` | characters shared between adjacent chunks | `50` |
|
|
174
|
+
| `--top-k` | retrieval cutoff the ranking is judged against | `10` |
|
|
175
|
+
| `--embedding-model` | sentence-transformers model name | `all-MiniLM-L6-v2` |
|
|
176
|
+
| `--prepend-section-context` / `--no-prepend-section-context` | prepend the section path to chunk text before embedding | off |
|
|
177
|
+
| `--loader` | PDF extraction backend (`pymupdf`, `pdfplumber`, `pypdf`) | `pymupdf` |
|
|
178
|
+
|
|
179
|
+
## Known limitations
|
|
180
|
+
|
|
181
|
+
- **The answer must appear in the document text.** Matching is verbatim or normalized
|
|
182
|
+
(whitespace, case, and Unicode compatibility forms such as ligatures are forgiven) —
|
|
183
|
+
it does **not** handle paraphrase or reworded answers.
|
|
184
|
+
- **Scanned / image-only pages are not read.** Pages with no extractable text are
|
|
185
|
+
flagged in an advisory, but `ragdoc` does no OCR.
|
|
186
|
+
- **The generation stage is not evaluated** — it is always reported as *not reached*.
|
|
187
|
+
`ragdoc` diagnoses retrieval-and-earlier, where the failures it targets live.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
[MIT](LICENSE) © 2026 Ahmed Imran
|
ragdoc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ragdoc
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ragdoc/)
|
|
4
|
+
[](https://pypi.org/project/ragdoc/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Find out which stage of your PDF RAG pipeline lost the answer.**
|
|
8
|
+
|
|
9
|
+
When a retrieval-augmented pipeline fails to answer a question you *know* the
|
|
10
|
+
document contains, the hard part is rarely the fix — it is working out **where** it
|
|
11
|
+
broke. A bad chunk size and a bad retriever look identical from the outside: in both
|
|
12
|
+
cases the answer just doesn't come back. `ragdoc` ends that guesswork. Give it a PDF,
|
|
13
|
+
a question, and the answer you know is in the document, and it tells you the single
|
|
14
|
+
stage that lost it — **extraction**, **chunking**, or **retrieval** — with the
|
|
15
|
+
evidence behind the verdict.
|
|
16
|
+
|
|
17
|
+
It is a headless, offline, deterministic CLI. No API keys, no LLM calls, no telemetry.
|
|
18
|
+
|
|
19
|
+
## Project status
|
|
20
|
+
|
|
21
|
+
ragdoc is in **beta** (`0.x`). Its attribution engine and CLI are covered by an
|
|
22
|
+
extensive automated test suite — unit tests plus integration tests against fixture
|
|
23
|
+
PDFs that exercise each stage and every failure mode. What is still maturing is
|
|
24
|
+
*breadth*: validation across a wide range of real-world PDFs is ongoing, so on unusual
|
|
25
|
+
layouts or heavily structured documents a verdict may warrant a careful read. Treat the
|
|
26
|
+
output as strong, evidence-backed signal rather than an oracle, and expect the occasional
|
|
27
|
+
rough edge before `1.0`. Reports of PDFs it gets wrong are especially welcome.
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
`ragdoc` runs your document through the forward pipeline once, then runs a chain of
|
|
32
|
+
backward checks that each consume one stage's output:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
load → chunk → index → retrieve (the forward pipeline)
|
|
36
|
+
│ │ │ │
|
|
37
|
+
locate → survive ───────→ rank (the backward checks)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **locate** — is the expected answer actually in the extracted text? If not, the
|
|
41
|
+
loader lost it → **extraction**.
|
|
42
|
+
- **survive** — does the answer survive chunking intact inside one chunk, with the
|
|
43
|
+
section context it needs to be findable? If a boundary cut it, or its heading was
|
|
44
|
+
never embedded → **chunking**.
|
|
45
|
+
- **rank** — does the chunk holding the answer rank within `top_k`? If it ranks too
|
|
46
|
+
low → **retrieval**.
|
|
47
|
+
|
|
48
|
+
The chain stops at the first failing stage; every later stage is reported as *not
|
|
49
|
+
reached*, never as a false pass. The answer is supplied by you — nothing is inferred
|
|
50
|
+
from the model's own scores — so the verdict is grounded, not another guess.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install ragdoc
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Requires Python 3.12. The first run downloads a small local embedding model
|
|
59
|
+
(`all-MiniLM-L6-v2`); every run after that is fully offline.
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ragdoc diagnose paper.pdf "What was the sample size?" "384 participants"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
extraction ✓
|
|
69
|
+
chunking ✗ section heading not embedded in chunk text
|
|
70
|
+
retrieval ✗ target chunk ranked 23 of 400; precision@5 = 0.00 (max 0.20); RR = 0.04
|
|
71
|
+
generation — not reached
|
|
72
|
+
|
|
73
|
+
VERDICT: chunking — the answer's chunk sits under a heading that is not embedded
|
|
74
|
+
FIX: prepend section path to chunk text before embedding
|
|
75
|
+
|
|
76
|
+
Run configuration:
|
|
77
|
+
loader: pymupdf
|
|
78
|
+
chunk size: 500
|
|
79
|
+
overlap: 50
|
|
80
|
+
embedding model: all-MiniLM-L6-v2
|
|
81
|
+
top_k: 5
|
|
82
|
+
prepend_section_context: False
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The answer can also be read from a file (handy for long or multi-line answers):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
ragdoc diagnose paper.pdf "What was the sample size?" --answer-file answer.txt
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Add `--verbose` for the full evidence behind each check, `--show-retrieved` to see the
|
|
92
|
+
retrieved chunks with the target marked, or `--ascii` for terminals that can't render
|
|
93
|
+
Unicode.
|
|
94
|
+
|
|
95
|
+
## Commands
|
|
96
|
+
|
|
97
|
+
### `diagnose` — one question, one verdict
|
|
98
|
+
```bash
|
|
99
|
+
ragdoc diagnose paper.pdf "What was the sample size?" "384 participants"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `compare` — how different loaders extract the same answer
|
|
103
|
+
Silent extraction failures are often a loader problem. `compare` runs every PDF loader
|
|
104
|
+
against the same answer so you can see which one actually preserves it.
|
|
105
|
+
```bash
|
|
106
|
+
ragdoc compare paper.pdf "384 participants"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `batch` — a failure distribution across a question set
|
|
110
|
+
One verdict tells you about one question; a *distribution* tells you where a pipeline
|
|
111
|
+
systematically breaks. Point `batch` at a JSON question set and it reports how the
|
|
112
|
+
failures split across the stages.
|
|
113
|
+
```bash
|
|
114
|
+
ragdoc batch paper.pdf questions.json --json results.json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The question set is a JSON array:
|
|
118
|
+
```json
|
|
119
|
+
[
|
|
120
|
+
{ "question": "What was the sample size?", "expected_answer": "384 participants" },
|
|
121
|
+
{ "question": "Which model performed best?", "expected_answer": "the gradient-boosted ensemble" }
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `sweep` — find the best configuration
|
|
126
|
+
Chunk size is a balance: too small splits answers apart (chunking failures), too big
|
|
127
|
+
dilutes them (retrieval failures). `sweep` runs the whole question set across a grid of
|
|
128
|
+
configurations and names the one that survives the most answers.
|
|
129
|
+
```bash
|
|
130
|
+
ragdoc sweep paper.pdf questions.json --chunk-size 500,800,1200 --overlap 0,50
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
It prints a table — one row per configuration, with the varied knobs, the no-failure
|
|
134
|
+
count, and the retrieval metrics — and highlights the winner. Use `--objective` to rank
|
|
135
|
+
by `no-failure` (default), `hit-rate`, or `mrr`.
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
Every knob has a sensible default and can be overridden on `diagnose` and `batch` (and
|
|
140
|
+
swept as a comma-separated list on `sweep`):
|
|
141
|
+
|
|
142
|
+
| Flag | Meaning | Default |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `--chunk-size` | characters per chunk | `500` |
|
|
145
|
+
| `--overlap` | characters shared between adjacent chunks | `50` |
|
|
146
|
+
| `--top-k` | retrieval cutoff the ranking is judged against | `10` |
|
|
147
|
+
| `--embedding-model` | sentence-transformers model name | `all-MiniLM-L6-v2` |
|
|
148
|
+
| `--prepend-section-context` / `--no-prepend-section-context` | prepend the section path to chunk text before embedding | off |
|
|
149
|
+
| `--loader` | PDF extraction backend (`pymupdf`, `pdfplumber`, `pypdf`) | `pymupdf` |
|
|
150
|
+
|
|
151
|
+
## Known limitations
|
|
152
|
+
|
|
153
|
+
- **The answer must appear in the document text.** Matching is verbatim or normalized
|
|
154
|
+
(whitespace, case, and Unicode compatibility forms such as ligatures are forgiven) —
|
|
155
|
+
it does **not** handle paraphrase or reworded answers.
|
|
156
|
+
- **Scanned / image-only pages are not read.** Pages with no extractable text are
|
|
157
|
+
flagged in an advisory, but `ragdoc` does no OCR.
|
|
158
|
+
- **The generation stage is not evaluated** — it is always reported as *not reached*.
|
|
159
|
+
`ragdoc` diagnoses retrieval-and-earlier, where the failures it targets live.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
[MIT](LICENSE) © 2026 Ahmed Imran
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ragdoc"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Headless diagnostic for PDF RAG pipelines: attributes a lost answer to the pipeline stage that lost it."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Ahmed Imran" }]
|
|
13
|
+
# Pinned to 3.12: the embedding stack (sentence-transformers / torch) does not
|
|
14
|
+
# reliably install on 3.14.
|
|
15
|
+
requires-python = ">=3.12,<3.13"
|
|
16
|
+
# PyMuPDF / pdfplumber / pypdf extract the PDF. sentence-transformers runs the
|
|
17
|
+
# embedding model locally, downloading it only on first use (offline thereafter);
|
|
18
|
+
# numpy backs the retriever's brute-force cosine scoring. The embedding stack is
|
|
19
|
+
# imported lazily, so the heavy import stays confined to the indexer.
|
|
20
|
+
dependencies = ["PyMuPDF>=1.24", "pdfplumber>=0.10", "pypdf>=6.21.2", "sentence-transformers>=2.2", "numpy>=1.24"]
|
|
21
|
+
keywords = ["rag", "pdf", "retrieval-augmented-generation", "embeddings", "llm", "diagnostics", "attribution"]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Environment :: Console",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
30
|
+
"Topic :: Text Processing :: General",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://ahmedimran.dev"
|
|
35
|
+
Source = "https://github.com/Sheikh-Ahmed-Imran/rag-doc"
|
|
36
|
+
Issues = "https://github.com/Sheikh-Ahmed-Imran/rag-doc/issues"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
ragdoc = "ragdoc.cli:main"
|
ragdoc-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""ragdoc — headless attribution engine for PDF RAG pipelines.
|
|
2
|
+
|
|
3
|
+
This top level re-exports the run configuration and the artifact/result types
|
|
4
|
+
that form the shared vocabulary of the pipeline and the attribution engine, so
|
|
5
|
+
callers can ``from ragdoc import RunConfig, ExtractedDoc, Chunk, Ranking,
|
|
6
|
+
CheckResult`` without reaching into submodules.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from ragdoc.artifacts import (
|
|
12
|
+
CharSpan,
|
|
13
|
+
Chunk,
|
|
14
|
+
CheckResult,
|
|
15
|
+
CheckStatus,
|
|
16
|
+
ExtractedDoc,
|
|
17
|
+
PageSpan,
|
|
18
|
+
Ranking,
|
|
19
|
+
RankedChunk,
|
|
20
|
+
)
|
|
21
|
+
from ragdoc.config import RunConfig
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"RunConfig",
|
|
25
|
+
"CharSpan",
|
|
26
|
+
"PageSpan",
|
|
27
|
+
"ExtractedDoc",
|
|
28
|
+
"Chunk",
|
|
29
|
+
"RankedChunk",
|
|
30
|
+
"Ranking",
|
|
31
|
+
"CheckStatus",
|
|
32
|
+
"CheckResult",
|
|
33
|
+
]
|