Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,98 +1,107 @@
|
|
| 1 |
import os
|
| 2 |
-
import
|
| 3 |
-
import logging
|
| 4 |
-
from fastapi import FastAPI, Request, HTTPException,
|
| 5 |
-
from fastapi.responses import JSONResponse
|
| 6 |
-
from fastapi.concurrency import run_in_threadpool
|
| 7 |
-
from gradio_client import Client
|
| 8 |
import uvicorn
|
| 9 |
-
from
|
| 10 |
-
|
| 11 |
-
# ---------------- CONFIG ----------------
|
| 12 |
-
VALID_API_KEY = os.getenv("APP_API_KEY") # Your FastAPI security key
|
| 13 |
-
SPACE_API_KEY = os.getenv("SPACE_API_KEY") # Hugging Face Space key
|
| 14 |
-
SPACE_NAME = os.getenv("SPACE_NAME", "Futuresony/Mr.Events") # default space
|
| 15 |
-
|
| 16 |
-
if not VALID_API_KEY or not SPACE_API_KEY:
|
| 17 |
-
raise RuntimeError("❌ APP_API_KEY and SPACE_API_KEY must be set as environment variables.")
|
| 18 |
|
| 19 |
-
#
|
| 20 |
-
|
| 21 |
-
level=logging.INFO,
|
| 22 |
-
format="%(asctime)s [%(levelname)s] %(message)s",
|
| 23 |
-
)
|
| 24 |
-
logger = logging.getLogger("prod-api")
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
# ---------------- FASTAPI APP ----------------
|
| 30 |
app = FastAPI(
|
| 31 |
-
title="
|
| 32 |
-
description="
|
| 33 |
-
version="1.0.0"
|
| 34 |
)
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
if client_ip not in client_requests:
|
| 46 |
-
client_requests[client_ip] = []
|
| 47 |
-
|
| 48 |
-
# remove old requests outside time window
|
| 49 |
-
client_requests[client_ip] = [
|
| 50 |
-
t for t in client_requests[client_ip] if now - t < WINDOW
|
| 51 |
-
]
|
| 52 |
-
|
| 53 |
-
if len(client_requests[client_ip]) >= RATE_LIMIT:
|
| 54 |
-
logger.warning(f"Rate limit exceeded for {client_ip}")
|
| 55 |
-
raise HTTPException(status_code=429, detail="Too many requests. Try again later.")
|
| 56 |
-
|
| 57 |
-
client_requests[client_ip].append(now)
|
| 58 |
-
|
| 59 |
-
# ---------------- ROUTES ----------------
|
| 60 |
-
@app.post("/chat")
|
| 61 |
-
async def chat(request: Request, _: None = Depends(rate_limiter)):
|
| 62 |
-
data = await request.json()
|
| 63 |
-
|
| 64 |
-
# API key check
|
| 65 |
-
api_key = request.headers.get("X-API-Key") or data.get("api_key")
|
| 66 |
-
if api_key != VALID_API_KEY:
|
| 67 |
-
logger.warning("Unauthorized access attempt.")
|
| 68 |
-
raise HTTPException(status_code=403, detail="Invalid API Key")
|
| 69 |
-
|
| 70 |
-
user_message = data.get("message")
|
| 71 |
-
if not user_message:
|
| 72 |
-
raise HTTPException(status_code=400, detail="Message is required")
|
| 73 |
-
|
| 74 |
try:
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
query=user_message,
|
| 81 |
-
|
|
|
|
| 82 |
api_name="/chat"
|
| 83 |
)
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
except Exception as e:
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
content={"error": "Internal Server Error", "details": str(e)},
|
| 93 |
-
)
|
| 94 |
|
| 95 |
-
# ---------------- ENTRY ----------------
|
| 96 |
if __name__ == "__main__":
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
import json
|
| 3 |
+
import traceback # Import traceback for detailed error logging
|
| 4 |
+
from fastapi import FastAPI, Request, HTTPException, status # Import status for clearer HTTP status codes
|
|
|
|
|
|
|
|
|
|
| 5 |
import uvicorn
|
| 6 |
+
from gradio_client import Client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
# Keys
|
| 9 |
+
VALID_API_KEY = os.getenv("APP_API_KEY") # for your FastAPI security
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
# Connect to hosted space
|
| 12 |
+
# Ensure this URL is correct and accessible
|
| 13 |
+
GRADIO_SPACE_URL = "Futuresony/Mr.Events"
|
| 14 |
+
try:
|
| 15 |
+
client = Client(GRADIO_SPACE_URL)
|
| 16 |
+
print(f"Successfully connected to Gradio Space: {GRADIO_SPACE_URL}")
|
| 17 |
+
except Exception as e:
|
| 18 |
+
print(f"Error connecting to Gradio Space at {GRADIO_SPACE_URL}: {e}")
|
| 19 |
+
print(traceback.format_exc())
|
| 20 |
+
client = None # Set client to None if connection fails
|
| 21 |
|
|
|
|
| 22 |
app = FastAPI(
|
| 23 |
+
title="LLM with Tools API",
|
| 24 |
+
description="API to interact with the LLM chatbot with tool capabilities.",
|
| 25 |
+
version="1.0.0",
|
| 26 |
)
|
| 27 |
|
| 28 |
+
@app.post("/chat", summary="Send a message to the chatbot")
|
| 29 |
+
async def chat(request: Request):
|
| 30 |
+
"""
|
| 31 |
+
Processes incoming chat messages, validates the API key,
|
| 32 |
+
and forwards the request to the Gradio Space for processing.
|
| 33 |
+
Returns the chatbot's response.
|
| 34 |
+
"""
|
| 35 |
+
print("\n--- Received new request to /chat ---")
|
| 36 |
+
api_key = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
try:
|
| 38 |
+
data = await request.json()
|
| 39 |
+
print(f"Request body: {data}")
|
| 40 |
+
|
| 41 |
+
# API key check from headers or JSON
|
| 42 |
+
api_key = request.headers.get("X-API-Key") or data.get("api_key")
|
| 43 |
+
print(f"API Key received: {'Yes' if api_key else 'No'}")
|
| 44 |
+
print(f"Validating against configured API Key: {'Yes' if VALID_API_KEY else 'No'}")
|
| 45 |
+
|
| 46 |
+
if not api_key or api_key != VALID_API_KEY:
|
| 47 |
+
print("API key validation failed.")
|
| 48 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or missing API Key")
|
| 49 |
+
|
| 50 |
+
user_message = data.get("message")
|
| 51 |
+
if not user_message:
|
| 52 |
+
print("Error: 'message' field is required.")
|
| 53 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="'message' field is required")
|
| 54 |
+
|
| 55 |
+
# Although the Gradio space loads history internally using the API key,
|
| 56 |
+
# you might consider if you need to pass any history from the FastAPI client.
|
| 57 |
+
# For this setup, we rely on the Space's internal history management per API key.
|
| 58 |
+
# Passing an empty list is acceptable if the Space ignores it and uses its internal state.
|
| 59 |
+
chat_history = [] # Assuming the Space manages history internally based on api_key
|
| 60 |
+
|
| 61 |
+
if client is None:
|
| 62 |
+
print("Error: Gradio client is not initialized.")
|
| 63 |
+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Chat service is unavailable. Gradio client failed to initialize.")
|
| 64 |
+
|
| 65 |
+
print(f"Calling Gradio Space API /chat with message='{user_message}' and api_key='{api_key}'...")
|
| 66 |
+
|
| 67 |
+
# Call your hosted Space
|
| 68 |
+
# Pass the api_key received in the FastAPI request to the Gradio chat function
|
| 69 |
+
# Ensure the parameter names match your Gradio function signature if using keywords
|
| 70 |
+
# The gradio_client's predict method maps arguments positionally unless keywords are used and matched by the target function
|
| 71 |
+
# Assuming the Gradio `chat` function signature is chat(query, chat_history, api_key)
|
| 72 |
+
# If your Gradio Space's chat function parameters are different, adjust the keywords here.
|
| 73 |
+
result = client.predict(
|
| 74 |
query=user_message,
|
| 75 |
+
chat_history=chat_history, # Pass the history (empty or from request)
|
| 76 |
+
api_key=api_key, # Pass the received api_key for history separation
|
| 77 |
api_name="/chat"
|
| 78 |
)
|
| 79 |
|
| 80 |
+
print(f"Received response from Gradio Space: {result}")
|
| 81 |
+
|
| 82 |
+
# Assuming the response from the Space is the final text response
|
| 83 |
+
# If the Space returns a more complex object, you might need to extract the text.
|
| 84 |
+
if isinstance(result, str):
|
| 85 |
+
return {"response": result}
|
| 86 |
+
else:
|
| 87 |
+
# Handle cases where the Gradio space might return something other than a string
|
| 88 |
+
print(f"Warning: Gradio Space returned non-string result: {result}")
|
| 89 |
+
return {"response": json.dumps(result)} # Return as JSON string if it's not a simple string
|
| 90 |
+
|
| 91 |
+
except HTTPException as http_exc:
|
| 92 |
+
print(f"HTTP Exception: {http_exc.detail}")
|
| 93 |
+
raise http_exc # Re-raise FastAPI HTTP exceptions
|
| 94 |
|
| 95 |
except Exception as e:
|
| 96 |
+
print(f"An unexpected error occurred: {e}")
|
| 97 |
+
print(traceback.format_exc()) # Log the full traceback
|
| 98 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"An internal server error occurred: {e}")
|
|
|
|
|
|
|
| 99 |
|
|
|
|
| 100 |
if __name__ == "__main__":
|
| 101 |
+
# Ensure this is run in a suitable environment for uvicorn (e.g., not directly in Colab)
|
| 102 |
+
# If running in a production environment like a Docker container or a proper server,
|
| 103 |
+
# you would typically use a command like 'uvicorn main:app --host 0.0.0.0 --port 80'
|
| 104 |
+
# This block is mainly for local testing.
|
| 105 |
+
print("Starting FastAPI server with uvicorn...")
|
| 106 |
+
#uvicorn.run(app, host="0.0.0.0", port=7860) # Commented out for Colab execution
|
| 107 |
+
print("Uvicorn run command is commented out. To run the FastAPI server, execute this script in a suitable environment.")
|