| β π 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" | |