demeter

Autonomous Hydroponic Intelligence
commit 49c94bc8c9ee07299af04245a087f10dd71c7102
parent 2edd4a2d45914d24a8dfbc3cee91bce978064525
Author: Arnav Gupta <66205884+arnav0103@users.noreply.github.com>
Date:   Wed, 25 Mar 2026 21:57:27 +0530

Merge pull request #12 from arnav0103/main

Added azure openai
Diffstat:
A.env.example | 13+++++++++++++
Magent/memory.py | 20++++++++------------
Magent/sub_agents/Explainer.py | 16+++++++++++++---
Magent/sub_agents/Researcher.py | 9+++++++--
Magent/sub_agents/Supervisor.py | 19+++++++++++++------
Magent/sub_agents/atmospheric_agent.py | 20+++++++++++---------
Magent/sub_agents/base_agent.py | 30++++++++++++++++--------------
Magent/sub_agents/judge_agent.py | 14+++++++++-----
Magent/sub_agents/water_agent.py | 18+++++++++++-------
Magent/sub_agents/water_and_atmospheric_dependencies/physics_engine.py | 28+++++++++++++++++-----------
Msimulator/requirements.txt | 1+
11 files changed, 119 insertions(+), 69 deletions(-)

diff --git a/.env.example b/.env.example @@ -0,0 +1,13 @@ +# Azure OpenAI Configuration +AZURE_OPENAI_API_KEY=your_api_key_here +AZURE_OPENAI_ENDPOINT=https://demeter-final.openai.azure.com/ +AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4.1 +AZURE_OPENAI_API_VERSION=2024-12-01-preview + +# Qdrant Configuration (if using memory service) +QDRANT_URL=http://localhost:6333 +QDRANT_API_KEY=your_qdrant_key_here + +# Simulator Configuration +SIMULATOR_STATE_URL=http://localhost:3001/simulation/state +SIMULATOR_ACTION_URL=http://localhost:3001/simulation/action diff --git a/agent/memory.py b/agent/memory.py @@ -1,7 +1,7 @@ import logging from mem0 import Memory import os -import time # <--- IMPORT ADDED +import time from dotenv import load_dotenv from qdrant_client import QdrantClient, models @@ -15,6 +15,12 @@ class FarmMemory: # 1. Setup Collection self._setup_collection() + # --- NEW CODE: Map Azure variables for mem0 natively --- + os.environ["LLM_AZURE_OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY", "") + os.environ["LLM_AZURE_DEPLOYMENT"] = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") + os.environ["LLM_AZURE_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT", "") + os.environ["LLM_AZURE_API_VERSION"] = os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview") + # 2. Initialize Mem0 config = { "vector_store": { @@ -27,11 +33,8 @@ class FarmMemory: } }, "llm": { - "provider": "openai", + "provider": "azure_openai", "config": { - "model": "qwen/qwen3-32b", - "api_key": os.getenv("GROQ_API_KEY"), - "openai_base_url": "https://api.groq.com/openai/v1", "max_tokens": 1500 } }, @@ -80,10 +83,7 @@ class FarmMemory: # 1. Use get_all() to fetch raw history (bypassing vector similarity) # This ensures we get the *actual* latest events, not just "relevant" ones history = self.memory.get_all(user_id=crop_id) - # print(f" -> Raw history count: {history}") - - # 2. Extract List from response results = [] if isinstance(history, dict): @@ -100,15 +100,12 @@ class FarmMemory: # 4. Slice the top N (Past 3) recent_results = results[:limit] - - # print(f" -> Extracted entries: {results}") # 5. Format the output formatted_lines = [] for item in recent_results: # Handle different mem0 versions where content might be in 'memory' or 'text' text = item.get("memory", item.get("text", str(item))) - # print(f" -> Processing entry: {text}") # Optional: Add a timestamp to the output for verification timestamp = item.get("created_at", "") @@ -121,7 +118,6 @@ class FarmMemory: clean_output = "\n".join(formatted_lines) - # print(f" -> Formatted Output:\n{clean_output}") return clean_output except Exception as e: diff --git a/agent/sub_agents/Explainer.py b/agent/sub_agents/Explainer.py @@ -1,8 +1,18 @@ import json +import os +from openai import AzureOpenAI +from dotenv import load_dotenv + +load_dotenv() class ExplainerAgent: - def __init__(self, llm_client): - self.llm = llm_client + def __init__(self): + self.llm = AzureOpenAI( + api_key=os.getenv("AZURE_OPENAI_API_KEY"), + api_version=os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview"), + azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT") + ) + self.deployment_name = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") def explain(self, current_fmu, similar_fmus, sub_agent_reports, final_decision): """ @@ -36,7 +46,7 @@ class ExplainerAgent: try: response = self.llm.chat.completions.create( - model="qwen/qwen3-32b", + model=self.deployment_name, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": context} diff --git a/agent/sub_agents/Researcher.py b/agent/sub_agents/Researcher.py @@ -2,7 +2,7 @@ import uuid from qdrant_client import models from fastembed import TextEmbedding from Qdrant.Client import client -from groq import Groq +from openai import AzureOpenAI import os from dotenv import load_dotenv @@ -11,7 +11,12 @@ load_dotenv() class ResearcherAgent: def __init__(self): self.client = client - self.llm = Groq(api_key=os.getenv("GROQ_API_KEY")) + self.llm = AzureOpenAI( + api_key=os.getenv("AZURE_OPENAI_API_KEY"), + api_version=os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview"), + azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT") + ) + self.deployment_name = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") self.collection = "Knowledge_Base" # FastEmbed is lightweight and runs locally on CPU self.encoder = TextEmbedding(model_name="BAAI/bge-small-en-v1.5") diff --git a/agent/sub_agents/Supervisor.py b/agent/sub_agents/Supervisor.py @@ -1,7 +1,7 @@ import os import json import numpy as np -from langchain_openai import ChatOpenAI +from langchain_openai import AzureChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage from langgraph.graph import StateGraph, END from agent.tools.actuation import convert_targets_to_actions @@ -10,6 +10,9 @@ from agent.Marl.bandit import ContextualBandit from agent.Marl.strategies import STRATEGIES, NUM_ACTIONS from agent.Qdrant.Store import store_fmu from agent.sub_agents.water_and_atmospheric_dependencies.physics_engine import predict_outcome +from dotenv import load_dotenv + +load_dotenv() # --- NEW TOOLS DEFINITION --- def check_cross_domain_conflicts(atmos, water): @@ -58,18 +61,22 @@ class SupervisorState(TypedDict): final_decision: str # "APPROVE" or "REJECT" critique: str # Feedback for sub-agents if Rejected -API_KEY = os.environ.get("GROQ_API_KEY") +API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") +ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") +DEPLOYMENT_NAME = os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") +API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview") class SupervisorAgent: def __init__(self, researcher_agent=None): self.name = "Supervisor" self.bandit = ContextualBandit(n_actions=NUM_ACTIONS, feature_dim=519) - if API_KEY: - self.model = ChatOpenAI( - base_url="https://api.groq.com/openai/v1", + if API_KEY and ENDPOINT: + self.model = AzureChatOpenAI( + azure_endpoint=ENDPOINT, api_key=API_KEY, - model="qwen/qwen3-32b", + api_version=API_VERSION, + deployment_name=DEPLOYMENT_NAME, temperature=0.0 # Zero temp for strict judging ) diff --git a/agent/sub_agents/atmospheric_agent.py b/agent/sub_agents/atmospheric_agent.py @@ -1,5 +1,5 @@ import os -from langchain_openai import ChatOpenAI +from langchain_openai import AzureChatOpenAI from langgraph.graph import StateGraph, END # Graph State & Nodes @@ -11,9 +11,10 @@ from agent.sub_agents.water_and_atmospheric_dependencies.retrieval import ask_hi from agent.sub_agents.water_and_atmospheric_dependencies.tools import calculate_vpd, web_search # Configuration -API_KEY = os.environ.get("GROQ_API_KEY") - -MODEL_ID = "qwen/qwen3-32b" +API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") +ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") +DEPLOYMENT_NAME = os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") +API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview") ATMOS_PROMPT = """ You are the Atmospheric Specialist for a Hydroponic Farm. @@ -38,14 +39,15 @@ class AtmosphericAgent: def __init__(self): self.name = "Atmospheric Agent" - if not API_KEY: - print(f"[{self.name}] ⚠️ No API Key found.") + if not API_KEY or not ENDPOINT: + print(f"[{self.name}] ⚠️ No Azure OpenAI credentials found.") self.model = None else: - llm = ChatOpenAI( - base_url="https://api.groq.com/openai/v1", + llm = AzureChatOpenAI( + azure_endpoint=ENDPOINT, api_key=API_KEY, - model="qwen/qwen3-32b", + api_version=API_VERSION, + deployment_name=DEPLOYMENT_NAME, temperature=0.2, model_kwargs={"tool_choice": "auto", "parallel_tool_calls": False} ) diff --git a/agent/sub_agents/base_agent.py b/agent/sub_agents/base_agent.py @@ -1,43 +1,45 @@ import os -from openai import OpenAI +from openai import AzureOpenAI from dotenv import load_dotenv load_dotenv() -# --- GROQ CONFIGURATION --- -# Common Groq Models: "llama3-70b-8192", "mixtral-8x7b-32768" -MODEL_ID = "qwen/qwen3-32b" -API_KEY = os.environ.get("GROQ_API_KEY") +# --- AZURE OPENAI CONFIGURATION --- +DEPLOYMENT_NAME = os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") +API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") +ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") +API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview") class BaseReasoningAgent: def __init__(self, name): self.name = name - if not API_KEY: - print(f"[{self.name}] ⚠️ WARNING: GROQ_API_KEY not found in environment.") + if not API_KEY or not ENDPOINT: + print(f"[{self.name}] ⚠️ WARNING: Azure OpenAI credentials not found in environment.") self.client = None else: try: - self.client = OpenAI( - base_url="https://api.groq.com/openai/v1", - api_key=os.getenv("GROQ_API_KEY") + self.client = AzureOpenAI( + api_key=API_KEY, + api_version=API_VERSION, + azure_endpoint=ENDPOINT ) except Exception as e: - print(f"[{self.name}] ⚠️ Groq Connection Error: {e}") + print(f"[{self.name}] ⚠️ Azure OpenAI Connection Error: {e}") self.client = None def _call_llm(self, prompt): """ - Helper method to send prompts to Groq Cloud. + Helper method to send prompts to Azure OpenAI. """ if not self.client: return "Error: LLM Client not connected (Check API Key)." print("Other Prompt:\n", prompt) try: - # Groq/OpenAI Chat Completion Structure + # Azure OpenAI Chat Completion Structure response = self.client.chat.completions.create( - model=MODEL_ID, + model=DEPLOYMENT_NAME, messages=[ {"role": "system", "content": f"You are the {self.name} Agent for a high-tech hydroponic farm."}, {"role": "user", "content": prompt} diff --git a/agent/sub_agents/judge_agent.py b/agent/sub_agents/judge_agent.py @@ -5,10 +5,13 @@ import re import tempfile from typing import TypedDict, Dict, Any, Optional -from langchain_openai import ChatOpenAI +from langchain_openai import AzureChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage from langgraph.graph import StateGraph, END from qdrant_client import models +from dotenv import load_dotenv + +load_dotenv() from Sentinel.fmu import FMU from agent.sub_agents.base_agent import BaseReasoningAgent @@ -46,10 +49,11 @@ class JudgeAgent(BaseReasoningAgent): self.qdrant = client # LLM for the "Deliberation" phase - self.llm = ChatOpenAI( - base_url="https://api.groq.com/openai/v1", - api_key=os.environ.get("GROQ_API_KEY"), - model="qwen/qwen3-32b", + self.llm = AzureChatOpenAI( + azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"), + api_key=os.environ.get("AZURE_OPENAI_API_KEY"), + api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview"), + deployment_name=os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1"), temperature=0.1 ) diff --git a/agent/sub_agents/water_agent.py b/agent/sub_agents/water_agent.py @@ -1,5 +1,5 @@ import os -from langchain_openai import ChatOpenAI +from langchain_openai import AzureChatOpenAI from langgraph.graph import StateGraph, END # Graph State & Nodes @@ -11,7 +11,10 @@ from agent.sub_agents.water_and_atmospheric_dependencies.retrieval import ask_hi from agent.sub_agents.water_and_atmospheric_dependencies.tools import check_ph_safety, web_search # Configuration -API_KEY = os.environ.get("GROQ_API_KEY") +API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") +ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") +DEPLOYMENT_NAME = os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") +API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview") # 🟢 UPDATE 1: Mention visual data availability in the prompt WATER_PROMPT = """ @@ -40,14 +43,15 @@ class WaterAgent: self.name = "Water Agent" # 1. Initialize Model - if not API_KEY: - print(f"[{self.name}] ⚠️ No API Key found.") + if not API_KEY or not ENDPOINT: + print(f"[{self.name}] ⚠️ No Azure OpenAI credentials found.") self.model = None else: - llm = ChatOpenAI( - base_url="https://api.groq.com/openai/v1", + llm = AzureChatOpenAI( + azure_endpoint=ENDPOINT, api_key=API_KEY, - model="qwen/qwen3-32b", # Keeping consistent model + api_version=API_VERSION, + deployment_name=DEPLOYMENT_NAME, temperature=0.2, model_kwargs={"tool_choice": "auto", "parallel_tool_calls": False} ) diff --git a/agent/sub_agents/water_and_atmospheric_dependencies/physics_engine.py b/agent/sub_agents/water_and_atmospheric_dependencies/physics_engine.py @@ -1,27 +1,33 @@ import os import json -from langchain_openai import ChatOpenAI +from langchain_openai import AzureChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage +from dotenv import load_dotenv + +load_dotenv() # Configuration -API_KEY = os.environ.get("GROQ_API_KEY") -MODEL_ID = "qwen/qwen3-32b" # Using the latest supported Groq model +API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") +ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") +DEPLOYMENT_NAME = os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1") +API_VERSION = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview") def predict_outcome(current_state: dict, proposed_action: dict) -> dict: """ - Stateless 'What-If' Engine using Groq (LLM-based Physics). + Stateless 'What-If' Engine using Azure OpenAI (LLM-based Physics). Takes a snapshot and an action, returns the PREDICTED future state. """ - if not API_KEY: - print(" ⚠️ Physics Engine Error: Missing GROQ_API_KEY") + if not API_KEY or not ENDPOINT: + print(" ⚠️ Physics Engine Error: Missing Azure OpenAI credentials") return {"predicted_health": 50.0, "risk_warning": "No API Key configured"} - # Initialize Groq Client - llm = ChatOpenAI( - base_url="https://api.groq.com/openai/v1", + # Initialize Azure OpenAI Client + llm = AzureChatOpenAI( + azure_endpoint=ENDPOINT, api_key=API_KEY, - model=MODEL_ID, + api_version=API_VERSION, + deployment_name=DEPLOYMENT_NAME, temperature=0.1, # Low temp for consistent physics logic max_tokens=1024, ) @@ -43,7 +49,7 @@ def predict_outcome(current_state: dict, proposed_action: dict) -> dict: ) try: - # Invoke Groq + # Invoke Azure OpenAI response = llm.invoke( [SystemMessage(content=system_prompt), HumanMessage(content=user_prompt)] ) diff --git a/simulator/requirements.txt b/simulator/requirements.txt @@ -7,3 +7,4 @@ Pillow python-dotenv azure-identity azure-digitaltwins-core +openai