{"slug": "agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio", "title": "Agentic farm advisory assistant built with Gemma 4 + Google AI Studio", "summary": "A developer built an agentic farm advisory assistant using Gemma 4 and Google AI Studio's Gemini API. The assistant diagnoses crop issues from photos, checks weather-based planting windows, and logs farm activity through real function-calling, prototyped in the browser and shipped as an Express backend with a chat UI.", "body_md": "We will walk through a complete, working project: an agentic farm advisory assistant built with Gemma 4 through Google AI Studio's Gemini API. It diagnoses crop issues from photos, checks weather-based planting windows, and logs farm activity through real function-calling — prototyped in the browser, then shipped as an Express backend with a chat UI.\n\nAn advisory chatbot for smallholder farmers and agro-logistics platforms that can:\n\nThe model never guesses market prices or weather data — every factual answer comes from an actual function call against a backend, not the model's own assumptions.\n\nBefore writing any code, the entire agent was designed inside aistudio.google.com:\n\n`gemma-4-31b-it`\n\nfor its multimodal (image) support\n\n```\nYou are a friendly, practical farm advisory assistant for smallholder farmers\nin Nigeria. Always use the provided tools for weather checks, market prices,\nand activity logging — never guess prices or weather data. When a farmer\nuploads a crop photo, examine it carefully before giving diagnosis and\nnext steps. Keep responses short, practical, and in plain language. Reply\nin the same language or Pidgin the farmer writes in.\n```\n\n`check_weather_window`\n\n, `get_market_price`\n\n, `log_farm_activity`\n\n, and `diagnose_crop_image`\n\n— each with a JSON schema and a scoped description`check_weather_window`\n\ninstead of answering from general knowledge`@google/genai`\n\nTesting the image diagnosis directly in the browser first was the most valuable step — it's far easier to spot a vague description problem (\"model just said 'looks unhealthy'\") in a live chat than after it's buried in server logs.\n\n``` js\n// tools.js\nexport const tools = [{\n  functionDeclarations: [\n    {\n      name: \"check_weather_window\",\n      description: \"Use when the farmer asks if it's safe or a good time to plant, spray, or harvest. Never guess weather.\",\n      parameters: {\n        type: \"OBJECT\",\n        properties: {\n          location: { type: \"STRING\", description: \"e.g. Port Harcourt, Owerri\" },\n          activity: { type: \"STRING\", description: \"planting, spraying, or harvesting\" }\n        },\n        required: [\"location\", \"activity\"]\n      }\n    },\n    {\n      name: \"get_market_price\",\n      description: \"Use ONLY when the farmer asks for the current price of a crop. Never guess a price.\",\n      parameters: {\n        type: \"OBJECT\",\n        properties: {\n          crop: { type: \"STRING\", description: \"e.g. cassava, maize, tomato\" },\n          market: { type: \"STRING\", description: \"e.g. Mile 1 Market, Port Harcourt\" }\n        },\n        required: [\"crop\"]\n      }\n    },\n    {\n      name: \"log_farm_activity\",\n      description: \"Use when the farmer reports completing an activity like planting, spraying, or harvesting.\",\n      parameters: {\n        type: \"OBJECT\",\n        properties: {\n          farmerId: { type: \"STRING\" },\n          activity: { type: \"STRING\", description: \"planting, spraying, harvesting\" },\n          crop: { type: \"STRING\" },\n          notes: { type: \"STRING\" }\n        },\n        required: [\"farmerId\", \"activity\", \"crop\"]\n      }\n    },\n    {\n      name: \"diagnose_crop_image\",\n      description: \"Use when the farmer uploads a photo of a crop showing signs of disease, pest damage, or poor health.\",\n      parameters: {\n        type: \"OBJECT\",\n        properties: {\n          crop: { type: \"STRING\", description: \"e.g. maize, tomato, cassava\" },\n          symptomDescription: { type: \"STRING\", description: \"Visible symptoms described from the image\" }\n        },\n        required: [\"crop\", \"symptomDescription\"]\n      }\n    }\n  ]\n}];\n\nconst weatherData = {\n  \"port harcourt\": { rainChance: 20, condition: \"clear, light wind\" },\n  \"owerri\": { rainChance: 70, condition: \"heavy rain expected\" }\n};\n\nconst marketPrices = {\n  cassava: { pricePerBag: 18500, currency: \"NGN\", market: \"Mile 1 Market\" },\n  maize: { pricePerBag: 22000, currency: \"NGN\", market: \"Mile 1 Market\" },\n  tomato: { pricePerBasket: 15000, currency: \"NGN\", market: \"Mile 1 Market\" }\n};\n\nconst activityLog = [];\n\nexport async function check_weather_window({ location, activity }) {\n  const key = location.toLowerCase();\n  const weather = weatherData[key];\n  if (!weather) return { error: \"Location not found in weather data\" };\n\n  const safe = activity === \"spraying\" ? weather.rainChance < 40 : true;\n  return { location, activity, ...weather, recommendation: safe ? \"Safe to proceed\" : \"Wait — rain expected, risk of runoff\" };\n}\n\nexport async function get_market_price({ crop, market }) {\n  const price = marketPrices[crop.toLowerCase()];\n  return price ? { crop, ...price } : { error: `No price data for ${crop}` };\n}\n\nexport async function log_farm_activity({ farmerId, activity, crop, notes }) {\n  const entry = { farmerId, activity, crop, notes: notes || \"\", date: \"2026-07-07\" };\n  activityLog.push(entry);\n  return { logged: true, ...entry };\n}\n\nexport async function diagnose_crop_image({ crop, symptomDescription }) {\n  // In production, this would call a vision model or trained classifier.\n  // Here Gemma 4's own multimodal reasoning already produced symptomDescription\n  // from the uploaded image before calling this tool.\n  const knownIssues = {\n    \"brown spots on leaves\": \"Likely leaf blight — recommend copper-based fungicide, improve drainage\",\n    \"wilting despite watering\": \"Possible bacterial wilt or root rot — check soil drainage and remove affected plants\"\n  };\n  const match = Object.keys(knownIssues).find(k => symptomDescription.toLowerCase().includes(k.split(\" \")[0]));\n  return {\n    crop,\n    diagnosis: match ? knownIssues[match] : \"Symptoms noted but inconclusive — recommend local extension officer visit\",\n  };\n}\n\nexport const toolFunctions = { check_weather_window, get_market_price, log_farm_activity, diagnose_crop_image };\n```\n\nThe core of the backend is a loop that keeps resolving tool calls — including image-based diagnosis — until Gemma 4 returns a final plain-text answer:\n\n``` python\n// server.js\nimport express from \"express\";\nimport { GoogleGenAI } from \"@google/genai\";\nimport { tools, toolFunctions } from \"./tools.js\";\n\nconst app = express();\napp.use(express.json({ limit: \"10mb\" })); // allow base64 image payloads\n\nconst client = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });\nconst MODEL = \"gemma-4-31b-it\";\nconst MAX_STEPS = 4;\n\nconst SYSTEM_INSTRUCTION = `You are a friendly, practical farm advisory assistant...`;\nconst sessions = new Map();\n\napp.post(\"/api/chat\", async (req, res) => {\n  const { sessionId = \"default\", message, imageBase64 } = req.body;\n\n  if (!sessions.has(sessionId)) {\n    sessions.set(sessionId, client.chats.create({\n      model: MODEL,\n      config: { systemInstruction: SYSTEM_INSTRUCTION, tools }\n    }));\n  }\n  const chat = sessions.get(sessionId);\n\n  const parts = imageBase64\n    ? [{ inlineData: { mimeType: \"image/jpeg\", data: imageBase64 } }, { text: message }]\n    : message;\n\n  let response = await chat.sendMessage({ message: parts });\n  let steps = 0;\n\n  while (response.functionCalls?.length && steps < MAX_STEPS) {\n    const call = response.functionCalls[0];\n    const result = await toolFunctions[call.name](call.args);\n    response = await chat.sendMessage({\n      message: [{ functionResponse: { name: call.name, response: result } }]\n    });\n    steps += 1;\n  }\n\n  res.json({ reply: response.text, toolStepsUsed: steps });\n});\n\napp.listen(3000, () => console.log(\"Agro advisory agent running on port 3000\"));\n```\n\nWith the server running (`npm start`\n\n) and a `GEMINI_API_KEY`\n\nfrom AI Studio set in `.env`\n\n, here's what real requests return.\n\n**Request — weather check before spraying:**\n\n```\ncurl -X POST http://localhost:3000/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sessionId\":\"farmer1\",\"message\":\"Is it safe to spray my maize in Owerri today?\"}'\n```\n\n**Response:**\n\n```\n{\n  \"reply\": \"Not today — Owerri has a 70% chance of rain, which could wash off the spray before it works. Wait for a drier day.\",\n  \"toolStepsUsed\": 1\n}\n```\n\n**Request — market price check:**\n\n```\ncurl -X POST http://localhost:3000/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sessionId\":\"farmer1\",\"message\":\"How much is cassava selling for now?\"}'\n```\n\n**Response:**\n\n```\n{\n  \"reply\": \"Cassava is currently going for NGN 18,500 per bag at Mile 1 Market.\",\n  \"toolStepsUsed\": 1\n}\n```\n\n**Request — crop photo diagnosis (image + text):**\n\n```\ncurl -X POST http://localhost:3000/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sessionId\":\"farmer1\",\"message\":\"My tomato plant looks sick, see photo\",\"imageBase64\":\"<base64 jpeg data>\"}'\n```\n\n**Response:**\n\n```\n{\n  \"reply\": \"I see wilting and yellowing leaves on your tomato plant. This looks like bacterial wilt or root rot. Check your soil drainage and remove the worst-affected plants to stop it spreading.\",\n  \"toolStepsUsed\": 1\n}\n```\n\n**Request — Pidgin, activity logging:**\n\n```\ncurl -X POST http://localhost:3000/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sessionId\":\"farmer1\",\"message\":\"I don finish plant my maize for farm today, farmer ID FARM-002\"}'\n```\n\n**Response:**\n\n```\n{\n  \"reply\": \"Good work! I don log say you plant maize today for your farm record (FARM-002). E dey saved.\",\n  \"toolStepsUsed\": 1\n}\n```\n\n`toolFunctions`\n\n, not the model's own guess, so a farmer never gets rain-safety advice invented on the spot`toolStepsUsed`\n\non every response makes it easy to log exactly what the agent did, useful for tracking advisory accuracy over a growing seasonSwap the mock weather and price data in `tools.js`\n\nfor a real weather API and a live market-price feed (e.g., from a state agriculture board or a partner logistics platform), move the activity log from an in-memory array to MongoDB, and replace the rule-based `diagnose_crop_image`\n\nmatching with a fine-tuned vision classifier once you have enough labeled crop-disease photos. For farmers in low-connectivity rural areas, consider porting the same tool schema to a self-hosted E2B/E4B deployment on an Android device or Jetson Orin Nano so diagnosis works even without a live network connection.", "url": "https://wpnews.pro/news/agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio", "canonical_source": "https://dev.to/shieldstring/agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio-14ca", "published_at": "2026-07-08 01:00:00+00:00", "updated_at": "2026-07-08 01:28:44.875806+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-agents", "developer-tools"], "entities": ["Gemma 4", "Google AI Studio", "Gemini API", "Express"], "alternates": {"html": "https://wpnews.pro/news/agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio", "markdown": "https://wpnews.pro/news/agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio.md", "text": "https://wpnews.pro/news/agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio.txt", "jsonld": "https://wpnews.pro/news/agentic-farm-advisory-assistant-built-with-gemma-4-google-ai-studio.jsonld"}}