diff --git a/.python-version b/.python-version index e69de29..18904af 100644 --- a/.python-version +++ b/.python-version @@ -0,0 +1 @@ +Python 3.11.11 \ No newline at end of file diff --git a/app.py b/app.py index b6ff188..98b847f 100644 --- a/app.py +++ b/app.py @@ -26,6 +26,7 @@ import requests import os from PyPDF2 import PdfReader from config import QUIZ_TYPES +from config import Config # Load environment variables load_dotenv() API_KEY = os.getenv("API_KEY_ACCESS") @@ -33,7 +34,7 @@ API_KEY = os.getenv("API_KEY_ACCESS") base_path = os.path.join("data", "config_files") QUESTIONS_PATH = os.path.join(base_path, "questions.json") THEME_CONTEXT_PATH = os.path.join(base_path, "theme_context.json") - +backend_base_url = Config.BACKEND_BASE_URL with open(THEME_CONTEXT_PATH, "r", encoding="utf-8") as f: @@ -175,7 +176,7 @@ async def chat_endpoint( detail="Invalid resume URL: Unable to fetch document" ) resume_docs = "\n".join(f"- {doc.page_content}" for doc in docs) - print(f"Loaded resume documents: {resume_docs}") # Debugging print + print(f"Loaded resume documents: {resume_docs[:100]}") # Debugging print full_history_docs = "" if request.full_history_url: @@ -188,14 +189,14 @@ async def chat_endpoint( detail="Invalid full history URL: Unable to fetch document" ) full_history_docs = "\n".join(f"- {doc.page_content}" for doc in docs) - print(f"Loaded full history documents: {full_history_docs}") # Debugging print + print(f"Loaded full history documents: {full_history_docs[:100]}") # Debugging print form_response_docs = "" if request.form_id: print(f"Fetching form response for form_id: {request.form_id}") # Debugging print try: x_api_key = os.getenv("BACKEND_XAPI_KEY") - url = f"{os.getenv('BACKEND_BASE_URL')}/v3/api/custom/theme-document/answer/{request.form_id}?x-project={x_api_key}" + url = f"{backend_base_url}/v3/api/custom/theme-document/answer/{request.form_id}?x-project={x_api_key}" result = requests.get(url) result.raise_for_status() # Ensure we raise an error for bad responses form_response = result.json()["data"] # Return response in JSON format @@ -268,7 +269,7 @@ async def generate_pdf_endpoint( detail="Invalid resume URL: Unable to fetch document" ) resume_docs = "\n".join(f"- {doc.page_content}" for doc in docs) - print(f"Loaded resume documents: {resume_docs}") # Debugging print + print(f"Loaded resume documents: {resume_docs[:100]}") # Debugging print full_history_docs = "" if request.full_history_url: @@ -281,14 +282,14 @@ async def generate_pdf_endpoint( detail="Invalid full history URL: Unable to fetch document" ) full_history_docs = "\n".join(f"- {doc.page_content}" for doc in docs) - print(f"Loaded full history documents: {full_history_docs}") # Debugging print + print(f"Loaded full history documents: {full_history_docs[:100]}") # Debugging print form_response_docs = "" if request.form_id: print(f"Fetching form response for form_id: {request.form_id}") # Debugging print try: x_api_key = os.getenv("BACKEND_XAPI_KEY") - url = f"{os.getenv('BACKEND_BASE_URL')}/v3/api/custom/theme-document/answer/{request.form_id}?x-project={x_api_key}" + url = f"{backend_base_url}/v3/api/custom/theme-document/answer/{request.form_id}?x-project={x_api_key}" result = requests.get(url) result.raise_for_status() # Ensure we raise an error for bad responses form_response = result.json()["data"] # Return response in JSON format @@ -331,7 +332,7 @@ async def generate_pdf_endpoint( file.write(pdf_content) # Upload the PDF to S3 using the API - upload_url = f"{os.getenv('BACKEND_BASE_URL')}/v3/api/custom/theme/doc-upload?x-project={x_api_key}" + upload_url = f"{backend_base_url}/v3/api/custom/theme/doc-upload?x-project={x_api_key}" with open(file_path, 'rb') as file: files = {'file': file} upload_response = requests.post(upload_url, files=files) @@ -411,21 +412,6 @@ async def generate_quiz_endpoint( ) - -async def get_conversation_data(conversation_id: str) -> dict: - """ - Fetch conversation data using the conversation ID. - Replace this with your actual implementation to fetch conversation data. - """ - try: - storage_path = "conversations.json" - with open(storage_path, 'r') as f: - convs = json.load(f) - convs_id = convs[conversation_id] - return convs_id - except Exception as e: - print(f"Error fetching conversation data: {e}") - return None @app.on_event("startup") diff --git a/config.py b/config.py index 3825883..fb72445 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,4 @@ +import os QUIZ_TYPES = { 1: { "name": "Single Line Text Inputs", @@ -20,4 +21,13 @@ QUIZ_TYPES = { } MODEL = "gpt-4o-mini" -TEMPERATURE = 0.7 \ No newline at end of file +TEMPERATURE = 0.7 + + +class Config: + API_KEY_ACCESS = os.getenv("API_KEY_ACCESS") + OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") + BACKEND_XAPI_KEY = os.getenv("BACKEND_XAPI_KEY") + BACKEND_BASE_URL = os.getenv("BACKEND_BASE_URL_") + + \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..cceadfa --- /dev/null +++ b/deploy.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +# Configuration +SERVER_USER="your_username" +SERVER_IP="your_server_ip" +APP_DIR="/path/to/your/app" +PORT=5042 +PYTHON_VERSION="3.11" +WORKERS=4 +THREADS=2 +TIMEOUT=120 +MAX_REQUESTS=1000 +MAX_REQUESTS_JITTER=50 +DEBUG_MODE=false # Set to true for verbose output + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Logging function +log() { + local level=$1 + local message=$2 + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + case $level in + "INFO") + echo -e "${BLUE}[$timestamp] INFO: $message${NC}" + ;; + "SUCCESS") + echo -e "${GREEN}[$timestamp] SUCCESS: $message${NC}" + ;; + "WARNING") + echo -e "${YELLOW}[$timestamp] WARNING: $message${NC}" + ;; + "ERROR") + echo -e "${RED}[$timestamp] ERROR: $message${NC}" + ;; + esac +} + +# Debug logging function +debug_log() { + if [ "$DEBUG_MODE" = true ]; then + echo -e "${YELLOW}[DEBUG] $1${NC}" + fi +} + +# Error handling +set -e +trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG +trap 'if [ $? -ne 0 ]; then log "ERROR" "Command failed: $last_command"; exit 1; fi' EXIT + +log "INFO" "Starting deployment process..." + +# Check if required packages are installed +log "INFO" "Checking required packages..." +if ! command -v ssh &> /dev/null; then + log "ERROR" "SSH is not installed. Please install it first." + exit 1 +fi + +# Create requirements.txt if it doesn't exist +if [ ! -f "requirements.txt" ]; then + log "INFO" "Creating requirements.txt..." + echo "fastapi==0.109.2 +uvicorn==0.27.1 +gunicorn==21.2.0 +python-dotenv==1.0.1 +langchain-openai==0.0.5 +requests==2.31.0 +PyPDF2==3.0.1" > requirements.txt + debug_log "Created requirements.txt with specific versions" +fi + +# Create gunicorn config file +log "INFO" "Creating Gunicorn configuration..." +cat > gunicorn_config.py << EOL +import multiprocessing +import os + +# Server socket +bind = "0.0.0.0:${PORT}" +backlog = 2048 + +# Worker processes +workers = ${WORKERS} +worker_class = "uvicorn.workers.UvicornWorker" +worker_connections = 1000 +timeout = ${TIMEOUT} +keepalive = 2 + +# Process naming +proc_name = "firefighter" +pythonpath = "." + +# Logging +accesslog = "logs/access.log" +errorlog = "logs/error.log" +loglevel = "info" + +# Server mechanics +daemon = False +pidfile = "gunicorn.pid" +umask = 0 +user = None +group = None +tmp_upload_dir = None + +# SSL +keyfile = None +certfile = None + +# Server hooks +def on_starting(server): + pass + +def on_reload(server): + pass + +def on_exit(server): + pass + +# Worker lifecycle +max_requests = ${MAX_REQUESTS} +max_requests_jitter = ${MAX_REQUESTS_JITTER} +graceful_timeout = 30 +preload_app = True + +# Debug +reload = False +reload_engine = "auto" +spew = False + +# Server mechanics +check_config = False +preload_app = True +EOL +debug_log "Created gunicorn_config.py with specified settings" + +# Copy files to server +log "INFO" "Copying files to server..." +debug_log "Using SCP to copy files to $SERVER_USER@$SERVER_IP:$APP_DIR/" +scp -r ./* $SERVER_USER@$SERVER_IP:$APP_DIR/ + +# SSH into server and set up environment +log "INFO" "Setting up environment on server..." +ssh $SERVER_USER@$SERVER_IP << EOF + set -e + cd $APP_DIR + + # Install Python 3.11 if not present + if ! command -v python${PYTHON_VERSION} &> /dev/null; then + echo "Installing Python ${PYTHON_VERSION}..." + sudo apt-get update + sudo apt-get install -y software-properties-common + sudo add-apt-repository -y ppa:deadsnakes/ppa + sudo apt-get update + sudo apt-get install -y python${PYTHON_VERSION} python${PYTHON_VERSION}-venv python${PYTHON_VERSION}-dev + fi + + # Create virtual environment if it doesn't exist + if [ ! -d "venv" ]; then + python${PYTHON_VERSION} -m venv venv + fi + + # Activate virtual environment and install dependencies + source venv/bin/activate + pip install --upgrade pip + pip install -r requirements.txt + + # Create systemd service file + sudo tee /etc/systemd/system/firefighter.service << 'EOL' +[Unit] +Description=Fire Fighter Interview API +After=network.target + +[Service] +User=$SERVER_USER +WorkingDirectory=$APP_DIR +Environment="PATH=$APP_DIR/venv/bin" +Environment="PYTHONPATH=$APP_DIR" +ExecStart=$APP_DIR/venv/bin/gunicorn -c gunicorn_config.py app:app +Restart=always +RestartSec=5 +StartLimitInterval=0 + +[Install] +WantedBy=multi-user.target +EOL + + # Create log directory + mkdir -p logs + + # Reload systemd and start service + sudo systemctl daemon-reload + sudo systemctl enable firefighter + sudo systemctl restart firefighter + + # Check service status + sudo systemctl status firefighter +EOF + +log "SUCCESS" "Deployment completed!" +log "INFO" "Your application should now be running at http://$SERVER_IP:$PORT" +log "INFO" "Check logs at $APP_DIR/logs/" + +# Print helpful commands +echo -e "\n${BLUE}Useful commands:${NC}" +echo "View service status: sudo systemctl status firefighter" +echo "View logs: sudo journalctl -u firefighter -f" +echo "Restart service: sudo systemctl restart firefighter" +echo "Stop service: sudo systemctl stop firefighter" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8e9e10f..e990346 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,4 +24,4 @@ PyPDF2==3.0.1 reportlab==4.3.1 python-docx==1.1.2 unstructured==0.17.2 - +pypdf==5.4.0 diff --git a/src/llm.py b/src/llm.py index 736b66b..17834a7 100644 --- a/src/llm.py +++ b/src/llm.py @@ -34,9 +34,13 @@ QUESTIONS_PATH = "./data/config_files/questions.json" with open(QUESTIONS_PATH, "r") as f: questions = json.load(f) +from config import Config + prompt_template = None MODEL = "gpt-4o-mini" + + def initialize_workflow(model) -> StateGraph: """Initialize LangGraph workflow""" workflow = StateGraph(state_schema=MessagesState) @@ -62,8 +66,8 @@ def fetch_conversation_history(conversation_id: str) -> List[Message]: """ Fetch conversation history from the API using the conversation ID. """ - x_api_key = os.getenv("BACKEND_XAPI_KEY") - base_url = os.getenv("BACKEND_BASE_URL") + x_api_key = Config.BACKEND_XAPI_KEY + base_url = Config.BACKEND_BASE_URL url = f"{base_url}/v3/api/custom/jordan/ai-chat/get-messages/{conversation_id}?x-project={x_api_key}" try: