24f3001764/llm_code_deployment
0
1import asyncio2import logging3import time4from datetime import datetime5from fastapi import FastAPI, HTTPException, BackgroundTasks6from fastapi.responses import JSONResponse7from src.models import TaskRequest, APIResponse, EvaluationPayload8from src.config import config9from src.utils import decode_and_save_attachments, sanitize_repo_name10from src.llm_generator import LLMAppGenerator11from src.github_manager import GitHubManager12from src.evaluator import EvaluationNotifier13 14# Configure logging15logging.basicConfig(16 level=logging.INFO,17 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'18)19logger = logging.getLogger(__name__)20 21# Initialize FastAPI22app = FastAPI(23 title="LLM Code Deployment API",24 description="Automated app generation and deployment system",25 version="1.0.0"26)27 28# Store task state (in production, use a database)29task_state = {}30 31 32@app.on_event("startup")33async def startup_event():34 """Validate configuration on startup"""35 try:36 config.validate()37 logger.info("Configuration validated successfully")38 except ValueError as e:39 logger.error(f"Configuration error: {e}")40 logger.error("Please set up your .env file based on .env.example")41 42 43@app.get("/")44async def root():45 """Health check endpoint"""46 return {47 "status": "running",48 "service": "LLM Code Deployment API",49 "version": "1.0.0"50 }51 52 53@app.post("/request", response_model=APIResponse)54async def handle_request(55 request: TaskRequest,56 background_tasks: BackgroundTasks57):58 """59 Main endpoint to receive task requests60 Validates secret, returns immediate 200, then processes in background61 """62 logger.info(f"Received request for task: {request.task}, round: {request.round}")63 64 # Verify secret65 if request.secret != config.STUDENT_SECRET:66 logger.warning(f"Invalid secret for task {request.task}")67 raise HTTPException(status_code=401, detail="Invalid secret")68 69 # Check if task is already being processed70 task_key = f"{request.task}-{request.round}"71 if task_key in task_state and task_state[task_key].get("status") == "processing":72 logger.warning(f"Task {task_key} is already being processed")73 return APIResponse(74 status="accepted",75 message="Task is already being processed",76 task=request.task,77 round=request.round78 )79 80 # Mark as processing81 task_state[task_key] = {82 "status": "processing",83 "started_at": datetime.now().isoformat()84 }85 86 # Schedule background processing87 if request.round == 1:88 background_tasks.add_task(process_build_task, request)89 else:90 background_tasks.add_task(process_revision_task, request)91 92 # Return immediate 200 response93 return APIResponse(94 status="accepted",95 message=f"Task {request.task} round {request.round} accepted for processing",96 task=request.task,97 round=request.round98 )99 100 101async def process_build_task(request: TaskRequest):102 """Process round 1: Build and deploy new app"""103 task_key = f"{request.task}-{request.round}"104 start_time = time.time()105 106 try:107 logger.info(f"Starting build task: {request.task}")108 109 # 1. Decode and save attachments110 attachments = await decode_and_save_attachments(request.attachments, request.task)111 logger.info(f"Saved {len(attachments)} attachments")112 113 # 2. Generate app using LLM114 generator = LLMAppGenerator()115 app_dir = await generator.generate_app(116 brief=request.brief,117 checks=request.checks,118 attachments=attachments,119 task_id=request.task120 )121 logger.info(f"Generated app at: {app_dir}")122 123 # 3. Create GitHub repo and deploy to Pages124 elapsed = time.time() - start_time125 if elapsed > config.EVALUATION_TIMEOUT - 60: # Leave 1 min buffer126 logger.warning(f"Approaching timeout ({elapsed:.1f}s elapsed)")127 128 github_mgr = GitHubManager()129 repo_url, commit_sha, pages_url = await github_mgr.create_and_deploy(130 app_dir=app_dir,131 task_id=request.task132 )133 logger.info(f"Deployed to GitHub Pages: {pages_url}")134 135 # 4. Notify evaluation URL136 elapsed = time.time() - start_time137 logger.info(f"Task processing took {elapsed:.1f}s")138 139 if elapsed > config.EVALUATION_TIMEOUT:140 logger.error(f"Task exceeded timeout ({elapsed:.1f}s > {config.EVALUATION_TIMEOUT}s)")141 raise TimeoutError(f"Task processing exceeded {config.EVALUATION_TIMEOUT}s")142 143 notifier = EvaluationNotifier()144 payload = EvaluationPayload(145 email=request.email,146 task=request.task,147 round=request.round,148 nonce=request.nonce,149 repo_url=repo_url,150 commit_sha=commit_sha,151 pages_url=pages_url152 )153 154 success = await notifier.notify(request.evaluation_url, payload)155 156 # Update task state157 task_state[task_key] = {158 "status": "completed" if success else "failed",159 "completed_at": datetime.now().isoformat(),160 "repo_url": repo_url,161 "pages_url": pages_url,162 "notification_sent": success163 }164 165 logger.info(f"Build task completed: {request.task}")166 167 except Exception as e:168 logger.error(f"Build task failed: {e}", exc_info=True)169 task_state[task_key] = {170 "status": "failed",171 "error": str(e),172 "failed_at": datetime.now().isoformat()173 }174 175 176async def process_revision_task(request: TaskRequest):177 """Process round 2: Update existing app"""178 task_key = f"{request.task}-{request.round}"179 start_time = time.time()180 181 try:182 logger.info(f"Starting revision task: {request.task}")183 184 # Get repo name from round 1185 round1_key = f"{request.task}-1"186 if round1_key not in task_state:187 raise ValueError("Round 1 must be completed before round 2")188 189 repo_name = sanitize_repo_name(request.task)190 191 # 1. Decode and save attachments192 attachments = await decode_and_save_attachments(request.attachments, request.task)193 logger.info(f"Saved {len(attachments)} attachments")194 195 # 2. Generate updated app using LLM196 generator = LLMAppGenerator()197 app_dir = await generator.generate_app(198 brief=request.brief,199 checks=request.checks,200 attachments=attachments,201 task_id=request.task202 )203 logger.info(f"Generated updated app at: {app_dir}")204 205 # 3. Update GitHub repo206 elapsed = time.time() - start_time207 if elapsed > config.EVALUATION_TIMEOUT - 60: # Leave 1 min buffer208 logger.warning(f"Approaching timeout ({elapsed:.1f}s elapsed)")209 210 github_mgr = GitHubManager()211 commit_sha, pages_url = await github_mgr.update_repo(212 repo_name=repo_name,213 app_dir=app_dir,214 update_message=f"Round {request.round} update"215 )216 logger.info(f"Updated GitHub Pages: {pages_url}")217 218 # 4. Notify evaluation URL219 elapsed = time.time() - start_time220 logger.info(f"Task processing took {elapsed:.1f}s")221 222 if elapsed > config.EVALUATION_TIMEOUT:223 logger.error(f"Task exceeded timeout ({elapsed:.1f}s > {config.EVALUATION_TIMEOUT}s)")224 raise TimeoutError(f"Task processing exceeded {config.EVALUATION_TIMEOUT}s")225 226 notifier = EvaluationNotifier()227 repo_url = f"https://github.com/{config.GITHUB_USERNAME}/{repo_name}"228 payload = EvaluationPayload(229 email=request.email,230 task=request.task,231 round=request.round,232 nonce=request.nonce,233 repo_url=repo_url,234 commit_sha=commit_sha,235 pages_url=pages_url236 )237 238 success = await notifier.notify(request.evaluation_url, payload)239 240 # Update task state241 task_state[task_key] = {242 "status": "completed" if success else "failed",243 "completed_at": datetime.now().isoformat(),244 "repo_url": repo_url,245 "pages_url": pages_url,246 "notification_sent": success247 }248 249 logger.info(f"Revision task completed: {request.task}")250 251 except Exception as e:252 logger.error(f"Revision task failed: {e}", exc_info=True)253 task_state[task_key] = {254 "status": "failed",255 "error": str(e),256 "failed_at": datetime.now().isoformat()257 }258 259 260@app.get("/status/{task_id}")261async def get_status(task_id: str):262 """Get status of a task"""263 results = {}264 for key, state in task_state.items():265 if key.startswith(task_id):266 results[key] = state267 268 if not results:269 raise HTTPException(status_code=404, detail="Task not found")270 271 return results272 273 274if __name__ == "__main__":275 import uvicorn276 uvicorn.run(277 "src.main:app",278 host=config.API_HOST,279 port=config.API_PORT,280 reload=True281 )282 