Spaces:
Build error
Build error
| """ | |
| 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() | |