Skip to content

Commit 6fa9985

Browse files
committed
feat: add script to ingest OPEA docs
- ingestion, backfill and export to HF complete for opea-docs
1 parent 28235d0 commit 6fa9985

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""ingest_opea_docs.py — Ingest local OPEA markdown/rst docs into chunks.json.
2+
3+
Pipeline shape in this repo:
4+
1) Parse + chunk local files into data/rag/trusted/chunks.json
5+
2) Backfill chunks.json into Supabase via backfill_supabase_rag.py
6+
7+
Usage:
8+
.venv/bin/python scripts/ingest_corpus/ingest_opea_docs.py
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import json
14+
import re
15+
import sys
16+
from collections import Counter
17+
from pathlib import Path
18+
19+
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
20+
21+
from scripts.ingest_corpus.base_ingestor import (
22+
BaseIngestor,
23+
REPO_ROOT,
24+
log,
25+
make_link_ratio_validator,
26+
make_min_word_validator,
27+
split_text_semantic,
28+
)
29+
30+
DATA_ROOT = REPO_ROOT / "datasets" / "opea-docs"
31+
NAMESPACE = "ai_ref_knowledge"
32+
SOURCE_NAME = "OPEA Documentation"
33+
TAGS = ["opea", "enterprise-ai", "genai", "docs", "P1"]
34+
35+
36+
def _clean_rst_noise(text: str) -> str:
37+
"""Remove high-noise Sphinx/rst directives while preserving prose."""
38+
cleaned = text
39+
cleaned = re.sub(r"(?m)^\s*\.\.\s+(toctree|code-block|note|warning|tip|important|seealso|image)::.*$", "", cleaned)
40+
cleaned = re.sub(r"(?m)^\s*:[a-zA-Z0-9_-]+:\s*.*$", "", cleaned)
41+
cleaned = re.sub(r"(?m)^\s*\.{2}\s+_.*?:\s*https?://\S+\s*$", "", cleaned)
42+
cleaned = re.sub(r"(?m)^\s*[=\-~`^\"']{3,}\s*$", "", cleaned)
43+
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
44+
return cleaned.strip()
45+
46+
47+
def _collect_files() -> list[Path]:
48+
md_files = sorted(DATA_ROOT.rglob("*.md"))
49+
rst_files = sorted(DATA_ROOT.rglob("*.rst"))
50+
return md_files + rst_files
51+
52+
53+
def run() -> dict[str, object]:
54+
if not DATA_ROOT.exists():
55+
raise FileNotFoundError(f"OPEA dataset directory missing: {DATA_ROOT}")
56+
57+
files = _collect_files()
58+
log.info("ingest_opea: discovered %s document files", len(files))
59+
60+
validators = [
61+
make_min_word_validator(20),
62+
make_link_ratio_validator(0.35),
63+
]
64+
ingestor = BaseIngestor(
65+
source_name=SOURCE_NAME,
66+
source_type="markdown",
67+
validators=validators,
68+
)
69+
70+
stats: Counter[str] = Counter()
71+
total_order = 0
72+
73+
for path in files:
74+
rel = path.relative_to(DATA_ROOT).as_posix()
75+
raw = path.read_text(encoding="utf-8", errors="ignore")
76+
cleaned = _clean_rst_noise(raw)
77+
if not cleaned:
78+
stats["empty_files"] += 1
79+
continue
80+
81+
chunks = split_text_semantic(cleaned, chunk_size=800, overlap_words=25)
82+
if not chunks:
83+
stats["empty_after_chunking"] += 1
84+
continue
85+
86+
metadata = [
87+
{
88+
"namespace": NAMESPACE,
89+
"dataset_root": "datasets/opea-docs",
90+
"relative_path": rel,
91+
"language": "en",
92+
"chunker_version": "opea-semantic-v1",
93+
}
94+
for _ in chunks
95+
]
96+
source_url = f"file://datasets/opea-docs/{rel}"
97+
added = ingestor.add(
98+
chunks,
99+
source_url=source_url,
100+
tags=TAGS,
101+
start_order=total_order,
102+
metadata=metadata,
103+
)
104+
105+
total_order += len(chunks)
106+
stats["files_processed"] += 1
107+
stats["chunks_candidate"] += len(chunks)
108+
stats["chunks_added"] += len(added)
109+
stats["chunks_rejected"] += len(chunks) - len(added)
110+
log.info("ingest_opea: processed %s -> %s/%s chunks added", rel, len(added), len(chunks))
111+
112+
total_after_flush = ingestor.flush()
113+
result: dict[str, object] = {
114+
"dataset_root": str(DATA_ROOT),
115+
"namespace": NAMESPACE,
116+
"source_name": SOURCE_NAME,
117+
"files_discovered": len(files),
118+
"files_processed": int(stats["files_processed"]),
119+
"chunks_candidate": int(stats["chunks_candidate"]),
120+
"chunks_added": int(stats["chunks_added"]),
121+
"chunks_rejected": int(stats["chunks_rejected"]),
122+
"empty_files": int(stats["empty_files"]),
123+
"empty_after_chunking": int(stats["empty_after_chunking"]),
124+
"ingestor_skip_stats": dict(ingestor.skip_stats),
125+
"total_chunks_after_flush": total_after_flush,
126+
}
127+
print(json.dumps(result, indent=2))
128+
return result
129+
130+
131+
if __name__ == "__main__":
132+
run()

0 commit comments

Comments
 (0)