mfoud444 commited on
Commit
dab7e8e
·
verified ·
1 Parent(s): 87ddf71

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -149
app.py CHANGED
@@ -1,161 +1,76 @@
1
- from fastapi import FastAPI, Request
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from fastapi.responses import JSONResponse
4
- from contextlib import asynccontextmanager
5
- from agentpress.thread_manager import ThreadManager
6
- from services.supabase import DBConnection
7
- from datetime import datetime, timezone
8
- from dotenv import load_dotenv
9
- from utils.config import config, EnvMode
10
- import asyncio
11
- from utils.logger import logger
12
- import uuid
13
- import time
14
- from collections import OrderedDict
15
 
16
- # Import the agent API module
17
- from agent import api as agent_api
18
- from sandbox import api as sandbox_api
19
- from services import billing as billing_api
 
 
20
 
21
- # Load environment variables (these will be available through config)
22
- load_dotenv()
23
 
24
- # Initialize managers
25
- db = DBConnection()
26
- thread_manager = None
27
- instance_id = "single"
 
 
28
 
29
- # Rate limiter state
30
- ip_tracker = OrderedDict()
31
- MAX_CONCURRENT_IPS = 25
 
32
 
33
- @asynccontextmanager
34
- async def lifespan(app: FastAPI):
35
- # Startup
36
- global thread_manager
37
- logger.info(f"Starting up FastAPI application with instance ID: {instance_id} in {config.ENV_MODE.value} mode")
38
-
39
- try:
40
- # Initialize database
41
- await db.initialize()
42
- thread_manager = ThreadManager()
43
-
44
- # Initialize the agent API with shared resources
45
- agent_api.initialize(
46
- thread_manager,
47
- db,
48
- instance_id
49
- )
50
-
51
- # Initialize the sandbox API with shared resources
52
- sandbox_api.initialize(db)
53
-
54
- # Initialize Redis connection
55
- from services import redis
56
- try:
57
- await redis.initialize_async()
58
- logger.info("Redis connection initialized successfully")
59
- except Exception as e:
60
- logger.error(f"Failed to initialize Redis connection: {e}")
61
- # Continue without Redis - the application will handle Redis failures gracefully
62
-
63
- # Start background tasks
64
- asyncio.create_task(agent_api.restore_running_agent_runs())
65
-
66
- yield
67
-
68
- # Clean up agent resources
69
- logger.info("Cleaning up agent resources")
70
- await agent_api.cleanup()
71
-
72
- # Clean up Redis connection
73
- try:
74
- logger.info("Closing Redis connection")
75
- await redis.close()
76
- logger.info("Redis connection closed successfully")
77
- except Exception as e:
78
- logger.error(f"Error closing Redis connection: {e}")
79
-
80
- # Clean up database connection
81
- logger.info("Disconnecting from database")
82
- await db.disconnect()
83
- except Exception as e:
84
- logger.error(f"Error during application startup: {e}")
85
- raise
86
 
87
- app = FastAPI(lifespan=lifespan)
 
 
88
 
89
- @app.middleware("http")
90
- async def log_requests_middleware(request: Request, call_next):
91
- start_time = time.time()
92
- client_ip = request.client.host
93
- method = request.method
94
- url = str(request.url)
95
- path = request.url.path
96
- query_params = str(request.query_params)
97
-
98
- # Log the incoming request
99
- logger.info(f"Request started: {method} {path} from {client_ip} | Query: {query_params}")
100
-
101
- try:
102
- response = await call_next(request)
103
- process_time = time.time() - start_time
104
- logger.debug(f"Request completed: {method} {path} | Status: {response.status_code} | Time: {process_time:.2f}s")
105
- return response
106
- except Exception as e:
107
- process_time = time.time() - start_time
108
- logger.error(f"Request failed: {method} {path} | Error: {str(e)} | Time: {process_time:.2f}s")
109
- raise
110
 
111
- # Define allowed origins based on environment
112
- allowed_origins = ["https://www.suna.so", "https://suna.so", "https://staging.suna.so", "http://localhost:3000"]
113
 
114
- # Add staging-specific origins
115
- if config.ENV_MODE == EnvMode.STAGING:
116
- allowed_origins.append("http://localhost:3000")
117
-
118
- # Add local-specific origins
119
- if config.ENV_MODE == EnvMode.LOCAL:
120
- allowed_origins.append("http://localhost:3000")
121
 
