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