NitinBot001 commited on
Commit
a164116
·
verified ·
1 Parent(s): 2b6e96f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +208 -111
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py
2
 
3
  import json
4
  import openai
@@ -29,6 +29,7 @@ class Config:
29
  model_name: str
30
  max_retries: int = 3
31
  temperature: float = 0.5
 
32
 
33
  @classmethod
34
  def from_env(cls):
@@ -40,7 +41,7 @@ class Config:
40
  )
41
 
42
  class EasyFarmsAssistant:
43
- """Enhanced EasyFarms AI Assistant with weather integration and persistent sessions"""
44
 
45
  def __init__(self, config: Optional[Config] = None, manager: Optional[ConversationManager] = None):
46
  """
@@ -67,7 +68,7 @@ class EasyFarmsAssistant:
67
  # Use the provided conversation manager or create a new one
68
  self.manager = manager or ConversationManager()
69
 
70
- # System prompts
71
  self.system_prompt = """You are the AI assistant for EasyForms Agritech Solutions. Your task is to provide users with clear, concise, and actionable responses regarding agriculture, crop management, production, treatment, weather alerts, and related queries.
72
 
73
  Core Capabilities:
@@ -78,27 +79,54 @@ Core Capabilities:
78
  - Market data and commodity prices
79
  - General agricultural guidance
80
 
81
- Rules:
82
- 1. Check if any relevant function_tools or datasets are available for this query.
83
- 2. If available, use the functions to fetch information and generate the final user-facing response.
84
- 3. If the functions or data are unavailable, do **not stop**; instead, generate a general, well-reasoned response based on your own knowledge.
85
- 4. Keep the response **simple, smooth, well-pointed, and concise**.
86
- 5. Structure the response with bullet points or numbered steps where helpful.
87
- 6. Provide practical, actionable advice a user can implement immediately.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  7. Use English or Hindi based on user preference.
89
- 8. If any information is uncertain, mention it clearly and suggest alternatives.
90
- 9. For weather-related queries, prioritize safety and timely alerts."""
 
91
 
92
- self.final_system = """You are the final response assistant for EasyForms Agritech Solutions. Use the outputs from previous function calls to generate a **clear, concise, actionable response** for the user.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- Rules:
95
- 1. Combine the function outputs and your own reasoning to answer the query.
96
- 2. Keep responses simple, smooth, well-pointed, and concise.
97
- 3. Structure response with headings or bullet points if helpful.
98
- 4. Provide practical advice that a farmer or user can implement immediately.
99
- 5. If some data is missing, clearly state it and offer alternatives.
100
- 6. Use English or Hindi based on the user preference.
101
- 7. For weather alerts, emphasize urgency and protective measures."""
102
 
103
  def _initialize_tools(self) -> List[Dict]:
104
  """Initialize and convert all function schemas to the new tools format"""
@@ -157,9 +185,57 @@ Rules:
157
  from alert import execute_function
158
  return execute_function(function_name, kwargs)
159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  def process_query(self, user_message: str, user_id: str, chat_id: Optional[str] = None, image_url: Optional[str] = None) -> Dict[str, Any]:
161
  """
162
- Process user query with user authentication and improved conversation management
163
 
164
  Args:
165
  user_message: The user's message
@@ -179,7 +255,8 @@ Rules:
179
  "is_new_chat": True,
180
  "user_message_id": None,
181
  "assistant_message_id": None,
182
- "total_messages": 0
 
183
  }
184
 
185
  # Handle chat ID
@@ -216,51 +293,42 @@ Rules:
216
  llm_message_content += f" [image_url: {image_url}]"
217
  messages.append({"role": "user", "content": llm_message_content})
218
 
219
- # Make API call
220
- response = self.client.chat.completions.create(
221
- model=self.config.model_name,
222
- messages=messages,
223
- tools=self.tools,
224
- tool_choice="auto",
225
- temperature=self.config.temperature
226
- )
227
 