122
- app.add_middleware(
123
- CORSMiddleware,
124
- allow_origins=allowed_origins,
125
- allow_credentials=True,
126
- allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
127
- allow_headers=["Content-Type", "Authorization"],
128
- )
129
 
130
- # Include the agent router with a prefix
131
- app.include_router(agent_api.router, prefix="/api")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- # Include the sandbox router with a prefix
134
- app.include_router(sandbox_api.router, prefix="/api")
135
-
136
- # Include the billing router with a prefix
137
- app.include_router(billing_api.router, prefix="/api")
138
-
139
- @app.get("/api/health")
140
- async def health_check():
141
- """Health check endpoint to verify API is working."""
142
- logger.info("Health check endpoint called")
143
- return {
144
- "status": "ok",
145
- "timestamp": datetime.now(timezone.utc).isoformat(),
146
- "instance_id": instance_id
147
- }
148
-
149
- if __name__ == "__main__":
150
- import uvicorn
151
-
152
- workers = 2
153
-
154
- logger.info(f"Starting server on 0.0.0.0:8000 with {workers} workers")
155
- uvicorn.run(
156
- "api:app",
157
- host="0.0.0.0",
158
- port=8000,
159
- workers=workers,
160
- reload=True
161
- )
 
1
+ FROM python:3.11-slim
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ # Set environment variables
4
+ ENV PYTHONUNBUFFERED=1 \
5
+ PYTHONDONTWRITEBYTECODE=1 \
6
+ ENV_MODE="production" \
7
+ PYTHONPATH=/app \
8
+ REDIS_URL="redis://localhost:6379/0"
9
 
10
+ WORKDIR /app
 
11
 
12
+ # Install system dependencies including Redis
13
+ RUN apt-get update && apt-get install -y --no-install-recommends \
14
+ build-essential \
15
+ curl \
16
+ redis-server \
17
+ && rm -rf /var/lib/apt/lists/*
18
 
19
+ # Configure Redis
20
+ RUN sed -i 's/bind 127.0.0.1 ::1/bind 0.0.0.0/' /etc/redis/redis.conf && \
21
+ sed -i 's/protected-mode yes/protected-mode no/' /etc/redis/redis.conf && \
22
+ sed -i 's/daemonize yes/daemonize no/' /etc/redis/redis.conf
23
 
24
+ # Create non-root user and set up directories
25
+ RUN useradd -m -u 1000 appuser && \
26
+ mkdir -p /app/logs && \
27
+ chown -R appuser:appuser /app && \
28
+ chown -R appuser:appuser /var/lib/redis && \
29
+ chown -R appuser:appuser /var/log/redis && \
30
+ chown -R appuser:appuser /etc/redis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ # Install Python dependencies
33
+ COPY --chown=appuser:appuser requirements.txt .
34
+ RUN pip install --no-cache-dir -r requirements.txt gunicorn
35
 
36
+ # Switch to non-root user
37
+ USER appuser
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ # Copy application code
40
+ COPY --chown=appuser:appuser . .
41
 
42
+ # Expose the port the app runs on
43
+ EXPOSE 7860 6379
 
 
 
 
 
44
 
45
+ # Calculate optimal worker count based on 16 vCPUs
46
+ # Using (2*CPU)+1 formula for CPU-bound applications
47
+ ENV WORKERS=33
48
+ ENV THREADS=2
49
+ ENV WORKER_CONNECTIONS=2000
 
 
50
 
51
+ # Create a startup script to run both Redis and Gunicorn
52
+ RUN echo '#!/bin/sh\n\
53
+ redis-server /etc/redis/redis.conf &\n\
54
+ gunicorn api:app \
55
+ --workers $WORKERS \
56
+ --worker-class uvicorn.workers.UvicornWorker \
57
+ --bind 0.0.0.0:7860 \
58
+ --timeout 600 \
59
+ --graceful-timeout 300 \
60
+ --keep-alive 250 \
61
+ --max-requests 2000 \
62
+ --max-requests-jitter 400 \
63
+ --forwarded-allow-ips '*' \
64
+ --worker-connections $WORKER_CONNECTIONS \
65
+ --worker-tmp-dir /dev/shm \
66
+ --preload \
67
+ --log-level info \
68
+ --access-logfile - \
69
+ --error-logfile - \
70
+ --capture-output \
71
+ --enable-stdio-inheritance \
72
+ --threads $THREADS\n\
73
+ wait' > /app/start.sh && chmod +x /app/start.sh
74
 
75
+ # Run the startup script
76
+ CMD ["/app/start.sh"]