demeter
Autonomous Hydroponic Intelligencegit clone https://github.com/maydayv7/demeter
agent/memory.py (5947B)
1import logging
2from mem0 import Memory
3import os
4import time
5from dotenv import load_dotenv
6from qdrant_client import QdrantClient, models
7
8load_dotenv()
9
10logging.getLogger("mem0").setLevel(logging.WARNING)
11logging.getLogger("httpx").setLevel(logging.WARNING)
12
13class FarmMemory:
14 def __init__(self):
15 # 1. Setup Collection
16 self._setup_collection()
17
18 # --- NEW CODE: Map Azure variables for mem0 natively ---
19 os.environ["LLM_AZURE_OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY", "")
20 os.environ["LLM_AZURE_DEPLOYMENT"] = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4.1")
21 os.environ["LLM_AZURE_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT", "")
22 os.environ["LLM_AZURE_API_VERSION"] = os.getenv("AZURE_OPENAI_API_VERSION", "2024-12-01-preview")
23
24 # 2. Initialize Mem0
25 config = {
26 "vector_store": {
27 "provider": "qdrant",
28 "config": {
29 "url": os.getenv("QDRANT_URL"),
30 "api_key": os.getenv("QDRANT_API_KEY"),
31 "collection_name": "Plant_Biographies_HF",
32 "port": 6333,
33 }
34 },
35 "llm": {
36 "provider": "azure_openai",
37 "config": {
38 "max_tokens": 1500
39 }
40 },
41 "embedder": {
42 "provider": "huggingface",
43 "config": {
44 "model": "all-MiniLM-L6-v2"
45 }
46 }
47 }
48
49 print("🧠 Initializing FarmMemory...")
50 self.memory = Memory.from_config(config)
51
52 def _setup_collection(self):
53 """Create the collection manually to guarantee dimension match"""
54 try:
55 client = QdrantClient(
56 url=os.getenv("QDRANT_URL"),
57 api_key=os.getenv("QDRANT_API_KEY"),
58 timeout=30
59 )
60
61 collection_name = "Plant_Biographies_HF"
62
63 if client.collection_exists(collection_name):
64 print(f"✅ Collection '{collection_name}' verified.")
65 else:
66 print(f"📝 Creating '{collection_name}' (384 dims)...")
67 client.create_collection(
68 collection_name=collection_name,
69 vectors_config=models.VectorParams(
70 size=384,
71 distance=models.Distance.COSINE
72 )
73 )
74 print(f"✅ Collection created.")
75 except Exception as e:
76 print(f"⚠️ Collection setup warning: {e}")
77
78 def get_plant_history(self, crop_id, limit=3):
79 """Retrieve the last N chronological entries for a crop"""
80 try:
81 print(f"🔍 [FarmMemory] Fetching last {limit} entries for: {crop_id}...")
82
83 # 1. Use get_all() to fetch raw history (bypassing vector similarity)
84 # This ensures we get the *actual* latest events, not just "relevant" ones
85 history = self.memory.get_all(user_id=crop_id)
86
87 # 2. Extract List from response
88 results = []
89 if isinstance(history, dict):
90 results = history.get("results", [])
91 elif isinstance(history, list):
92 results = history
93
94 if not results:
95 return f"No biography found for {crop_id}."
96
97 # 3. Sort by 'created_at' (Descending) to get Newest -> Oldest
98 # Safe sort: defaults to empty string if 'created_at' is missing
99 results.sort(key=lambda x: x.get('created_at', ''), reverse=True)
100
101 # 4. Slice the top N (Past 3)
102 recent_results = results[:limit]
103
104 # 5. Format the output
105 formatted_lines = []
106 for item in recent_results:
107 # Handle different mem0 versions where content might be in 'memory' or 'text'
108 text = item.get("memory", item.get("text", str(item)))
109
110 # Optional: Add a timestamp to the output for verification
111 timestamp = item.get("created_at", "")
112 if timestamp:
113 # Cleanup timestamp for readability (e.g., 2024-02-09T10:00:00 -> 2024-02-09 10:00)
114 timestamp = timestamp.replace("T", " ").split(".")[0]
115 formatted_lines.append(f"[{timestamp}] {text}")
116 else:
117 formatted_lines.append(f"- {text}")
118
119 clean_output = "\n".join(formatted_lines)
120
121 return clean_output
122
123 except Exception as e:
124 print(f"❌ [FarmMemory] Error: {e}")
125 return f"Error retrieving history: {str(e)}"
126
127 def log_event(self, crop_id, event_text):
128 """Log a new event in the plant's biography"""
129 try:
130 # Capture the result to check if LLM extracted it correctly
131 result = self.memory.add(event_text, user_id=crop_id)
132 print(f"🧠 Biography Updated for {crop_id}")
133 except Exception as e:
134 print(f"❌ Memory Write Error: {e}")
135
136
137# execute
138if __name__ == "__main__":
139 farm_memory = FarmMemory()
140 print("\n✅ System Online.\n")
141
142 # 1. WRITE: Manually log a test event
143 test_crop = "BATCH-VERDANT-X1"
144 # print(f"📝 Logging event for {test_crop}...")
145 farm_memory.log_event(test_crop, "CRITICAL TEST: Detected mild nutrient burn. Flushed system with pH 5.8 water.")
146
147 # CRITICAL FIX: Wait for Vector DB indexing
148 print("⏳ Waiting for memory indexing...")
149 time.sleep(2)
150
151 # 2. READ: Fetch it back
152 print(f"\n🔍 Retrieving history for {test_crop}...")
153 history = farm_memory.get_plant_history(test_crop)
154
155 print("-" * 40)
156 print(f"MEMORY OUTPUT:\n{history}")
157 print("-" * 40)