228
- message = response.choices[0].message
229
-
230
- if hasattr(message, 'tool_calls') and message.tool_calls:
231
- # Add the assistant's message with tool calls
232
- messages.append({
233
- "role": "assistant",
234
- "tool_calls": [
235
- {
236
- "id": tool_call.id,
237
- "type": "function",
238
- "function": {
239
- "name": tool_call.function.name,
240
- "arguments": tool_call.function.arguments
241
- }
242
- } for tool_call in message.tool_calls
243
- ]
244
- })
245
 
246
- # Execute all tool calls
247
- for tool_call in message.tool_calls:
248
- function_name = tool_call.function.name
249
- function_args = json.loads(tool_call.function.arguments)
250
-
251
- logger.info(f"Calling function: {function_name} with args: {function_args}")
 
 
 
 
 
 
 
 
252
 
253
- # Call the function
254
- function_result = self.call_function(function_name, function_args)
 
255
 
256
- # Add function result to messages
257
- messages.append({
258
- "role": "tool",
259
- "tool_call_id": tool_call.id,
260
- "content": json.dumps(function_result)
261
- })
262
-
263
- # Add final system prompt for generating response
 
 
264
  messages.append({
265
  "role": "system",
266
  "content": self.final_system
@@ -273,8 +341,20 @@ Rules:
273
  temperature=self.config.temperature
274
  )
275
  response_content = final_response.choices[0].message.content
276
- else:
277
- response_content = message.content
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
  # Add messages to conversation history with unique IDs
280
  user_msg = self.manager.add_message(chat_id, user_id, "user", user_message, image_url)
@@ -286,7 +366,8 @@ Rules:
286
  "is_new_chat": is_new_chat,
287
  "user_message_id": user_msg.get("message_id"),
288
  "assistant_message_id": assistant_msg.get("message_id"),
289
- "total_messages": len(self.manager.get_history(chat_id, user_id))
 
290
  }
291
 
292
  except Exception as e:
@@ -297,7 +378,8 @@ Rules:
297
  "is_new_chat": True,
298
  "user_message_id": None,
299
  "assistant_message_id": None,
300
- "total_messages": 0
 
301
  }
302
 
303
  def get_chat_info(self, chat_id: str, user_id: str) -> Dict[str, Any]:
@@ -355,7 +437,7 @@ Rules:
355
 
356
  # Utility class for generating example queries (can be used for testing)
357
  class QuickQueries:
358
- """Pre-defined query templates for common farming questions"""
359
 
360
  @staticmethod
361
  def crop_recommendation(N: int, P: int, K: int, temp: float, humidity: float, ph: float = 6.5) -> str:
@@ -372,6 +454,30 @@ class QuickQueries:
372
  """Generate weather alert query"""
373
  location_str = f" for {location}" if location else ""
374
  return f"What are the current weather alerts and conditions{location_str}? How will this affect farming?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
 
377
  # Test function to validate configuration
@@ -399,55 +505,46 @@ def test_configuration():
399
  return False
400
 
401
 
402
- # Test the new conversation management
403
- def test_conversation_management():
404
- """Test the enhanced conversation management"""
405
  try:
406
  assistant = EasyFarmsAssistant()
407
 
408
- # Test 1: New conversation
409
- print("\n=== Test 1: New Conversation ===")
410
- result1 = assistant.process_query("Hello, I need help with farming", "test_user_123")
411
- print(f"Chat ID: {result1['chat_id']}")
412
- print(f"Is new chat: {result1['is_new_chat']}")
413
- print(f"User message ID: {result1['user_message_id']}")
414
- print(f"Assistant message ID: {result1['assistant_message_id']}")
415
- print(f"Total messages: {result1['total_messages']}")
416
-
417
- # Test 2: Continue conversation
418
- print("\n=== Test 2: Continue Conversation ===")
419
- chat_id = result1['chat_id']
420
- result2 = assistant.process_query("What crops grow well in sandy soil?", "test_user_123", chat_id=chat_id)
421
- print(f"Chat ID: {result2['chat_id']}")
422
- print(f"Is new chat: {result2['is_new_chat']}")
423
- print(f"User message ID: {result2['user_message_id']}")
424
- print(f"Assistant message ID: {result2['assistant_message_id']}")
425
- print(f"Total messages: {result2['total_messages']}")
426
 
