File size: 6,767 Bytes
0ee953b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""
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()