Aniket2006 commited on
Commit
cb547c1
·
1 Parent(s): aaf7391

Restore original simple chatbot from initial commit

Browse files
Files changed (1) hide show
  1. app.py +142 -218
app.py CHANGED
@@ -1,10 +1,10 @@
1
  """
2
- AGROW Agricultural Chatbot Service (Simplified)
3
- ================================================
4
- AI-powered agricultural advisor with:
5
- - Single LLM call (no multi-stage reasoning)
6
- - Multi-API key fallback
7
  - Supabase conversation storage
 
8
  """
9
 
10
  import os
@@ -14,15 +14,14 @@ import uuid
14
  from datetime import datetime
15
  from typing import Optional, List, Dict, Any
16
  import traceback
17
- import requests
18
 
19
  from fastapi import FastAPI, HTTPException
20
  from fastapi.middleware.cors import CORSMiddleware
21
- from fastapi.responses import StreamingResponse
22
  from pydantic import BaseModel
23
- import asyncio
24
 
25
  from supabase_client import SupabaseClient
 
26
 
27
  # ============================================================================
28
  # LOGGING
@@ -35,77 +34,29 @@ logging.basicConfig(
35
  logger = logging.getLogger("ChatbotService")
36
 
37
  # ============================================================================
38
- # GEMINI SETUP - Multi-API Key Fallback System
39
  # ============================================================================
40
- def load_gemini_api_keys() -> List[str]:
41
- """Load all available Gemini API keys from environment."""
42
- keys = []
43
- primary = os.environ.get("GEMINI_API_KEY")
44
- if primary:
45
- keys.append(primary)
46
- for i in range(1, 6):
47
- key = os.environ.get(f"GEMINI_API_KEY_{i}")
48
- if key and key not in keys:
49
- keys.append(key)
50
- return keys
51
-
52
- GEMINI_API_KEYS = load_gemini_api_keys()
53
- current_key_index = 0
54
- logger.info(f"Loaded {len(GEMINI_API_KEYS)} Gemini API key(s)")
55
-
56
- # Find working model
57
- def get_available_model(api_key: str):
58
- """Try to find an available Gemini model."""
59
- models_to_try = ["gemini-2.0-flash", "gemini-1.5-flash", "gemini-1.5-pro"]
60
- if not api_key:
61
- return None, None
62
- for model in models_to_try:
63
- url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
64
- try:
65
- resp = requests.post(
66
- f"{url}?key={api_key}",
67
- json={"contents": [{"parts": [{"text": "test"}]}]},
68
- timeout=10
69
- )
70
- if resp.status_code in [200, 429]:
71
- logger.info(f"Found working model: {model}")
72
- return url, model
73
- except:
74
- pass
75
- return None, None
76
-
77
- GEMINI_URL, GEMINI_MODEL = None, None
78
- if GEMINI_API_KEYS:
79
- GEMINI_URL, GEMINI_MODEL = get_available_model(GEMINI_API_KEYS[0])
80
- logger.info(f"Using Gemini model: {GEMINI_MODEL}")
81
 
82
  # Supabase client
83
  supabase = SupabaseClient()
84
 
85
  # ============================================================================
86
- # SIMPLE SYSTEM PROMPT
87
  # ============================================================================
88
- SYSTEM_PROMPT = """You are AGROW AI, an expert agricultural advisor for Indian farmers.
89
-
90
- SPECIALIZATIONS:
91
- - Crop health analysis and diagnosis
92
- - Irrigation and water management
93
- - Pest and disease identification
94
- - Weather-based farming advice
95
- - Soil health recommendations
96
-
97
- COMMUNICATION STYLE:
98
- - Use simple, practical language farmers understand
99
- - Give actionable, prioritized recommendations
100
- - Reference local conditions when available
101
- - Be concise but thorough
102
-
103
- When you lack specific data, provide general guidance based on the query."""
104
 
105
- # ============================================================================
106
- # FASTAPI SETUP
107
- # ============================================================================
108
- app = FastAPI(title="AGROW Chatbot Service", version="2.0")
109
  app.add_middleware(
110
  CORSMiddleware,
111
  allow_origins=["*"],
@@ -114,10 +65,6 @@ app.add_middleware(
114
  allow_headers=["*"],
115
  )
116
 
117
- print("=" * 50)
118
- print(f"===== Application Startup at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====")
119
- print("=" * 50)
120
-
121
  # ============================================================================
122
  # REQUEST/RESPONSE MODELS
123
  # ============================================================================
@@ -125,16 +72,13 @@ class ChatRequest(BaseModel):
125
  session_id: str
126
  message: str
127
  user_id: Optional[str] = None
128
- field_id: Optional[str] = None
129
-
130
- class ResponseContent(BaseModel):
131
- message: str
132
- confidence: float = 0.8
133
 
134
  class ChatResponse(BaseModel):
135
- response: ResponseContent
136
  session_id: str
137
  message_id: str
 
138
  timestamp: str
139
 
140
  class SessionRequest(BaseModel):
@@ -146,85 +90,69 @@ class SessionResponse(BaseModel):
146
  title: str
147
  created_at: str
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  # ============================================================================
150
- # LLM CALL WITH FALLBACK
151
  # ============================================================================
152
- def get_next_api_key():
153
- global current_key_index
154
- current_key_index = (current_key_index + 1) % len(GEMINI_API_KEYS)
155
- logger.info(f"Rotated to API key {current_key_index + 1}/{len(GEMINI_API_KEYS)}")
156
- return GEMINI_API_KEYS[current_key_index]
157
-
158
- def call_gemini_api(prompt: str) -> str:
159
- """Call Gemini API with fallback across multiple API keys."""
160
- global current_key_index
161
 
162
- if not GEMINI_API_KEYS:
163
- return "Please configure GEMINI_API_KEY for real responses."
 
 
 
164
 
165
- keys_tried = 0
166
- while keys_tried < len(GEMINI_API_KEYS):
167
- current_key = GEMINI_API_KEYS[current_key_index]
168
- url = f"{GEMINI_URL}?key={current_key}"
169
-
170
- try:
171
- response = requests.post(
172
- url,
173
- headers={"Content-Type": "application/json"},
174
- json={
175
- "contents": [{"parts": [{"text": prompt}]}],
176
- "generationConfig": {
177
- "temperature": 0.7,
178
- "maxOutputTokens": 1024,
179
- }
180
- },
181
- timeout=60
182
- )
183
-
184
- if response.status_code == 200:
185
- data = response.json()
186
- if "candidates" in data and len(data["candidates"]) > 0:
187
- return data["candidates"][0]["content"]["parts"][0]["text"]
188
- return "No response generated."
189
-
190
- elif response.status_code in [429, 403, 500, 502, 503]:
191
- logger.warning(f"API key {current_key_index + 1} got {response.status_code}, rotating...")
192
- get_next_api_key()
193
- keys_tried += 1
194
- else:
195
- logger.error(f"Gemini API error: {response.status_code}")
196
- return f"API error: {response.status_code}"
197
-
198
- except Exception as e:
199
- logger.error(f"Gemini request error: {e}")
200
- get_next_api_key()
201
- keys_tried += 1
202
 
203
- return "All API keys exhausted. Please try again later."
204
-
205
- # ============================================================================
206
- # SIMPLE RESPONSE GENERATOR (SINGLE LLM CALL)
207
- # ============================================================================
208
- def generate_simple_response(query: str, history: List[Dict] = None) -> str:
209
- """Generate response with a single LLM call."""
210
 
211
- # Build conversation context
212
- history_text = ""
213
- if history and len(history) > 0:
214
- recent = history[-6:] # Last 3 exchanges
215
- for msg in recent:
216
- role = "User" if msg.get("role") == "user" else "Assistant"
217
- history_text += f"{role}: {msg.get('content', '')[:200]}\n"
218
 
219
- prompt = f"""{SYSTEM_PROMPT}
220
-
221
- {f"Recent conversation:{chr(10)}{history_text}" if history_text else ""}
222
-
223
- User Query: {query}
224
-
225
- Provide a helpful, actionable response:"""
226
-
227
- return call_gemini_api(prompt)
 
 
 
 
 
 
 
 
 
228
 
229
  # ============================================================================
230
  # API ENDPOINTS
@@ -233,27 +161,35 @@ Provide a helpful, actionable response:"""
233
  async def root():
234
  return {
235
  "service": "AGROW Chatbot Service",
236
- "version": "2.0-simple",
237
- "status": "running"
 
 
 
 
 
238
  }
239
 
240
  @app.get("/health")
241
  async def health():
242
  return {
243
  "status": "healthy",
244
- "gemini_configured": GEMINI_URL is not None,
245
- "api_keys_loaded": len(GEMINI_API_KEYS)
246
  }
247
 
 
248
  @app.post("/session/new", response_model=SessionResponse)
249
  async def create_session(request: SessionRequest):
250
  """Create a new chat session."""
251
  logger.info(f"Creating new session for user: {request.user_id}")
 
252
  try:
253
  session = supabase.create_session(
254
  user_id=request.user_id,
255
  title=request.title or "New Conversation"
256
  )
 
257
  return SessionResponse(
258
  session_id=session["id"],
259
  title=session["title"],
@@ -263,14 +199,15 @@ async def create_session(request: SessionRequest):
263
  logger.error(f"Failed to create session: {e}")
264
  raise HTTPException(500, str(e))
265
 
 
266
  @app.post("/chat", response_model=ChatResponse)
267
  async def chat(request: ChatRequest):
268
- """Send a message and get AI response (single LLM call)."""
269
- logger.info(f"Chat request - Session: {request.session_id}")
270
 
271
  try:
272
- # Get conversation history
273
- history = supabase.get_messages(request.session_id) or []
274
 
275
  # Save user message
276
  user_msg_id = supabase.add_message(
@@ -279,24 +216,31 @@ async def chat(request: ChatRequest):
279
  content=request.message
280
  )
281
 
282
- # Generate response (SINGLE LLM CALL)
283
- logger.info("Generating response (single LLM call)")
284
- response_text = generate_simple_response(request.message, history)
 
 
 
285
 
286
  # Save assistant response
287
  assistant_msg_id = supabase.add_message(
288
  session_id=request.session_id,
289
  role="assistant",
290
- content=response_text
 
291
  )
292
 
 
293
  supabase.update_session_timestamp(request.session_id)
 
294
  logger.info(f"Response generated - {len(response_text)} chars")
295
 
296
  return ChatResponse(
297
- response=ResponseContent(message=response_text),
298
  session_id=request.session_id,
299
  message_id=assistant_msg_id,
 
300
  timestamp=datetime.now().isoformat()
301
  )
302
 
@@ -305,84 +249,64 @@ async def chat(request: ChatRequest):
305
  logger.error(traceback.format_exc())
306
  raise HTTPException(500, str(e))
307
 
308
- @app.post("/chat/stream")
309
- async def chat_stream(request: ChatRequest):
310
- """Streaming chat endpoint (also uses single LLM call, then streams)."""
311
- logger.info(f"Stream chat request - Session: {request.session_id}")
312
-
313
- try:
314
- history = supabase.get_messages(request.session_id) or []
315
-
316
- supabase.add_message(
317
- session_id=request.session_id,
318
- role="user",
319
- content=request.message
320
- )
321
-
322
- # Generate full response first
323
- response_text = generate_simple_response(request.message, history)
324
-
325
- assistant_msg_id = supabase.add_message(
326
- session_id=request.session_id,
327
- role="assistant",
328
- content=response_text
329
- )
330
-
331
- supabase.update_session_timestamp(request.session_id)
332
-
333
- # Stream response in chunks
334
- async def generate_stream():
335
- chunk_size = 15
336
- delay = 0.03
337
-
338
- # Send metadata
339
- yield f"data: {json.dumps({'type': 'metadata', 'session_id': request.session_id, 'message_id': assistant_msg_id})}\n\n"
340
-
341
- # Stream text chunks
342
- for i in range(0, len(response_text), chunk_size):
343
- chunk = response_text[i:i+chunk_size]
344
- yield f"data: {json.dumps({'type': 'chunk', 'text': chunk})}\n\n"
345
- await asyncio.sleep(delay)
346
-
347
- # Done signal
348
- yield f"data: {json.dumps({'type': 'done', 'full_text': response_text})}\n\n"
349
-
350
- return StreamingResponse(generate_stream(), media_type="text/event-stream")
351
-
352
- except Exception as e:
353
- logger.error(f"Stream chat error: {e}")
354
- raise HTTPException(500, str(e))
355
 
356
- @app.get("/session/{session_id}/history")
357
  async def get_history(session_id: str):
358
  """Get conversation history for a session."""
 
 
359
  try:
360
  messages = supabase.get_messages(session_id)
361
- return {"session_id": session_id, "messages": messages}
 
 
 
 
 
 
 
 
 
 
 
 
362
  except Exception as e:
 
363
  raise HTTPException(500, str(e))
364
 
 
365
  @app.get("/sessions/{user_id}")
366
  async def list_sessions(user_id: str):
367
  """List all chat sessions for a user."""
368
  logger.info(f"Listing sessions for user: {user_id}")
 
369
  try:
370
  sessions = supabase.get_user_sessions(user_id)
371
- return {"user_id": user_id, "sessions": sessions, "count": len(sessions)}
 
 
 
 
 
372
  except Exception as e:
 
373
  raise HTTPException(500, str(e))
374
 
 
375
  @app.delete("/session/{session_id}")
376
  async def delete_session(session_id: str):
377
- """Delete a chat session."""
378
  logger.info(f"Deleting session: {session_id}")
 
379
  try:
380
  supabase.delete_session(session_id)
381
  return {"status": "deleted", "session_id": session_id}
382
  except Exception as e:
 
383
  raise HTTPException(500, str(e))
384
 
 
385
  if __name__ == "__main__":
386
  import uvicorn
387
- logger.info("Starting AGROW Chatbot Service v2.0 (Simple)")
388
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  """
2
+ AGROW Agricultural Chatbot Service
3
+ ===================================
4
+ AI-powered agricultural advisor using Gemini LLM with:
5
+ - Context from pipeline outputs (stress, NDVI, forecasts)
 
6
  - Supabase conversation storage
7
+ - Session management
8
  """
9
 
10
  import os
 
14
  from datetime import datetime
15
  from typing import Optional, List, Dict, Any
16
  import traceback
 
17
 
18
  from fastapi import FastAPI, HTTPException
19
  from fastapi.middleware.cors import CORSMiddleware
 
20
  from pydantic import BaseModel
21
+ import google.generativeai as genai
22
 
23
  from supabase_client import SupabaseClient
24
+ from prompts import SYSTEM_PROMPT, build_context_prompt
25
 
26
  # ============================================================================
27
  # LOGGING
 
34
  logger = logging.getLogger("ChatbotService")
35
 
36
  # ============================================================================
37
+ # GEMINI SETUP
38
  # ============================================================================
39
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
40
+ if GEMINI_API_KEY:
41
+ genai.configure(api_key=GEMINI_API_KEY)
42
+ model = genai.GenerativeModel('gemini-1.5-flash')
43
+ logger.info("Gemini API configured successfully")
44
+ else:
45
+ model = None
46
+ logger.warning("GEMINI_API_KEY not set - chatbot will return mock responses")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  # Supabase client
49
  supabase = SupabaseClient()
50
 
51
  # ============================================================================
52
+ # FASTAPI
53
  # ============================================================================
54
+ app = FastAPI(
55
+ title="AGROW Chatbot Service",
56
+ description="AI agricultural advisor with conversation storage",
57
+ version="1.0.0"
58
+ )
 
 
 
 
 
 
 
 
 
 
 
59
 
 
 
 
 
60
  app.add_middleware(
61
  CORSMiddleware,
62
  allow_origins=["*"],
 
65
  allow_headers=["*"],
66
  )
67
 
 
 
 
 
68
  # ============================================================================
69
  # REQUEST/RESPONSE MODELS
70
  # ============================================================================
 
72
  session_id: str
73
  message: str
74
  user_id: Optional[str] = None
75
+ field_context: Optional[Dict[str, Any]] = None # Pipeline data
 
 
 
 
76
 
77
  class ChatResponse(BaseModel):
78
+ response: str
79
  session_id: str
80
  message_id: str
81
+ context_used: List[str]
82
  timestamp: str
83
 
84
  class SessionRequest(BaseModel):
 
90
  title: str
91
  created_at: str
92
 
93
+ class MessageModel(BaseModel):
94
+ id: str
95
+ role: str
96
+ content: str
97
+ created_at: str
98
+
99
+ class HistoryResponse(BaseModel):
100
+ session_id: str
101
+ messages: List[MessageModel]
102
+
103
+ class SessionListItem(BaseModel):
104
+ id: str
105
+ title: str
106
+ created_at: str
107
+ updated_at: str
108
+ message_count: int
109
+
110
  # ============================================================================
111
+ # HELPER FUNCTIONS
112
  # ============================================================================
113
+ def generate_response(user_message: str, history: List[Dict], context: Optional[Dict] = None) -> tuple[str, List[str]]:
114
+ """Generate AI response using Gemini."""
115
+ context_used = []
 
 
 
 
 
 
116
 
117
+ # Build conversation history for context
118
+ conversation = []
119
+ for msg in history[-10:]: # Last 10 messages for context
120
+ role = "user" if msg.get("role") == "user" else "model"
121
+ conversation.append({"role": role, "parts": [msg.get("content", "")]})
122
 
123
+ # Build context prompt if pipeline data available
124
+ context_prompt = ""
125
+ if context:
126
+ context_prompt = build_context_prompt(context)
127
+ context_used = list(context.keys())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ # Combine system prompt with context
130
+ full_system = SYSTEM_PROMPT
131
+ if context_prompt:
132
+ full_system += f"\n\n## Current Field Analysis:\n{context_prompt}"
 
 
 
133
 
134
+ if model is None:
135
+ # Mock response if no API key
136
+ return f"I received your question: '{user_message}'. Please configure GEMINI_API_KEY for real responses.", []
 
 
 
 
137
 
138
+ try:
139
+ # Create chat with system instruction
140
+ chat = model.start_chat(history=conversation)
141
+
142
+ # Generate response
143
+ response = chat.send_message(
144
+ f"[System: {full_system}]\n\nUser: {user_message}",
145
+ generation_config=genai.types.GenerationConfig(
146
+ temperature=0.7,
147
+ max_output_tokens=1024,
148
+ )
149
+ )
150
+
151
+ return response.text, context_used
152
+
153
+ except Exception as e:
154
+ logger.error(f"Gemini error: {e}")
155
+ return f"I apologize, but I encountered an error. Please try again. Error: {str(e)}", []
156
 
157
  # ============================================================================
158
  # API ENDPOINTS
 
161
  async def root():
162
  return {
163
  "service": "AGROW Chatbot Service",
164
+ "version": "1.0.0",
165
+ "endpoints": {
166
+ "/chat": "POST - Send message, get AI response",
167
+ "/session/new": "POST - Create new chat session",
168
+ "/session/{id}/history": "GET - Get conversation history",
169
+ "/sessions/{user_id}": "GET - List user's sessions"
170
+ }
171
  }
172
 
173
  @app.get("/health")
174
  async def health():
175
  return {
176
  "status": "healthy",
177
+ "gemini_configured": model is not None,
178
+ "supabase_configured": supabase.is_configured()
179
  }
180
 
181
+
182
  @app.post("/session/new", response_model=SessionResponse)
183
  async def create_session(request: SessionRequest):
184
  """Create a new chat session."""
185
  logger.info(f"Creating new session for user: {request.user_id}")
186
+
187
  try:
188
  session = supabase.create_session(
189
  user_id=request.user_id,
190
  title=request.title or "New Conversation"
191
  )
192
+
193
  return SessionResponse(
194
  session_id=session["id"],
195
  title=session["title"],
 
199
  logger.error(f"Failed to create session: {e}")
200
  raise HTTPException(500, str(e))
201
 
202
+
203
  @app.post("/chat", response_model=ChatResponse)
204
  async def chat(request: ChatRequest):
205
+ """Send a message and get AI response."""
206
+ logger.info(f"Chat request - Session: {request.session_id}, Message: {request.message[:50]}...")
207
 
208
  try:
209
+ # Load conversation history
210
+ history = supabase.get_messages(request.session_id)
211
 
212
  # Save user message
213
  user_msg_id = supabase.add_message(
 
216
  content=request.message
217
  )
218
 
219
+ # Generate AI response
220
+ response_text, context_used = generate_response(
221
+ request.message,
222
+ history,
223
+ request.field_context
224
+ )
225
 
226
  # Save assistant response
227
  assistant_msg_id = supabase.add_message(
228
  session_id=request.session_id,
229
  role="assistant",
230
+ content=response_text,
231
+ context_used=context_used
232
  )
233
 
234
+ # Update session timestamp
235
  supabase.update_session_timestamp(request.session_id)
236
+
237
  logger.info(f"Response generated - {len(response_text)} chars")
238
 
239
  return ChatResponse(
240
+ response=response_text,
241
  session_id=request.session_id,
242
  message_id=assistant_msg_id,
243
+ context_used=context_used,
244
  timestamp=datetime.now().isoformat()
245
  )
246
 
 
249
  logger.error(traceback.format_exc())
250
  raise HTTPException(500, str(e))
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
+ @app.get("/session/{session_id}/history", response_model=HistoryResponse)
254
  async def get_history(session_id: str):
255
  """Get conversation history for a session."""
256
+ logger.info(f"Loading history for session: {session_id}")
257
+
258
  try:
259
  messages = supabase.get_messages(session_id)
260
+
261
+ return HistoryResponse(
262
+ session_id=session_id,
263
+ messages=[
264
+ MessageModel(
265
+ id=msg.get("id", ""),
266
+ role=msg.get("role", ""),
267
+ content=msg.get("content", ""),
268
+ created_at=msg.get("created_at", "")
269
+ )
270
+ for msg in messages
271
+ ]
272
+ )
273
  except Exception as e:
274
+ logger.error(f"History error: {e}")
275
  raise HTTPException(500, str(e))
276
 
277
+
278
  @app.get("/sessions/{user_id}")
279
  async def list_sessions(user_id: str):
280
  """List all chat sessions for a user."""
281
  logger.info(f"Listing sessions for user: {user_id}")
282
+
283
  try:
284
  sessions = supabase.get_user_sessions(user_id)
285
+
286
+ return {
287
+ "user_id": user_id,
288
+ "sessions": sessions,
289
+ "count": len(sessions)
290
+ }
291
  except Exception as e:
292
+ logger.error(f"List sessions error: {e}")
293
  raise HTTPException(500, str(e))
294
 
295
+
296
  @app.delete("/session/{session_id}")
297
  async def delete_session(session_id: str):
298
+ """Delete a chat session and its messages."""
299
  logger.info(f"Deleting session: {session_id}")
300
+
301
  try:
302
  supabase.delete_session(session_id)
303
  return {"status": "deleted", "session_id": session_id}
304
  except Exception as e:
305
+ logger.error(f"Delete error: {e}")
306
  raise HTTPException(500, str(e))
307
 
308
+
309
  if __name__ == "__main__":
310
  import uvicorn
311
+ logger.info("Starting AGROW Chatbot Service")
312
  uvicorn.run(app, host="0.0.0.0", port=7860)