"""Prompt builder + safety guardrails + few-shots. Distilled from step4_app.py to a much smaller, maintainable surface. The 30-rule monolith was compressed to the rules that actually fire. """ from __future__ import annotations from typing import List, Optional # ── Safety guardrails: shown to the LLM, also enforced by citation_guard.py ── SAFETY_GUARDRAILS = { "pest": ( "SAFETY: Recommend ONLY ICAR-approved insecticides with EXACT dose and unit " "(e.g. 'Imidacloprid 17.8% SL @ 0.3 ml/L'). Never recommend banned chemicals " "(Endosulfan, Monocrotophos, Phorate, Phosphamidon)." ), "disease": ( "SAFETY: Recommend ONLY ICAR-approved fungicides with EXACT dose and unit. " "Always include timing and method (foliar spray vs drench) and a 1-line IPM note." ), "nutrient": ( "SAFETY: Quote standard ICAR fertiliser doses per acre/hectare. Mention soil " "test if the question is generic ('what fertiliser')." ), "weather": ( "SAFETY: For frost/heat/drought/flood, give protective steps for the next 48-72h. " "Do not predict beyond Open-Meteo's 7-day horizon." ), "price": ( "SAFETY: Do NOT quote prices from the retrieved KCC pairs (they may be years old). " "Direct the farmer to the live mandi forecast (P25/P50/P75) for current price ranges." ), "scheme": ( "SAFETY: Quote scheme names and amounts ONLY if confident. Always end with the " "official portal URL (pmkisan.gov.in, pmfby.gov.in) so the farmer can verify." ), } # Hard rules that override retrieval if it suggests something dangerous. HARD_OVERRIDES = [ # (pest_or_disease_substring, banned_chemical_substring, replacement_advice) ("brown plant hopper", "imidacloprid", "For BPH (Brown Plant Hopper) on rice, NEVER use Imidacloprid (causes resurgence). " "Use Buprofezin 25% SC @ 1 ml/L or Pymetrozine 50% WG @ 0.6 g/L."), ("bph", "imidacloprid", "For BPH (Brown Plant Hopper) on rice, NEVER use Imidacloprid (causes resurgence). " "Use Buprofezin 25% SC @ 1 ml/L or Pymetrozine 50% WG @ 0.6 g/L."), ] BANNED_CHEMICALS = { # India CIBRC banned + restricted (as of 2024) "endosulfan", "monocrotophos", "phorate", "phosphamidon", "methyl parathion", "carbosulfan", "phosalone", "aldrin", "heptachlor", "lindane", "ddt", "bhc", "chlordane", "dieldrin", "ethyl parathion", "fenthion", "tetradifon", "pentachlorophenol", "dicofol", "ethyl mercury chloride", "trichlorfon", } # Minimum citations required in answers for these problem types. REQUIRES_CITATION = {"pest", "disease", "nutrient", "scheme"} # ── Few-shot examples — problem-type filtered ──────────────────────────────── FEW_SHOTS = { "pest": [ { "q": "My cotton has whitefly attack. What should I spray?", "a": ("**Diagnosis:** Whitefly (*Bemisia tabaci*) — common on cotton, causes " "yellowing and sooty mould.\n" "**Spray:** Diafenthiuron 50% WP @ 1.2 g/L of water [1] OR " "Pyriproxyfen 10% EC @ 1 ml/L [2].\n" "**Volume:** 200 L/acre. Spray under-leaf surface.\n" "**Timing:** Evening, repeat after 10 days if needed.\n" "**IPM:** Yellow sticky traps (5/acre); avoid Imidacloprid sprays in " "boll-formation stage (resistance build-up).") }, ], "disease": [ { "q": "Wheat leaves have orange-red rust pustules. Treatment?", "a": ("**Diagnosis:** Wheat leaf/stripe rust (*Puccinia* spp.) — confirmed by " "orange-red pustules.\n" "**Spray:** Propiconazole 25% EC @ 1 ml/L [1] OR Tebuconazole 250 EC @ " "1 ml/L [2].\n" "**Volume:** 200 L/acre, ensure full canopy coverage.\n" "**Timing:** At first sign of pustules; repeat after 12-15 days if rust " "persists.\n" "**IPM:** Sow rust-resistant varieties (HD-3086, DBW-187, PBW-725).") }, ], "nutrient": [ { "q": "Maize leaves are turning pale yellow. Which fertiliser?", "a": ("**Diagnosis:** Likely nitrogen deficiency (chlorosis spreads from older " "leaves first).\n" "**Top-dress:** Urea @ 50 kg/acre at knee-high stage [1] OR foliar Urea 2% " "(20 g/L) for quick correction [2].\n" "**Confirm:** Get a soil test — if pH > 8 the issue may be Zn or S, not N.") }, ], } # ── Prompt builder ─────────────────────────────────────────────────────────── _SYSTEM = """You are an expert Indian agricultural advisor (KCC + ICAR). Answer farmer queries with practical, locally-relevant, ICAR-validated guidance. ABSOLUTE RULES: 1. Give EXACT chemical doses with units (e.g. "1 ml/L" not "small amount"). 2. NEVER recommend banned chemicals: Endosulfan, Monocrotophos, Phorate, Phosphamidon. 3. For pest/disease/nutrient/scheme answers, cite source numbers like [1] [2] from the context provided. If you cannot cite, say so explicitly. 4. Match the farmer's language: reply in Hindi if they wrote Hindi, English if English, Hinglish if Hinglish, etc. 5. Be concise (60-150 words). Structure: Diagnosis → Action → Dose → Timing → IPM/Safety. 6. If the retrieved Q&A do NOT support a confident answer, ask 1-2 clarifying questions instead of guessing. """ def build_prompt(query: str, context: str, *, history: Optional[List[dict]] = None, problem_type: str = "general", language: str = "English", detected_crop: Optional[str] = None, state: str = "", district: str = "", low_confidence: bool = False) -> str: parts = [_SYSTEM] # Few-shot — pick at most 1 example matching the problem type. examples = FEW_SHOTS.get(problem_type) or [] if examples: ex = examples[0] parts.append(f"\n### Example Q\n{ex['q']}\n### Example A\n{ex['a']}\n") # Safety note for this problem type safety = SAFETY_GUARDRAILS.get(problem_type, "") if safety: parts.append(f"\n[GUARDRAIL] {safety}") # Metadata banner meta = [] if detected_crop: meta.append(f"Crop: {detected_crop}") if state: meta.append(f"State: {state}") if district: meta.append(f"District: {district}") if problem_type and problem_type != "general": meta.append(f"Problem type: {problem_type.upper()}") if meta: parts.append(f"\n[CONTEXT] {' | '.join(meta)}") if low_confidence: parts.append( "\n[LOW CONFIDENCE] Retrieved answers don't strongly match the query. " "Ask the farmer 1-2 clarifying questions BEFORE recommending a chemical." ) # Conversation history (last 4 turns) if history: for turn in history[-4:]: role = turn.get("role", "user").capitalize() content = turn.get("content", "") if content: parts.append(f"\n{role}: {content}") parts.append(f"\n\n{context}") parts.append(f"\n\n### FARMER QUESTION (language: {language})\n{query}") parts.append(f"\n\n### YOUR ANSWER (in {language}, with citations [1][2] where applicable):") return "\n".join(parts)