Skip to content

AI Recommendations API

AI-powered, context-aware recommendations using Retrieval-Augmented Generation (RAG). The system combines block-level data with policy documents (PDFs in ai-docs/) to generate actionable recommendations for a specific intervention.

The pipeline uses LangChain + OpenAI (gpt-4o-mini) with a Chroma vector store persisted at data/vectorstore/. Policy PDFs are embedded with OpenAIEmbeddings and retrieved by similarity search (top k=5 chunks).

Prerequisites

The AI recommendation feature requires:

  • OPENAI_API_KEY environment variable set on the server
  • RAG dependencies installed (langchain, langchain-openai, langchain-chroma, langchain-community, chromadb, pypdf)
  • At least one PDF policy document placed in the ai-docs/ directory

The vector store is built lazily on the first POST /api/ai-recommendation call (or you can trigger it explicitly with POST /api/ai-recommendation/init). All other LEAF DSS features work without these prerequisites — when they are missing, the AI endpoints degrade gracefully (HTTP 503) rather than breaking the rest of the app.


Architecture

User Request
Block Data (name, district, intervention, feasibility_score, metrics, filters)
Chroma Vector Store ← PDF Policy Documents (ai-docs/)  [OpenAIEmbeddings]
Top k=5 relevant policy chunks retrieved (similarity search)
OpenAI gpt-4o-mini generates recommendation with retrieved context
Response: recommendation text + source PDF filenames

The RAG pipeline:

  1. Ingestion — PDFs in ai-docs/ are loaded with PyPDFLoader, split into overlapping chunks (chunk_size=1000, chunk_overlap=200) and embedded into Chroma using OpenAIEmbeddings. This happens once and is persisted to data/vectorstore/.
  2. Retrieval — When a recommendation is requested, the system builds a context query from the intervention plus the passing/failing metrics and queries Chroma for the top k=5 chunks.
  3. Generation — Retrieved context + block data are sent to gpt-4o-mini, which generates a tailored recommendation (kept under ~300 words).
  4. Citation — The unique source PDF filenames behind the retrieved chunks are returned in sources.

Livestock query enrichment (LEAF-57)

When the intervention is a livestock commodity (Dairy, Goatery, Piggery, Backyard_Poultry, Duckery, Fishery_Activity) or the parent Livestock category, the retrieval query is enriched with animal-husbandry vocabulary (veterinary access, fodder, milk collection, Pashu Sakhi, DAY-NRLM livestock convergence) so livestock policy chunks surface reliably.


Get AI Recommendation

Generates a context-aware recommendation for a specific block and intervention. The recommendation considers the block's variable values, which criteria are met vs. not met, and relevant policy-document context.

POST /api/ai-recommendation

Request Body

{
  "block_name": "Digboi",
  "district_name": "Tinsukia",
  "intervention": "Dairy",
  "feasibility_score": 65.5,
  "metrics": [
    {
      "label": "Agricultural Land %",
      "value": 45.2,
      "in_range": true,
      "min": 30,
      "max": 70
    },
    {
      "label": "Water Availability",
      "value": 15.0,
      "in_range": false,
      "min": 20,
      "max": 50
    }
  ],
  "filters": [
    {
      "column": "AD",
      "min_val": 30,
      "max_val": 70,
      "weight": 1.0
    }
  ]
}
Field Type Required Description
block_name string No Block name (defaults to "Unknown")
district_name string No District name (defaults to "Unknown")
intervention string Yes Selected intervention name. If empty/omitted, the endpoint returns 200 with a prompt to select one (no LLM call).
feasibility_score number No Current feasibility score, 0–100 (defaults to 0)
metrics array No Variable metrics with in-range status — tells the AI which criteria pass/fail (defaults to [])
metrics[].label string - Human-readable variable name
metrics[].value number - Current value for this block
metrics[].in_range boolean - Whether the value falls in the acceptable range
metrics[].min number - Minimum threshold
metrics[].max number - Maximum threshold
filters array No Active filter configurations (accepted and passed through; defaults to [])

Response

{
  "recommendation": "Digboi block shows moderate readiness (65.5%) for Dairy. Agricultural land (45.2%) is within range, but water availability (15.0%) is below the 20% minimum. Priority actions: 1) Invest in water-harvesting structures... 2) Strengthen fodder cultivation and veterinary access...",
  "sources": [
    "Assam_Dairy_Policy_2023.pdf",
    "NABARD_Livestock_Guidelines.pdf"
  ],
  "feasibility_score": 65.5,
  "metrics_analyzed": 2,
  "gaps_identified": 1,
  "retrieved_context": [
    {
      "content": "…chunk text used as context…",
      "source": "Assam_Dairy_Policy_2023.pdf"
    }
  ]
}
Field Type Description
recommendation string Generated recommendation text
sources array of strings Unique PDF filenames behind the retrieved context. Note: these are filenames only — the API does not return page numbers or per-source relevance scores.
feasibility_score number Echoes the submitted feasibility score
metrics_analyzed integer Count of metrics received
gaps_identified integer Count of metrics with in_range: false
retrieved_context array The raw retrieved chunks used for generation (content + source), included for verification/debugging

Graceful degradation

When RAG is unavailable (missing dependencies or OPENAI_API_KEY), the endpoint still returns a JSON body with a recommendation message, an error field, and an empty sources array — with HTTP status 503. When no intervention is supplied it returns 200 with a "Please select an intervention" message.

