● 🔄 Complete Function Flow Input: "Create an order for John Doe at 123 Main St, San Francisco" Let me trace every single function call with this exact example! --- 📞 FUNCTION CALL SEQUENCE: STEP 1: User Clicks Send Button FILE: ui/app.py LINE: 448-452 User clicks "Send" button in Gradio UI ↓ FUNCTION CALLED: send_message(message, conv_state) message = "Create an order for John Doe at 123 Main St, San Francisco" conv_state = ConversationManager object Function Code: def send_message(message, conv_state): """Handle send button click""" chat_history, tools, new_state = handle_chat_message(message, conv_state) # ↑ # CALLS THIS NEXT return chat_history, tools, new_state, "" --- STEP 2: handle_chat_message() FILE: ui/app.py LINE: 223-241 FUNCTION: handle_chat_message(message, conversation_state) message = "Create an order for John Doe at 123 Main St, San Francisco" conversation_state = ConversationManager object Function Code: def handle_chat_message(message, conversation_state): if not message.strip(): return ... # Process message through chat engine response, tool_calls = chat_engine.process_message(message, conversation_state) # ↑ # CALLS THIS NEXT # Return updated UI return conversation_state.get_formatted_history(), conversation_state.get_tool_calls(), conversation_state --- STEP 3: chat_engine.process_message() FILE: chat/chat_engine.py LINE: 58-73 FUNCTION: ChatEngine.process_message(user_message, conversation) user_message = "Create an order for John Doe at 123 Main St, San Francisco" conversation = ConversationManager object Function Code: def process_message(self, user_message, conversation): """Process user message and return AI response""" return self.provider.process_message(user_message, conversation) # ↑ # self.provider = GeminiProvider (from chat_engine.py:26) # CALLS GeminiProvider.process_message() NEXT --- STEP 4: GeminiProvider.process_message() FILE: chat/providers/gemini_provider.py LINE: 173-212 FUNCTION: GeminiProvider.process_message(user_message, conversation) user_message = "Create an order for John Doe at 123 Main St, San Francisco" conversation = ConversationManager object Function Code: def process_message(self, user_message, conversation): """Process user message with Gemini""" if not self.api_available: return self._handle_no_api(), [] # Lazy initialization on first use self._ensure_initialized() # ← CALLS THIS if not initialized if not self._initialized: return "⚠️ Failed to initialize...", [] try: # Build conversation history for Gemini chat = self.model.start_chat(history=self._convert_history(conversation)) # ↑ # CALLS _convert_history() # Send message and get response response = chat.send_message(user_message, safety_settings={...}) # ↑ # 🌐 API CALL TO GOOGLE GEMINI # Sends: "Create an order for John Doe at 123 Main St, San Francisco" # Add user message to conversation conversation.add_message("user", user_message) # Process response and handle function calls return self._process_response(response, conversation, chat) # ↑ # CALLS THIS NEXT --- STEP 5: Gemini API Processes Request 🌐 GOOGLE GEMINI API (External) RECEIVES: - System Prompt: "You are an AI assistant for FleetMind..." - User Message: "Create an order for John Doe at 123 Main St, San Francisco" - Available Tools: [geocode_address, create_order] AI ANALYZES: "User wants to create an order. I have: ✅ Customer Name: John Doe ✅ Address: 123 Main St, San Francisco ❌ GPS Coordinates: Missing! DECISION: Call geocode_address tool first to get coordinates." RETURNS TO CODE: response = { candidates: [{ content: { parts: [{ function_call: { name: "geocode_address", args: { "address": "123 Main St, San Francisco" } } }] } }] } --- STEP 6: _process_response() - Detects Function Call FILE: chat/providers/gemini_provider.py LINE: 226-393 FUNCTION: _process_response(response, conversation, chat) response = Response from Gemini with function_call conversation = ConversationManager object chat = Gemini chat session Function Code: def _process_response(self, response, conversation, chat): """Process Gemini's response and handle function calls""" tool_calls_made = [] try: # Check ALL parts for function calls parts = response.candidates[0].content.parts logger.info(f"Processing response with {len(parts)} part(s)") # ↑ # LOGS: "Processing response with 1 part(s)" for part in parts: if hasattr(part, 'function_call'): fc = part.function_call if fc and hasattr(fc, 'name') and fc.name: has_function_call = True logger.info(f"Detected function call: {fc.name}") # ↑ # LOGS: "Detected function call: geocode_address" break if has_function_call: # Handle function calls (potentially multiple in sequence) current_response = response max_iterations = 10 for iteration in range(max_iterations): # ← LOOP STARTS # Extract function call details first_part = current_response.candidates[0].content.parts[0] function_call = first_part.function_call function_name = function_call.name # "geocode_address" function_args = dict(function_call.args) # {"address": "123 Main St, San Francisco"} logger.info(f"Gemini executing function: {function_name} (iteration {iteration + 1})") # ↑ # LOGS: "Gemini executing function: geocode_address (iteration 1)" # Execute the tool tool_result = execute_tool(function_name, function_args) # ↑ # CALLS execute_tool() NEXT --- STEP 7: execute_tool() - Routes to Handler FILE: chat/tools.py LINE: 92-118 FUNCTION: execute_tool(tool_name, tool_input) tool_name = "geocode_address" tool_input = {"address": "123 Main St, San Francisco"} Function Code: def execute_tool(tool_name, tool_input): """Route tool execution to appropriate handler""" try: if tool_name == "geocode_address": return handle_geocode_address(tool_input) # ↑ # CALLS THIS NEXT elif tool_name == "create_order": return handle_create_order(tool_input) else: return {"success": False, "error": f"Unknown tool: {tool_name}"} except Exception as e: logger.error(f"Tool execution error ({tool_name}): {e}") return {"success": False, "error": str(e)} --- STEP 8: handle_geocode_address() FILE: chat/tools.py LINE: 121-150 FUNCTION: handle_geocode_address(tool_input) tool_input = {"address": "123 Main St, San Francisco"} Function Code: def handle_geocode_address(tool_input): """Execute geocoding tool""" address = tool_input.get("address", "") # "123 Main St, San Francisco" if not address: return {"success": False, "error": "Address is required"} logger.info(f"Geocoding address: {address}") # ↑ # LOGS: "Geocoding address: 123 Main St, San Francisco" result = geocoding_service.geocode(address) # ↑ # CALLS geocoding_service.geocode() NEXT return { "success": True, "latitude": result["lat"], "longitude": result["lng"], "formatted_address": result["formatted_address"], "confidence": result["confidence"], "message": f"Address geocoded successfully ({result['confidence']})" } --- STEP 9: GeocodingService.geocode() FILE: chat/geocoding.py LINE: 28-65 FUNCTION: GeocodingService.geocode(address) address = "123 Main St, San Francisco" Function Code: def geocode(self, address): """Geocode an address to coordinates""" if not address: return self._error_response("Address is required") # Use mock or real API if self.use_mock: # True (no HERE_API_KEY configured) return self._geocode_mock(address) # ↑ # CALLS THIS NEXT else: return self._geocode_here(address) --- STEP 10: _geocode_mock() - Returns Coordinates FILE: chat/geocoding.py LINE: 52-70 FUNCTION: _geocode_mock(address) address = "123 Main St, San Francisco" Function Code: def _geocode_mock(self, address): """Mock geocoding using city detection""" address_lower = address.lower() # Try to detect city in address for city_name, (lat, lng) in CITY_COORDINATES.items(): if city_name in address_lower: logger.info(f"Mock geocoding detected city: {city_name}") # ↑ # LOGS: "Mock geocoding detected city: san francisco" return { "lat": lat, # 37.7749 "lng": lng, # -122.4194 "formatted_address": address, "confidence": "mock" } # Default to San Francisco if no city detected return { "lat": 37.7749, "lng": -122.4194, "formatted_address": address, "confidence": "mock" } RETURNS: { "lat": 37.7749, "lng": -122.4194, "formatted_address": "123 Main St, San Francisco", "confidence": "mock" } --- STEP 11: Back to handle_geocode_address() FILE: chat/tools.py LINE: 141-150 result = { "lat": 37.7749, "lng": -122.4194, "formatted_address": "123 Main St, San Francisco", "confidence": "mock" } RETURNS: { "success": True, "latitude": 37.7749, "longitude": -122.4194, "formatted_address": "123 Main St, San Francisco", "confidence": "mock", "message": "Address geocoded successfully (mock)" } --- STEP 12: Back to _process_response() - Tool Result Received FILE: chat/providers/gemini_provider.py LINE: 285-310 tool_result = { "success": True, "latitude": 37.7749, "longitude": -122.4194, "formatted_address": "123 Main St, San Francisco", "confidence": "mock", "message": "Address geocoded successfully (mock)" } # Track for transparency tool_calls_made.append({ "tool": "geocode_address", "input": {"address": "123 Main St, San Francisco"}, "result": tool_result }) conversation.add_tool_result("geocode_address", function_args, tool_result) # Send function result back to Gemini current_response = chat.send_message( genai.protos.Content( parts=[genai.protos.Part( function_response=genai.protos.FunctionResponse( name="geocode_address", response={"result": tool_result} ) )] ) ) # ↑ # 🌐 API CALL TO GEMINI WITH GEOCODING RESULT --- STEP 13: Gemini Receives Geocoding Result 🌐 GOOGLE GEMINI API (External) RECEIVES: - Function: geocode_address - Result: { "success": True, "latitude": 37.7749, "longitude": -122.4194 } AI ANALYZES: "Great! I now have GPS coordinates: ✅ Customer Name: John Doe ✅ Address: 123 Main St, San Francisco ✅ Latitude: 37.7749 ✅ Longitude: -122.4194 DECISION: Now I can create the order in the database! Call create_order tool." RETURNS TO CODE: response = { candidates: [{ content: { parts: [{ function_call: { name: "create_order", args: { "customer_name": "John Doe", "delivery_address": "123 Main St, San Francisco", "delivery_lat": 37.7749, "delivery_lng": -122.4194, "priority": "standard" } } }] } }] } --- STEP 14: Loop Continues - Detects create_order FILE: chat/providers/gemini_provider.py LINE: 252-285 # Still in the for loop (iteration 2) first_part = current_response.candidates[0].content.parts[0] has_fc = True # Another function call detected function_call = first_part.function_call function_name = function_call.name # "create_order" function_args = dict(function_call.args) # {customer_name, address, lat, lng...} logger.info(f"Gemini executing function: {function_name} (iteration 2)") # ↑ # LOGS: "Gemini executing function: create_order (iteration 2)" # Execute the tool tool_result = execute_tool(function_name, function_args) # ↑ # CALLS execute_tool() AGAIN --- STEP 15: execute_tool() - Routes to create_order FILE: chat/tools.py LINE: 92-118 FUNCTION: execute_tool(tool_name, tool_input) tool_name = "create_order" tool_input = { "customer_name": "John Doe", "delivery_address": "123 Main St, San Francisco", "delivery_lat": 37.7749, "delivery_lng": -122.4194, "priority": "standard" } Function Code: def execute_tool(tool_name, tool_input): try: if tool_name == "geocode_address": return handle_geocode_address(tool_input) elif tool_name == "create_order": return handle_create_order(tool_input) # ↑ # CALLS THIS NEXT --- STEP 16: handle_create_order() FILE: chat/tools.py LINE: 153-242 FUNCTION: handle_create_order(tool_input) tool_input = { "customer_name": "John Doe", "delivery_address": "123 Main St, San Francisco", "delivery_lat": 37.7749, "delivery_lng": -122.4194, "priority": "standard" } Function Code: def handle_create_order(tool_input): """Execute order creation tool""" # Extract fields with defaults customer_name = tool_input.get("customer_name") # "John Doe" customer_phone = tool_input.get("customer_phone") # None customer_email = tool_input.get("customer_email") # None delivery_address = tool_input.get("delivery_address") # "123 Main St, San Francisco" delivery_lat = tool_input.get("delivery_lat") # 37.7749 delivery_lng = tool_input.get("delivery_lng") # -122.4194 priority = tool_input.get("priority", "standard") # "standard" special_instructions = tool_input.get("special_instructions") # None weight_kg = tool_input.get("weight_kg", 5.0) # 5.0 # Validate required fields if not all([customer_name, delivery_address, delivery_lat, delivery_lng]): return {"success": False, "error": "Missing required fields..."} # Generate order ID now = datetime.now() order_id = f"ORD-{now.strftime('%Y%m%d%H%M%S')}" # ↑ # e.g., "ORD-20251114015858" # Handle time window time_window_end = now + timedelta(hours=6) # 6 hours from now time_window_start = now + timedelta(hours=2) # 2 hours from now # Insert into database query = """ INSERT INTO orders ( order_id, customer_name, customer_phone, customer_email, delivery_address, delivery_lat, delivery_lng, time_window_start, time_window_end, priority, weight_kg, status, special_instructions ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ params = ( order_id, # "ORD-20251114015858" customer_name, # "John Doe" customer_phone, # None customer_email, # None delivery_address, # "123 Main St, San Francisco" delivery_lat, # 37.7749 delivery_lng, # -122.4194 time_window_start, # 2025-11-14 03:58:58 time_window_end, # 2025-11-14 07:58:58 priority, # "standard" weight_kg, # 5.0 "pending", # status special_instructions # None ) try: execute_write(query, params) # ↑ # CALLS THIS NEXT - DATABASE WRITE! --- STEP 17: execute_write() - INSERT INTO DATABASE FILE: database/connection.py LINE: 71-97 FUNCTION: execute_write(query, params) query = "INSERT INTO orders (...) VALUES (%s, %s, ...)" params = ("ORD-20251114015858", "John Doe", None, None, "123 Main St...", ...) Function Code: def execute_write(query, params=None): """Execute a write query (INSERT, UPDATE, DELETE)""" try: # Connect to PostgreSQL conn = get_db_connection() # ↑ # Opens connection to localhost:5432/fleetmind logger.info("Database connection established: fleetmind@localhost") cursor = conn.cursor() # Execute INSERT query cursor.execute(query, params) # ↑ # 💾 EXECUTES SQL: # INSERT INTO orders (order_id, customer_name, ...) # VALUES ('ORD-20251114015858', 'John Doe', ...) conn.commit() # ← SAVES TO POSTGRESQL PERMANENTLY! rows_affected = cursor.rowcount # 1 cursor.close() conn.close() logger.info("Database connection closed") return rows_affected # Returns 1 DATABASE STATE: -- New row added to orders table: ORDER_ID: ORD-20251114015858 CUSTOMER_NAME: John Doe CUSTOMER_PHONE: NULL CUSTOMER_EMAIL: NULL DELIVERY_ADDRESS: 123 Main St, San Francisco DELIVERY_LAT: 37.7749 DELIVERY_LNG: -122.4194 STATUS: pending PRIORITY: standard WEIGHT_KG: 5.0 CREATED_AT: 2025-11-14 01:58:58 --- STEP 18: Back to handle_create_order() - Success! FILE: chat/tools.py LINE: 224-242 execute_write(query, params) # Returned 1 (success) logger.info(f"Order created: {order_id}") # ↑ # LOGS: "Order created: ORD-20251114015858" return { "success": True, "order_id": "ORD-20251114015858", "status": "pending", "customer": "John Doe", "address": "123 Main St, San Francisco", "deadline": "2025-11-14 07:58", "priority": "standard", "message": "Order ORD-20251114015858 created successfully!" } --- STEP 19: Back to _process_response() - Second Tool Complete FILE: chat/providers/gemini_provider.py LINE: 285-310 tool_result = { "success": True, "order_id": "ORD-20251114015858", "status": "pending", "customer": "John Doe", ... } # Track for transparency tool_calls_made.append({ "tool": "create_order", "input": { "customer_name": "John Doe", "delivery_address": "123 Main St, San Francisco", "delivery_lat": 37.7749, "delivery_lng": -122.4194 }, "result": tool_result }) conversation.add_tool_result("create_order", function_args, tool_result) # Send function result back to Gemini current_response = chat.send_message( genai.protos.Content( parts=[genai.protos.Part( function_response=genai.protos.FunctionResponse( name="create_order", response={"result": tool_result} ) )] ) ) # ↑ # 🌐 API CALL TO GEMINI WITH ORDER CREATION RESULT --- STEP 20: Gemini Generates Final Response 🌐 GOOGLE GEMINI API (External) RECEIVES: - Function: create_order - Result: { "success": True, "order_id": "ORD-20251114015858", "customer": "John Doe", "address": "123 Main St, San Francisco" } AI ANALYZES: "Perfect! Order successfully created in database! Let me tell the user with a nice summary." GENERATES TEXT RESPONSE: "Awesome! I have created the order for John Doe. **Here's a summary:** • Order ID: ORD-20251114015858 • Customer: John Doe • Address: 123 Main St, San Francisco • Status: Pending • Priority: Standard The order has been successfully saved to the database!" RETURNS TO CODE: response = { candidates: [{ content: { parts: [{ text: "Awesome! I have created the order for John Doe.\n\n**Here's a summary:**..." }] } }] } --- STEP 21: _process_response() - Extract Final Text FILE: chat/providers/gemini_provider.py LINE: 272-356 # Loop detects no more function calls logger.info(f"No more function calls after iteration 2") # Extract text from final response parts = current_response.candidates[0].content.parts logger.info(f"Extracting text from {len(parts)} parts") for idx, part in enumerate(parts): if hasattr(part, 'text') and part.text: logger.info(f"Part {idx} has text: {part.text[:50]}...") final_text += part.text # final_text = "Awesome! I have created the order for John Doe..." logger.info(f"Returning response: {final_text[:100]}") conversation.add_message("assistant", final_text) return final_text, tool_calls_made # ↑ ↑ # Response List of 2 tool calls [geocode, create_order] RETURNS: ( "Awesome! I have created the order for John Doe.\n\n**Here's a summary:**...", [ {"tool": "geocode_address", "input": {...}, "result": {...}}, {"tool": "create_order", "input": {...}, "result": {...}} ] ) --- STEP 22: Back Through All Functions ← Returns to: GeminiProvider.process_message() (line 206) ← Returns to: ChatEngine.process_message() (line 58) ← Returns to: handle_chat_message() (line 223) ← Returns to: send_message() (line 443) ← Returns to: Gradio UI (line 448) --- STEP 23: Gradio Updates UI FILE: ui/app.py LINE: 448-452 send_btn.click( fn=send_message, outputs=[chatbot, tool_display, conversation_state, msg_input] # ↑ ↑ # Updates Shows tool calls ) CHATBOT DISPLAYS: User: "Create an order for John Doe at 123 Main St, San Francisco"