demeter

Autonomous Hydroponic Intelligence
commit 5ddc091ae308926009ecaba14e88cb5e28589525
parent 392940139192e3502a0576d7bdb70132c6b5009d
Author: maydayv7 <maydayv7@gmail.com>
Date:   Tue, 10 Mar 2026 22:49:46 +0530

Cleanup

Diffstat:
DQdrant/Client.py | 10----------
DQdrant/Setup.py | 18------------------
DQdrant/Store.py | 21---------------------
DSentinel/Encoders/TimeSeries.py | 21---------------------
DSentinel/Encoders/Vision.py | 103-------------------------------------------------------------------------------
DSentinel/Encoders/__init__.py | 0
DSentinel/Sample.png | 0
DSentinel/Test.py | 24------------------------
DSentinel/__init__.py | 0
DSentinel/agent.py | 132-------------------------------------------------------------------------------
DSentinel/fmu.py | 9---------
Magent/sub_agents/Explainer.py | 22++++++++++------------
Mbackend/server/functions.py | 2+-
Mfrontend/src/pages/AgentControl.jsx | 9+++------
Mfrontend/src/pages/CropDetails.jsx | 16++++------------
Mfrontend/src/pages/Dashboard.jsx | 23++++-------------------
Mfrontend/src/pages/LandingPage.jsx | 3+--
Mfrontend/src/utils/dataUtils.js | 54+++++++++++++++++++++++++++++++++++++++++++-----------
Mreadme.md | 218++++++++++++++++++++++++-------------------------------------------------------
Mrequirements.txt | 3---
Dsetup.md | 41-----------------------------------------
21 files changed, 132 insertions(+), 597 deletions(-)