427
- # Test 3: Get messages
428
- print(f"\n=== Test 3: Messages in chat {chat_id} ===")
429
- messages = assistant.get_messages(chat_id, "test_user_123")
430
- for msg in messages:
431
- print(f" Message {msg.get('message_id')}: {msg.get('role')} - {msg.get('content')[:50]}...")
 
 
 
432
 
433
- # Test 4: User-defined chat ID
434
- print("\n=== Test 4: User-defined Chat ID ===")
435
- custom_chat_id = "my_farming_chat_2024"
436
- result3 = assistant.process_query("Tell me about wheat farming", "test_user_123", chat_id=custom_chat_id)
437
- print(f"Custom chat ID: {result3['chat_id']}")
438
- print(f"Is new chat: {result3['is_new_chat']}")
 
 
439
 
440
- # Test 5: Get all chats
441
- print("\n=== Test 5: All Chats ===")
442
- all_chats = assistant.get_all_chats("test_user_123")
443
- for chat in all_chats[:3]: # Show first 3
444
- print(f" {chat.get('session_id')}: {chat.get('title')} ({chat.get('message_count')} messages)")
 
 
 
445
 
446
- print("\nConversation management tests completed!")
447
  return True
448
 
449
  except Exception as e:
450
- print(f"❌ Conversation management test failed: {e}")
451
  return False
452
 
453
 
@@ -456,10 +553,10 @@ if __name__ == "__main__":
456
  if test_configuration():
457
  print("✅ Basic configuration ready!")
458
 
459
- print("\n=== Testing Enhanced Conversation Management ===")
460
- if test_conversation_management():
461
  print("✅ All systems ready!")
462
  else:
463
- print("⚠️ Basic functions work, but conversation management needs attention")
464
  else:
465
  print("❌ Please fix configuration issues before running the assistant.")
 
1
+ # app.py - Enhanced with unlimited multi-function execution
2
 
3
  import json
4
  import openai
 
29
  model_name: str
30
  max_retries: int = 3
31
  temperature: float = 0.5
32
+ max_function_rounds: int = 5 # Maximum rounds of function calling to prevent infinite loops
33
 
34
  @classmethod
35
  def from_env(cls):
 
41
  )
42
 
43
  class EasyFarmsAssistant:
44
+ """Enhanced EasyFarms AI Assistant with unlimited multi-function execution"""
45
 
46
  def __init__(self, config: Optional[Config] = None, manager: Optional[ConversationManager] = None):
47
  """
 
68
  # Use the provided conversation manager or create a new one
69
  self.manager = manager or ConversationManager()
70
 
71
+ # Enhanced system prompt for multi-function execution
72
  self.system_prompt = """You are the AI assistant for EasyForms Agritech Solutions. Your task is to provide users with clear, concise, and actionable responses regarding agriculture, crop management, production, treatment, weather alerts, and related queries.
73
 
74
  Core Capabilities:
 
79
  - Market data and commodity prices
80
  - General agricultural guidance
81
 
82
+ IMPORTANT - Multi-Function Execution Guidelines:
83
+ 1. **You can and SHOULD use multiple functions in a single response** when it provides better value to the user.
84
+ 2. **You can call the same function multiple times** with different parameters to compare or analyze different scenarios.
85
+ 3. **Think comprehensively** - if a user asks about crops AND market prices, use both functions.
86
+ 4. **Make comparisons** - if asked to compare, call the function multiple times with different parameters.
87
+ 5. **Provide complete solutions** - gather all necessary data before responding.
88
+
89
+ Example Multi-Function Scenarios:
90
+ - "Compare wheat and rice prices" → Call get_market_prices twice (once for wheat, once for rice)
91
+ - "What crop should I grow and what's its market price?" → Call get_crop_recommendation AND get_market_prices
92
+ - "Check prices in Gujarat and Maharashtra" → Call get_market_prices multiple times for different states
93
+ - "Recommend crops for sandy and loamy soil" → Call get_crop_recommendation multiple times
94
+ - "Give fertilizer advice and check market prices for wheat" → Call both functions
95
+
96
+ Response Rules:
97
+ 1. Use all available function_tools when they can help answer the query comprehensively.
98
+ 2. If comparing multiple items, execute the function for each item separately.
99
+ 3. If data is unavailable from functions, supplement with your own agricultural knowledge.
100
+ 4. Present results clearly - use tables, comparisons, or bullet points as appropriate.
101
+ 5. Keep responses concise but comprehensive.
102
+ 6. Provide practical, actionable advice.
103
  7. Use English or Hindi based on user preference.
