mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-21 14:35:27 -04:00
137 lines
4.4 KiB
Python
137 lines
4.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Build the RAG vector index for the OpenMC codebase.
|
||
|
|
|
||
|
|
This is the index-building half of the RAG pipeline. All operations are local
|
||
|
|
once the embedding model has been downloaded and cached (see embeddings.py for
|
||
|
|
details on model download, caching, and network behavior). It walks the repo,
|
||
|
|
chunks every
|
||
|
|
C++/Python/RST file (via chunker.py), embeds all chunks into 384-dim vectors
|
||
|
|
(via embeddings.py), and stores them in a local LanceDB database on disk. The
|
||
|
|
result is a .claude/cache/rag_index/ directory containing two tables — "code"
|
||
|
|
and "docs" — that openmc_search.py queries at search time.
|
||
|
|
|
||
|
|
Building the full index takes ~5 minutes on a 10-core machine. The bottleneck
|
||
|
|
is the embedding step (running all chunks through the MiniLM model on CPU).
|
||
|
|
|
||
|
|
Can be run standalone: python indexer.py
|
||
|
|
Or called programmatically: from indexer import build_index; build_index()
|
||
|
|
The MCP server (openmc_mcp_server.py) uses the latter when the agent calls
|
||
|
|
openmc_rag_rebuild.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import lancedb
|
||
|
|
import sys
|
||
|
|
import time
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# This file lives at .claude/tools/rag/indexer.py. The sys.path insert lets
|
||
|
|
# us import sibling modules (embeddings, chunker) when run as a standalone
|
||
|
|
# script. When imported from the MCP server, the server has already done this.
|
||
|
|
TOOLS_DIR = Path(__file__).resolve().parent.parent
|
||
|
|
sys.path.insert(0, str(TOOLS_DIR / "rag"))
|
||
|
|
|
||
|
|
from embeddings import EmbeddingProvider
|
||
|
|
from chunker import chunk_file
|
||
|
|
|
||
|
|
|
||
|
|
OPENMC_ROOT = Path(__file__).resolve().parents[3]
|
||
|
|
CACHE_DIR = OPENMC_ROOT / ".claude" / "cache"
|
||
|
|
INDEX_DIR = CACHE_DIR / "rag_index"
|
||
|
|
|
||
|
|
CODE_PATTERNS = [
|
||
|
|
"src/**/*.cpp",
|
||
|
|
"include/openmc/**/*.h",
|
||
|
|
"openmc/**/*.py",
|
||
|
|
"tests/**/*.py",
|
||
|
|
"examples/**/*.py",
|
||
|
|
]
|
||
|
|
|
||
|
|
DOC_PATTERNS = [
|
||
|
|
"docs/**/*.rst",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def collect_chunks(patterns, openmc_root):
|
||
|
|
"""Collect all chunks from files matching the given patterns."""
|
||
|
|
chunks = []
|
||
|
|
for pattern in patterns:
|
||
|
|
for filepath in sorted(openmc_root.glob(pattern)):
|
||
|
|
if "__pycache__" in str(filepath):
|
||
|
|
continue
|
||
|
|
file_chunks = chunk_file(filepath, openmc_root)
|
||
|
|
chunks.extend(file_chunks)
|
||
|
|
return chunks
|
||
|
|
|
||
|
|
|
||
|
|
def build_index():
|
||
|
|
"""Build or rebuild the complete vector index."""
|
||
|
|
start = time.time()
|
||
|
|
|
||
|
|
# Collect all chunks
|
||
|
|
print("Collecting code chunks...")
|
||
|
|
code_chunks = collect_chunks(CODE_PATTERNS, OPENMC_ROOT)
|
||
|
|
print(f" {len(code_chunks)} code chunks")
|
||
|
|
|
||
|
|
print("Collecting doc chunks...")
|
||
|
|
doc_chunks = collect_chunks(DOC_PATTERNS, OPENMC_ROOT)
|
||
|
|
print(f" {len(doc_chunks)} doc chunks")
|
||
|
|
|
||
|
|
all_chunks = code_chunks + doc_chunks
|
||
|
|
if not all_chunks:
|
||
|
|
print("ERROR: No chunks collected!", file=sys.stderr)
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
# Create embeddings
|
||
|
|
all_texts = [c["text"] for c in all_chunks]
|
||
|
|
print("Creating embedding provider...")
|
||
|
|
embedder = EmbeddingProvider()
|
||
|
|
print(f" dim={embedder.dim}")
|
||
|
|
|
||
|
|
print("Embedding chunks...")
|
||
|
|
all_embeddings = embedder.embed(all_texts)
|
||
|
|
|
||
|
|
# Build LanceDB tables
|
||
|
|
INDEX_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
|
db = lancedb.connect(str(INDEX_DIR))
|
||
|
|
|
||
|
|
# Separate code vs doc records by index (code_chunks come first in all_chunks)
|
||
|
|
n_code = len(code_chunks)
|
||
|
|
code_records = []
|
||
|
|
doc_records = []
|
||
|
|
for i, (chunk, emb) in enumerate(zip(all_chunks, all_embeddings)):
|
||
|
|
record = {
|
||
|
|
"text": chunk["text"],
|
||
|
|
"filepath": chunk["filepath"],
|
||
|
|
"kind": chunk["kind"],
|
||
|
|
"symbol": chunk.get("symbol", ""),
|
||
|
|
"start_line": chunk.get("start_line", 0),
|
||
|
|
"end_line": chunk.get("end_line", 0),
|
||
|
|
"vector": emb,
|
||
|
|
}
|
||
|
|
if i < n_code:
|
||
|
|
code_records.append(record)
|
||
|
|
else:
|
||
|
|
doc_records.append(record)
|
||
|
|
|
||
|
|
# Create tables (drop existing)
|
||
|
|
result = db.table_names() if hasattr(db, "table_names") else db.list_tables()
|
||
|
|
existing = result.tables if hasattr(result, "tables") else list(result)
|
||
|
|
for table_name in ("code", "docs"):
|
||
|
|
if table_name in existing:
|
||
|
|
db.drop_table(table_name)
|
||
|
|
|
||
|
|
if code_records:
|
||
|
|
db.create_table("code", code_records)
|
||
|
|
print(f" Created 'code' table: {len(code_records)} rows")
|
||
|
|
|
||
|
|
if doc_records:
|
||
|
|
db.create_table("docs", doc_records)
|
||
|
|
print(f" Created 'docs' table: {len(doc_records)} rows")
|
||
|
|
|
||
|
|
elapsed = time.time() - start
|
||
|
|
print(f"Done in {elapsed:.1f}s")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
build_index()
|