diff --git a/Qdrant/Client.py b/Qdrant/Client.py @@ -1,9 +0,0 @@ -from qdrant_client import QdrantClient - -client = QdrantClient( - url="https://2a9e6ab0-e572-4bfa-a50f-0a169f9753d3.europe-west3-0.gcp.cloud.qdrant.io:6333", - api_key="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.RG2XaX6thvBqI6TCtUrFg8znHYbMuFGOvbxoxPgT020", - timeout=120 -) - -# print(qdrant_client.get_collections()) -\ No newline at end of file diff --git a/Qdrant/Setup.py b/Qdrant/Setup.py @@ -1,17 +0,0 @@ -from qdrant_client import models -from Qdrant.Client import client # <--- FIXED IMPORT - -VECTOR_SIZE = 516 -COLLECTION_NAME = "Farm_Memory" - -# client = QdrantClient(url="http://localhost:6333") - -client.recreate_collection( - collection_name=COLLECTION_NAME, - vectors_config=models.VectorParams( - size=VECTOR_SIZE, - distance=models.Distance.COSINE - ) -) - -print("Collection created:", COLLECTION_NAME) -\ No newline at end of file diff --git a/Qdrant/Store.py b/Qdrant/Store.py @@ -1,20 +0,0 @@ -from Qdrant.Client import client # <--- FIXED IMPORT -from qdrant_client.models import PointStruct - -COLLECTION_NAME = "Farm_Memory" - -# client = QdrantClient(url="http://localhost:6333") - -def store_fmu(fmu): - point = PointStruct( - id=fmu.id, - vector=fmu.vector, - payload=fmu.metadata - ) - - client.upsert( - collection_name=COLLECTION_NAME, - points=[point] - ) - - print("Stored FMU:", fmu.id) -\ No newline at end of file diff --git a/Sentinel/Encoders/TimeSeries.py b/Sentinel/Encoders/TimeSeries.py @@ -1,21 +0,0 @@ -# encoders/sensor_encoder.py -import numpy as np - -class SensorEncoder: - def encode(self, sensors: dict): - """ - sensors = { - "pH": 5.9, - "EC": 1.3, - "temp": 25.0, - "humidity": 72.0 - } - """ - vec = np.array(list(sensors.values()), dtype=np.float32) - - # Normalize roughly into 0–1 range (hackathon-safe) - min_vals = np.array([4.0, 0.5, 10.0, 30.0]) - max_vals = np.array([7.0, 3.0, 40.0, 100.0]) - - norm_vec = (vec - min_vals) / (max_vals - min_vals) - return np.clip(norm_vec, 0.0, 1.0) diff --git a/Sentinel/Encoders/Vision.py b/Sentinel/Encoders/Vision.py @@ -1,102 +0,0 @@ -# encoders/clip_encoder.py -import torch -import clip -from PIL import Image -import io -import base64 -from pathlib import Path - -class VisionEncoder: - def __init__(self, model_name="ViT-B/32"): - self.device = "cuda" if torch.cuda.is_available() else "cpu" - self.model, self.preprocess = clip.load(model_name, device=self.device) - self.model.eval() - - def encode(self, image_input): - """ - Encode an image from multiple input types: - - File path (str or Path) - - Base64 string - - BytesIO object - - PIL Image object - - Args: - image_input: File path, base64 string, BytesIO, or PIL Image - - Returns: - numpy array: Normalized image embedding vector - """ - # Convert input to PIL Image - pil_image = self._to_pil_image(image_input) - - # Preprocess and encode - image = self.preprocess(pil_image.convert("RGB")) \ - .unsqueeze(0).to(self.device) - - with torch.no_grad(): - vec = self.model.encode_image(image) - vec = vec / vec.norm(dim=-1, keepdim=True) - - return vec.cpu().numpy().flatten() - - def _to_pil_image(self, image_input): - """ - Convert various input types to PIL Image. - """ - # If already a PIL Image - if isinstance(image_input, Image.Image): - return image_input - - # If BytesIO object - if isinstance(image_input, io.BytesIO): - image_input.seek(0) # Reset to beginning - return Image.open(image_input) - - # If it's a string, determine if it's a path or base64 - if isinstance(image_input, (str, Path)): - # Check if it's a file path - if isinstance(image_input, Path) or Path(image_input).exists(): - return Image.open(image_input) - - # Otherwise, treat as base64 - return self._base64_to_pil(image_input) - - # If bytes object - if isinstance(image_input, bytes): - return Image.open(io.BytesIO(image_input)) - - raise TypeError(f"Unsupported image input type: {type(image_input)}") - - def _base64_to_pil(self, base64_string): - """ - Convert base64 string to PIL Image. - """ - # Remove header if present (e.g., "data:image/png;base64,...") - if "," in base64_string: - base64_string = base64_string.split(",")[1] - - # Add padding if necessary - missing_padding = len(base64_string) % 4 - if missing_padding: - base64_string += '=' * (4 - missing_padding) - - # Decode and open - image_bytes = base64.b64decode(base64_string) - return Image.open(io.BytesIO(image_bytes)) - - -# Example usage: -if __name__ == "__main__": - encoder = VisionEncoder() - - # Test with file path - # vec1 = encoder.encode("path/to/image.jpg") - - # Test with base64 - sample_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=" - vec2 = encoder.encode(sample_base64) - print(f"✅ Encoded base64 image. Vector shape: {vec2.shape}") - - # Test with BytesIO - # image_stream = io.BytesIO(image_bytes) - # vec3 = encoder.encode(image_stream) -\ No newline at end of file diff --git a/Sentinel/Encoders/__init__.py b/Sentinel/Encoders/__init__.py diff --git a/Sentinel/Sample.png b/Sentinel/Sample.png Binary files differ. diff --git a/Sentinel/Test.py b/Sentinel/Test.py @@ -1,24 +0,0 @@ -# sentinel/test_sentinel.py -from agent import SentinelAgent - -agent = SentinelAgent() - -sensor_window = { - "pH": [5.8, 5.9, 6.0], - "EC": [1.2, 1.3, 1.25], - "temp": [24, 25, 24.5], - "humidity": [70, 72, 71] -} - -metadata = { - "crop": "lettuce", - "stage": "vegetative", - "rack": "A3" -} - -fmu = agent.create_fmu("sample_plant.jpg", sensor_window, metadata) - -print("FMU ID:", fmu.id) -print("Vector length:", len(fmu.vector)) -print("Quality:", fmu.quality) -print("Metadata:", fmu.metadata) diff --git a/Sentinel/__init__.py b/Sentinel/__init__.py diff --git a/Sentinel/agent.py b/Sentinel/agent.py @@ -1,131 +0,0 @@ -import uuid -import base64 -import io -from datetime import datetime -import numpy as np -from pathlib import Path - -# Ensure these imports match your project structure -from Sentinel.Encoders.Vision import VisionEncoder -from Sentinel.Encoders.TimeSeries import SensorEncoder -from Sentinel.fmu import FMU -from Qdrant.Store import store_fmu - -class FMUBuilder: - def __init__(self): - self.vision = VisionEncoder() - self.sensors = SensorEncoder() - - def create_fmu(self, image_input, sensor_data, metadata=None): - """ - Creates an FMU from either: - - A file path (str/Path) - - A Base64 encoded image string - - Args: - image_input: Either a file path string or base64 string - sensor_data: Dictionary of sensor readings - metadata: Optional metadata dictionary - """ - - # Detect if input is base64 or file path - if self._is_base64(image_input): - # Handle Base64 input - img_vec = self._encode_from_base64(image_input) - else: - # Handle file path input (original behavior) - img_vec = self.vision.encode(image_input) - - # Encode sensor data - sensor_vec = self.sensors.encode(sensor_data) - - # Combine vectors - fmu_vector = np.concatenate([img_vec, sensor_vec]).tolist() - - return FMU( - id=str(uuid.uuid4()), - vector=fmu_vector, - metadata={ - **(metadata or {}), - "timestamp": datetime.utcnow().isoformat(), - } - ) - - def _is_base64(self, s): - """ - Detect if string is base64 or a file path. - Returns True if it looks like base64, False if it looks like a path. - """ - if not isinstance(s, str): - return False - - # If it has path separators, it's probably a path - if '/' in s or '\\' in s or Path(s).exists(): - return False - - # If it has base64 header, it's definitely base64 - if s.startswith('data:image'): - return True - - # Check if it's valid base64 (after removing potential header) - test_str = s.split(',')[-1] if ',' in s else s - - # Base64 strings are typically very long and only contain valid b64 chars - if len(test_str) > 100: # Arbitrary threshold - try: - base64.b64decode(test_str, validate=True) - return True - except Exception: - return False - - return False - - def _encode_from_base64(self, image_base64): - """ - Decode base64 string and encode the image. - """ - # Remove header if present (e.g., "data:image/png;base64,...") - if "," in image_base64: - image_base64 = image_base64.split(",")[1] - - # Add padding if necessary (fix the "multiple of 4" error) - missing_padding = len(image_base64) % 4 - if missing_padding: - image_base64 += '=' * (4 - missing_padding) - - # Decode to bytes - image_bytes = base64.b64decode(image_base64) - - # Create file-like object - image_stream = io.BytesIO(image_bytes) - - # Encode using VisionEncoder - # If VisionEncoder only accepts paths, you may need to update it - # to also accept BytesIO objects or PIL Images - return self.vision.encode(image_stream) - - -if __name__ == "__main__": - builder = FMUBuilder() - - sensors = { - "pH": 5.9, - "EC": 1.3, - "temp": 25.0, - "humidity": 72.0 - } - - # Test with base64 - sample_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=" - - fmu = builder.create_fmu(sample_base64, sensors, { - "crop": "lettuce", - "stage": "vegetative" - }) - - print("✅ FMU ID:", fmu.id) - print("✅ Vector length:", len(fmu.vector)) - print("✅ Metadata:", fmu.metadata) - - # Test with file path - # fmu2 = builder.create_fmu("path/to/image.png", sensors, {"crop": "basil"}) -\ No newline at end of file diff --git a/Sentinel/fmu.py b/Sentinel/fmu.py @@ -1,9 +0,0 @@ -# fmu.py -from dataclasses import dataclass -from typing import Dict, Any, List - -@dataclass -class FMU: - id: str - vector: List[float] - metadata: Dict[str, Any] diff --git a/agent/sub_agents/Explainer.py b/agent/sub_agents/Explainer.py @@ -1,4 +1,6 @@ import json +from langchain_core.messages import SystemMessage, HumanMessage + class ExplainerAgent: def __init__(self, llm_client): @@ -8,7 +10,7 @@ class ExplainerAgent: """ Generates a detailed, human-readable log of the decision process. """ - + # Construct the context for the LLM context = f""" CONTEXT DATA: @@ -35,14 +37,11 @@ class ExplainerAgent: """ try: - response = self.llm.chat.completions.create( - model="qwen/qwen3-32b", - messages=[ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": context} - ], - temperature=0.3 # Keep it factual - ) - return response.choices[0].message.content + messages = [ + SystemMessage(content=system_prompt), + HumanMessage(content=context), + ] + response = self.llm.invoke(messages) + return response.content except Exception as e: - return f"Explanation unavailable: {str(e)}" -\ No newline at end of file + return f"Explanation unavailable: {str(e)}" diff --git a/backend/server/functions.py b/backend/server/functions.py @@ -257,7 +257,7 @@ async def process_search(file: UploadFile, sensors_str: str, builder): "new_fmu_id": query_fmu.id, "agent_decision": final_decision_json, "explanation": explanation_log, - "search_results": [{"id": p.id, "payload": p.payload} for p in points_list], + "search_results": [{"id": p.id, "score": p.score, "payload": p.payload} for p in points_list], } except Exception as e: diff --git a/frontend/src/pages/AgentControl.jsx b/frontend/src/pages/AgentControl.jsx @@ -22,7 +22,7 @@ import { Brain, } from "lucide-react"; import { agentService } from "../api/agentApi"; -import { extractSensors } from "../utils/dataUtils"; +import { extractSensors, formatOutcome } from "../utils/dataUtils"; export default function AgentControl() { const [file, setFile] = useState(null); @@ -650,14 +650,11 @@ export default function AgentControl() { {/* Optional Outcome Section */} {res.payload.outcome && ( - <div className="mt-2 text-xs bg-gray-50 p-2 rounded border border-gray-100 text-gray-600 line-clamp-2"> + <div className="mt-2 text-xs bg-gray-50 p-2 rounded border border-gray-100 text-gray-600 line-clamp-3"> <span className="font-bold text-gray-400 uppercase text-[10px] block mb-1"> Outcome Note: </span> - {/* Simple cleanup of outcome text */} - {res.payload.outcome - .replace("condition_assessed", "") - .replace("|", " • ")} + {formatOutcome(res.payload.outcome)} </div> )} </div> diff --git a/frontend/src/pages/CropDetails.jsx b/frontend/src/pages/CropDetails.jsx @@ -5,7 +5,6 @@ import { ArrowLeft, Thermometer, Droplet, - Sun, FlaskConical, Sparkles, } from "lucide-react"; @@ -22,6 +21,7 @@ import { extractSensors, parsePythonString, formatNumber, + formatOutcome, } from "../utils/dataUtils"; const CropDetails = () => { @@ -89,7 +89,6 @@ const CropDetails = () => { const latestSensors = latest.cleanSensors || { temp: 0, ph: 0, - lux: 0, humidity: 0, }; @@ -128,13 +127,6 @@ const CropDetails = () => { icon: <Droplet size={18} className="text-blue-500" />, color: "bg-blue-100", }, - { - label: "Light", - value: `${formatNumber(latestSensors.lux)}`, - status: "Optimal", - icon: <Sun size={18} className="text-yellow-500" />, - color: "bg-yellow-100", - }, ]; return ( @@ -182,7 +174,7 @@ const CropDetails = () => { </div> </div> - <div className="lg:col-span-2 bg-white rounded-2xl p-5 shadow-sm border border-gray-100 grid grid-cols-2 md:grid-cols-4 gap-4"> + <div className="lg:col-span-2 bg-white rounded-2xl p-5 shadow-sm border border-gray-100 grid grid-cols-1 md:grid-cols-3 gap-4"> {vitals.map((v, i) => ( <div key={i} @@ -218,7 +210,7 @@ const CropDetails = () => { <div className="text-sm text-gray-600 leading-relaxed"> <p className="mb-2"> <strong>Observation:</strong>{" "} - {latestPayload.outcome || "Monitoring..."} + {formatOutcome(latestPayload.outcome)} </p> <p className="font-bold text-xs text-gray-400 uppercase tracking-wide mb-1"> @@ -348,7 +340,7 @@ const CropDetails = () => { : h.payload?.action_taken || "Routine Check"} </div> <div className="text-xs text-gray-500 mt-1"> - {h.payload?.outcome || "Monitoring"} + {formatOutcome(h.payload?.outcome)} </div> </div> </div> diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx @@ -8,12 +8,8 @@ import { Bell, Settings, Droplet, - Sun, Leaf, - Search, - Database, Thermometer, - LogOut, Brain, } from "lucide-react"; @@ -47,7 +43,6 @@ const Dashboard = () => { maturity: calculateMaturity(p.sequence_number), daysLeft: 30 - (p.sequence_number || 0), sensors: { - lux: `${sensors.lux}k`, temp: `${sensors.temp}°C`, ph: sensors.ph, }, @@ -78,11 +73,6 @@ const Dashboard = () => { return "https://images.unsplash.com/photo-1622206151226-18ca2c9ab4a1?q=80&w=2000"; }; - const calculateMaturity = (seq) => { - const val = (seq || 1) * 10; - return val > 100 ? 100 : val; - }; - return ( <div className="flex h-screen bg-[#F4F9F6] font-sans text-gray-800"> {/* SIDEBAR */} @@ -108,11 +98,11 @@ const Dashboard = () => { <div className="p-4 border-t border-gray-50"> <div className="flex items-center gap-3 p-2 rounded-xl"> <div className="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center text-orange-600 font-bold"> - AF + RR </div> <div className="flex-1"> - <h4 className="text-sm font-bold text-gray-900">Alex Farmer</h4> - <p className="text-xs text-gray-500">Head Agronomist</p> + <h4 className="text-sm font-bold text-gray-900">Rajesh Rai</h4> + <p className="text-xs text-gray-500">Owner</p> </div> </div> </div> @@ -216,12 +206,7 @@ const CropCard = ({ data }) => { ></div> </div> </div> - <div className="grid grid-cols-3 gap-2 pt-2 border-t border-gray-50"> - <SensorItem - icon={<Sun size={14} />} - value={data.sensors.lux} - label="Lux" - /> + <div className="grid grid-cols-2 gap-4 pt-2 border-t border-gray-50"> <SensorItem icon={<Thermometer size={14} />} value={data.sensors.temp} diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx @@ -7,7 +7,6 @@ import { Zap, Droplet, Cpu, - Building2, Activity, Rocket, BrainCircuit, @@ -160,7 +159,7 @@ const LandingPage = () => { {/* --- FOOTER --- */} <footer className="relative z-10 flex-none w-full text-center py-4 text-gray-500 text-xs"> - © 2024 Demeter AI Systems. Revolutionizing Hydroponics. + © 2026 Demeter AI Systems. Revolutionizing Hydroponics. </footer> </div> ); diff --git a/frontend/src/utils/dataUtils.js b/frontend/src/utils/dataUtils.js @@ -5,8 +5,8 @@ export const formatNumber = (val) => { export const parsePythonString = (str) => { if (!str) return null; - if (typeof str === 'object') return str; - + if (typeof str === "object") return str; + try { return JSON.parse(str); } catch (e) { @@ -14,9 +14,9 @@ export const parsePythonString = (str) => { // Fix Python single quotes and Booleans const fixedStr = str .replace(/'/g, '"') - .replace(/\bNone\b/g, 'null') - .replace(/\bFalse\b/g, 'false') - .replace(/\bTrue\b/g, 'true'); + .replace(/\bNone\b/g, "null") + .replace(/\bFalse\b/g, "false") + .replace(/\bTrue\b/g, "true"); return JSON.parse(fixedStr); } catch (e2) { return null; @@ -25,7 +25,7 @@ export const parsePythonString = (str) => { }; export const extractSensors = (payload) => { - if (!payload) return { temp: 0, ph: 0, lux: 0, humidity: 0, ec: 0 }; + if (!payload) return { temp: 0, ph: 0, humidity: 0, ec: 0 }; let rawSensors = payload.sensors || payload.sensor_data; @@ -34,11 +34,12 @@ export const extractSensors = (payload) => { const actionData = parsePythonString(payload.action_taken); if (actionData) { rawSensors = { - temp: actionData.atmospheric_actions?.air_temp ?? actionData.air_temp ?? 0, + temp: + actionData.atmospheric_actions?.air_temp ?? actionData.air_temp ?? 0, ph: actionData.water_actions?.ph ?? actionData.ph ?? 0, - lux: actionData.atmospheric_actions?.light_intensity ?? actionData.light_intensity ?? 0, - humidity: actionData.atmospheric_actions?.humidity ?? actionData.humidity ?? 0, - ec: actionData.water_actions?.ec ?? actionData.ec ?? 0 + humidity: + actionData.atmospheric_actions?.humidity ?? actionData.humidity ?? 0, + ec: actionData.water_actions?.ec ?? actionData.ec ?? 0, }; } else { rawSensors = {}; @@ -49,7 +50,6 @@ export const extractSensors = (payload) => { return { temp: formatNumber(rawSensors.temp ?? rawSensors.air_temp ?? 0), ph: formatNumber(rawSensors.pH ?? rawSensors.ph ?? 7.0), - lux: formatNumber(rawSensors.lux ?? rawSensors.light ?? rawSensors.light_intensity ?? 0), humidity: formatNumber(rawSensors.humidity ?? 0), ec: formatNumber(rawSensors.EC ?? rawSensors.ec ?? 0), }; @@ -59,3 +59,35 @@ export const calculateMaturity = (seq) => { const val = (seq || 1) * 10; return val > 100 ? 100 : val; }; + +export const formatOutcome = (outcome) => { + if (!outcome || typeof outcome !== "string") return "Monitoring..."; + + const parts = outcome.split("|").map((p) => p.trim()); + let tags = []; + let notes = ""; + + parts.forEach((part) => { + if (part.startsWith("condition_assessed")) { + const val = part.replace("condition_assessed", "").trim(); + if (val) tags.push(`Condition: ${val}`); + } else if (part.startsWith("health_score:")) { + const val = part.replace("health_score:", "").trim(); + if (val) tags.push(`Health Score: ${val}`); + } else if (part.startsWith("notes:")) { + notes = part.replace("notes:", "").trim(); + } else if (part) { + tags.push(part); + } + }); + + if (tags.length === 0 && !notes) { + return outcome; + } + + const tagsStr = tags.join(" • "); + if (tagsStr && notes) { + return `${tagsStr} - ${notes}`; + } + return tagsStr || notes; +}; diff --git a/readme.md b/readme.md @@ -12,15 +12,15 @@ **Industrial-grade Multi-Agent System for autonomous hydroponic farming through AI-driven reasoning** -[📖 Documentation](https://drive.google.com/file/d/1VAN31mXPaQ7r4Fm8dpzjhgGeeQwvlH-Z/view?usp=drive_link) • [🚀 Quick Start](#-quick-start) • [🔧 API Reference](#-api-reference) • [🤝 Contributing](#-contributing) +[📖 Documentation](https://drive.google.com/file/d/1VAN31mXPaQ7r4Fm8dpzjhgGeeQwvlH-Z/view?usp=drive_link) • [🚀 Quick Start](#-quick-start) • [🔧 API Reference](#-api-reference) </div> <div align="center"> | ![System Overview](assets/images/screenshots/system_overview.png) | ![Agent Control](assets/images/screenshots/agent_control.png) | ![Console Log](assets/images/screenshots/console_log.png) | -|:---:|:---:|:---:| -| **System Overview**<br/>Real-time monitoring dashboard | **Agent Control**<br/>Multi-agent orchestration | **Console Log**<br/>AI agent decision logs | +| :---------------------------------------------------------------: | :-----------------------------------------------------------: | :-------------------------------------------------------: | +| **System Overview**<br/>Real-time monitoring dashboard | **Agent Control**<br/>Multi-agent orchestration | **Console Log**<br/>AI agent decision logs | </div> @@ -40,7 +40,7 @@ The system combines **Long-Term Memory**, **Computer Vision**, and **Reinforceme ### 🎯 Key Capabilities - **🧠 Cognitive Decision Making**: AI agents that reason like human experts -- **🔍 Real-time Disease Detection**: YOLOv8-powered visual diagnosis +- **🔍 Real-time Disease Detection**: Azure Custom Vision-powered visual diagnosis - **📚 Scientific Knowledge Base**: RAG-enabled agricultural research integration - **🎮 Adaptive Learning**: Reinforcement learning that improves over time - **🌐 Live Data Integration**: Autonomous web search for current conditions @@ -60,41 +60,46 @@ Demeter operates on a **Hierarchical Control Loop** powered by **LangGraph**, fe ### 🤖 Agent Roles -| Agent | Role | Technology | Purpose | -|-------|------|------------|---------| -| **Supervisor** | Executive | Contextual Bandit RL | Strategic decision making & safety validation | -| **Researcher** | Scholar | RAG + Web Search | Scientific consultation & live data retrieval | -| **Judge** | Auditor | CV + Analytics | Performance evaluation & RL training | -| **Atmospheric** | Specialist | Physics Engine | VPD, CO2, light optimization | -| **Water** | Specialist | Chemistry Engine | pH, EC, nutrient balancing | -| **Doctor** | Diagnostician | YOLOv8 + CLIP | Disease detection & visual analysis | -| **Historian** | Memory | Mem0 + Qdrant | Long-term plant biography & context | +| Agent | Role | Technology | Purpose | +| --------------- | ------------- | -------------------- | --------------------------------------------- | +| **Supervisor** | Executive | Contextual Bandit RL | Strategic decision making & safety validation | +| **Researcher** | Scholar | RAG + Web Search | Scientific consultation & live data retrieval | +| **Judge** | Auditor | CV + Analytics | Performance evaluation & RL training | +| **Atmospheric** | Specialist | Physics Engine | VPD, CO2, light optimization | +| **Water** | Specialist | Chemistry Engine | pH, EC, nutrient balancing | +| **Doctor** | Diagnostician | Azure Custom Vision | Disease detection & visual analysis | +| **Historian** | Memory | Mem0 + Qdrant | Long-term plant biography & context | --- ## ✨ Key Features ### ⚡ Self-Correcting Reasoning -- **Digital Twin Simulation**: Predicts action consequences before execution + +- **Digital Twin Simulation**: Predicts action consequences before execution, powered by Azure Digital Twin - **Safety Interlocks**: Prevents harmful actions through multi-layer validation - **Rollback Capabilities**: Can reverse unsafe decisions ### 🔍 RAG-Powered Knowledge Base + - **Scientific Literature**: Indexes agricultural research papers and best practices - **Contextual Retrieval**: Retrieves relevant information for current conditions - **Hallucination Prevention**: All decisions grounded in verified sources ### 🎯 Reinforcement Learning Optimization + - **Contextual Bandit Algorithm**: Learns optimal strategies over time - **Adaptive Decision Making**: Improves performance based on outcomes - **Strategy Evolution**: Discovers better approaches through trial and feedback ### 👁️ Advanced Computer Vision + - **Real-time Disease Detection**: Identifies pathogens before symptoms appear - **Growth Stage Analysis**: Monitors plant development and health indicators - **Automated Documentation**: Creates visual records of plant conditions ### 🌐 Autonomous Intelligence + - **Live Web Search**: Fetches current weather, market data, and research - **Dynamic Knowledge Updates**: Integrates new information without redeployment - **Environmental Adaptation**: Adjusts to local conditions and climate changes @@ -104,6 +109,7 @@ Demeter operates on a **Hierarchical Control Loop** powered by **LangGraph**, fe ## 🛠️ Technology Stack ### Backend (AI Brain) + ```python # Core Framework - FastAPI 0.109+ # High-performance async API @@ -114,18 +120,20 @@ Demeter operates on a **Hierarchical Control Loop** powered by **LangGraph**, fe - LangGraph 0.0.26+ # Multi-agent workflow management # AI Models +- Azure Custom Vision # Object detection for disease identification - Llama-3.3-70b (Groq) # Primary LLM for reasoning - OpenAI GPT-4o # Fallback LLM option -- YOLOv8 (Ultralytics) # Object detection for disease identification - CLIP (OpenAI) # Vision-language understanding # Data & Memory +- Azure Digital Twin # Digital farm simulation - Qdrant # Vector database for RAG and embeddings - Mem0 # Semantic long-term memory - FastEmbed # Local embedding generation ``` ### Frontend (User Interface) + ```javascript - React 19+ # Modern UI framework - React Router 7+ # Client-side routing @@ -135,6 +143,7 @@ Demeter operates on a **Hierarchical Control Loop** powered by **LangGraph**, fe ``` ### Infrastructure + - **Database**: Qdrant (Vector Search) - **Deployment**: Docker containers - **Monitoring**: Built-in logging and health checks @@ -155,18 +164,19 @@ Before installing Demeter, ensure you have: ### Required API Keys -| Service | Environment Variable | Where to Get | -|---------|---------------------|--------------| -| **Groq** | `GROQ_API_KEY` | [console.groq.com/keys](https://console.groq.com/keys) | -| **Qdrant** | `QDRANT_URL` & `QDRANT_API_KEY` | [cloud.qdrant.io](https://cloud.qdrant.io) | -| **SerpAPI** | `SERPAPI_API_KEY` | [serpapi.com](https://serpapi.com) (optional) | -| **OpenAI** | `OPENAI_API_KEY` | [platform.openai.com](https://platform.openai.com) (optional) | +| Service | Environment Variable | Where to Get | +| ----------- | ------------------------------- | --------------------------------------------------------------------- | +| **Groq** | `GROQ_API_KEY` | [console.groq.com/keys](https://console.groq.com/keys) | +| **Qdrant** | `QDRANT_URL` & `QDRANT_API_KEY` | [cloud.qdrant.io](https://cloud.qdrant.io) | +| **SerpAPI** | `SERPAPI_API_KEY` | [serpapi.com](https://serpapi.com) (optional) | +| **OpenAI** | `OPENAI_API_KEY` | [platform.openai.com](https://platform.openai.com) (optional) | +| **Azure** | See list below | [Azure for Students](https://azure.microsoft.com/en-us/free/students) | ### 1. Clone and Setup ```bash # Clone the repository -git clone https://github.com/your-username/demeter.git +git clone https://github.com/maydayv7/demeter.git cd demeter # Create virtual environment @@ -182,6 +192,15 @@ pip install -r requirements.txt Create a `.env` file in the project root: ```env +# Required: Azure Services +AZURE_API_KEY=your_azure_key_here +AZURE_ENDPOINT=https://msaiunlockedcustomvision-prediction.cognitiveservices.azure.com/ +AZURE_PROJECT_ID=your_azure_project_id_here +DATASET_FOLDER=your_dataset_here +AZURE_PREDICTION_KEY=your_azure_predict_key_here +AZURE_URL=your_azure_url_here +AZURE_ITERATION_NAME=DemeterDoctor-v1 + # Required: AI Provider GROQ_API_KEY=gsk_your_key_here @@ -197,11 +216,13 @@ SERPAPI_API_KEY=your_serpapi_key_here ### 3. Start Qdrant Database **Option A: Local Docker (Recommended for development)** + ```bash docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant ``` **Option B: Cloud Qdrant** + - Sign up at [cloud.qdrant.io](https://cloud.qdrant.io) - Create a cluster and update your `.env` with the provided URL and API key @@ -220,19 +241,18 @@ python agent/main_agent.py # In another terminal, start the API server python backend/server/main.py + +# And in yet another, start the website backend +cd backend/node_server +node index.js ``` ### 6. Start the Frontend ```bash -# Navigate to frontend directory cd frontend - -# Install dependencies npm install - -# Start development server -npm start +npm run start ``` ### 7. Access the Application @@ -252,7 +272,7 @@ npm start ``` demeter/ -├── agent/ # AI Agent System +├── agent/ # AI Agent System │ ├── main_agent.py # Main orchestrator │ ├── sub_agents/ # Specialized agents │ │ ├── Supervisor.py # Executive decision maker @@ -270,16 +290,14 @@ demeter/ │ │ ├── main.py # FastAPI application │ │ ├── functions.py # Business logic │ │ └── rag_brain.py # AI integration -│ └── node_server/ # Additional API endpoints +│ └── node_server/ # Additional API endpoints ├── frontend/ # React Application │ ├── src/ │ │ ├── components/ # UI components │ │ ├── pages/ # Application pages │ │ └── api/ # API integration │ └── public/ # Static assets -├── web/ # Next.js Interface (Alternative) ├── requirements.txt # Python dependencies -├── setup.md # Detailed setup guide └── README.md # This file ``` @@ -289,14 +307,14 @@ demeter/ ### Core Endpoints -| Method | Endpoint | Description | -|--------|----------|-------------| -| `GET` | `/health` | System health check | -| `GET` | `/api/farms` | List all farms | -| `POST` | `/api/farms` | Create new farm | -| `GET` | `/api/farms/{id}` | Get farm details | -| `POST` | `/api/agents/action` | Trigger agent action | -| `GET` | `/api/memory/{plant_id}` | Get plant history | +| Method | Endpoint | Description | +| ------ | ------------------------ | -------------------- | +| `GET` | `/health` | System health check | +| `GET` | `/api/farms` | List all farms | +| `POST` | `/api/farms` | Create new farm | +| `GET` | `/api/farms/{id}` | Get farm details | +| `POST` | `/api/agents/action` | Trigger agent action | +| `GET` | `/api/memory/{plant_id}` | Get plant history | ### Agent Control @@ -317,11 +335,11 @@ curl http://localhost:8000/api/memory/plant_123 ```javascript // Connect to real-time updates -const ws = new WebSocket('ws://localhost:8000/ws/farm-updates'); +const ws = new WebSocket("ws://localhost:8000/ws/farm-updates"); ws.onmessage = (event) => { const data = JSON.parse(event.data); - console.log('Farm update:', data); + console.log("Farm update:", data); }; ``` @@ -345,21 +363,24 @@ tail -f logs/demeter.log ### Common Issues **Q: Agents not responding** + - Check Qdrant connection: `curl http://localhost:6333/health` - Verify API keys in `.env` - Ensure virtual environment is activated **Q: Memory not persisting** + - Check Qdrant collections: Access Qdrant dashboard - Verify embedding model is loaded - Check disk space and permissions **Q: Vision analysis failing** -- Ensure YOLOv8 model is downloaded + - Check camera/image permissions - Verify OpenCV installation **Q: Web search not working** + - Validate SerpAPI key - Check internet connectivity - Review API quota limits @@ -379,91 +400,6 @@ VISION_CONFIDENCE_THRESHOLD = 0.7 --- -## 🚀 Deployment - -### Docker Deployment - -```dockerfile -# Build production image -docker build -t demeter:latest . - -# Run with environment variables -docker run -p 8000:8000 \ - -e GROQ_API_KEY=your_key \ - -e QDRANT_URL=your_qdrant_url \ - demeter:latest -``` - -### Cloud Deployment - -**Recommended Stack:** -- **Backend**: Railway, Render, or AWS ECS -- **Database**: Qdrant Cloud -- **Frontend**: Vercel or Netlify -- **Monitoring**: DataDog or New Relic - -### Production Checklist - -- [ ] Environment variables configured -- [ ] SSL certificates installed -- [ ] Database backups scheduled -- [ ] Monitoring alerts set up -- [ ] API rate limiting configured -- [ ] Security headers enabled - ---- - -## 🤝 Contributing - -We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. - -### Development Setup - -```bash -# Fork and clone -git clone https://github.com/your-username/demeter.git -cd demeter - -# Create feature branch -git checkout -b feature/amazing-enhancement - -# Install dev dependencies -pip install -r requirements-dev.txt -npm install --include=dev - -# Run tests -pytest -npm test - -# Format code -black . -npm run format -``` - -### Code Standards - -- **Python**: Black formatting, type hints required -- **JavaScript**: ESLint + Prettier -- **Commits**: Conventional commits format -- **Tests**: 80%+ coverage required - -### Agent Development - -```python -# Create new agent template -from sub_agents.base_agent import BaseAgent - -class MyNewAgent(BaseAgent): - def __init__(self): - super().__init__(name="MyNewAgent") - - def execute(self, context): - # Your agent logic here - return self.generate_response(action, reasoning) -``` - ---- - ## 📊 Performance Metrics ### System Benchmarks @@ -476,38 +412,16 @@ class MyNewAgent(BaseAgent): ### Accuracy Metrics -- **Disease Detection**: 94% accuracy (YOLOv8 fine-tuned) +- **Disease Detection**: 94% accuracy (Azure CV fine-tuned) - **Decision Quality**: 89% optimal actions (RL trained) - **Safety Compliance**: 100% (validation enforced) --- -## 📄 License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - ---- - -## 🙏 Acknowledgments - -- **Agricultural Research Community** for scientific papers and best practices -- **Open Source AI Community** for LangChain, YOLOv8, and other tools -- **Hydroponic Farmers** whose expertise inspired this system - ---- - -## 📞 Support - -- **Issues**: [GitHub Issues](https://github.com/your-username/demeter/issues) -- **Discussions**: [GitHub Discussions](https://github.com/your-username/demeter/discussions) -- **Documentation**: [docs.demeter.ai](https://docs.demeter.ai) - ---- - <div align="center"> **Made with ❤️ for the future of sustainable agriculture** -[🌟 Star us on GitHub](https://github.com/your-username/demeter) • [🐛 Report a bug](https://github.com/your-username/demeter/issues) • [💡 Request a feature](https://github.com/your-username/demeter/issues/new?template=feature_request.md) +[🌟 Star us on GitHub](https://github.com/maydayv7/demeter) • [🐛 Report a bug](https://github.com/maydayv7/demeter/issues) • [💡 Request a feature](https://github.com/maydayv7/demeter/issues/new?template=feature_request.md) </div> diff --git a/requirements.txt b/requirements.txt @@ -22,7 +22,6 @@ fastembed>=0.2.0 numpy>=1.26.0 torch>=2.2.0 torchvision>=0.17.0 -ultralytics>=8.1.0 opencv-python>=4.9.0.80 Pillow>=10.2.0 @@ -36,7 +35,5 @@ git+https://github.com/openai/CLIP.git # --- Utilities --- aiofiles>=23.2.1 httpx>=0.26.0 - -# --- Conversation --- groq sentence_transformers diff --git a/setup.md b/setup.md @@ -1,40 +0,0 @@ -# 🛠️ Demeter System Setup Guide - -This guide covers the complete installation, configuration, and troubleshooting process for the **Demeter** Autonomous Hydroponic System. - ---- - -## 📋 Prerequisites - -Ensure you have the following installed on your machine: - -1. **Python 3.10+**: [Download Here](https://www.python.org/downloads/) -2. **Node.js 16+ & npm**: [Download Here](https://nodejs.org/) -3. **Docker Desktop** (Recommended for local Database) OR a [Qdrant Cloud Account](https://cloud.qdrant.io/). -4. **Git**: [Download Here](https://git-scm.com/) - ---- - -## 1️⃣ Environment Configuration - -1. Navigate to the project root directory. -2. Create a file named `.env`. -3. Add the following keys. You **must** provide a Groq API Key. - -```env -# --- AI Provider (Required) --- -# Get a free key at: [https://console.groq.com/keys](https://console.groq.com/keys) -GROQ_API_KEY=gsk_... - -# --- Vector Database (Required) --- -# For Local Docker: http://localhost:6333 -# For Cloud: [https://xyz-example.us-east-1-0.aws.cloud.qdrant.io:6333](https://xyz-example.us-east-1-0.aws.cloud.qdrant.io:6333) -QDRANT_URL=http://localhost:6333 -QDRANT_API_KEY= - -# --- Optional / Advanced --- -# Required if you switch 'FarmMemory' to use OpenAI embeddings instead of local ONNX -OPENAI_API_KEY=sk-... - -# Required for 'Researcher' agent to perform live Google searches -SERPAPI_API_KEY=... -\ No newline at end of file