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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pgrag = pgrag_inspector.cli:app
@@ -0,0 +1 @@
1
+ pgrag_inspector
@@ -0,0 +1,3 @@
1
+ """pgRAG-Inspector: Local-First RAG Retrieval, Chunking & pgvector Diagnostic Workbench."""
2
+
3
+ __version__ = "0.2.0"
@@ -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]