""" Deployment Agent - Generates deployment code and configuration """ import os import yaml import torch from datetime import datetime from typing import Dict, Optional from jinja2 import Template from src.models.schemas import ( TaskType, DeploymentType, BenchmarkResult, UserRequirements ) class DeploymentAgent: """ Generates production-ready deployment code for selected models. Supports FastAPI, Gradio, and Docker deployments with appropriate configuration files. """ def __init__(self, output_dir: str = "deployments"): self.output_dir = output_dir self._load_templates() def _load_templates(self): """Load deployment templates""" self.templates = {} # FastAPI template fastapi_template = '''""" FastAPI deployment for {{ model_id }} Generated by HuggingFace Model Selector """ from fastapi import FastAPI, HTTPException from pydantic import BaseModel from transformers import pipeline import torch import uvicorn import time from typing import Dict, Any app = FastAPI( title="{{ model_id }} API", description="Model deployment for {{ task_type }}", version="1.0.0" ) # Load model print("Loading model {{ model_id }}...") model = pipeline("{{ task_type }}", model="{{ model_id }}") print("Model loaded successfully!") class InferenceRequest(BaseModel): text: str parameters: Dict[str, Any] = {} class InferenceResponse(BaseModel): result: Any inference_time: float @app.post("/predict", response_model=InferenceResponse) async def predict(request: InferenceRequest): """Run inference on input text""" try: start_time = time.time() result = model(request.text, **request.parameters) inference_time = time.time() - start_time return InferenceResponse( result=result, inference_time=inference_time ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/health") async def health(): """Health check endpoint""" return {"status": "healthy", "model": "{{ model_id }}"} @app.get("/info") async def info(): """Model information""" return { "model_id": "{{ model_id }}", "task": "{{ task_type }}", "device": "cuda" if torch.cuda.is_available() else "cpu" } if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) ''' self.templates[DeploymentType.FASTAPI] = Template(fastapi_template) # Gradio template gradio_template = '''""" Gradio deployment for {{ model_id }} Generated by HuggingFace Model Selector """ import gradio as gr from transformers import pipeline import torch # Load model print("Loading model {{ model_id }}...") model = pipeline("{{ task_type }}", model="{{ model_id }}") print("Model loaded successfully!") def predict(text): """Run inference on input text""" try: result = model(text) return result except Exception as e: return f"Error: {str(e)}" # Create interface interface = gr.Interface( fn=predict, inputs=gr.Textbox(label="Input Text", lines=3), outputs=gr.Textbox(label="Result", lines=5), title="{{ model_id }}", description="Model deployment for {{ task_type }}", examples=[["This is a sample input"]] ) if __name__ == "__main__": interface.launch(server_name="0.0.0.0", server_port=7860) ''' self.templates[DeploymentType.GRADIO] = Template(gradio_template) # Dockerfile template docker_template = '''FROM python:3.9-slim WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y \\ gcc \\ g++ \\ && rm -rf /var/lib/apt/lists/* # Copy requirements COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . # Expose port EXPOSE 8000 # Run the application CMD ["python", "app.py"] ''' self.templates["dockerfile"] = Template(docker_template) def generate_deployment(self, model_id: str, task_type: TaskType, deployment_type: DeploymentType, benchmark_results: Optional[BenchmarkResult] = None, requirements: Optional[UserRequirements] = None) -> Dict[str, str]: """ Generate deployment code and configuration. Returns: Dictionary mapping filenames to file contents """ deployment_files = {} # Create timestamp for unique folder timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") safe_model_id = model_id.replace("/", "_") deploy_folder = f"{self.output_dir}/{safe_model_id}_{timestamp}" # Generate main application file if deployment_type == DeploymentType.FASTAPI: deployment_files["app.py"] = self.templates[DeploymentType.FASTAPI].render( model_id=model_id, task_type=task_type.value ) elif deployment_type == DeploymentType.GRADIO: deployment_files["app.py"] = self.templates[DeploymentType.GRADIO].render( model_id=model_id, task_type=task_type.value ) # Generate requirements.txt deployment_files["requirements.txt"] = self._generate_requirements( task_type, deployment_type ) # Generate Dockerfile if requested if deployment_type == DeploymentType.DOCKER: deployment_files["Dockerfile"] = self.templates["dockerfile"].render() # Generate docker-compose.yml deployment_files["docker-compose.yml"] = self._generate_docker_compose( model_id, requirements ) # Generate configuration file deployment_files["config.yaml"] = self._generate_config( model_id, task_type, benchmark_results, requirements ) # Generate README deployment_files["README.md"] = self._generate_readme( model_id, task_type, deployment_type, benchmark_results ) # Add folder info deployment_files["_folder"] = deploy_folder return deployment_files def _generate_requirements(self, task_type: TaskType, deployment_type: DeploymentType) -> str: """Generate requirements.txt""" requirements = "# Generated requirements\n" requirements += "transformers>=4.35.0\n" requirements += "torch>=2.0.0\n" requirements += "huggingface-hub>=0.19.0\n" if deployment_type == DeploymentType.FASTAPI: requirements += "fastapi>=0.104.0\n" requirements += "uvicorn>=0.24.0\n" requirements += "pydantic>=2.0.0\n" elif deployment_type == DeploymentType.GRADIO: requirements += "gradio>=4.0.0\n" # Task-specific requirements - using correct TaskType names if task_type in [TaskType.IMAGE_CLASSIFICATION, TaskType.OBJECT_DETECTION]: requirements += "pillow>=10.0.0\n" elif task_type == TaskType.SPEECH_TO_TEXT: # Changed from SPEECH_RECOGNITION requirements += "librosa>=0.10.0\n" elif task_type == TaskType.TEXT_TO_SPEECH: requirements += "librosa>=0.10.0\n" elif task_type == TaskType.OCR: requirements += "pillow>=10.0.0\n" requirements += "pytesseract>=0.3.10\n" return requirements def _generate_docker_compose(self, model_id: str, requirements: Optional[UserRequirements]) -> str: """Generate docker-compose.yml""" compose = "version: '3.8'\n\n" compose += "services:\n" compose += " model-service:\n" compose += " build: .\n" compose += " ports:\n" compose += " - \"8000:8000\"\n" compose += " environment:\n" compose += f" - MODEL_ID={model_id}\n" compose += " restart: unless-stopped\n" # Add GPU support if needed if requirements and any("gpu" in c.value for c in requirements.hardware_constraints): compose += " runtime: nvidia\n" compose += " environment:\n" compose += " - NVIDIA_VISIBLE_DEVICES=all\n" return compose def _generate_config(self, model_id: str, task_type: TaskType, benchmark_results: Optional[BenchmarkResult], requirements: Optional[UserRequirements]) -> str: """Generate YAML configuration""" config = { "model": { "id": model_id, "task": task_type.value, }, "deployment": { "batch_size": 1, "max_length": 512, "device": "cuda" if torch.cuda.is_available() else "cpu" } } if benchmark_results and not benchmark_results.error: config["performance"] = { "latency_ms": benchmark_results.latency_ms, "memory_mb": benchmark_results.memory_usage_mb, "throughput_sps": benchmark_results.throughput } if requirements: config["requirements"] = {} # Add hardware constraints if requirements.hardware_constraints: config["requirements"]["hardware_constraints"] = [c.value for c in requirements.hardware_constraints] # Add max model size if requirements.max_model_size_gb: config["requirements"]["max_model_size_gb"] = requirements.max_model_size_gb # Add task-specific requirements based on task type if requirements.task_type == TaskType.TRANSLATION and requirements.translation_reqs: req = requirements.translation_reqs config["requirements"]["source_language"] = req.source_language.value config["requirements"]["target_language"] = req.target_language.value if req.domain: config["requirements"]["domain"] = req.domain elif requirements.task_type == TaskType.TEXT_TO_SPEECH and requirements.tts_reqs: req = requirements.tts_reqs config["requirements"]["language"] = req.language.value config["requirements"]["voice_type"] = req.voice_type.value elif requirements.task_type == TaskType.SPEECH_TO_TEXT and requirements.stt_reqs: req = requirements.stt_reqs config["requirements"]["language"] = req.language.value if req.domain: config["requirements"]["domain"] = req.domain elif requirements.llm_reqs: req = requirements.llm_reqs config["requirements"]["model_size"] = req.model_size.value config["requirements"]["context_length"] = req.context_length elif requirements.ocr_reqs: req = requirements.ocr_reqs config["requirements"]["languages"] = [lang.value for lang in req.languages] config["requirements"]["handwritten"] = req.handwritten return yaml.dump(config, default_flow_style=False) def _generate_readme(self, model_id: str, task_type: TaskType, deployment_type: DeploymentType, benchmark_results: Optional[BenchmarkResult]) -> str: """Generate README.md""" readme = f"# {model_id} Deployment\n\n" readme += "This deployment was automatically generated by the HuggingFace Model Selector.\n\n" readme += "## Model Information\n\n" readme += f"- **Model ID**: {model_id}\n" readme += f"- **Task**: {task_type.value}\n" readme += f"- **Deployment Type**: {deployment_type.value}\n\n" if benchmark_results and not benchmark_results.error: readme += "## Performance Metrics\n\n" readme += f"- **Average Latency**: {benchmark_results.latency_ms:.2f} ms\n" readme += f"- **Memory Usage**: {benchmark_results.memory_usage_mb:.2f} MB\n" readme += f"- **Throughput**: {benchmark_results.throughput_samples_per_second:.2f} samples/second\n\n" readme += "## Quick Start\n\n" readme += "### 1. Install dependencies\n" readme += "```bash\n" readme += "pip install -r requirements.txt\n" readme += "```\n\n" readme += "### 2. Run the application\n" readme += "```bash\n" readme += "python app.py\n" readme += "```\n\n" readme += "### 3. Test the API\n\n" if deployment_type == DeploymentType.FASTAPI: readme += "```bash\n" readme += "# Health check\n" readme += "curl http://localhost:8000/health\n\n" readme += "# Run inference\n" readme += 'curl -X POST http://localhost:8000/predict \\\n' readme += ' -H "Content-Type: application/json" \\\n' readme += ' -d \'{"text": "Your input text here"}\'\n' readme += "```\n" elif deployment_type == DeploymentType.GRADIO: readme += "Open http://localhost:7860 in your browser to use the Gradio interface.\n" if deployment_type == DeploymentType.DOCKER: readme += "\n## Docker Deployment\n\n" readme += "```bash\n" readme += "# Build the image\n" readme += "docker build -t model-service .\n\n" readme += "# Run the container\n" readme += "docker run -p 8000:8000 model-service\n\n" readme += "# Or use docker-compose\n" readme += "docker-compose up\n" readme += "```\n" return readme def save_deployment_files(self, deployment_files: Dict[str, str]) -> str: """Save generated files to disk""" folder = deployment_files.pop("_folder") os.makedirs(folder, exist_ok=True) for filename, content in deployment_files.items(): filepath = os.path.join(folder, filename) with open(filepath, "w", encoding="utf-8") as f: f.write(content) print(f" Created {filepath}") return folder