104
+ 8. For weather-related queries, prioritize safety and timely alerts.
105
+
106
+ Remember: Don't hesitate to use multiple functions - it's better to provide complete information than partial data."""
107
 
108
+ self.final_system = """You are the final response assistant for EasyForms Agritech Solutions. You have received outputs from multiple function calls. Your job is to synthesize all this information into a **clear, comprehensive, and actionable response** for the user.
109
+
110
+ Response Synthesis Guidelines:
111
+ 1. **Analyze all function outputs** - Look at every function result provided.
112
+ 2. **Create comparisons** - If multiple similar functions were called, create comparison tables or clear comparisons.
113
+ 3. **Highlight insights** - Point out interesting patterns, best options, or important differences.
114
+ 4. **Structure intelligently**:
115
+ - Use tables for price comparisons
116
+ - Use bullet points for multiple recommendations
117
+ - Use numbered steps for sequential advice
118
+ - Use paragraphs for explanations
119
+ 5. **Be comprehensive but concise** - Include all important information without being verbose.
120
+ 6. **Provide actionable recommendations** - End with clear next steps or advice.
121
+ 7. **Handle missing data gracefully** - If some functions returned errors, work with available data.
122
+ 8. **Use appropriate formatting** - Make the response easy to scan and understand.
123
 
124
+ Example Response Structures:
125
+ - For price comparisons: Create a comparison table showing commodity, location, and prices
126
+ - For crop recommendations: List recommended crops with reasoning
127
+ - For multi-aspect queries: Use sections with clear headings
128
+
129
+ Remember: The user asked one question but you have data from multiple sources - combine them intelligently."""
 
 
130
 
131
  def _initialize_tools(self) -> List[Dict]:
132
  """Initialize and convert all function schemas to the new tools format"""
 
185
  from alert import execute_function
186
  return execute_function(function_name, kwargs)
187
 
188
+ def _execute_tool_calls_round(self, messages: List[Dict], tool_calls) -> tuple[List[Dict], int]:
189
+ """
190
+ Execute a round of tool calls and return updated messages with function count
191
+
192
+ Args:
193
+ messages: Current message history
194
+ tool_calls: Tool calls to execute
195
+
196
+ Returns:
197
+ Tuple of (updated messages, number of functions executed)
198
+ """
199
+ function_count = 0
200
+
201
+ # Add the assistant's message with tool calls
202
+ messages.append({
203
+ "role": "assistant",
204
+ "tool_calls": [
205
+ {
206
+ "id": tool_call.id,
207
+ "type": "function",
208
+ "function": {
209
+ "name": tool_call.function.name,
210
+ "arguments": tool_call.function.arguments
211
+ }
212
+ } for tool_call in tool_calls
213
+ ]
214
+ })
215
+
216
+ # Execute all tool calls
217
+ for tool_call in tool_calls:
218
+ function_name = tool_call.function.name
219
+ function_args = json.loads(tool_call.function.arguments)
220
+
221
+ logger.info(f"Calling function: {function_name} with args: {function_args}")
222
+
223
+ # Call the function
224
+ function_result = self.call_function(function_name, function_args)
225
+ function_count += 1
226
+
227
+ # Add function result to messages
228
+ messages.append({
229
+ "role": "tool",
230
+ "tool_call_id": tool_call.id,
231
+ "content": json.dumps(function_result)
232
+ })
233
+
234
+ return messages, function_count
235
+
236
  def process_query(self, user_message: str, user_id: str, chat_id: Optional[str] = None, image_url: Optional[str] = None) -> Dict[str, Any]:
237
  """
