GitHub Actions commited on
Commit
276a5e7
Β·
1 Parent(s): 0e27589

Deploy backend from GitHub Actions

Browse files

πŸš€ Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

Files changed (6) hide show
  1. .env.hf +122 -0
  2. Dockerfile +16 -7
  3. Dockerfile.hf +58 -0
  4. rag/chat.py +5 -5
  5. start_server.py +169 -27
  6. start_server_hf.py +188 -0
.env.hf ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =============================================================================
2
+ # HuggingFace Spaces Environment Variables
3
+ # =============================================================================
4
+ # Configure these in your HuggingFace Space settings (Repository > Settings > Variables)
5
+
6
+ # =============================================================================
7
+ # Required API Keys (Configure in HF Space Settings)
8
+ # =============================================================================
9
+ # OpenAI API Key - Required for translation service
10
+ # Add as secret: OPENAI_API_KEY
11
+ OPENAI_API_KEY=
12
+
13
+ # OpenRouter API Key - Required for AI chat service
14
+ # Add as secret: OPENROUTER_API_KEY
15
+ OPENROUTER_API_KEY=
16
+
17
+ # Google Gemini API Key - Alternative AI service
18
+ # Add as secret: GEMINI_API_KEY
19
+ GEMINI_API_KEY=
20
+
21
+ # =============================================================================
22
+ # Database Configuration
23
+ # =============================================================================
24
+ # SQLite Database URL (HF Spaces default)
25
+ DATABASE_URL=sqlite:///./database/auth.db
26
+
27
+ # =============================================================================
28
+ # Vector Database (Qdrant) Configuration
29
+ # =============================================================================
30
+ # Qdrant URL for vector storage (optional - defaults to local)
31
+ QDRANT_URL=http://localhost:6333
32
+
33
+ # =============================================================================
34
+ # Model Configuration
35
+ # =============================================================================
36
+ # OpenAI Model for translation (use stable model)
37
+ OPENAI_MODEL=gpt-4o-mini
38
+
39
+ # OpenRouter Model for chat (use free tier model for reliability)
40
+ OPENROUTER_MODEL=meta-llama/llama-3.2-3b-instruct:free
41
+
42
+ # Gemini Model configuration
43
+ GEMINI_MODEL=gemini-2.0-flash-lite
44
+
45
+ # =============================================================================
46
+ # HuggingFace Spaces Auto-Configuration
47
+ # =============================================================================
48
+ # These are automatically set by HF Spaces
49
+ SPACE_ID=
50
+ HF_HOME=/app/.cache/huggingface
51
+ TRANSFORMERS_CACHE=/app/.cache/transformers
52
+
53
+ # =============================================================================
54
+ # Server Configuration
55
+ # =============================================================================
56
+ # Server port (HF Spaces default - must be 7860)
57
+ PORT=7860
58
+
59
+ # Server host (required for containerized deployments)
60
+ HOST=0.0.0.0
61
+
62
+ # Number of worker processes (keep at 1 for HF Spaces free tier)
63
+ WORKERS=1
64
+
65
+ # Log level (INFO for production, DEBUG for troubleshooting)
66
+ LOG_LEVEL=INFO
67
+
68
+ # =============================================================================
69
+ # Environment Detection
70
+ # =============================================================================
71
+ NODE_ENV=production
72
+ ENVIRONMENT=production
73
+
74
+ # =============================================================================
75
+ # Security Configuration
76
+ # =============================================================================
77
+ # JWT Secret Key (generate a strong random string)
78
+ # Add as secret: JWT_SECRET_KEY
79
+ JWT_SECRET_KEY=
80
+
81
+ # JWT Algorithm
82
+ JWT_ALGORITHM=HS256
83
+
84
+ # JWT Expiration Time (7 days)
85
+ JWT_EXPIRE_MINUTES=10080
86
+
87
+ # =============================================================================
88
+ # CORS Configuration
89
+ # =============================================================================
90
+ # Allowed origins for HF Spaces
91
+ CORS_ORIGINS=https://huggingface.co,https://*.hf.space,https://mrowaisabdullah.github.io
92
+
93
+ # =============================================================================
94
+ # Rate Limiting (Conservative for HF Spaces)
95
+ # =============================================================================
96
+ RATE_LIMIT_ENABLED=true
97
+ RATE_LIMIT_RPM=30
98
+ RATE_LIMIT_RPH=500
99
+ TRANSLATION_RPM=5
100
+
101
+ # =============================================================================
102
+ # Cache Configuration (Memory-based for HF Spaces)
103
+ # =============================================================================
104
+ CACHE_BACKEND=memory
105
+ CACHE_DEFAULT_TTL=24
106
+ REDIS_URL=
107
+
108
+ # =============================================================================
109
+ # Feature Flags (Optimized for HF Spaces)
110
+ # =============================================================================
111
+ FEATURE_STREAMING=true
112
+ FEATURE_QUALITY_CHECK=false
113
+ FEATURE_BATCH_TRANSLATION=false
114
+ OPENAI_AGENTS_ENABLED=false
115
+
116
+ # =============================================================================
117
+ # Monitoring (Basic for HF Spaces)
118
+ # =============================================================================
119
+ MONITORING_ENABLED=true
120
+ HEALTH_ENDPOINT=/health
121
+ LOG_FILE_ENABLED=true
122
+ LOG_FILE_PATH=logs/app.log
Dockerfile CHANGED
@@ -1,11 +1,14 @@
1
- # Use Python 3.11 slim image
2
  FROM python:3.11-slim