Errors

Code Description
503 RAG service unavailable — dependencies not installed or OPENAI_API_KEY missing. Body still includes recommendation, error, and sources: [].
500 Processing error during recommendation generation. Body includes recommendation (error message), error, and sources: [].

Example

curl -X POST https://leaf-asrlm.in/api/ai-recommendation \
  -H "Content-Type: application/json" \
  -d '{
    "block_name": "Digboi",
    "district_name": "Tinsukia",
    "intervention": "Dairy",
    "feasibility_score": 65.5,
    "metrics": [
      {"label": "Agricultural Land %", "value": 45.2, "in_range": true, "min": 30, "max": 70},
      {"label": "Water Availability", "value": 15.0, "in_range": false, "min": 20, "max": 50}
    ]
  }'
import requests

response = requests.post(
    "https://leaf-asrlm.in/api/ai-recommendation",
    json={
        "block_name": "Digboi",
        "district_name": "Tinsukia",
        "intervention": "Dairy",
        "feasibility_score": 65.5,
        "metrics": [
            {"label": "Agricultural Land %", "value": 45.2,
             "in_range": True, "min": 30, "max": 70},
            {"label": "Water Availability", "value": 15.0,
             "in_range": False, "min": 20, "max": 50}
        ]
    }
)

data = response.json()
if response.status_code == 200:
    print("Recommendation:\n", data["recommendation"])
    print("\nSources:")
    for src in data.get("sources", []):
        print(f"  - {src}")
else:
    print(f"Error {response.status_code}: {data.get('error')}")
const res = await fetch("https://leaf-asrlm.in/api/ai-recommendation", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    block_name: "Digboi",
    district_name: "Tinsukia",
    intervention: "Dairy",
    feasibility_score: 65.5,
    metrics: [
      { label: "Agricultural Land %", value: 45.2, in_range: true, min: 30, max: 70 },
      { label: "Water Availability", value: 15.0, in_range: false, min: 20, max: 50 }
    ]
  })
});
const data = await res.json();
console.log(data.recommendation);
console.log("Sources:", data.sources);

Initialize / Rebuild Vector Store

Loads the RAG vector store, building it from the PDFs in ai-docs/ if it does not already exist on disk. Because generation builds the store lazily on first use, calling this endpoint is optional — it lets you warm the cache (and surface any embedding errors) ahead of the first recommendation request.

POST /api/ai-recommendation/init

Request Body

No body required.

Response

{
  "success": true
}
Field Type Description
success boolean true if the vector store loaded/built successfully; false on failure (with an error field).

Errors

Code Response Description
503 {"success": false, "error": "..."} RAG dependencies / OPENAI_API_KEY not available
500 {"success": false, "error": "..."} Error while loading or building the vector store

Example

curl -X POST https://leaf-asrlm.in/api/ai-recommendation/init
import requests

r = requests.post("https://leaf-asrlm.in/api/ai-recommendation/init")
result = r.json()
if result.get("success"):
    print("Vector store ready")
else:
    print(f"Error: {result.get('error')}")

When to force a rebuild

To pick up newly added or changed PDFs, delete the persisted store (data/vectorstore/) so the next call re-embeds — or use the Upload AI Document endpoint below, which wipes the persisted store for you. The first rebuild takes roughly 30–120 seconds depending on the number and size of PDFs.


Upload AI Document

Adds a single PDF to the AI knowledge base. The file is saved into the ai-docs/ retrieval pool and the persisted vector store is deleted, so the next POST /api/ai-recommendation call lazily rebuilds the embeddings to include the new document. Re-embedding is deferred to that next call, so this endpoint returns immediately.

Admin only — gated by the standard admin guard (?admin=1 query parameter or X-Admin: 1 header). There is no other authentication.

POST /api/config/upload-ai-doc?admin=1

Request

multipart/form-data with a single file field named file. The file must be a .pdf.

Field Type Required Description
file file (PDF) Yes The PDF document to add to the knowledge base. Non-PDF files are rejected with 400.

Response

{
  "ok": true,
  "filename": "Assam_Dairy_Policy_2023.pdf",
  "message": "Document added. AI knowledge base will rebuild on the next recommendation request."
}
Field Type Description
ok boolean true when the upload was accepted
filename string The sanitized name the file was saved under (werkzeug.secure_filename)
message string Human-readable status note

Errors

Code Description
400 No file provided, no file selected, or the file is not a .pdf
403 Caller is not an admin (missing ?admin=1 / X-Admin: 1)
500 Server error while saving the document

Example

curl -X POST "https://leaf-asrlm.in/api/config/upload-ai-doc?admin=1" \
  -F "file=@Assam_Dairy_Policy_2023.pdf"
import requests

with open("Assam_Dairy_Policy_2023.pdf", "rb") as f:
    r = requests.post(
        "https://leaf-asrlm.in/api/config/upload-ai-doc",
        params={"admin": 1},
        files={"file": f},
    )
print(r.status_code, r.json())
const form = new FormData();
form.append("file", pdfFileInput.files[0]);

const res = await fetch("https://leaf-asrlm.in/api/config/upload-ai-doc?admin=1", {
  method: "POST",
  body: form
});
console.log(await res.json());