Skip to content

Case study — KisanGyan

A pan-India, voice-first agricultural advisor serving farmers in eight Indian languages plus English — with no state-specific or language-specific branches anywhere in the codebase.

The challenge

Agricultural advice is only useful if it is locally correct. Soil, rainfall, crop calendars, mandi behaviour, festivals and language all change across district lines, and a recommendation that is right in Nashik can be actively harmful in Ballia.

The obvious way to handle that is to launch in one state, hard-code its assumptions, and generalise later. Every team that does this discovers the same thing eighteen months in: the second state requires rewriting the first one.

The second challenge was the user. Our farmers often have limited literacy, a mid-range Android phone bought on EMI, and connectivity that disappears the moment they walk into their field. An interface that assumes typing, reading and a live connection is an interface most of them will not use twice.

The approach

Decisions that shaped the product

Region as configuration, never as code

States, languages, crops, festivals and feature availability all live in a configuration layer with feature flags. Activating a new state is a data change and a flag flip, not a release. There is no `state == "MH"` conditional anywhere in the codebase — a rule we enforced from the first commit precisely because it is impossible to retrofit.

Voice as the primary interface

Speech recognition and synthesis run behind a provider-agnostic interface, so the engine can change without touching the app. The farmer speaks; Mitra replies aloud. Text exists as a fallback, not as the default — which inverts the usual assumption and drove several architectural decisions, including streaming responses over WebSocket so speech begins before generation finishes.

A persona, not a chatbot

Every farmer-facing model call flows through a single system prompt template assembled with the farmer's profile, their farm history, current weather, local market conditions and the festival calendar. Mitra has consistent honorifics, greetings and a voice per language, and it never refers to itself as 'the AI' or 'the app'. Consistency here is a technical property, enforced by making the template the only path to the model.

Grounded answers, not remembered ones

Agronomic guidance is retrieved before it is generated. Curated knowledge — crop calendars, package-of-practices, pest and disease references, government advisories — is chunked, embedded and searched with a hybrid of keyword and vector matching, and only the retrieved passages are given to the model to answer from. That is what makes the advice auditable and updatable: correcting a recommendation means editing a document, not retraining a model. It also means Mitra can say it does not know, which a model answering from memory will rarely do.

Offline as the default state

The Flutter app treats its local SQLite database as the source of truth and the network as a synchronisation detail. A farmer can photograph a diseased leaf, get a diagnosis, log it in their diary and plan a rotation with the phone in flight mode. Everything reconciles when signal returns.

Inference on the device where it counts

Crop disease detection runs as a TensorFlow Lite model on the handset. That decision removed an image upload from the critical path, made diagnosis instant, cut per-diagnosis cost to zero and — most importantly — made the single most-used feature work without a network.

Strategic advice, not just tactical

The recommendation engine scores candidate crops on soil match, rotation benefit, monsoon fit, market outlook and risk. Rotation history is tracked across seasons against research-backed agronomic rules, and declining soil indicators trigger proactive regeneration suggestions the farmer never asked for. This is the difference between an advisory app and an agronomist.

Architecture

How the system is put together

Layer by layer, with the technology and the reason it is there.

Mobile

Flutter 3.x, Android from API 23, offline-first sqflite store, TFLite inference, ARB localisation across nine languages

API

Python 3.12 + FastAPI, REST and WebSocket, Pydantic v2 schemas, SQLAlchemy 2.0 models, Alembic migrations

Data

PostgreSQL 16 with pgvector for retrieval and PostGIS for geospatial queries; Redis for cache and queues; S3-compatible object storage for media

AI

A large language model behind the Mitra persona, multilingual speech recognition and synthesis, and a retrieval-augmented generation (RAG) layer over the agronomic knowledge base using 768-dimension multilingual embeddings

Workers

Scheduled jobs for advisory nudges, satellite NDVI refresh, soil assessment, market prediction and mandi price ingestion

Integrations

Official and commercial weather feeds, satellite vegetation indices, government soil and market data, SMS OTP, push notifications and messaging channels — each behind an interface so any provider can be swapped

Infrastructure

Docker on an Indian cloud region, Caddy edge, CI-driven staged deploys with migration gates

Operations

Admin console for the operations team: farmer records, outreach campaigns, crop-doctor review and per-language message drafting

Engineering highlights

The parts worth talking about

Nine languages, one codebase, zero branches

The Hindi belt alone spans ten states with wildly different cropping patterns, and Bengal, Odisha, Gujarat, Karnataka and the Telugu states each bring their own crops, calendars and scripts. Because region and language data are configuration, adding one means seeding crops, festivals and rotation rules — not touching application logic.

Notification discipline as an engineering problem

Early weather alerting woke farmers at 1 a.m. and repeated identical messages. We rebuilt the gate: only cyclone warnings pierce 21:00-07:00 quiet hours, alert signatures no longer hash volatile advisory text, and every push passes a per-event dedup check, a same-kind cooldown and a per-farmer daily cap. Push notification quality is a product feature, and it took a schema-free rewrite of the decision layer to get right.

RAG that works across nine languages

Mitra does not answer from model memory. Every agronomic claim is retrieved from a curated knowledge base first and the model answers from those passages — which is what keeps the advice correct and updatable without retraining. A single multilingual embedding space means a question asked in Marathi retrieves knowledge written in Hindi; maintaining nine parallel monolingual indices would have multiplied both the ingestion work and the drift.

Streaming voice over WebSocket

Text-to-speech begins on the first sentence rather than waiting for the full response. On a slow connection that is the difference between a conversation and a form submission.

Takeaways

What we would tell you to do

  1. 01

    Configuration-driven regionalisation is nearly free on day one and nearly impossible on day five hundred.

  2. 02

    On-device inference is not only about latency — it is about cost, privacy and the network not existing.

  3. 03

    Notification restraint earns more retention than notification volume.

  4. 04

    If the persona is the product, make the persona template the only code path to the model.

Next case study

An AI interior visualisation product where the differentiator is not the model — it is the cultural rule engine wrapped around it.

Read the Nivista case study

This is how we would build yours.

Same approach, same discipline, applied to whatever you are trying to make.