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\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"