238
+ Process user query with unlimited multi-function execution capability
239
 
240
  Args:
241
  user_message: The user's message
 
255
  "is_new_chat": True,
256
  "user_message_id": None,
257
  "assistant_message_id": None,
258
+ "total_messages": 0,
259
+ "functions_executed": 0
260
  }
261
 
262
  # Handle chat ID
 
293
  llm_message_content += f" [image_url: {image_url}]"
294
  messages.append({"role": "user", "content": llm_message_content})
295
 
296
+ # Track total functions executed
297
+ total_functions_executed = 0
 
 
 
 
 
 
298
 
299
+ # Iterative function calling - allow multiple rounds
300
+ for round_num in range(self.config.max_function_rounds):
301
+ logger.info(f"Function calling round {round_num + 1}/{self.config.max_function_rounds}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
+ # Make API call
304
+ response = self.client.chat.completions.create(
305
+ model=self.config.model_name,
306
+ messages=messages,
307
+ tools=self.tools,
308
+ tool_choice="auto",
309
+ temperature=self.config.temperature
310
+ )
311
+
312
+ message = response.choices[0].message
313
+
314
+ # Check if there are tool calls
315
+ if hasattr(message, 'tool_calls') and message.tool_calls:
316
+ logger.info(f"Round {round_num + 1}: Executing {len(message.tool_calls)} function(s)")
317
 
318
+ # Execute this round of tool calls
319
+ messages, functions_executed = self._execute_tool_calls_round(messages, message.tool_calls)
320
+ total_functions_executed += functions_executed
321
 
322
+ # Continue to next round to see if AI wants to call more functions
323
+ continue
324
+ else:
325
+ # No more tool calls, we have the final response
326
+ response_content = message.content
327
+ logger.info(f"Function calling completed after {round_num + 1} round(s). Total functions executed: {total_functions_executed}")
328
+ break
329
+ else:
330
+ # Max rounds reached, add final system prompt and get response
331
+ logger.warning(f"Max function rounds ({self.config.max_function_rounds}) reached")
332
  messages.append({
333
  "role": "system",
334
  "content": self.final_system
 
341
  temperature=self.config.temperature
342
  )
343
  response_content = final_response.choices[0].message.content
344
+
345
+ # If functions were executed, add final synthesis prompt
346
+ if total_functions_executed > 0 and not response_content:
347
+ messages.append({
348
+ "role": "system",
349
+ "content": self.final_system
350
+ })
351
+
352
+ final_response = self.client.chat.completions.create(
353
+ model=self.config.model_name,
354
+ messages=messages,
355
+ temperature=self.config.temperature
356
+ )
357
+ response_content = final_response.choices[0].message.content
358
 
359
  # Add messages to conversation history with unique IDs
360
  user_msg = self.manager.add_message(chat_id, user_id, "user", user_message, image_url)
 
366
  "is_new_chat": is_new_chat,
367
  "user_message_id": user_msg.get("message_id"),
368
  "assistant_message_id": assistant_msg.get("message_id"),
369
+ "total_messages": len(self.manager.get_history(chat_id, user_id)),
370
+ "functions_executed": total_functions_executed
371
  }
372
 
373
  except Exception as e:
 
378
  "is_new_chat": True,
379
  "user_message_id": None,
380
  "assistant_message_id": None,
381
+ "total_messages": 0,
382
+ "functions_executed": 0
383
  }
384
 
385
  def get_chat_info(self, chat_id: str, user_id: str) -> Dict[str, Any]:
 
437
 
438
  # Utility class for generating example queries (can be used for testing)
439
  class QuickQueries:
440
+ """Pre-defined query templates for common farming questions with multi-function examples"""
441
 
442
  @staticmethod
