venthrive-api / scripts /ingest_book.py
GitHub Actions
Sync from GitHub · 2026-06-25 17:24 UTC
e561008
Raw History Blame Contribute Delete
6.48 kB
"""
"Built on Rock" — Pinecone Ingestion Script
──────────────────────────────────────────────────────────────────────────────
Run this ONCE after setting up your Pinecone index to populate it with
content from "Built on Rock" by Iyanuoluwa Joseph Akinnusi.
Prerequisites:
1. Create a Pinecone index named 'venthrive-bff' with:
- Dimensions: 1024
- Metric: Cosine
- Cloud: AWS us-east-1 (Serverless, free tier)
2. Set PINECONE_API_KEY in backend/.env
3. Ensure the PDF is in the APEX folder (path configured below)
Usage (from the VenThrive/backend/ directory):
python scripts/ingest_book.py
No local ML library needed — embeddings are generated by Pinecone's
hosted multilingual-e5-large model (1024 dimensions).
"""
import os
import sys
import time
# Load backend env
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(os.path.dirname(__file__)), ".env"))
from pinecone import Pinecone
import pypdf
# ─── Config ───────────────────────────────────────────────────────────────────
# Absolute path — the PDF lives in the sibling APEX project folder
_BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
PDF_PATH = os.path.join(_BASE, "APEX", "Built On Rock by Iyanuoluwa Joseph Akinnusi-1.pdf")
CHUNK_WORDS = 400
OVERLAP_WORDS = 50
BATCH_SIZE = 90 # Pinecone Inference allows up to 96 inputs per request
EMBED_MODEL = "multilingual-e5-large"
def extract_pdf_text(path: str) -> str:
if not os.path.exists(path):
raise FileNotFoundError(
f"\n[ERR] PDF not found: {path}\n"
"Ensure 'Built On Rock by Iyanuoluwa Joseph Akinnusi-1.pdf' "
"is in the APEX folder."
)
reader = pypdf.PdfReader(path)
return "\n".join(page.extract_text() or "" for page in reader.pages)
def chunk_text(text: str, chunk_words: int, overlap_words: int) -> list[dict]:
words = text.split()
chunks = []
i = 0
while i < len(words):
chunk_text_str = " ".join(words[i : i + chunk_words])
chunks.append({
"id": f"chunk_{len(chunks):04d}",
"text": chunk_text_str,
"meta": {
"source": "built_on_rock",
"chunk_id": len(chunks),
"word_start": i,
"word_end": min(i + chunk_words, len(words)),
},
})
i += chunk_words - overlap_words
return chunks
def main():
api_key = os.environ.get("PINECONE_API_KEY", "")
index_name = os.environ.get("PINECONE_INDEX_NAME", "venthrive-bff")
if not api_key:
print("[ERR] PINECONE_API_KEY not set in backend/.env")
return
# ── Extract text ──────────────────────────────────────────────────────────
print(f"[PDF] Reading: {os.path.basename(PDF_PATH)}")
text = extract_pdf_text(PDF_PATH)
words = len(text.split())
print(f" {words:,} words extracted from PDF")
# ── Chunk ─────────────────────────────────────────────────────────────────
chunks = chunk_text(text, CHUNK_WORDS, OVERLAP_WORDS)
print(f" {len(chunks)} chunks ({CHUNK_WORDS} words each, {OVERLAP_WORDS} overlap)")
# ── Connect to Pinecone ───────────────────────────────────────────────────
print(f"\n[PIN] Connecting to Pinecone index '{index_name}'...")
pc = Pinecone(api_key=api_key)
index = pc.Index(index_name)
stats = index.describe_index_stats()
existing = stats.get("total_vector_count", 0)
print(f" Existing vectors: {existing}")
if existing > 0:
print(f" Index already has {existing} vectors.")
ans = input(" Clear and re-ingest? (y/N): ").strip().lower()
if ans == 'y':
index.delete(delete_all=True)
print(" ✓ Cleared")
else:
print(" Skipping ingestion.")
return
# ── Embed + upsert in batches ─────────────────────────────────────────────
print(f"\n[AI] Embedding + uploading {len(chunks)} chunks via Pinecone Inference...")
print(" (Model: multilingual-e5-large, 1024 dims — no local ML needed)")
for i in range(0, len(chunks), BATCH_SIZE):
batch = chunks[i : i + BATCH_SIZE]
texts = [c["text"] for c in batch]
# Generate embeddings via Pinecone Inference API
embedding_result = pc.inference.embed(
model=EMBED_MODEL,
inputs=texts,
parameters={"input_type": "passage", "truncate": "END"},
)
embeddings = [e.values for e in embedding_result]
# Upsert vectors
vectors = [
{
"id": chunk["id"],
"values": embeddings[j],
"metadata": {**chunk["meta"], "text": chunk["text"]},
}
for j, chunk in enumerate(batch)
]
index.upsert(vectors=vectors)
done = min(i + BATCH_SIZE, len(chunks))
print(f" {done}/{len(chunks)} chunks uploaded")
# Small pause to stay within Pinecone rate limits
if done < len(chunks):
time.sleep(0.5)
# ── Done ──────────────────────────────────────────────────────────────────
final = index.describe_index_stats()
total = final.get("total_vector_count", "?")
print(f"\n[OK] Ingestion complete! Index '{index_name}' now has {total} vectors.")
print(" VenThrive AI will now draw from 'Built on Rock' for every relevant query.")
if __name__ == "__main__":
main()