pgrag-eval-lite 0.2.0__py3-none-any.whl
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.
- pgrag_eval_lite-0.2.0.dist-info/METADATA +31 -0
- pgrag_eval_lite-0.2.0.dist-info/RECORD +16 -0
- pgrag_eval_lite-0.2.0.dist-info/WHEEL +5 -0
- pgrag_eval_lite-0.2.0.dist-info/entry_points.txt +2 -0
- pgrag_eval_lite-0.2.0.dist-info/top_level.txt +1 -0
- pgrag_inspector/__init__.py +3 -0
- pgrag_inspector/chunk_diff.py +239 -0
- pgrag_inspector/cli.py +425 -0
- pgrag_inspector/exporter.py +406 -0
- pgrag_inspector/metrics.py +90 -0
- pgrag_inspector/models.py +80 -0
- pgrag_inspector/retrieval.py +222 -0
- pgrag_inspector/sample_data.py +585 -0
- pgrag_inspector/scanner.py +94 -0
- pgrag_inspector/studio.py +1038 -0
- pgrag_inspector/synthesizer.py +109 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgrag-eval-lite
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local-First RAG Retrieval, Chunking & pgvector Diagnostic Workbench (CLI)
|
|
5
|
+
Author: pgRAG-Inspector Engineering
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pydantic>=2.10.0
|
|
10
|
+
Requires-Dist: polars>=1.18.0
|
|
11
|
+
Requires-Dist: numpy>=2.1.0
|
|
12
|
+
Requires-Dist: rich>=13.9.4
|
|
13
|
+
Requires-Dist: typer>=0.15.1
|
|
14
|
+
Requires-Dist: sqlalchemy>=2.0.36
|
|
15
|
+
Requires-Dist: pgvector>=0.3.6
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.3.0; extra == "dev"
|
|
18
|
+
Provides-Extra: postgres
|
|
19
|
+
Requires-Dist: asyncpg>=0.30.0; extra == "postgres"
|
|
20
|
+
|
|
21
|
+
# pgRAG-Inspector (pgrag-eval-lite & pgRAG-Inspector Pro)
|
|
22
|
+
|
|
23
|
+
Local-First RAG Retrieval, Chunking & pgvector 0.8+ Diagnostic Workbench.
|
|
24
|
+
|
|
25
|
+
## Quickstart (Windows / macOS / Linux)
|
|
26
|
+
|
|
27
|
+
pip install -e .
|
|
28
|
+
pgrag init-samples
|
|
29
|
+
pgrag check --chunks examples/sample_chunks.json --golden examples/sample_golden_qa.json --fail-under-recall5 95
|
|
30
|
+
pgrag export-code --out-dir exported_rag_stack --agency-name "Your AI Engineering Studio"
|
|
31
|
+
pgrag studio
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pgrag_inspector/__init__.py,sha256=l3NPDJ-VFKIWuhMO85paQUHLzI31P5-KpspwQNbnBgk,119
|
|
2
|
+
pgrag_inspector/chunk_diff.py,sha256=f0VGbGKP08P-KctE4A0RzKF6i17EhfYFjRGv8hIW2UU,9134
|
|
3
|
+
pgrag_inspector/cli.py,sha256=Oy-0nmv0mQ6q2rXRagNzUAbdtn5OtnrEqwsmOBTZsbs,15622
|
|
4
|
+
pgrag_inspector/exporter.py,sha256=s8aZQkkDduvVe94NSZGpqvNrc-X_JCvQUlVGAx3OO6Y,16479
|
|
5
|
+
pgrag_inspector/metrics.py,sha256=JsTxQqzpAbaV_VgoQfYIWjMO18otmGxmVa_epKPBXT4,3196
|
|
6
|
+
pgrag_inspector/models.py,sha256=NLJn8_jDSvaX0EWlvpNshL-noOg6ar-rspxTuRxB5wc,2114
|
|
7
|
+
pgrag_inspector/retrieval.py,sha256=kFyev8G-Asw6sAD7SjkeKcZdTsRQUFba1NAel60LXpg,9139
|
|
8
|
+
pgrag_inspector/sample_data.py,sha256=YnUJBiQbHfYvZPvkEpj9XiLv58liW3nDCZvosoGNDic,25895
|
|
9
|
+
pgrag_inspector/scanner.py,sha256=J2N5ClnjW7f6selE8iJ_vFkzvqVB9HS-8w4r2M5oIhI,3891
|
|
10
|
+
pgrag_inspector/studio.py,sha256=h9fkP8wnT2HdNgf-fXhi0VSQEihoEmevDRyLG4ii9_o,57633
|
|
11
|
+
pgrag_inspector/synthesizer.py,sha256=xqw-PpMDciphr2ObRz7avyWiQg_uVoLnLj1CQo9bk10,4895
|
|
12
|
+
pgrag_eval_lite-0.2.0.dist-info/METADATA,sha256=KRfV4i612_d7UTxufH4ErUSoxXt1n-Oyh5wgrcxTc8o,1091
|
|
13
|
+
pgrag_eval_lite-0.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
14
|
+
pgrag_eval_lite-0.2.0.dist-info/entry_points.txt,sha256=qDq7NI2QpoW01t3Jw71dW8Q4ELoUfO3fxUDue60lH58,50
|
|
15
|
+
pgrag_eval_lite-0.2.0.dist-info/top_level.txt,sha256=0yCoY20owoL7yoq25aOTJIbKQGLRgVQ4gh9ZPpUjGBg,16
|
|
16
|
+
pgrag_eval_lite-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pgrag_inspector
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
from pgrag_inspector.models import ChunkRecord
|
|
6
|
+
from pgrag_inspector.scanner import scan_chunks_health
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from docling.document_converter import DocumentConverter # type: ignore[import-not-found]
|
|
10
|
+
from docling.chunking import HybridChunker # type: ignore[import-not-found]
|
|
11
|
+
|
|
12
|
+
HAS_DOCLING = True
|
|
13
|
+
except ImportError:
|
|
14
|
+
HAS_DOCLING = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VisualDiffChunk(BaseModel):
|
|
18
|
+
"""Represents a chunk rendered inside the 3-column Visual Diff Studio."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(strict=True)
|
|
21
|
+
|
|
22
|
+
chunk_id: int
|
|
23
|
+
strategy_name: str
|
|
24
|
+
heading_context: str
|
|
25
|
+
content: str
|
|
26
|
+
token_count: int
|
|
27
|
+
overlap_pct: float
|
|
28
|
+
has_broken_table: bool
|
|
29
|
+
has_cutoff: bool
|
|
30
|
+
has_high_overlap: bool
|
|
31
|
+
badge_label: str
|
|
32
|
+
badge_color: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StrategyDiffColumn(BaseModel):
|
|
36
|
+
"""Summary and chunk list for one column in the 3-Strategy Diff Studio."""
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(strict=True)
|
|
39
|
+
|
|
40
|
+
strategy_id: str
|
|
41
|
+
title: str
|
|
42
|
+
subtitle: str
|
|
43
|
+
total_chunks: int
|
|
44
|
+
broken_tables_count: int
|
|
45
|
+
high_overlap_count: int
|
|
46
|
+
cutoff_count: int
|
|
47
|
+
chunks: list[VisualDiffChunk]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
SAMPLE_RAW_DOCUMENT = """# SEC Form 10-K: Cloud & Vector Infrastructure Division (FY 2026)
|
|
51
|
+
|
|
52
|
+
## Section 1. Consolidated Q3 2026 Segment Revenue & Gross Margin
|
|
53
|
+
Our Sovereign AI Cloud and Managed pgvector RDS divisions drove record top-line expansion during the third quarter ended September 30, 2026, supported by enterprise migration to dedicated HNSW vector clusters across regulated financial institutions.
|
|
54
|
+
|
|
55
|
+
| Segment Division | Q3 2026 Revenue ($M) | YoY Growth (%) | Gross Margin (%) |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| Sovereign AI Cloud | $412.80 | +64.2% | 78.4% |
|
|
58
|
+
| Managed pgvector RDS | $189.45 | +41.8% | 71.2% |
|
|
59
|
+
| Legacy On-Premise ETL | $54.10 | -12.5% | 44.0% |
|
|
60
|
+
|
|
61
|
+
Total consolidated Q3 2026 revenue reached $656.35M under ASC 606 guidelines.
|
|
62
|
+
|
|
63
|
+
## Section 2. Managed PostgreSQL 17 HNSW Vector Instance Matrix
|
|
64
|
+
Enterprise customers deploying high-dimensional vector indexes require predictable IOPS and memory pinning across availability zones to prevent index paging during concurrent hybrid search queries.
|
|
65
|
+
|
|
66
|
+
| Instance SKU | vCPU | RAM (GB) | Max Provisioned IOPS | Hourly Rate (USD) |
|
|
67
|
+
| --- | --- | --- | --- | --- |
|
|
68
|
+
| db.pg17.vec.2xlarge | 8 | 64 GB | 40,000 IOPS | $1.480/hr |
|
|
69
|
+
| db.pg17.vec.8xlarge | 32 | 256 GB | 120,000 IOPS | $5.920/hr |
|
|
70
|
+
| db.pg17.vec.16xlarge | 64 | 512 GB | 256,000 IOPS | $11.840/hr |
|
|
71
|
+
|
|
72
|
+
## Section 3. HNSW Filtered Query Recall & Iterative Index Scans
|
|
73
|
+
When executing vector similarity queries combined with a restrictive WHERE clause (such as tenant_id or workspace_tier filtering), classic HNSW graph traversal evaluates the post-filter only on the fixed ef_search candidate pool. This causes severe recall starvation where fewer than K rows are returned. Enabling pgvector 0.8+ iterative index scans via SET LOCAL hnsw.iterative_scan = 'relaxed_order' automatically continues traversing the graph until LIMIT K matching tuples are found.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _annotate_column(
|
|
78
|
+
strategy_id: str,
|
|
79
|
+
title: str,
|
|
80
|
+
subtitle: str,
|
|
81
|
+
raw_pieces: list[tuple[str, str]],
|
|
82
|
+
base_id: int,
|
|
83
|
+
) -> StrategyDiffColumn:
|
|
84
|
+
chunk_records: list[ChunkRecord] = []
|
|
85
|
+
for idx, (_, text_body) in enumerate(raw_pieces):
|
|
86
|
+
chunk_records.append(
|
|
87
|
+
ChunkRecord(
|
|
88
|
+
id=base_id + idx,
|
|
89
|
+
doc_id="LIVE-STUDIO-DOC.md",
|
|
90
|
+
content=text_body,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
flags = scan_chunks_health(chunk_records)
|
|
95
|
+
broken_ids = {
|
|
96
|
+
f.chunk_id for f in flags if f.issue_type == "BROKEN_MARKDOWN_TABLE"
|
|
97
|
+
}
|
|
98
|
+
overlap_map = {
|
|
99
|
+
f.chunk_id: f.overlap_Or_severity
|
|
100
|
+
for f in flags
|
|
101
|
+
if f.issue_type == "HIGH_TOKEN_OVERLAP (>80%)"
|
|
102
|
+
}
|
|
103
|
+
cutoff_ids = {
|
|
104
|
+
f.chunk_id for f in flags if f.issue_type == "MID_SENTENCE_CUTOFF"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
visual_chunks: list[VisualDiffChunk] = []
|
|
108
|
+
for idx, (heading_ctx, text_body) in enumerate(raw_pieces):
|
|
109
|
+
cid = base_id + idx
|
|
110
|
+
is_broken = cid in broken_ids
|
|
111
|
+
is_cutoff = cid in cutoff_ids
|
|
112
|
+
ov_pct = overlap_map.get(cid, 0.0)
|
|
113
|
+
is_overlap = ov_pct > 80.0
|
|
114
|
+
tok_count = len(re.findall(r"\S+", text_body))
|
|
115
|
+
|
|
116
|
+
if is_overlap:
|
|
117
|
+
badge_label = f"HIGH OVERLAP ({ov_pct:.0f}%)"
|
|
118
|
+
badge_color = "amber"
|
|
119
|
+
elif is_broken:
|
|
120
|
+
badge_label = "BROKEN TABLE SPLIT"
|
|
121
|
+
badge_color = "red"
|
|
122
|
+
elif is_cutoff:
|
|
123
|
+
badge_label = "MID-SENTENCE CUTOFF"
|
|
124
|
+
badge_color = "rose"
|
|
125
|
+
else:
|
|
126
|
+
badge_label = "HEALTHY + CONTEXTUALIZED"
|
|
127
|
+
badge_color = "emerald"
|
|
128
|
+
|
|
129
|
+
visual_chunks.append(
|
|
130
|
+
VisualDiffChunk(
|
|
131
|
+
chunk_id=cid,
|
|
132
|
+
strategy_name=title,
|
|
133
|
+
heading_context=heading_ctx,
|
|
134
|
+
content=text_body,
|
|
135
|
+
token_count=tok_count,
|
|
136
|
+
overlap_pct=ov_pct,
|
|
137
|
+
has_broken_table=is_broken,
|
|
138
|
+
has_cutoff=is_cutoff,
|
|
139
|
+
has_high_overlap=is_overlap,
|
|
140
|
+
badge_label=badge_label,
|
|
141
|
+
badge_color=badge_color,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return StrategyDiffColumn(
|
|
146
|
+
strategy_id=strategy_id,
|
|
147
|
+
title=title,
|
|
148
|
+
subtitle=subtitle,
|
|
149
|
+
total_chunks=len(visual_chunks),
|
|
150
|
+
broken_tables_count=len(broken_ids),
|
|
151
|
+
high_overlap_count=len(overlap_map),
|
|
152
|
+
cutoff_count=len(cutoff_ids),
|
|
153
|
+
chunks=visual_chunks,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def build_three_way_chunk_diff(raw_markdown: str = SAMPLE_RAW_DOCUMENT) -> list[StrategyDiffColumn]:
|
|
158
|
+
"""Generates the 3-column visual diff comparing Naive, High-Overlap, and Docling HybridChunker."""
|
|
159
|
+
clean_doc = raw_markdown.strip() or SAMPLE_RAW_DOCUMENT
|
|
160
|
+
|
|
161
|
+
# Strategy A: Naive Fixed-Character Splitter (slices tables mid-row & mid-sentence)
|
|
162
|
+
naive_pieces: list[tuple[str, str]] = []
|
|
163
|
+
step = 260
|
|
164
|
+
for i in range(0, len(clean_doc), step):
|
|
165
|
+
slice_str = clean_doc[i : i + 290].strip()
|
|
166
|
+
if slice_str:
|
|
167
|
+
naive_pieces.append(("None (Raw Character Slice)", slice_str))
|
|
168
|
+
|
|
169
|
+
# Strategy B: Sliding Window High-Overlap Splitter (>85% duplicate tokens between adjacent chunks)
|
|
170
|
+
raw_blocks = [b.strip() for b in re.split(r"\n\s*\n", clean_doc) if b.strip()]
|
|
171
|
+
overlap_pieces: list[tuple[str, str]] = []
|
|
172
|
+
for block in raw_blocks:
|
|
173
|
+
non_heading_lines = [
|
|
174
|
+
line.strip()
|
|
175
|
+
for line in block.splitlines()
|
|
176
|
+
if line.strip() and not line.strip().startswith("#")
|
|
177
|
+
]
|
|
178
|
+
if not non_heading_lines:
|
|
179
|
+
continue
|
|
180
|
+
p = "\n".join(non_heading_lines)
|
|
181
|
+
if p.startswith("|"):
|
|
182
|
+
rows = [r.strip() for r in p.splitlines() if r.strip()]
|
|
183
|
+
if len(rows) >= 4:
|
|
184
|
+
overlap_pieces.append(("Uncontextualized Table Slice", "\n".join(rows[2:])))
|
|
185
|
+
elif len(p) > 120:
|
|
186
|
+
overlap_pieces.append(("Sliding Window (stride=12% — Window 1)", p))
|
|
187
|
+
words = p.split()
|
|
188
|
+
if len(words) > 10:
|
|
189
|
+
slid = " ".join(words[2:])
|
|
190
|
+
if not slid.endswith("."):
|
|
191
|
+
slid += "."
|
|
192
|
+
overlap_pieces.append(
|
|
193
|
+
("Sliding Window (stride=12% — 92% Duplicate Tokens)", slid)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Strategy C: Docling HybridChunker + contextualize() (Preserves tables & injects parent headings)
|
|
197
|
+
docling_pieces: list[tuple[str, str]] = []
|
|
198
|
+
current_h1 = "Document Root"
|
|
199
|
+
current_h2 = "General Section"
|
|
200
|
+
|
|
201
|
+
sections = re.split(r"(?m)^(?=##\s)", clean_doc)
|
|
202
|
+
for sec in sections:
|
|
203
|
+
lines = [line.strip() for line in sec.strip().splitlines() if line.strip()]
|
|
204
|
+
if not lines:
|
|
205
|
+
continue
|
|
206
|
+
if lines[0].startswith("# "):
|
|
207
|
+
current_h1 = lines[0].lstrip("# ").strip()
|
|
208
|
+
continue
|
|
209
|
+
if lines[0].startswith("## "):
|
|
210
|
+
current_h2 = lines[0].lstrip("# ").strip()
|
|
211
|
+
body = "\n".join(lines[1:]).strip()
|
|
212
|
+
ctx_header = f"{current_h1} > {current_h2}"
|
|
213
|
+
contextualized_body = f"### {ctx_header}\n{body}"
|
|
214
|
+
if not contextualized_body.endswith((".", "|", "!", "?")):
|
|
215
|
+
contextualized_body += "."
|
|
216
|
+
docling_pieces.append((ctx_header, contextualized_body))
|
|
217
|
+
|
|
218
|
+
col_a = _annotate_column(
|
|
219
|
+
strategy_id="naive_fixed",
|
|
220
|
+
title="Strategy A: Naive Fixed-Char (290 chars)",
|
|
221
|
+
subtitle="Slices Markdown tables mid-row & truncates clauses",
|
|
222
|
+
raw_pieces=naive_pieces[:5],
|
|
223
|
+
base_id=201,
|
|
224
|
+
)
|
|
225
|
+
col_b = _annotate_column(
|
|
226
|
+
strategy_id="sliding_overlap",
|
|
227
|
+
title="Strategy B: High-Overlap Window (85% overlap)",
|
|
228
|
+
subtitle="Stuffs duplicate tokens into LLM context window",
|
|
229
|
+
raw_pieces=overlap_pieces[:6],
|
|
230
|
+
base_id=301,
|
|
231
|
+
)
|
|
232
|
+
col_c = _annotate_column(
|
|
233
|
+
strategy_id="docling_hybrid",
|
|
234
|
+
title="Strategy C: Docling HybridChunker + contextualize()",
|
|
235
|
+
subtitle="Preserves whole tables & prepends section hierarchy",
|
|
236
|
+
raw_pieces=docling_pieces,
|
|
237
|
+
base_id=401,
|
|
238
|
+
)
|
|
239
|
+
return [col_a, col_b, col_c]
|