443
  def crop_recommendation(N: int, P: int, K: int, temp: float, humidity: float, ph: float = 6.5) -> str:
 
454
  """Generate weather alert query"""
455
  location_str = f" for {location}" if location else ""
456
  return f"What are the current weather alerts and conditions{location_str}? How will this affect farming?"
457
+
458
+ @staticmethod
459
+ def multi_function_queries() -> List[str]:
460
+ """Generate example multi-function queries"""
461
+ return [
462
+ # Multiple function calls - same function multiple times
463
+ "Compare wheat and rice market prices in Gujarat",
464
+ "What are the market prices for tomato in Gujarat, Maharashtra, and Punjab?",
465
+ "Recommend crops for sandy soil and loamy soil, which one is better?",
466
+
467
+ # Multiple function calls - different functions
468
+ "What crop should I grow with N=90, P=42, K=43, temperature 25°C, humidity 80% and what's the market price for that crop?",
469
+ "Give me crop recommendations and current weather alerts for my region",
470
+ "I want to grow wheat, tell me fertilizer recommendations and current market prices",
471
+
472
+ # Complex multi-function queries
473
+ "Compare tomato and potato prices across different states and tell me which crop has better market potential",
474
+ "What are the best crops for my soil (N=80, P=40, K=50, temp=22°C, humidity=75%) and their current market prices?",
475
+ "Give me weather alerts, crop recommendations for my conditions, and market prices for the recommended crops",
476
+
477
+ # Sequential same function calls
478
+ "Get market prices for wheat, rice, maize, and sugarcane in Gujarat",
479
+ "Compare fertilizer requirements for wheat in black soil, red soil, and sandy soil",
480
+ ]
481
 
482
 
483
  # Test function to validate configuration
 
505
  return False
506
 
507
 
508
+ # Test the multi-function execution
509
+ def test_multi_function_execution():
510
+ """Test the enhanced multi-function execution"""
511
  try:
512
  assistant = EasyFarmsAssistant()
513
 
514
+ print("\n=== Testing Multi-Function Execution ===\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
+ # Test 1: Single function call
517
+ print("Test 1: Single Function Call")
518
+ result1 = assistant.process_query(
519
+ "What crop should I grow with N=90, P=42, K=43, temperature 25°C, humidity 80%?",
520
+ "test_user_multi"
521
+ )
522
+ print(f"Functions executed: {result1.get('functions_executed', 0)}")
523
+ print(f"Response preview: {result1['response'][:200]}...\n")
524
 
525
+ # Test 2: Multiple different functions
526
+ print("Test 2: Multiple Different Functions")
527
+ result2 = assistant.process_query(
528
+ "What crop should I grow with N=90, P=42, K=43, temperature 25°C, humidity 80% and what's the current market price for wheat?",
529
+ "test_user_multi"
530
+ )
531
+ print(f"Functions executed: {result2.get('functions_executed', 0)}")
532
+ print(f"Response preview: {result2['response'][:200]}...\n")
533
 
534
+ # Test 3: Same function multiple times
535
+ print("Test 3: Same Function Multiple Times")
536
+ result3 = assistant.process_query(
537
+ "Compare wheat and rice market prices in Gujarat",
538
+ "test_user_multi"
539
+ )
540
+ print(f"Functions executed: {result3.get('functions_executed', 0)}")
541
+ print(f"Response preview: {result3['response'][:200]}...\n")
542
 
543
+ print("✅ Multi-function execution tests completed!")
544
  return True
545
 
546
  except Exception as e:
547
+ print(f"❌ Multi-function test failed: {e}")
548
  return False
549
 
550
 
 
553
  if test_configuration():
554
  print("✅ Basic configuration ready!")
555
 
556
+ print("\n=== Testing Multi-Function Execution ===")
557
+ if test_multi_function_execution():
558
  print("✅ All systems ready!")
559
  else:
560
+ print("⚠️ Basic functions work, but multi-function execution needs attention")
561
  else:
562
  print("❌ Please fix configuration issues before running the assistant.")