RAG4Vrikshayurveda / augment_and_reindex.py
Viraj77's picture
Initial commit: RAG for Vrikshayurveda with forward+backward chaining inference engine
0ee953b
Raw
History Blame Contribute Delete
6.77 kB
"""
JAIM - Data Augmentation & Re-indexing Script
Generates 4 paraphrased symptom variants per entry using Groq (Llama 3.3 70B),
embeds everything with BAAI/bge-large-en-v1.5 (1024 dims),
and batch-upserts all vectors into Pinecone.
Run this ONCE before starting the app.
Requires: A Pinecone index with dimension=1024, metric=cosine.
"""
import os
import json
import time
import requests
from dotenv import load_dotenv
from pinecone import Pinecone
from sentence_transformers import SentenceTransformer
from embed_pdf import extract_entries_from_pdf
load_dotenv()
# ─── Config ──────────────────────────────────────────────────────────────────
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
INDEX_NAME = os.getenv("PINECONE_INDEX_NAME", "jaim3")
EMBEDDING_MODEL = "BAAI/bge-large-en-v1.5"
GROQ_MODEL = "llama-3.3-70b-versatile"
PDF_PATH = "db.pdf"
NUM_PARAPHRASES = 4
BATCH_SIZE = 50
# ─── Paraphrase Generation via Groq ──────────────────────────────────────────
def generate_paraphrases(symptoms: str) -> list[str]:
"""Use Groq (Llama 3.3 70B) to generate paraphrased symptom variants."""
url = "https://api.groq.com/openai/v1/chat/completions"
headers = {
"Authorization": f"Bearer {GROQ_API_KEY}",
"Content-Type": "application/json",
}
prompt = f"""Generate exactly {NUM_PARAPHRASES} different paraphrased versions of the following plant symptom description.
Each paraphrase should:
- Use different vocabulary and sentence structure
- Preserve the exact medical/botanical meaning
- Sound like how a farmer or gardener would naturally describe the problem
Return ONLY a valid JSON array of {NUM_PARAPHRASES} strings. No extra text, no markdown.
Original symptoms: "{symptoms}"
"""
payload = {
"model": GROQ_MODEL,
"messages": [
{"role": "system", "content": "You paraphrase text. Return only valid JSON arrays."},
{"role": "user", "content": prompt},
],
"temperature": 0.8,
"max_tokens": 512,
}
for attempt in range(3):
resp = requests.post(url, json=payload, headers=headers, timeout=30)
if resp.status_code == 429:
wait = 2 ** (attempt + 1)
print(f" ⏳ Rate limited, waiting {wait}s...")
time.sleep(wait)
continue
resp.raise_for_status()
break
else:
raise RuntimeError("Groq rate limit exceeded after 3 retries")
content = resp.json()["choices"][0]["message"]["content"].strip()
# Handle potential markdown code-block wrapping
if content.startswith("```"):
content = content.split("\n", 1)[1].rsplit("```", 1)[0].strip()
paraphrases = json.loads(content)
return paraphrases[:NUM_PARAPHRASES]
# ─── Main Pipeline ────────────────────────────────────────────────────────────
def main():
print("πŸ”„ Loading BAAI/bge-large-en-v1.5 embedding model (1024 dims)...")
model = SentenceTransformer(EMBEDDING_MODEL)
print("πŸ“„ Extracting entries from PDF...")
entries = extract_entries_from_pdf(PDF_PATH)
print(f" Found {len(entries)} entries")
print("🌲 Connecting to Pinecone...")
pc = Pinecone(api_key=PINECONE_API_KEY)
index = pc.Index(INDEX_NAME)
all_vectors = []
for i, entry in enumerate(entries):
original_id = f"entry_{i}_chunk_0"
symptoms = entry["symptoms"]
print(f"\n{'═' * 60}")
print(f"πŸ“ Entry {i} | Cause: {entry['cause_given']}")
print(f" Original: {symptoms[:80]}...")
# ── Generate paraphrases via Groq ─────────────────────────
print(f" πŸ€– Generating {NUM_PARAPHRASES} paraphrases...")
try:
paraphrases = generate_paraphrases(symptoms)
except Exception as e:
print(f" ⚠️ Failed to generate paraphrases: {e}")
paraphrases = []
time.sleep(1) # Rate-limit buffer between Groq calls
# ── Shared metadata (same for original + all variants) ────
base_metadata = {
"disorder": entry["disorder"],
"cause_given": entry["cause_given"],
"symptoms": entry["symptoms"],
"cause_elaborated": entry["cause_elaborated"],
"possible_causes": entry["possible_causes"],
"treatment_material": entry["treatment_material"],
}
# ── Embed original symptoms ──────────────────────────────
# BGE spec: documents do NOT need a prefix
original_embedding = model.encode(symptoms).tolist()
all_vectors.append({
"id": original_id,
"values": original_embedding,
"metadata": {
**base_metadata,
"is_augmented": False,
"parent_id": original_id,
}
})
# ── Embed paraphrased variants ───────────────────────────
for j, para in enumerate(paraphrases):
aug_embedding = model.encode(para).tolist()
all_vectors.append({
"id": f"entry_{i}_aug_{j}",
"values": aug_embedding,
"metadata": {
**base_metadata,
"symptoms": para, # Store paraphrased version
"is_augmented": True,
"parent_id": original_id,
}
})
print(f" βœ… Variant {j + 1}: {para[:80]}...")
# ── Batch upsert to Pinecone ──────────────────────────────────
total = len(all_vectors)
print(f"\nπŸ“€ Uploading {total} vectors to Pinecone in batches of {BATCH_SIZE}...")
for start in range(0, total, BATCH_SIZE):
batch = all_vectors[start : start + BATCH_SIZE]
index.upsert(vectors=batch)
print(f" Batch {start // BATCH_SIZE + 1}: {len(batch)} vectors uploaded")
print("\nβœ… Augmentation & re-indexing complete!")
stats = index.describe_index_stats()
print(f"πŸ“Š Index stats: {stats}")
print(f" Expected: {len(entries)} originals + {len(entries) * NUM_PARAPHRASES} augmented = {total} total")
if __name__ == "__main__":
main()