Python/FastAPI App Crashes with "exit status 1" - No Logs

Current behavior:
The deployed Serverless Function crashes immediately on any request with a
FUNCTION_INVOCATION_FAILED error. The logs only show Python process exited with exit status: 1 and
provide no Python traceback.
Expected behavior:
The FastAPI application should start correctly, connect to the database, and serve requests, just
as it does on my local machine.

he application uses FastAPI, SQLAlchemy, and connects to a Neon PostgreSQL database. The crash
seems to happen during the initial database connection attempt with SQLAlchemy’s create_engine.
api/index.py (relevant part):
1 from sqlalchemy import create_engine
2 from sqlalchemy.pool import NullPool # Also tried with pre-ping pool
3 import os
4
5 DATABASE_URL = os.getenv(“DATABASE_URL”)
6
7 # We have tried multiple configurations, including NullPool and pre-ping
8 db_engine = create_engine(
9 DATABASE_URL,
10 pool_pre_ping=True, # This was one of the attempted fixes
11 pool_recycle=300
12 ) if DATABASE_URL else None
13
14 # … rest of FastAPI app
requirements.txt:
1 fastapi==0.111.0
2 uvicorn==0.29.0
3 gunicorn==22.0.0
4 groq==0.9.0
5 python-dotenv==1.0.1
6 SQLAlchemy==2.0.30
7 psycopg2-binary==2.9.9
8 Jinja2==3.1.4

    • Framework: Python / FastAPI / SQLAlchemy
    • Database: Neon (PostgreSQL)
    • Project ID: prj_NYQoGb6XAAPCxUU98HN1WqMH1C7T
    • A recent failed invocation ID: tp5rh-1762855106666-e06ef947a54b
      Crucially, I have already tried ALL of the following with no success:
    1. Switching between Neon’s pooling and non-pooling connection URLs.
    2. Using both psycopg2-binary and psycopg drivers.
    3. Implementing both NullPool and pool_pre_ping=True for SQLAlchemy.
    4. Pinning all dependency versions.
    5. Using a vercel.json file.
    6. Completely deleting and recreating the project on Vercel.
      The issue seems to be a low-level runtime problem. Please help investigate the internal platform
      logs. Thank you.

Hi @sanyaspel-8618, welcome to the Vercel Community!

I’m sorry that you are facing this issue. Did you confirm the environment variables are set up correctly in your Vercel project?

If you need more help, please share your public repo or a minimal reproducible example. That will let us all work together from the same code to figure out what’s going wrong.

I was able to get a FastAPI project working using our template:

So, it’s not a Python/FastAPI issue.

Hi, thanks for the reply.

Yes, I have triple-checked the environment variables (DATABASE_URL and GROQ_API_KEY). They are set
correctly in the Vercel project settings.

The project is open-source, you can access the repository here:

As I mentioned in my original post, we have already tried all the standard solutions (switching
pooling modes, changing drivers, pinning Python version, etc.) without success. The application
continues to crash with exit status 1 and no Python traceback.

Could you please escalate this and have someone look at the internal platform logs for the project?

Project ID: prj_NYQoGb6XAAPCxUU98HN1WqMH1C7T

Thank you.

Thanks for the additional information. Let me try to recreate the issue.

On a side note, based on the readme it looks like you’re trying to use Vercel as a proxy to bypass regional blocking for Groq. Have you tried using Vercel Groq Integration? It might be simpler.

Thanks for looking into it.

Regarding the Groq proxy, you are right. I wasn’t aware of the official Vercel Groq Integration
when I started. I will definitely look into refactoring to use it once the main database connection
issue is resolved.

My primary concern right now is indeed the exit status 1 crash related to the database connection.
I’m looking forward to hearing what you find when you try to reproduce it.

Thank you!

Just wanted to share that by using Vercel AI Gateway you don’t need to make your own proxy to bypass the regional blocking.

I was about atleast get it to respond: https://a-proxy-request-three.vercel.app/api/health

I moved the index.py to the root folder and updated the code to for more debug logs:

import os
import logging
import traceback
from pathlib import Path
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy import create_engine, Column, Integer, String, Boolean, BigInteger, Text, DateTime, LargeBinary, ForeignKey
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from sqlalchemy.exc import SQLAlchemyError, OperationalError, DatabaseError
from groq import Groq

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# --- 1. ОБЩАЯ НАСТРОЙКА ---
app = FastAPI()

