Rebuild precomputed retrieval without vector indexes
Browse files- .gitignore +6 -1
- scripts/generate_precomputed_examples.py +1 -10
- services/precomputed_assets.py +0 -2
- services/rag_pipeline.py +1 -1
- tests/test_precomputed_assets.py +62 -0
.gitignore
CHANGED
|
@@ -13,4 +13,9 @@ wheels/
|
|
| 13 |
.env
|
| 14 |
|
| 15 |
# DS_Store files
|
| 16 |
-
.DS_Store
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
.env
|
| 14 |
|
| 15 |
# DS_Store files
|
| 16 |
+
.DS_Store
|
| 17 |
+
|
| 18 |
+
# Precomputed example retrieval indexes are rebuilt from chunks.json
|
| 19 |
+
assets/example_bills/**/vector_store/
|
| 20 |
+
*.faiss
|
| 21 |
+
*.pkl
|
scripts/generate_precomputed_examples.py
CHANGED
|
@@ -23,13 +23,7 @@ from services.example_bills import EXAMPLE_BILL_MANIFEST_PATH, ExampleBill, load
|
|
| 23 |
from services.ingest import fetch_url_content
|
| 24 |
from services.precomputed_assets import write_precomputed_asset
|
| 25 |
from services.providers import get_default_api_key, instantiate_client
|
| 26 |
-
from services.rag_pipeline import
|
| 27 |
-
build_vector_store,
|
| 28 |
-
document_hash_for_text,
|
| 29 |
-
generate_analysis_once,
|
| 30 |
-
save_vector_store,
|
| 31 |
-
split_into_chunks,
|
| 32 |
-
)
|
| 33 |
|
| 34 |
|
| 35 |
def _parse_args() -> argparse.Namespace:
|
|
@@ -68,7 +62,6 @@ def _has_complete_assets(bill: ExampleBill) -> bool:
|
|
| 68 |
and bill.document_path.exists()
|
| 69 |
and bill.chunks_path.exists()
|
| 70 |
and bill.metadata_path.exists()
|
| 71 |
-
and bill.vector_store_dir.exists()
|
| 72 |
)
|
| 73 |
|
| 74 |
|
|
@@ -113,8 +106,6 @@ def main() -> int:
|
|
| 113 |
document_text = fetch_url_content(bill.source_url, timeout_seconds=args.fetch_timeout)
|
| 114 |
chunks = split_into_chunks(document_text)
|
| 115 |
analysis = generate_analysis_once(provider_client, document_text)
|
| 116 |
-
vector_store = build_vector_store(chunks)
|
| 117 |
-
save_vector_store(vector_store, bill.vector_store_dir)
|
| 118 |
|
| 119 |
document_hash = document_hash_for_text(document_text)
|
| 120 |
write_precomputed_asset(
|
|
|
|
| 23 |
from services.ingest import fetch_url_content
|
| 24 |
from services.precomputed_assets import write_precomputed_asset
|
| 25 |
from services.providers import get_default_api_key, instantiate_client
|
| 26 |
+
from services.rag_pipeline import document_hash_for_text, generate_analysis_once, split_into_chunks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
def _parse_args() -> argparse.Namespace:
|
|
|
|
| 62 |
and bill.document_path.exists()
|
| 63 |
and bill.chunks_path.exists()
|
| 64 |
and bill.metadata_path.exists()
|
|
|
|
| 65 |
)
|
| 66 |
|
| 67 |
|
|
|
|
| 106 |
document_text = fetch_url_content(bill.source_url, timeout_seconds=args.fetch_timeout)
|
| 107 |
chunks = split_into_chunks(document_text)
|
| 108 |
analysis = generate_analysis_once(provider_client, document_text)
|
|
|
|
|
|
|
| 109 |
|
| 110 |
document_hash = document_hash_for_text(document_text)
|
| 111 |
write_precomputed_asset(
|
services/precomputed_assets.py
CHANGED
|
@@ -33,8 +33,6 @@ def load_precomputed_asset(bill: ExampleBill) -> PrecomputedExampleAsset | None:
|
|
| 33 |
return None
|
| 34 |
if not bill.chunks_path.exists():
|
| 35 |
return None
|
| 36 |
-
if not bill.vector_store_dir.exists():
|
| 37 |
-
return None
|
| 38 |
|
| 39 |
analysis_payload = json.loads(bill.analysis_path.read_text(encoding="utf-8"))
|
| 40 |
document_text = bill.document_path.read_text(encoding="utf-8")
|
|
|
|
| 33 |
return None
|
| 34 |
if not bill.chunks_path.exists():
|
| 35 |
return None
|
|
|
|
|
|
|
| 36 |
|
| 37 |
analysis_payload = json.loads(bill.analysis_path.read_text(encoding="utf-8"))
|
| 38 |
document_text = bill.document_path.read_text(encoding="utf-8")
|
services/rag_pipeline.py
CHANGED
|
@@ -263,7 +263,7 @@ def load_vector_store(vector_store_dir: Path) -> FAISS:
|
|
| 263 |
|
| 264 |
def hydrate_precomputed_example(asset: PrecomputedExampleAsset) -> CachedDocumentArtifacts:
|
| 265 |
analysis = AnalysisResult.model_validate(asset.analysis_payload)
|
| 266 |
-
vector_store =
|
| 267 |
artifacts = CachedDocumentArtifacts(
|
| 268 |
document_hash=asset.bill.document_hash or document_hash_for_text(asset.document_text),
|
| 269 |
document_text=asset.document_text,
|
|
|
|
| 263 |
|
| 264 |
def hydrate_precomputed_example(asset: PrecomputedExampleAsset) -> CachedDocumentArtifacts:
|
| 265 |
analysis = AnalysisResult.model_validate(asset.analysis_payload)
|
| 266 |
+
vector_store = build_vector_store(asset.chunks)
|
| 267 |
artifacts = CachedDocumentArtifacts(
|
| 268 |
document_hash=asset.bill.document_hash or document_hash_for_text(asset.document_text),
|
| 269 |
document_text=asset.document_text,
|
tests/test_precomputed_assets.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
from services.example_bills import ExampleBill
|
| 6 |
+
from services.precomputed_assets import load_precomputed_asset
|
| 7 |
+
from services.rag_pipeline import hydrate_precomputed_example
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def test_load_precomputed_asset_does_not_require_vector_store_dir(monkeypatch, tmp_path) -> None:
|
| 11 |
+
from services import example_bills as example_bills_module
|
| 12 |
+
|
| 13 |
+
monkeypatch.setattr(example_bills_module, "EXAMPLE_BILL_ASSETS_ROOT", tmp_path)
|
| 14 |
+
bill = ExampleBill(
|
| 15 |
+
id="example-bill",
|
| 16 |
+
title="Example Bill",
|
| 17 |
+
source_url="https://example.com/bill.pdf",
|
| 18 |
+
document_hash="hash",
|
| 19 |
+
asset_dir="example-bill",
|
| 20 |
+
)
|
| 21 |
+
root_dir = tmp_path / bill.asset_dir
|
| 22 |
+
root_dir.mkdir(parents=True)
|
| 23 |
+
(root_dir / "analysis.json").write_text(json.dumps({"executive_summary": "Summary"}), encoding="utf-8")
|
| 24 |
+
(root_dir / "document.txt").write_text("Document text", encoding="utf-8")
|
| 25 |
+
(root_dir / "chunks.json").write_text(json.dumps(["Chunk one", "Chunk two"]), encoding="utf-8")
|
| 26 |
+
|
| 27 |
+
asset = load_precomputed_asset(bill)
|
| 28 |
+
|
| 29 |
+
assert asset is not None
|
| 30 |
+
assert asset.document_text == "Document text"
|
| 31 |
+
assert asset.chunks == ["Chunk one", "Chunk two"]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def test_hydrate_precomputed_example_rebuilds_vector_store_from_chunks(monkeypatch, tmp_path) -> None:
|
| 35 |
+
from services import example_bills as example_bills_module
|
| 36 |
+
|
| 37 |
+
monkeypatch.setattr(example_bills_module, "EXAMPLE_BILL_ASSETS_ROOT", tmp_path)
|
| 38 |
+
bill = ExampleBill(
|
| 39 |
+
id="example-bill",
|
| 40 |
+
title="Example Bill",
|
| 41 |
+
source_url="https://example.com/bill.pdf",
|
| 42 |
+
document_hash="hash",
|
| 43 |
+
asset_dir="example-bill",
|
| 44 |
+
)
|
| 45 |
+
root_dir = tmp_path / bill.asset_dir
|
| 46 |
+
root_dir.mkdir(parents=True)
|
| 47 |
+
(root_dir / "analysis.json").write_text(json.dumps({"executive_summary": "Summary"}), encoding="utf-8")
|
| 48 |
+
(root_dir / "document.txt").write_text("Document text", encoding="utf-8")
|
| 49 |
+
(root_dir / "chunks.json").write_text(json.dumps(["Chunk one", "Chunk two"]), encoding="utf-8")
|
| 50 |
+
|
| 51 |
+
asset = load_precomputed_asset(bill)
|
| 52 |
+
assert asset is not None
|
| 53 |
+
|
| 54 |
+
built_chunks: list[list[str]] = []
|
| 55 |
+
|
| 56 |
+
monkeypatch.setattr("services.rag_pipeline.build_vector_store", lambda chunks: built_chunks.append(list(chunks)) or object())
|
| 57 |
+
|
| 58 |
+
artifacts = hydrate_precomputed_example(asset)
|
| 59 |
+
|
| 60 |
+
assert built_chunks == [["Chunk one", "Chunk two"]]
|
| 61 |
+
assert artifacts.vector_store is not None
|
| 62 |
+
assert artifacts.document_text == "Document text"
|