Louis-François Bouchard
compaction study: harness for compaction-vs-keep-everything on a long lesson
0ad2e61 Download data/scraping_scripts/graphrag_prep_input.py from towardsai-tutors/ai-tutor-chatbot: direct link, hf CLI and curl.
- Browser
- Download file 4.37 kB
-
https://huggingface.co/spaces/towardsai-tutors/ai-tutor-chatbot/resolve/a190140a9b070a1b0a7130d595fe46441c9c4e6c/data/scraping_scripts/graphrag_prep_input.py
- Command line
-
hf download hf://spaces/towardsai-tutors/ai-tutor-chatbot@a190140a9b070a1b0a7130d595fe46441c9c4e6c/data/scraping_scripts/graphrag_prep_input.py
-
curl -L -o graphrag_prep_input.py https://huggingface.co/spaces/towardsai-tutors/ai-tutor-chatbot/resolve/a190140a9b070a1b0a7130d595fe46441c9c4e6c/data/scraping_scripts/graphrag_prep_input.py
4.37 kB
| """Prepare GraphRAG input from the existing corpus. | |
| Emits one JSONL row per corpus document into the GraphRAG workspace input dir, | |
| using the canonical raw markdown mirrors as text and the KB manifest as the | |
| metadata source of truth. The row ``id`` is set to our ``doc_id`` so GraphRAG's | |
| ``text_units.document_ids`` map straight back to ``corpus_manifest.jsonl`` | |
| (source / url / lesson) at retrieval time -- no GraphRAG metadata propagation | |
| needed. | |
| This is part of the GraphRAG-vs-RAG experiment (branch experiment/graphrag-vs-rag). | |
| The workspace under data/graphrag/ is local-only and never uploaded to the HF | |
| vector-db bundle. | |
| uv run -m data.scraping_scripts.graphrag_prep_input # full corpus | |
| uv run -m data.scraping_scripts.graphrag_prep_input --limit 10 # smoke subset | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import logging | |
| from pathlib import Path | |
| logger = logging.getLogger("graphrag_prep_input") | |
| MANIFEST_PATH = "data/kb/generated/corpus_manifest.jsonl" | |
| DEFAULT_OUT = "data/graphrag/input/corpus.jsonl" | |
| def load_manifest(path: str) -> list[dict]: | |
| records: list[dict] = [] | |
| with open(path, encoding="utf-8") as handle: | |
| for line in handle: | |
| line = line.strip() | |
| if line: | |
| records.append(json.loads(line)) | |
| return records | |
| def read_doc_text(record: dict) -> str: | |
| """Canonical raw markdown for a manifest record, or '' if unreadable.""" | |
| path = record.get("path") | |
| if not path: | |
| return "" | |
| try: | |
| return Path(path).read_text(encoding="utf-8").strip() | |
| except OSError as exc: | |
| logger.warning( | |
| "Skipping %s: cannot read %s (%s)", record.get("doc_id"), path, exc | |
| ) | |
| return "" | |
| def build_rows(records: list[dict], sources: set[str] | None = None) -> list[dict]: | |
| rows: list[dict] = [] | |
| for record in records: | |
| if sources and record.get("source") not in sources: | |
| continue | |
| text = read_doc_text(record) | |
| if not text: | |
| continue | |
| rows.append( | |
| { | |
| "id": record["doc_id"], | |
| "title": record.get("title") or record["doc_id"], | |
| "text": text, | |
| "source": record.get("source", ""), | |
| "url": record.get("url", ""), | |
| "source_group": record.get("source_group", ""), | |
| } | |
| ) | |
| return rows | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--manifest", default=MANIFEST_PATH) | |
| parser.add_argument("--out", default=DEFAULT_OUT) | |
| parser.add_argument( | |
| "--limit", | |
| type=int, | |
| default=0, | |
| help="First N documents only (for a cheap smoke index); 0 = all.", | |
| ) | |
| parser.add_argument( | |
| "--sources", | |
| nargs="*", | |
| default=[], | |
| help="Restrict to these source keys (e.g. full_stack_ai_engineering); " | |
| "empty = all sources. Used to scope the GraphRAG index to the eval " | |
| "battery's sources and keep indexing within budget.", | |
| ) | |
| parser.add_argument( | |
| "--doc-ids", | |
| nargs="*", | |
| default=[], | |
| help="Restrict to these exact doc_ids (e.g. a single long lesson for the " | |
| "knowledge-compaction study).", | |
| ) | |
| return parser.parse_args() | |
| def main() -> None: | |
| logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") | |
| args = parse_args() | |
| records = load_manifest(args.manifest) | |
| if args.sources: | |
| wanted = set(args.sources) | |
| records = [r for r in records if r.get("source") in wanted] | |
| if args.doc_ids: | |
| wanted_ids = set(args.doc_ids) | |
| records = [r for r in records if r.get("doc_id") in wanted_ids] | |
| if args.limit: | |
| records = records[: args.limit] | |
| rows = build_rows(records) | |
| out_path = Path(args.out) | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| with open(out_path, "w", encoding="utf-8") as handle: | |
| for row in rows: | |
| handle.write(json.dumps(row, ensure_ascii=False) + "\n") | |
| total_tokens = sum(r.get("tokens", 0) for r in records) | |
| logger.info( | |
| "Wrote %d/%d docs (%.1fM manifest tokens) -> %s", | |
| len(rows), | |
| len(records), | |
| total_tokens / 1e6, | |
| out_path, | |
| ) | |
| if __name__ == "__main__": | |
| main() | |