3
 
4
- # Set environment variables
5
  ENV PYTHONUNBUFFERED=1 \
6
  PYTHONDONTWRITEBYTECODE=1 \
7
  PIP_NO_CACHE_DIR=1 \
8
- PIP_DISABLE_PIP_VERSION_CHECK=1
 
 
 
9
 
10
  # Set the working directory
11
  WORKDIR /app
@@ -16,7 +19,12 @@ RUN apt-get update && apt-get install -y \
16
  g++ \
17
  curl \
18
  build-essential \
19
- && rm -rf /var/lib/apt/lists/*
 
 
 
 
 
20
 
21
  # Copy requirements first for better caching
22
  COPY pyproject.toml ./
@@ -31,9 +39,10 @@ COPY . .
31
  # Create necessary directories and set permissions
32
  RUN mkdir -p database logs && \
33
  touch database/auth.db && \
34
- chmod +x start_server.py
 
35
 
36
- # For HF Spaces, we run as root to avoid permission issues
37
  # USER user
38
 
39
  # Expose the port (HF Spaces default)
@@ -43,5 +52,5 @@ EXPOSE 7860
43
  HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
44
  CMD curl -f http://localhost:7860/health || exit 1
45
 
46
- # Run the application
47
  CMD ["python", "start_server.py"]
 
1
+ # Use Python 3.11 slim image for Hugging Face Spaces
2
  FROM python:3.11-slim
3
 
4
+ # Set environment variables for HF Spaces
5
  ENV PYTHONUNBUFFERED=1 \
6
  PYTHONDONTWRITEBYTECODE=1 \
7
  PIP_NO_CACHE_DIR=1 \
8
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
9
+ HF_HOME=/app/.cache/huggingface \
10
+ TRANSFORMERS_CACHE=/app/.cache/transformers \
11
+ PORT=7860
12
 
13
  # Set the working directory
14
  WORKDIR /app
 
19
  g++ \
20
  curl \
21
  build-essential \
22
+ git \
23
+ && rm -rf /var/lib/apt/lists/* \
24
+ && apt-get clean
25
+
26
+ # Create cache directories
27
+ RUN mkdir -p /app/.cache/huggingface /app/.cache/transformers /app/logs
28
 
29
  # Copy requirements first for better caching
30
  COPY pyproject.toml ./
 
39
  # Create necessary directories and set permissions
40
  RUN mkdir -p database logs && \
41
  touch database/auth.db && \
42
+ chmod +x start_server.py && \
43
+ chmod -R 755 /app
44
 
45
+ # For HF Spaces compatibility, run as root (avoid permission issues)
46
  # USER user
47
 
48
  # Expose the port (HF Spaces default)
 
52
  HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
53
  CMD curl -f http://localhost:7860/health || exit 1
54
 
55
+ # Run the application with proper error handling
56
  CMD ["python", "start_server.py"]
Dockerfile.hf ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.11 slim image for Hugging Face Spaces
2
+ FROM python:3.11-slim
3
+
4
+ # Set environment variables for HF Spaces
5
+ ENV PYTHONUNBUFFERED=1 \
6
+ PYTHONDONTWRITEBYTECODE=1 \
7
+ PIP_NO_CACHE_DIR=1 \
8
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
9
+ HF_HOME=/app/.cache/huggingface \
10
+ TRANSFORMERS_CACHE=/app/.cache/transformers \
11
+ PORT=7860
12
+
13
+ # Set the working directory
14
+ WORKDIR /app
15
+
16
+ # Install system dependencies
17
+ RUN apt-get update && apt-get install -y \
18
+ gcc \
19
+ g++ \
20
+ curl \
21
+ build-essential \
22
+ git \
23
+ && rm -rf /var/lib/apt/lists/* \
24
+ && apt-get clean
25
+
26
+ # Create cache directories
27
+ RUN mkdir -p /app/.cache/huggingface /app/.cache/transformers /app/logs
28
+
29
+ # Copy requirements first for better caching
30
+ COPY pyproject.toml ./
31
+
32
+ # Install Python dependencies
33
+ RUN pip install --upgrade pip setuptools wheel
34
+ RUN pip install --no-cache-dir -e .
35
+
36
+ # Copy the rest of the application code
37
+ COPY . .
38
+
39
+ # Create necessary directories and set permissions
40
+ RUN mkdir -p database logs && \
41
+ touch database/auth.db && \
42
+ chmod +x start_server.py && \
43
+ chmod -R 755 /app
44
+
45
+ # Create non-root user for security (commented out for HF Spaces compatibility)
46
+ # RUN adduser --disabled-password --gecos '' appuser && \
47
+ # chown -R appuser:appuser /app
48
+ # USER appuser
49
+
50
+ # Expose the port (HF Spaces default)
51
+ EXPOSE 7860
52
+
53
+ # Health check for HF Spaces monitoring
54
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
55
+ CMD curl -f http://localhost:7860/health || exit 1
56
+
57
+ # Run the application with proper error handling
58
+ CMD ["python", "start_server.py"]
rag/chat.py CHANGED
@@ -58,7 +58,7 @@ class ChatHandler:
58
  self.retrieval_engine = RetrievalEngine(
59
  qdrant_manager=qdrant_manager,
60
  embedder=self.embedder,
61
- score_threshold=0.5, # Lowered to 0.5 to better match document scores
62
  enable_mmr=True,
63
  mmr_lambda=0.5
64
  )
@@ -77,19 +77,19 @@ class ChatHandler:
77
  Returns:
78
  Adaptive threshold value
79
  """
80
- base_threshold = 0.5
81
 
82
  # Lower threshold for very specific queries (longer)
83
  if query_length > 100:
84
- return max(0.5, base_threshold - 0.2)
85
 
86
  # Raise threshold if too many results found
87
  if result_count > 20:
88
- return min(0.9, base_threshold + 0.2)
89
 
90
  # Lower threshold if very few results found
91
  if result_count < 3:
92
- return max(0.5, base_threshold - 0.1)
93
 
94
  return base_threshold
95
 
 
58
  self.retrieval_engine = RetrievalEngine(
59
  qdrant_manager=qdrant_manager,
60
  embedder=self.embedder,
61
+ score_threshold=0.2, # Lowered to 0.2 to match document scores (0.38+)
62
  enable_mmr=True,
63
  mmr_lambda=0.5
64
  )
 
77
  Returns:
78
  Adaptive threshold value
79
  """
80
+ base_threshold = 0.2
81
 
82
  # Lower threshold for very specific queries (longer)
83
  if query_length > 100:
84
+ return max(0.1, base_threshold - 0.1)
85
 
86
  # Raise threshold if too many results found
87
  if result_count > 20:
88
+ return min(0.5, base_threshold + 0.3)
89
 
90
  # Lower threshold if very few results found
91
  if result_count < 3:
92
+ return max(0.1, base_threshold - 0.1)
93
 
94
  return base_threshold
95
 
start_server.py CHANGED
@@ -1,46 +1,188 @@
1
  """
2
- Startup script for Hugging Face Spaces deployment.
3
- Initializes the database and starts the FastAPI server.
4
  """
5
  import os
6
  import sys
 
 
 
7
  from pathlib import Path
8
  import subprocess
 
9
 
10
- def main():
11
- """Initialize database and start the server."""
12
- print("πŸš€ Starting server initialization...")
 
 
 
 
 
 
 
13
 
14
- # Change to backend directory if needed
15
- if os.path.exists("backend"):
16
- os.chdir("backend")
17
- print("Changed to backend directory")
18
 
19
- # Initialize database first
20
- print("πŸ“¦ Initializing database...")
21
- result = subprocess.run([sys.executable, "init_database.py"], capture_output=True, text=True)
22
- if result.returncode == 0:
23
- print("βœ… Database initialization completed successfully!")
24
- else:
25
- print(f"⚠️ Database initialization failed: {result.stderr}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # Check if database file exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  db_path = Path("database/auth.db")
29
  if db_path.exists():
30
- print("βœ… Database file found!")
 
 
 
 
 
 
31
  else:
32
- print("⚠️ Database file not found. The server will create it on startup.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- # Print environment variables for debugging
35
- print("πŸ” Environment check:")
36
- print(f" - OPENAI_API_KEY: {'βœ…' if os.getenv('OPENAI_API_KEY') else '❌'}")
37
- print(f" - OPENROUTER_API_KEY: {'βœ…' if os.getenv('OPENROUTER_API_KEY') else '❌'}")
38
- print(f" - DATABASE_URL: {'βœ…' if os.getenv('DATABASE_URL') else '❌'}")
39
 
40
- # Start the FastAPI server
41
- print("🌟 Starting FastAPI server...")
42
- os.execvp(sys.executable, [sys.executable, "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"])
43
 
 
 
 
 
 
 
44
 
45
  if __name__ == "__main__":
46
  main()
 
1
  """
2
+ Robust startup script for Hugging Face Spaces deployment.
3
+ Initializes the database and starts the FastAPI server with comprehensive error handling.
4
  """
5
  import os
6
  import sys
7
+ import time
8
+ import logging
9
+ import asyncio
10
  from pathlib import Path
11
  import subprocess
12
+ from datetime import datetime
13
 
14
+ # Configure logging
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
18
+ handlers=[
19
+ logging.FileHandler('logs/startup.log'),
20
+ logging.StreamHandler(sys.stdout)
21
+ ]
22
+ )
23
+ logger = logging.getLogger(__name__)
24
 
25
+ def check_environment():
26
+ """Check if required environment variables are set."""
27
+ logger.info("πŸ” Checking environment variables...")
 
28
 
29
+ required_vars = ['OPENAI_API_KEY', 'OPENROUTER_API_KEY']
30
+ missing_vars = []
31
+
32
+ for var in required_vars:
33
+ if not os.getenv(var):
34
+ missing_vars.append(var)
35
+ logger.error(f"❌ {var} is not set")
36
+ else:
37
+ logger.info(f"βœ… {var} is set")
38
+
39
+ # Optional variables
40
+ optional_vars = ['DATABASE_URL', 'QDRANT_URL', 'HF_SPACE_ID']
41
+ for var in optional_vars:
42
+ if os.getenv(var):
43
+ logger.info(f"βœ… {var} is set")
44
+ else:
45
+ logger.info(f"⚠️ {var} is not set (optional)")
46
+
47
+ if missing_vars:
48
+ logger.error(f"❌ Missing required environment variables: {missing_vars}")
49
+ return False
50
+
51
+ return True
52
 
53
+ def initialize_database():
54
+ """Initialize the database with retries."""
55
+ logger.info("πŸ“¦ Initializing database...")
56
+ max_retries = 3
57
+
58
+ for attempt in range(max_retries):
59
+ try:
60
+ # Check if init_database.py exists
61
+ if not Path("init_database.py").exists():
62
+ logger.warning("⚠️ init_database.py not found, skipping database initialization")
63
+ return True
64
+
65
+ result = subprocess.run(
66
+ [sys.executable, "init_database.py"],
67
+ capture_output=True,
68
+ text=True,
69
+ timeout=30
70
+ )
71
+
72
+ if result.returncode == 0:
73
+ logger.info("βœ… Database initialization completed successfully!")
74
+ if result.stdout:
75
+ logger.info(f"Database init output: {result.stdout}")
76
+ return True
77
+ else:
78
+ logger.error(f"⚠️ Database initialization attempt {attempt + 1} failed: {result.stderr}")
79
+ if attempt < max_retries - 1:
80
+ time.sleep(2)
81
+
82
+ except subprocess.TimeoutExpired:
83
+ logger.error(f"⚠️ Database initialization timeout on attempt {attempt + 1}")
84
+ if attempt < max_retries - 1:
85
+ time.sleep(2)
86
+ except Exception as e:
87
+ logger.error(f"⚠️ Database initialization error on attempt {attempt + 1}: {str(e)}")
88
+ if attempt < max_retries - 1:
89
+ time.sleep(2)
90
+
91
+ logger.error("❌ Database initialization failed after all attempts")
92
+ return False
93
+
94
+ def verify_database_file():
95
+ """Check if database file exists and is accessible."""
96
  db_path = Path("database/auth.db")
97
  if db_path.exists():
98
+ try:
99
+ size = db_path.stat().st_size
100
+ logger.info(f"βœ… Database file found! Size: {size} bytes")
101
+ return True
102
+ except Exception as e:
103
+ logger.error(f"❌ Error accessing database file: {str(e)}")
104
+ return False
105
  else:
106
+ logger.info("⚠️ Database file not found. The server will create it on startup.")
107
+ return True
108
+
109
+ def create_directories():
110
+ """Create necessary directories."""
111
+ directories = ['database', 'logs', '.cache/huggingface', '.cache/transformers']
112
+ for directory in directories:
113
+ Path(directory).mkdir(parents=True, exist_ok=True)
114
+ logger.info(f"βœ… Directory {directory} ready")
115
+
116
+ def start_server():
117
+ """Start the FastAPI server with error handling."""
118
+ logger.info("🌟 Starting FastAPI server...")
119
+
120
+ # Get port from environment or use default
121
+ port = int(os.getenv('PORT', 7860))
122
+ host = os.getenv('HOST', '0.0.0.0')
123
+ workers = int(os.getenv('WORKERS', 1))
124
+
125
+ cmd = [
126
+ sys.executable, "-m", "uvicorn",
127
+ "main:app",
128
+ "--host", host,
129
+ "--port", str(port),
130
+ "--workers", str(workers),
131
+ "--log-level", "info",
132
+ "--access-log"
133
+ ]
134
+
135
+ logger.info(f"Command: {' '.join(cmd)}")
136
+
137
+ try:
138
+ # Replace current process with uvicorn
139
+ os.execvp(sys.executable, cmd)
140
+ except Exception as e:
141
+ logger.error(f"❌ Failed to start server: {str(e)}")
142
+ sys.exit(1)
143
+
144
+ def main():
145
+ """Main startup function."""
146
+ logger.info("πŸš€ Starting server initialization for Hugging Face Spaces...")
147
+ logger.info(f"πŸ“… Timestamp: {datetime.utcnow().isoformat()}")
148
+ logger.info(f"🐍 Python version: {sys.version}")
149
+ logger.info(f"πŸ“ Working directory: {os.getcwd()}")
150
+
151
+ # Change to backend directory if needed
152
+ if os.path.exists("backend") and not os.getcwd().endswith("backend"):
153
+ os.chdir("backend")
154
+ logger.info(f"Changed to backend directory: {os.getcwd()}")
155
+
156
+ try:
157
+ # Create necessary directories
158
+ create_directories()
159
+
160
+ # Check environment
161
+ if not check_environment():
162
+ logger.error("❌ Environment check failed. Exiting...")
163
+ sys.exit(1)
164
+
165
+ # Initialize database
166
+ if not initialize_database():
167
+ logger.error("❌ Database initialization failed. Continuing anyway...")
168
+
169
+ # Verify database file
170
+ verify_database_file()
171
 
172
+ # Print final status
173
+ logger.info("πŸŽ‰ Initialization completed successfully!")
174
+ logger.info(f"🌍 Environment: {'HF Spaces' if os.getenv('SPACE_ID') else 'Local'}")
175
+ logger.info(f"πŸ”§ Port: {os.getenv('PORT', 7860)}")
 
176
 
177
+ # Start server
178
+ start_server()
 
179
 
180
+ except KeyboardInterrupt:
181
+ logger.info("⏹️ Startup interrupted by user")
182
+ sys.exit(0)
183
+ except Exception as e:
184
+ logger.error(f"πŸ’₯ Fatal error during startup: {str(e)}", exc_info=True)
185
+ sys.exit(1)
186
 
187
  if __name__ == "__main__":
188
  main()
start_server_hf.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Robust startup script for Hugging Face Spaces deployment.
3
+ Initializes the database and starts the FastAPI server with comprehensive error handling.
4
+ """
5
+ import os
6
+ import sys
7
+ import time
8
+ import logging
9
+ import asyncio
10
+ from pathlib import Path
11
+ import subprocess
12
+ from datetime import datetime
13
+
14
+ # Configure logging
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
18
+ handlers=[
19
+ logging.FileHandler('/app/logs/startup.log'),
20
+ logging.StreamHandler(sys.stdout)
21
+ ]
22
+ )
23
+ logger = logging.getLogger(__name__)
24
+
25
+ def check_environment():
26
+ """Check if required environment variables are set."""
27
+ logger.info("πŸ” Checking environment variables...")
28
+
29
+ required_vars = ['OPENAI_API_KEY', 'OPENROUTER_API_KEY']
30
+ missing_vars = []
31
+
32
+ for var in required_vars:
33
+ if not os.getenv(var):
34
+ missing_vars.append(var)
35
+ logger.error(f"❌ {var} is not set")
36
+ else:
37
+ logger.info(f"βœ… {var} is set")
38
+
39
+ # Optional variables
40
+ optional_vars = ['DATABASE_URL', 'QDRANT_URL', 'HF_SPACE_ID']
41
+ for var in optional_vars:
42
+ if os.getenv(var):
43
+ logger.info(f"βœ… {var} is set")
44
+ else:
45
+ logger.info(f"⚠️ {var} is not set (optional)")
46
+
47
+ if missing_vars:
48
+ logger.error(f"❌ Missing required environment variables: {missing_vars}")
49
+ return False
50
+
51
+ return True
52
+
53
+ def initialize_database():
54
+ """Initialize the database with retries."""
55
+ logger.info("πŸ“¦ Initializing database...")
56
+ max_retries = 3
57
+
58
+ for attempt in range(max_retries):
59
+ try:
60
+ # Check if init_database.py exists
61
+ if not Path("init_database.py").exists():
62
+ logger.warning("⚠️ init_database.py not found, skipping database initialization")
63
+ return True
64
+
65
+ result = subprocess.run(
66
+ [sys.executable, "init_database.py"],
67
+ capture_output=True,
68
+ text=True,
69
+ timeout=30
70
+ )
71
+
72
+ if result.returncode == 0:
73
+ logger.info("βœ… Database initialization completed successfully!")
74
+ if result.stdout:
75
+ logger.info(f"Database init output: {result.stdout}")
76
+ return True
77
+ else:
78
+ logger.error(f"⚠️ Database initialization attempt {attempt + 1} failed: {result.stderr}")
79
+ if attempt < max_retries - 1:
80
+ time.sleep(2)
81
+
82
+ except subprocess.TimeoutExpired:
83
+ logger.error(f"⚠️ Database initialization timeout on attempt {attempt + 1}")
84
+ if attempt < max_retries - 1:
85
+ time.sleep(2)
86
+ except Exception as e:
87
+ logger.error(f"⚠️ Database initialization error on attempt {attempt + 1}: {str(e)}")
88
+ if attempt < max_retries - 1:
89
+ time.sleep(2)
90
+
91
+ logger.error("❌ Database initialization failed after all attempts")
92
+ return False
93
+
94
+ def verify_database_file():
95
+ """Check if database file exists and is accessible."""
96
+ db_path = Path("database/auth.db")
97
+ if db_path.exists():
98
+ try:
99
+ size = db_path.stat().st_size
100
+ logger.info(f"βœ… Database file found! Size: {size} bytes")
101
+ return True
102
+ except Exception as e:
103
+ logger.error(f"❌ Error accessing database file: {str(e)}")
104
+ return False
105
+ else:
106
+ logger.info("⚠️ Database file not found. The server will create it on startup.")
107
+ return True
108
+
109
+ def create_directories():
110
+ """Create necessary directories."""
111
+ directories = ['database', 'logs', '.cache/huggingface', '.cache/transformers']
112
+ for directory in directories:
113
+ Path(directory).mkdir(parents=True, exist_ok=True)
114
+ logger.info(f"βœ… Directory {directory} ready")
115
+
116
+ def start_server():
117
+ """Start the FastAPI server with error handling."""
118
+ logger.info("🌟 Starting FastAPI server...")
119
+
120
+ # Get port from environment or use default
121
+ port = int(os.getenv('PORT', 7860))
122
+ host = os.getenv('HOST', '0.0.0.0')
123
+ workers = int(os.getenv('WORKERS', 1))
124
+
125
+ cmd = [
126
+ sys.executable, "-m", "uvicorn",
127
+ "main:app",
128
+ "--host", host,
129
+ "--port", str(port),
130
+ "--workers", str(workers),
131
+ "--log-level", "info",
132
+ "--access-log"
133
+ ]
134
+
135
+ logger.info(f"Command: {' '.join(cmd)}")
136
+
137
+ try:
138
+ # Replace current process with uvicorn
139
+ os.execvp(sys.executable, cmd)
140
+ except Exception as e:
141
+ logger.error(f"❌ Failed to start server: {str(e)}")
142
+ sys.exit(1)
143
+
144
+ def main():
145
+ """Main startup function."""
146
+ logger.info("πŸš€ Starting server initialization for Hugging Face Spaces...")
147
+ logger.info(f"πŸ“… Timestamp: {datetime.utcnow().isoformat()}")
148
+ logger.info(f"🐍 Python version: {sys.version}")
149
+ logger.info(f"πŸ“ Working directory: {os.getcwd()}")
150
+
151
+ # Change to backend directory if needed
152
+ if os.path.exists("backend") and not os.getcwd().endswith("backend"):
153
+ os.chdir("backend")
154
+ logger.info(f"Changed to backend directory: {os.getcwd()}")
155
+
156
+ try:
157
+ # Create necessary directories
158
+ create_directories()
159
+
160
+ # Check environment
161
+ if not check_environment():
162
+ logger.error("❌ Environment check failed. Exiting...")
163
+ sys.exit(1)
164
+
165
+ # Initialize database
166
+ if not initialize_database():
167
+ logger.error("❌ Database initialization failed. Continuing anyway...")
168
+
169
+ # Verify database file
170
+ verify_database_file()
171
+
172
+ # Print final status
173
+ logger.info("πŸŽ‰ Initialization completed successfully!")
174
+ logger.info(f"🌍 Environment: {'HF Spaces' if os.getenv('SPACE_ID') else 'Local'}")
175
+ logger.info(f"πŸ”§ Port: {os.getenv('PORT', 7860)}")
176
+
177
+ # Start server
178
+ start_server()
179
+
180
+ except KeyboardInterrupt:
181
+ logger.info("⏹️ Startup interrupted by user")
182
+ sys.exit(0)
183
+ except Exception as e:
184
+ logger.error(f"πŸ’₯ Fatal error during startup: {str(e)}", exc_info=True)
185
+ sys.exit(1)
186
+
187
+ if __name__ == "__main__":
188
+ main()