Spaces:
Sleeping
Sleeping
File size: 9,519 Bytes
141953b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
import os
from fastapi import FastAPI, Request, HTTPException, Form
import uvicorn
from gradio_client import Client
from fastapi.responses import Response
import json
import re # Import re for potential future use (e.g., parsing messages)
import asyncio # Import asyncio for async operations
# Connect to your hosted Gradio Space (Futuresony/Mr.Events)
# This client is used by BOTH the /chat and /webhook endpoints to interact with the core chatbot
try:
client = Client("Futuresony/Mr.Events")
print("Gradio Client for 'Futuresony/Mr.Events' initialized.")
except Exception as e:
print(f"Error initializing Gradio Client for 'Futuresony/Mr.Events': {e}")
print("Ensure the Space name is correct and it is accessible.")
client = None # Set client to None if initialization fails
# Get your secure API key for THIS FastAPI application and the hosted Space from environment
# Assuming the same API key (APP_API_KEY) is used for both.
VALID_API_KEY = os.getenv("APP_API_KEY")
# Add a print statement to confirm if the API key is loaded
print(f"APP_API_KEY loaded: {'Yes' if VALID_API_KEY else 'No'}")
if not VALID_API_KEY:
print("Warning: APP_API_KEY secret not set. API key validation and calls to hosted space may fail.")
app = FastAPI()
# --- Chat Endpoint (Existing Functionality) ---
@app.post("/chat")
async def chat(request: Request):
"""
Handles chat requests via a JSON payload, validates API key,
and calls the hosted Gradio chatbot with history.
"""
print("\n--- Received POST request at /chat ---")
data = await request.json()
# API Key Check for THIS FastAPI application
api_key = request.headers.get("X-API-Key") # Get API key from headers
print(f"API Key from header: {api_key[:4]}...") if api_key else "No API Key in header"
if not VALID_API_KEY or api_key != VALID_API_KEY:
print("API Key validation failed.")
raise HTTPException(status_code=403, detail="Invalid API Key")
print("API Key validation successful.")
# Get user message
user_message = data.get("message")
if not user_message:
print("Error: 'message' is required in the request body.")
raise HTTPException(status_code=400, detail="Message is required")
print(f"User message: {user_message}")
# Get chat history (assuming it's sent in the request body for stateless API)
# The chat_history is expected to be a list of lists: [[user_msg, bot_msg], ...]
# If not provided, initialize as empty list.
chat_history = data.get("chat_history", [])
# print(f"Received chat history: {chat_history}") # Be cautious logging history
# --- Call the hosted Gradio chatbot ---
if client is None:
print("Error: Gradio Client not initialized. Cannot call chatbot.")
raise HTTPException(status_code=500, detail="Chatbot service not available.")
try:
print(f"Calling hosted Gradio Space 'Futuresony/Mr.Events' /chat endpoint from /chat...")
# Note: The Gradio ChatInterface API typically expects query (current message)
# and chat_history (history *before* the current turn).
# Use the same VALID_API_KEY for the hosted space call
result = await client.predict( # Use await because client.predict can be async
query=user_message,
chat_history=chat_history, # Pass the history directly
api_key=VALID_API_KEY, # Pass the APP_API_KEY to the hosted space
api_name="/chat" # Ensure this matches the API endpoint exposed by the hosted Gradio app
)
print(f"Received raw result from hosted Space: {result}")
# The result from client.predict on a ChatInterface is typically the assistant's response string
assistant_response = result
if not isinstance(assistant_response, str):
print(f"Warning: Hosted Space returned unexpected result type: {type(assistant_response)}. Raw result: {result}")
# Attempt to convert to string or handle appropriately
assistant_response = str(assistant_response)
print(f"Formatted assistant response: {assistant_response}")
except Exception as e:
print(f"Error calling hosted Gradio Space from /chat: {e}")
raise HTTPException(status_code=500, detail=f"Error communicating with chatbot model: {e}")
return {"response": assistant_response}
# --- Twilio Webhook Endpoint ---
# In-memory dictionary to store history per sender (NOT for production!)
# Replace this with a persistent storage solution (database, file storage) for production.
conversation_histories = {}
# For production-level history management, you would initialize and interact with
# a database or other persistent storage here.
@app.post("/webhook")
async def webhook(
# Explicitly receive form data parameters expected from Twilio
From: str = Form(...), # Sender's phone number
Body: str = Form(...), # Message content
# Twilio sends other parameters like MessageSid, To, AccountSid, etc.
# You can receive them here if needed:
# MessageSid: str = Form(None),
# To: str = Form(None),
request: Request = None # Keep request for raw access if needed
):
"""
Handles incoming Twilio webhook requests for new messages,
processes them with the chatbot, and returns TwiML.
Note: This implementation uses in-memory history (NOT for production).
"""
print("\n--- Received POST request at /webhook from Twilio ---")
# Access the incoming message and sender number directly from Form parameters
incoming_message = Body
sender_number = From
print(f"Parsed Incoming Message: '{incoming_message}' from {sender_number}")
# --- Conversation History Management (In-Memory - NOT Persistent!) ---
# In a real application, you would load/save history from a database/file.
chat_history = conversation_histories.get(sender_number, [])
print(f"Retrieved in-memory history for {sender_number}: {chat_history}")
# --- Call Chatbot Logic ---
if client is None:
print("Error: Gradio Client not initialized. Cannot call chatbot from webhook.")
bot_response = "Error: Chatbot service is not available."
else:
try:
# Use the same VALID_API_KEY for the hosted space call from webhook
print(f"Calling hosted Gradio Space 'Futuresony/Mr.Events' /chat endpoint from /webhook...")
print(f" Query: {incoming_message}")
# print(f" History: {chat_history}") # Be cautious logging history
# Call the hosted chatbot with the retrieved history
# Gradio client expects query (current message) and chat_history (history *before* current turn)
result = await client.predict( # Use await
query=incoming_message,
chat_history=chat_history, # Pass the retrieved history
api_key=VALID_API_KEY, # Pass the APP_API_KEY to the hosted space
api_name="/chat" # Ensure this matches the API endpoint exposed by the hosted Gradio app
)
print(f"Received raw result from hosted Space for webhook: {result}")
bot_response = result
if not isinstance(bot_response, str):
print(f"Warning: Hosted Space returned unexpected result type for webhook: {type(bot_response)}. Raw result: {result}")
bot_response = str(bot_response)
print(f"Formatted chatbot response for webhook: {bot_response}")
except Exception as e:
print(f"Error calling hosted Gradio Space from /webhook: {e}")
bot_response = f"An error occurred while processing your request: {e}" # Provide a user-friendly error message
# --- Update and Store History (In-Memory - NOT Persistent!) ---
# Append the current turn (user message + bot response)
chat_history.append([incoming_message, bot_response])
conversation_histories[sender_number] = chat_history
print(f"Updated in-memory history for {sender_number}: {conversation_histories[sender_number]}")
# --- Generate TwiML Response ---
# Twilio expects TwiML XML to know what to do with the message
# Use f-string with triple single quotes for multi-line string to avoid conflicts with HTML-like tags
twiml_response = f'''<Response><Message>{bot_response}</Message></Response>'''
print(f"Generated TwiML response: {twiml_response}")
# Return TwiML with the correct media type
return Response(content=twiml_response, media_type="application/xml")
if __name__ == "__main__":
# When running this app.py directly (e.g., with `uvicorn app:app --reload`),
# this block is executed. On Hugging Face Spaces, the environment typically
# runs the FastAPI application directly without executing this block.
# If you need specific initializations (like loading RAG data, initializing cache)
# when running on Spaces via FastAPI directly, you might need to move them
# outside this __main__ block or ensure they are called on app startup.
# Example (commented out, adjust based on your needs):
# from app_components import authenticate_google_sheets, load_business_info, initialize_cache
# authenticate_google_sheets()
# load_business_info()
# initialize_cache()
# cleanup_expired_cache_entries() # Optional
print("Starting FastAPI application with Uvicorn...")
uvicorn.run(app, host="0.0.0.0", port=7860) # HF default port
|