mashrur950's picture
Initial commit: FleetMind MCP with GitHub Actions auto-sync
d69447e
|
raw
history blame
23.9 kB

● πŸ”„ 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\nHere'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\nHere'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"