Bot_test / app.py
Futuresony's picture
Rename app (16).py to app.py
dd8a5e8 verified
raw
history blame
9.52 kB
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