# --- 2. НАСТРОЙКА ДЛЯ ПРОКСИ-GROQ ---
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
groq_client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None

# --- 3. НАСТРОЙКА ДЛЯ ВЕБ-ИСТОРИИ (SERVERLESS-СОВМЕСТИМАЯ) ---
DATABASE_URL = os.getenv("DATABASE_URL")
Base = declarative_base()

# Для Vercel serverless - без connection pooling
def get_engine():
    """Create database engine with error handling"""
    if not DATABASE_URL:
        logger.warning("DATABASE_URL is not configured")
        return None
    
    try:
        logger.info("Creating database engine...")
        engine = create_engine(
            DATABASE_URL,
            poolclass=None,  # Отключаем pooling для serverless
            connect_args={"connect_timeout": 10}
        )
        logger.info("Database engine created successfully")
        return engine
    except OperationalError as e:
        logger.error(f"Database operational error: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        return None
    except DatabaseError as e:
        logger.error(f"Database error: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        return None
    except Exception as e:
        logger.error(f"Unexpected error creating database engine: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        return None

# Template path - работает как локально, так и на Vercel
try:
    template_dir = Path(__file__).parent / "templates"
    templates = Jinja2Templates(directory=str(template_dir))
    logger.info(f"Templates directory set to: {template_dir}")
except Exception as e:
    logger.error(f"Error setting up templates: {type(e).__name__} - {str(e)}")
    logger.error(f"Traceback: {traceback.format_exc()}")
    # Fallback to relative path
    templates = Jinja2Templates(directory="templates")


# --- МОДЕЛИ ДЛЯ ВЕБ-ИСТОРИИ (SQLAlchemy) ---
class Pair(Base):
    __tablename__ = "pairs"
    id = Column(Integer, primary_key=True)
    client_id = Column(BigInteger, unique=True, nullable=False)
    performer_id = Column(BigInteger, unique=True, nullable=False)
    is_frozen = Column(Boolean, default=False)

class User(Base):
    __tablename__ = "users"
    user_id = Column(BigInteger, primary_key=True, autoincrement=False)
    display_name = Column(String, nullable=False)

class History(Base):
    __tablename__ = "history"
    id = Column(Integer, primary_key=True)
    pair_id = Column(Integer, ForeignKey("pairs.id"), nullable=False)
    message_id = Column(BigInteger, nullable=False, index=True)
    sender_id = Column(BigInteger, nullable=False)
    date = Column(DateTime, nullable=False)
    text = Column(Text)
    original_text = Column(Text)
    media_blob = Column(LargeBinary)
    media_url = Column(String)  # Добавлено недостающее поле
    status = Column(String, default="sent")
    edit_date = Column(DateTime)
    log_message_id = Column(Integer)


# --- ЛОГИКА ДЛЯ ПРОКСИ-GROQ ---
MODEL_INFO = {
    "llama3-70b-8192": {"size": 70, "quality": 10}, "llama3-8b-8192": {"size": 8, "quality": 7},
    "llama-3.1-70b-versatile": {"size": 70, "quality": 9.5}, "mixtral-8x7b-32768": {"size": 56, "quality": 8.5},
    "gemma-7b-it": {"size": 7, "quality": 6},
}

def find_best_alternative(models_list):
    best_model, max_score = None, -1
    for model_id in models_list:
        score = MODEL_INFO.get(model_id, {}).get("quality", 0)
        if score > max_score:
            max_score, best_model = score, model_id
    return best_model or "gemma-7b-it"

@app.post("/api/groq")
async def proxy_to_groq(request: Request):
    """Proxy endpoint for Groq API with comprehensive error handling"""
    if not groq_client:
        logger.error("Groq API call attempted but GROQ_API_KEY is not set")
        raise HTTPException(status_code=500, detail="GROQ_API_KEY is not set.")
    
    try:
        logger.info("Received Groq API proxy request")
        data = await request.json()
        model = data.get("model", "llama-3.1-70b-versatile")
        
        if "messages" not in data:
            logger.error("Missing 'messages' field in request data")
            raise HTTPException(status_code=400, detail="Missing 'messages' field in request")
        
        logger.info(f"Attempting to use model: {model}")
        
        try:
            chat_completion = groq_client.chat.completions.create(messages=data["messages"], model=model)
            logger.info(f"Successfully completed chat with model: {model}")
        except Exception as e:
            error_str = str(e)
            logger.warning(f"Error with model {model}: {error_str}")
            
            if "model_decommissioned" in error_str or "not found" in error_str.lower():
                logger.info("Model decommissioned, attempting to find alternative...")
                try:
                    models_response = groq_client.models.list()
                    available_ids = [m.id for m in models_response.data]
                    best_alternative = find_best_alternative(available_ids)
                    logger.info(f"Switching from {model} to {best_alternative}")
                    chat_completion = groq_client.chat.completions.create(messages=data["messages"], model=best_alternative)
                    logger.info(f"Successfully completed chat with alternative model: {best_alternative}")
                except Exception as fallback_error:
                    logger.error(f"Failed to use alternative model: {type(fallback_error).__name__} - {str(fallback_error)}")
                    logger.error(f"Traceback: {traceback.format_exc()}")
                    raise HTTPException(
                        status_code=500, 
                        detail=f"Failed with original and alternative models: {str(fallback_error)}"
                    )
            elif "rate_limit" in error_str.lower():
                logger.error(f"Rate limit error: {error_str}")
                raise HTTPException(status_code=429, detail=f"Rate limit exceeded: {error_str}")
            elif "authentication" in error_str.lower() or "api key" in error_str.lower():
                logger.error(f"Authentication error: {error_str}")
                raise HTTPException(status_code=401, detail=f"Authentication failed: {error_str}")
            else:
                logger.error(f"Unexpected Groq API error: {type(e).__name__} - {error_str}")
                logger.error(f"Traceback: {traceback.format_exc()}")
                raise e
        
        return chat_completion.dict()
        
    except HTTPException:
        raise  # Re-raise HTTP exceptions as-is
    except KeyError as e:
        logger.error(f"Missing required field: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=400, detail=f"Missing required field: {str(e)}")
    except Exception as e:
        logger.error(f"Unexpected error in Groq proxy: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=500, detail=f"Error: {type(e).__name__} - {str(e)}")


# --- ЛОГИКА ДЛЯ ВЕБ-ИСТОРИИ (SERVERLESS-СОВМЕСТИМАЯ) ---
def get_db() -> Session:
    """Dependency для получения DB сессии (serverless-совместимая версия) с error handling"""
    if not DATABASE_URL:
        logger.error("Database access attempted but DATABASE_URL is not set")
        raise HTTPException(status_code=500, detail="DATABASE_URL is not set.")
    
    engine = None
    db = None
    
    try:
        logger.info("Creating database session...")
        engine = get_engine()
        if not engine:
            logger.error("Failed to create database engine - get_engine returned None")
            raise HTTPException(status_code=500, detail="Failed to create database engine.")
        
        # Создаем таблицы если их нет (для serverless это безопасно делать на каждом запросе)
        try:
            Base.metadata.create_all(bind=engine)
            logger.info("Database tables checked/created successfully")
        except OperationalError as e:
            logger.error(f"Database operational error during table creation: {str(e)}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            raise HTTPException(status_code=503, detail=f"Database connection failed: {str(e)}")
        except DatabaseError as e:
            logger.error(f"Database error during table creation: {str(e)}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
        except Exception as e:
            logger.warning(f"Warning: Could not create tables: {type(e).__name__} - {str(e)}")
        
        # Создаем новую сессию для каждого запроса
        try:
            SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
            db = SessionLocal()
            logger.info("Database session created successfully")
        except Exception as e:
            logger.error(f"Failed to create database session: {type(e).__name__} - {str(e)}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            raise HTTPException(status_code=500, detail=f"Failed to create database session: {str(e)}")
        
        yield db
        
    except HTTPException:
        raise  # Re-raise HTTP exceptions as-is
    except Exception as e:
        logger.error(f"Unexpected error in get_db: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
    finally:
        # Cleanup resources
        if db:
            try:
                db.close()
                logger.debug("Database session closed")
            except Exception as e:
                logger.warning(f"Error closing database session: {str(e)}")
        
        if engine:
            try:
                engine.dispose()
                logger.debug("Database engine disposed")
            except Exception as e:
                logger.warning(f"Error disposing database engine: {str(e)}")

def get_user_display_name(db_session, user_id: int) -> str:
    """Get user display name with error handling"""
    try:
        user = db_session.query(User).filter(User.user_id == user_id).first()
        return user.display_name if user else f"ID: {user_id}"
    except SQLAlchemyError as e:
        logger.error(f"Database error getting user {user_id}: {str(e)}")
        return f"ID: {user_id}"
    except Exception as e:
        logger.error(f"Unexpected error getting user {user_id}: {type(e).__name__} - {str(e)}")
        return f"ID: {user_id}"

@app.get("/", response_class=HTMLResponse)
async def list_pairs(request: Request, db: Session = Depends(get_db)):
    """List all conversation pairs with comprehensive error handling"""
    try:
        logger.info("Fetching list of conversation pairs")
        pairs = db.query(Pair).all()
        logger.info(f"Found {len(pairs)} conversation pairs")
        
        pair_details = []
        for p in pairs:
            try:
                pair_details.append({
                    "client_id": p.client_id,
                    "client_name": get_user_display_name(db, p.client_id),
                    "performer_name": get_user_display_name(db, p.performer_id),
                    "is_frozen": p.is_frozen
                })
            except Exception as e:
                logger.error(f"Error processing pair {p.id}: {type(e).__name__} - {str(e)}")
                # Continue with other pairs even if one fails
                continue
        
        return templates.TemplateResponse("index.html", {"request": request, "pairs": pair_details})
        
    except SQLAlchemyError as e:
        logger.error(f"Database error in list_pairs: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=503, detail=f"Database error: {str(e)}")
    except Exception as e:
        logger.error(f"Unexpected error in list_pairs: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=500, detail=f"Error loading pairs: {str(e)}")

@app.get("/dialog/{client_id}", response_class=HTMLResponse)
async def show_dialog(request: Request, client_id: int, db: Session = Depends(get_db)):
    """Show dialog for a specific client with comprehensive error handling"""
    try:
        logger.info(f"Fetching dialog for client_id: {client_id}")
        
        pair = db.query(Pair).filter(Pair.client_id == client_id).first()
        if not pair:
            logger.warning(f"Dialog not found for client_id: {client_id}")
            raise HTTPException(status_code=404, detail="Диалог не найден")
        
        logger.info(f"Found pair {pair.id} for client {client_id}")
        
        try:
            messages = db.query(History).filter(History.pair_id == pair.id).order_by(History.date).all()
            logger.info(f"Found {len(messages)} messages for pair {pair.id}")
        except SQLAlchemyError as e:
            logger.error(f"Database error fetching messages: {type(e).__name__} - {str(e)}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            raise HTTPException(status_code=503, detail=f"Error fetching messages: {str(e)}")
        
        client_name = get_user_display_name(db, pair.client_id)
        performer_name = get_user_display_name(db, pair.performer_id)
        
        return templates.TemplateResponse("dialog.html", {
            "request": request,
            "client_id": client_id,
            "client_name": client_name,
            "performer_name": performer_name,
            "messages": messages
        })
        
    except HTTPException:
        raise  # Re-raise HTTP exceptions as-is
    except SQLAlchemyError as e:
        logger.error(f"Database error in show_dialog: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=503, detail=f"Database error: {str(e)}")
    except Exception as e:
        logger.error(f"Unexpected error in show_dialog: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=500, detail=f"Error loading dialog: {str(e)}")

@app.get("/api/dialog/{client_id}")
async def get_dialog_data(client_id: int, db: Session = Depends(get_db)):
    """Возвращает данные диалога в формате JSON с comprehensive error handling"""
    try:
        logger.info(f"API: Fetching dialog data for client_id: {client_id}")
        
        pair = db.query(Pair).filter(Pair.client_id == client_id).first()
        if not pair:
            logger.warning(f"API: Dialog not found for client_id: {client_id}")
            raise HTTPException(status_code=404, detail="Диалог не найден")
        
        logger.info(f"API: Found pair {pair.id} for client {client_id}")

        try:
            messages = db.query(History).filter(History.pair_id == pair.id).order_by(History.date).all()
            logger.info(f"API: Found {len(messages)} messages for pair {pair.id}")
        except SQLAlchemyError as e:
            logger.error(f"Database error fetching messages in API: {type(e).__name__} - {str(e)}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            raise HTTPException(status_code=503, detail=f"Error fetching messages: {str(e)}")

        # Преобразуем сообщения в формат, удобный для JSON
        messages_data = []
        for msg in messages:
            try:
                messages_data.append({
                    "id": msg.id,
                    "sender_id": msg.sender_id,
                    "text": msg.text,
                    "original_text": msg.original_text,
                    "date": msg.date.isoformat() if msg.date else None,
                    "status": msg.status,
                    "media_url": msg.media_url
                })
            except Exception as e:
                logger.error(f"Error serializing message {msg.id}: {type(e).__name__} - {str(e)}")
                # Continue with other messages even if one fails
                continue

        return {"messages": messages_data}
        
    except HTTPException:
        raise  # Re-raise HTTP exceptions as-is
    except SQLAlchemyError as e:
        logger.error(f"Database error in get_dialog_data: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=503, detail=f"Database error: {str(e)}")
    except Exception as e:
        logger.error(f"Unexpected error in get_dialog_data: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        raise HTTPException(status_code=500, detail=f"Error fetching dialog data: {str(e)}")


# --- HEALTH CHECK ---
@app.get("/api/health")
def health_check():
    """Health check endpoint с comprehensive diagnostics"""
    try:
        logger.info("Health check requested")
        
        # Test database connection if configured
        db_status = "not_configured"
        db_error = None
        if DATABASE_URL:
            try:
                engine = get_engine()
                if engine:
                    # Try to execute a simple query
                    with engine.connect() as conn:
                        conn.execute("SELECT 1")
                    db_status = "connected"
                    logger.info("Health check: Database connection successful")
                else:
                    db_status = "error"
                    db_error = "Failed to create engine"
                    logger.warning("Health check: Failed to create database engine")
            except Exception as e:
                db_status = "error"
                db_error = str(e)
                logger.error(f"Health check: Database connection failed: {str(e)}")
        
        # Test Groq client if configured
        groq_status = "not_configured"
        groq_error = None
        if GROQ_API_KEY:
            if groq_client:
                groq_status = "configured"
                logger.info("Health check: Groq client configured")
            else:
                groq_status = "error"
                groq_error = "Failed to initialize client"
                logger.warning("Health check: Groq client initialization failed")
        
        status = {
            "status": "ok" if db_status in ["connected", "not_configured"] and groq_status in ["configured", "not_configured"] else "degraded",
            "groq": {
                "status": groq_status,
                "error": groq_error
            },
            "database": {
                "status": db_status,
                "error": db_error
            },
            "timestamp": os.popen('date -u +"%Y-%m-%dT%H:%M:%SZ"').read().strip()
        }
        
        logger.info(f"Health check result: {status['status']}")
        return status
        
    except Exception as e:
        logger.error(f"Error in health check: {type(e).__name__} - {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
        return {
            "status": "error",
            "error": str(e),
            "type": type(e).__name__
        }


# --- GLOBAL EXCEPTION HANDLER ---
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    """Global exception handler to catch any unhandled errors"""
    logger.error(f"UNHANDLED EXCEPTION: {type(exc).__name__} - {str(exc)}")
    logger.error(f"Request URL: {request.url}")
    logger.error(f"Request method: {request.method}")
    logger.error(f"Traceback: {traceback.format_exc()}")
    
    # Return a generic error response
    return JSONResponse(
        status_code=500,
        content={
            "error": "Internal server error",
            "type": type(exc).__name__,
            "detail": str(exc),
            "path": str(request.url)
        }
    )


# Log startup
logger.info("=" * 60)
logger.info("Application starting up...")
logger.info(f"GROQ_API_KEY configured: {GROQ_API_KEY is not None}")
logger.info(f"DATABASE_URL configured: {DATABASE_URL is not None}")
logger.info("=" * 60)

You should be able to get some information from the logs.

1 Like

Hey! We’re running this session next week after our recent announcement of Gel joining the team. Because you had posted about Python, I thought I’d give you a heads up in case you wanted to join us! :smiley: