From 38c96bdc2cea1704369953cbbb953dc87d453daf Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 16:37:10 +0100 Subject: [PATCH 1/9] refactor: Modernize environment management and backend architecture Environment & Configuration: - Unified env management with .env.example template and consolidated .gitignore - Added dev-local.sh, dev-hybrid.sh, dev-backend.sh for clear dev workflows - Updated package.json scripts and removed obsolete shell scripts Frontend Improvements: - Enhanced API URL detection for seamless local/prod switching - Improved error handling and fallback configuration Backend Architecture Refactor: - Refactored agents.py with DRY principles and best practices - Added ProductionDetector, LLMClientFactory, JSONResponseParser classes - Eliminated code duplication in agent initialization - Fixed all lint errors: removed unused imports/variables, unnecessary f-strings - Enhanced type hints and comprehensive docstrings - Centralized LLM client creation with environment-aware Helicone integration Production Requirements: - Helicone monitoring REQUIRED in production, optional in development - Robust environment detection via VERCEL/NODE_ENV/ENVIRONMENT variables All changes tested and verified working across local/hybrid/production modes. --- .env.example | 38 +++++++ .gitignore | 30 +++++- README.md | 84 +++++----------- backend/.gitignore | 1 - backend/api/agents.py | 212 ++++++++++++++++++++++++++++++---------- backend/api/main.py | 2 +- dev-backend.sh | 40 ++++++++ dev-hybrid.sh | 35 +++++++ dev-local.sh | 43 ++++++++ frontend/.gitignore | 34 +++++++ frontend/pages/about.js | 4 +- frontend/pages/index.js | 31 ++++-- package.json | 1 + start.sh | 108 -------------------- start_backend.sh | 40 -------- start_frontend.sh | 24 ----- 16 files changed, 427 insertions(+), 300 deletions(-) create mode 100644 .env.example delete mode 100644 backend/.gitignore create mode 100755 dev-backend.sh create mode 100755 dev-hybrid.sh create mode 100755 dev-local.sh delete mode 100755 start.sh delete mode 100755 start_backend.sh delete mode 100755 start_frontend.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c11e1f4 --- /dev/null +++ b/.env.example @@ -0,0 +1,38 @@ +# ============================================================================= +# No More Jockeys - Environment Configuration +# ============================================================================= +# This is the main environment file for the entire project. +# Copy this to .env and fill in your actual values. + +# ----------------------------------------------------------------------------- +# ANTHROPIC API CONFIGURATION +# ----------------------------------------------------------------------------- +# Required for AI functionality +ANTHROPIC_API_KEY=your_anthropic_api_key_here + +# ----------------------------------------------------------------------------- +# HELICONE MONITORING +# ----------------------------------------------------------------------------- +# REQUIRED in production for request monitoring +# OPTIONAL in development (will use direct Anthropic API if not set) +HELICONE_API_KEY=your_helicone_key_here + +# ----------------------------------------------------------------------------- +# API URL CONFIGURATION +# ----------------------------------------------------------------------------- +# Frontend will auto-detect based on NODE_ENV, but you can override: +# +# For local development (default behavior): +# NEXT_PUBLIC_API_URL=http://localhost:8000 +# +# To test frontend locally against production backend: +# NEXT_PUBLIC_API_URL=https://backend-pu7w8cumu-set4.vercel.app +# +# For production deployment: +# NEXT_PUBLIC_API_URL=https://your-production-backend.vercel.app + +# ----------------------------------------------------------------------------- +# DEVELOPMENT MODE CONFIGURATION +# ----------------------------------------------------------------------------- +# Uncomment to set explicit development mode +# NODE_ENV=development diff --git a/.gitignore b/.gitignore index e85314f..1d25a48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,36 @@ +# Dependencies node_modules/ -.envrc +venv/ +__pycache__/ + +# Environment files .env .env.local +.env*.local + +# Build outputs .next/ -.vercel -__pycache__/ -venv/ -*.pyc dist/ build/ + +# Deployment +.vercel + +# Logs *.log + +# OS files .DS_Store +.envrc + +# Editor files *.swp *.swo *~ + +# Python +*.pyc + +# Temporary files +backend.log +frontend.log diff --git a/README.md b/README.md index c3bc119..7780599 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,38 @@ # No More Jockeys -Multi-LLM No More Jockeys game implementation with separate backend and frontend deployments. +Multi-LLM No More Jockeys game with FastAPI backend and Next.js frontend. -## Architecture - -- **Backend**: FastAPI Python application (`/backend`) -- **Frontend**: Next.js React application (`/frontend`) - -## Development +## Quick Start ```bash -# Install dependencies and start both services -npm run dev - -# Or start individually: -npm run dev:backend # Starts on http://localhost:8000 -npm run dev:frontend # Starts on http://localhost:3000 +cp .env.example .env +# Add your ANTHROPIC_API_KEY to .env +./dev-local.sh ``` -## Deployment - -This project uses GitHub Actions to automatically deploy to Vercel when changes are pushed to the main branch. - -### Setup GitHub Actions Deployment - -1. **Create Vercel Projects**: - - Create separate Vercel projects for backend and frontend - - Note the project IDs from each project's settings - -2. **Get Vercel Credentials**: - ```bash - # Install Vercel CLI - npm i -g vercel - - # Login and get your tokens - vercel login - vercel --scope your-team-name - ``` +## Development Modes -3. **Add GitHub Secrets**: - Go to your GitHub repository → Settings → Secrets and variables → Actions, and add: - - `VERCEL_TOKEN`: Your Vercel token - - `VERCEL_ORG_ID`: Your Vercel organization/team ID - - `VERCEL_BACKEND_PROJECT_ID`: Backend project ID from Vercel - - `VERCEL_FRONTEND_PROJECT_ID`: Frontend project ID from Vercel - -4. **Manual Deployment** (if needed): - ```bash - # Deploy backend - cd backend && vercel --prod - - # Deploy frontend - cd frontend && vercel --prod - ``` +```bash +./dev-local.sh # Frontend + Backend locally +./dev-hybrid.sh # Frontend local → Production backend +./dev-backend.sh # Backend only +``` -### Environment Variables +## Environment -For production deployments, set these in your Vercel project settings: +Edit `.env`: +```bash +ANTHROPIC_API_KEY=your_api_key_here +HELICONE_API_KEY=optional_dev_key # Required in production +# NEXT_PUBLIC_API_URL=optional_override +``` -**Frontend**: -- `NEXT_PUBLIC_API_URL`: Your backend Vercel URL +## Deployment -**Backend**: -- `ANTHROPIC_API_KEY`: Your Anthropic API key (if using AI features) +Set in Vercel dashboard: +- **Backend**: `ANTHROPIC_API_KEY`, `HELICONE_API_KEY` (required in production) +- **Frontend**: `NEXT_PUBLIC_API_URL` ## Game Rules -No More Jockeys is a game where players take turns naming a person and a category that person has never been. The challenge is to avoid categories that have already been "banned" by previous players. - -## Tech Stack - -- **Backend**: FastAPI, Python, Anthropic Claude API -- **Frontend**: Next.js, React -- **Deployment**: Vercel with GitHub Actions -- **Development**: Concurrent local development setup \ No newline at end of file +Players take turns naming a person and declaring a category they belong to. That category becomes banned. Don't name anyone from banned categories or you're eliminated. \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index e985853..0000000 --- a/backend/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vercel diff --git a/backend/api/agents.py b/backend/api/agents.py index 3a6837b..192497e 100644 --- a/backend/api/agents.py +++ b/backend/api/agents.py @@ -2,6 +2,9 @@ from langchain_core.messages import SystemMessage, HumanMessage import json import os +from typing import Dict, List, Optional +from enum import Enum +from dataclasses import dataclass from .prompts import ( PLAYER_SYSTEM_PROMPT, PLAYER_TURN_PROMPT, @@ -12,48 +15,155 @@ from .game_state import GameState, Move, Player from datetime import datetime -def _parse_json_response(response_content: str) -> dict: - """Extracts and parses JSON from a string that might contain extra text.""" - content = response_content.strip() + +class Environment(Enum): + """Environment types for production detection.""" + DEVELOPMENT = "development" + PRODUCTION = "production" + + +@dataclass +class ValidationResult: + """Result of move validation.""" + is_valid: bool + violations: List[str] + explanations: Dict[str, str] + + +class ProductionDetector: + """Handles production environment detection.""" - # Try to find a complete JSON object - brace_count = 0 - start_idx = -1 - end_idx = -1 + @staticmethod + def is_production() -> bool: + """Detect if running in production environment.""" + return ( + os.environ.get('VERCEL') == '1' or + os.environ.get('NODE_ENV') == 'production' or + os.environ.get('ENVIRONMENT') == 'production' + ) + + +class LLMClientFactory: + """Factory for creating LLM clients with appropriate configuration.""" - for i, char in enumerate(content): - if char == '{': - if start_idx == -1: - start_idx = i - brace_count += 1 - elif char == '}': - brace_count -= 1 - if brace_count == 0 and start_idx != -1: - end_idx = i + 1 - break + @staticmethod + def create_anthropic_client( + model_name: str, + temperature: float, + max_tokens: int, + role: Optional[str] = None, + player_id: Optional[int] = None + ) -> ChatAnthropic: + """Create ChatAnthropic client with environment-appropriate configuration.""" + anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") + if not anthropic_api_key: + raise ValueError("ANTHROPIC_API_KEY environment variable is required") + + is_production = ProductionDetector.is_production() + helicone_key = os.environ.get('HELICONE_API_KEY') + + # Production: Helicone required + if is_production: + if not helicone_key or helicone_key == 'your_helicone_key_here': + raise ValueError( + "HELICONE_API_KEY is required in production environment. " + "Please set it in your deployment environment variables." + ) + return LLMClientFactory._create_helicone_client( + model_name, temperature, max_tokens, anthropic_api_key, + helicone_key, role, player_id + ) + + # Development: Helicone optional + if helicone_key and helicone_key != 'your_helicone_key_here': + return LLMClientFactory._create_helicone_client( + model_name, temperature, max_tokens, anthropic_api_key, + helicone_key, role, player_id + ) + + # Development: Direct Anthropic API + return ChatAnthropic( + model=model_name, + anthropic_api_key=anthropic_api_key, + temperature=temperature, + max_tokens=max_tokens + ) - if start_idx != -1 and end_idx != -1: - json_content = content[start_idx:end_idx] - print(f"Extracted JSON: {json_content}") - return json.loads(json_content) - else: - print(f"No valid JSON found, trying full content: {content}") - return json.loads(content) + @staticmethod + def _create_helicone_client( + model_name: str, + temperature: float, + max_tokens: int, + anthropic_api_key: str, + helicone_key: str, + role: Optional[str], + player_id: Optional[int] + ) -> ChatAnthropic: + """Create Helicone-enabled ChatAnthropic client.""" + headers = { + "Helicone-Auth": f"Bearer {helicone_key}", + "Helicone-Property-App": "no-more-jockeys", + } + + if role: + headers["Helicone-Property-Role"] = role + if player_id: + headers["Helicone-Property-Player"] = f"player-{player_id}" + + return ChatAnthropic( + model=model_name, + anthropic_api_key=anthropic_api_key, + anthropic_api_url="https://api.helicone.ai/v1", + temperature=temperature, + max_tokens=max_tokens, + default_headers=headers + ) + + +class JSONResponseParser: + """Handles parsing JSON responses from LLM outputs.""" + + @staticmethod + def parse_json_response(response_content: str) -> Dict: + """Extract and parse JSON from LLM response that might contain extra text.""" + content = response_content.strip() + + # Try to find a complete JSON object + brace_count = 0 + start_idx = -1 + end_idx = -1 + + for i, char in enumerate(content): + if char == '{': + if start_idx == -1: + start_idx = i + brace_count += 1 + elif char == '}': + brace_count -= 1 + if brace_count == 0 and start_idx != -1: + end_idx = i + 1 + break + + if start_idx != -1 and end_idx != -1: + json_content = content[start_idx:end_idx] + print(f"Extracted JSON: {json_content}") + return json.loads(json_content) + else: + print(f"No valid JSON found, trying full content: {content}") + return json.loads(content) class JockeyAgent: + """AI agent that plays the No More Jockeys game.""" + def __init__(self, player_id: int, model_name: str = "claude-3-5-sonnet-20241022"): + """Initialize the jockey agent with LLM client and system prompt.""" self.player_id = player_id - self.llm = ChatAnthropic( - model=model_name, - anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY"), - anthropic_api_url="https://api.helicone.ai/v1", + self.llm = LLMClientFactory.create_anthropic_client( + model_name=model_name, temperature=0.7, max_tokens=200, - default_headers={ - "Helicone-Auth": f"Bearer {os.environ.get('HELICONE_API_KEY')}", - "Helicone-Property-App": "no-more-jockeys", - "Helicone-Property-Player": f"player-{player_id}" - } + role="player", + player_id=player_id ) self.system_prompt = PLAYER_SYSTEM_PROMPT.format(player_id=player_id) @@ -93,7 +203,7 @@ def take_turn(self, game_state: GameState, feedback: str = None) -> dict: try: print(f"Raw response content: {response.content}") - move_data = _parse_json_response(response.content) + move_data = JSONResponseParser.parse_json_response(response.content) # Validate required fields if not all(key in move_data for key in ["person", "category", "reasoning"]): raise ValueError("Missing required fields") @@ -109,18 +219,15 @@ def take_turn(self, game_state: GameState, feedback: str = None) -> dict: } class ValidatorAgent: + """AI agent that validates moves and provides person information.""" + def __init__(self, model_name: str = "claude-3-5-sonnet-20241022"): - self.llm = ChatAnthropic( - model=model_name, - anthropic_api_key=os.environ.get("ANTHROPIC_API_KEY"), - anthropic_api_url="https://api.helicone.ai/v1", + """Initialize the validator agent with LLM client.""" + self.llm = LLMClientFactory.create_anthropic_client( + model_name=model_name, temperature=0.1, # Low temperature for consistency max_tokens=300, - default_headers={ - "Helicone-Auth": f"Bearer {os.environ.get('HELICONE_API_KEY')}", - "Helicone-Property-App": "no-more-jockeys", - "Helicone-Property-Role": "validator" - } + role="validator" ) def get_person_info(self, person: str) -> dict: @@ -132,7 +239,7 @@ def get_person_info(self, person: str) -> dict: response = self.llm.invoke(messages) try: - return _parse_json_response(response.content) + return JSONResponseParser.parse_json_response(response.content) except Exception as e: print(f"Error parsing person info: {e}") print(f"Person info response content was: '{response.content}'") @@ -164,7 +271,7 @@ def validate_move(self, person: str, banned_categories: list[dict]) -> tuple[boo response = self.llm.invoke(messages) try: - result = _parse_json_response(response.content) + result = JSONResponseParser.parse_json_response(response.content) return result["safe"], result.get("violations", []), result.get("explanations", {}) except Exception as e: print(f"Error parsing validation response: {e}") @@ -173,7 +280,15 @@ def validate_move(self, person: str, banned_categories: list[dict]) -> tuple[boo return True, [], {"error": f"Validation parsing failed: {str(e)}"} class GameOrchestrator: + """Orchestrates the No More Jockeys game between human and AI players.""" + def __init__(self, human_player_name: str = None, ai_retry_attempts: int = 2): + """Initialize the game orchestrator. + + Args: + human_player_name: Name of human player, if any + ai_retry_attempts: Number of retry attempts for AI players when invalid moves are made + """ self.human_player_name = human_player_name self.has_human = human_player_name is not None self.ai_retry_attempts = ai_retry_attempts # Number of retry attempts for AI players @@ -249,9 +364,7 @@ def play_turn(self, human_move: dict = None) -> dict: # Track retry attempts current_move = move_data - current_valid = is_valid current_violations = violations - current_explanations = explanations for retry_num in range(1, self.ai_retry_attempts + 1): # Generate feedback based on all previous attempts @@ -259,7 +372,7 @@ def play_turn(self, human_move: dict = None) -> dict: feedback = f"Your choice '{current_move['person']}' violated: {', '.join(current_violations)}. Choose someone else." else: # For multiple retries, provide comprehensive feedback - feedback = f"Multiple attempts failed. Choose a completely different person who does NOT fall into any banned categories." + feedback = "Multiple attempts failed. Choose a completely different person who does NOT fall into any banned categories." print(f"🔄 AI Player {current_player.id} attempting retry {retry_num}/{self.ai_retry_attempts}...") @@ -329,6 +442,7 @@ def play_turn(self, human_move: dict = None) -> dict: "waiting_for_human": False } - def _get_winner(self) -> int | None: + def _get_winner(self) -> Optional[int]: + """Get the ID of the winning player, if any.""" active = self.game_state.get_active_players() return active[0].id if len(active) == 1 else None diff --git a/backend/api/main.py b/backend/api/main.py index 4bff048..13da52d 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -5,7 +5,7 @@ from dotenv import load_dotenv from .agents import GameOrchestrator -# Load local environment variables from .env file for locl development +# Load environment variables (development only - production uses system env vars) load_dotenv() app = FastAPI() diff --git a/dev-backend.sh b/dev-backend.sh new file mode 100755 index 0000000..67407a1 --- /dev/null +++ b/dev-backend.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# ============================================================================= +# No More Jockeys - Backend Only Development +# ============================================================================= +# Use Case: Run backend locally for API testing/development +# Helicone: NO (direct Anthropic API) + +set -e + +echo "🎯 No More Jockeys - Backend Only Mode" +echo "=======================================" +echo "Backend: http://localhost:8000" +echo "Helicone: DISABLED (direct Anthropic API for development)" +echo "API Docs: http://localhost:8000/docs" +echo + +# Check environment +if [ ! -f ".env" ]; then + echo "❌ .env file not found. Please copy .env.example to .env and configure it." + exit 1 +fi + +if [ -z "$(grep ANTHROPIC_API_KEY .env | cut -d= -f2)" ]; then + echo "❌ ANTHROPIC_API_KEY not set in .env file" + exit 1 +fi + +# Load environment variables +export $(grep -v '^#' .env | xargs) + +# Unset Helicone to ensure direct API usage +unset HELICONE_API_KEY + +echo "✅ Environment configured" +echo " • Helicone: DISABLED" +echo + +# Start backend only +npm run dev:backend diff --git a/dev-hybrid.sh b/dev-hybrid.sh new file mode 100755 index 0000000..bb6b1b4 --- /dev/null +++ b/dev-hybrid.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# ============================================================================= +# No More Jockeys - Hybrid Development (Frontend → Production Backend) +# ============================================================================= +# Use Case: Run frontend locally connected to production backend +# Helicone: YES (production monitoring) + +set -e + +echo "🎯 No More Jockeys - Hybrid Development Mode" +echo "==============================================" +echo "Frontend: http://localhost:3000 → Backend: https://backend-pu7w8cumu-set4.vercel.app" +echo "Helicone: ENABLED (production backend requires monitoring)" +echo + +# Check environment +if [ ! -f ".env" ]; then + echo "❌ .env file not found. Please copy .env.example to .env and configure it." + exit 1 +fi + +# Load environment variables +export $(grep -v '^#' .env | xargs) + +# Set production backend URL +export NEXT_PUBLIC_API_URL=https://backend-pu7w8cumu-set4.vercel.app + +echo "✅ Environment configured" +echo " • API URL: $NEXT_PUBLIC_API_URL" +echo " • Helicone: ${HELICONE_API_KEY:+ENABLED}${HELICONE_API_KEY:-DISABLED}" +echo + +# Start frontend only +cd frontend && npm run dev diff --git a/dev-local.sh b/dev-local.sh new file mode 100755 index 0000000..93ae2e7 --- /dev/null +++ b/dev-local.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# ============================================================================= +# No More Jockeys - Local Development (Frontend → Local Backend) +# ============================================================================= +# Use Case: Run frontend locally connected to local backend +# Helicone: NO (direct Anthropic API) + +set -e + +echo "🎯 No More Jockeys - Local Development Mode" +echo "=============================================" +echo "Frontend: http://localhost:3000 → Backend: http://localhost:8000" +echo "Helicone: DISABLED (direct Anthropic API for development)" +echo + +# Check environment +if [ ! -f ".env" ]; then + echo "❌ .env file not found. Please copy .env.example to .env and configure it." + exit 1 +fi + +if [ -z "$(grep ANTHROPIC_API_KEY .env | cut -d= -f2)" ]; then + echo "❌ ANTHROPIC_API_KEY not set in .env file" + exit 1 +fi + +# Load environment variables +export $(grep -v '^#' .env | xargs) + +# Unset Helicone to ensure direct API usage +unset HELICONE_API_KEY + +# Ensure we're using local API URL +export NEXT_PUBLIC_API_URL=http://localhost:8000 + +echo "✅ Environment configured" +echo " • API URL: $NEXT_PUBLIC_API_URL" +echo " • Helicone: DISABLED" +echo + +# Start both services +npm run dev diff --git a/frontend/.gitignore b/frontend/.gitignore index e985853..5bbb991 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel .vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/frontend/pages/about.js b/frontend/pages/about.js index 3f245f9..b370def 100644 --- a/frontend/pages/about.js +++ b/frontend/pages/about.js @@ -4,9 +4,7 @@ export default function About() { return (
diff --git a/frontend/pages/index.js b/frontend/pages/index.js index 54a6a1d..5a09c39 100644 --- a/frontend/pages/index.js +++ b/frontend/pages/index.js @@ -14,18 +14,35 @@ export default function Home() { const [humanMove, setHumanMove] = useState({ person: '', category: '' }); const [darkMode, setDarkMode] = useState(false); - // Force fresh deployment - trigger rebuild - const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://backend-pu7w8cumu-set4.vercel.app'; + // API URL configuration based on environment + const getApiUrl = () => { + // 1. Explicit override via environment variable (highest priority) + if (process.env.NEXT_PUBLIC_API_URL) { + return process.env.NEXT_PUBLIC_API_URL; + } + + // 2. Auto-detect based on NODE_ENV + if (process.env.NODE_ENV === 'production') { + return 'https://backend-pu7w8cumu-set4.vercel.app'; + } + + // 3. Default to local development + return 'http://localhost:8000'; + }; + + const API_URL = getApiUrl(); const createGame = async (withHuman = false, playerName = '') => { setLoading(true); try { + const requestBody = withHuman + ? { human_player_name: playerName || 'You' } + : {}; + const res = await fetch(`${API_URL}/api/game/create`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - human_player_name: withHuman ? (playerName || 'You') : null - }), + body: JSON.stringify(requestBody), }); const data = await res.json(); @@ -160,9 +177,7 @@ export default function Home() {

No More Jockeys - AI Battle

Watch AI agents compete or join the battle yourself!

diff --git a/package.json b/package.json index e77881f..ca3a752 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dev": "npm run install:check && concurrently \"npm:dev:backend\" \"npm:dev:frontend\"", "dev:backend": "cd backend && (test -d venv || python3 -m venv venv) && (. venv/bin/activate && pip install -q -r requirements.txt && uvicorn api.main:app --reload --port 8000)", "dev:frontend": "cd frontend && (test -d node_modules || npm install) && npm run dev", + "dev:frontend-prod": "cd frontend && (test -d node_modules || npm install) && NEXT_PUBLIC_API_URL=https://backend-pu7w8cumu-set4.vercel.app npm run dev", "install": "npm-run-all install:backend install:frontend", "install:check": "npm run install:backend:check && npm run install:frontend:check", "install:backend": "cd backend && python3 -m venv venv && . venv/bin/activate && pip install -r requirements.txt", diff --git a/start.sh b/start.sh deleted file mode 100755 index 9f7fe11..0000000 --- a/start.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash - -# No More Jockeys - Unified Startup Script -# Starts both backend and frontend simultaneously - -set -e - -echo "🎯 No More Jockeys - Starting Full Stack Application" -echo "==================================================" - -# Check if we're in the right directory -if [ ! -f "backend/requirements.txt" ] || [ ! -f "frontend/package.json" ]; then - echo "❌ Please run this script from the project root directory" - exit 1 -fi - -# Check if ANTHROPIC_API_KEY is set -if [ -z "$ANTHROPIC_API_KEY" ]; then - echo "❌ ANTHROPIC_API_KEY environment variable is not set" - echo "Please set it with: export ANTHROPIC_API_KEY='your-key-here'" - exit 1 -fi - -# Function to cleanup background processes on exit -cleanup() { - echo "" - echo "🛑 Shutting down services..." - if [ ! -z "$BACKEND_PID" ]; then - kill -TERM $BACKEND_PID 2>/dev/null || true - fi - if [ ! -z "$FRONTEND_PID" ]; then - kill -TERM $FRONTEND_PID 2>/dev/null || true - fi - exit 0 -} - -# Set up cleanup trap -trap cleanup SIGINT SIGTERM - -# Backend Setup -echo "🔧 Setting up backend..." -cd backend - -# Create virtual environment if it doesn't exist -if [ ! -d "venv" ]; then - echo "📦 Creating Python virtual environment..." - python -m venv venv -fi - -# Activate virtual environment and install dependencies -echo "📥 Installing backend dependencies..." -source venv/bin/activate -pip install -r requirements.txt > /dev/null 2>&1 - -# Start backend in background -echo "🚀 Starting FastAPI backend on http://localhost:8000" -uvicorn api.main:app --reload --port 8000 > ../backend.log 2>&1 & -BACKEND_PID=$! - -cd .. - -# Frontend Setup -echo "🔧 Setting up frontend..." -cd frontend - -# Install dependencies if node_modules doesn't exist -if [ ! -d "node_modules" ]; then - echo "📦 Installing frontend dependencies..." - npm install > /dev/null 2>&1 -fi - -# Start frontend in background -echo "🚀 Starting Next.js frontend on http://localhost:3000" -npm run dev > ../frontend.log 2>&1 & -FRONTEND_PID=$! - -cd .. - -# Wait for services to start -echo "⏳ Waiting for services to start..." -sleep 3 - -# Check if services are running -if kill -0 $BACKEND_PID 2>/dev/null && kill -0 $FRONTEND_PID 2>/dev/null; then - echo "" - echo "✅ All services started successfully!" - echo "" - echo "🌐 Frontend: http://localhost:3000" - echo "🔧 Backend API: http://localhost:8000" - echo "📊 API Docs: http://localhost:8000/docs" - echo "" - echo "📝 Logs:" - echo " Backend: tail -f backend.log" - echo " Frontend: tail -f frontend.log" - echo "" - echo "Press Ctrl+C to stop all services" - echo "" - - # Wait for user interrupt - wait -else - echo "❌ Failed to start services. Check logs:" - echo "Backend log:" - cat backend.log 2>/dev/null || echo "No backend log found" - echo "Frontend log:" - cat frontend.log 2>/dev/null || echo "No frontend log found" - cleanup -fi diff --git a/start_backend.sh b/start_backend.sh deleted file mode 100755 index 6181822..0000000 --- a/start_backend.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# No More Jockeys Backend Startup Script - -echo "🎯 Starting No More Jockeys Backend..." - -# Check if we're in the right directory -if [ ! -f "backend/requirements.txt" ]; then - echo "❌ Please run this script from the project root directory" - exit 1 -fi - -# Check if ANTHROPIC_API_KEY is set -if [ -z "$ANTHROPIC_API_KEY" ]; then - echo "❌ ANTHROPIC_API_KEY environment variable is not set" - echo "Please set it with: export ANTHROPIC_API_KEY='your-key-here'" - exit 1 -fi - -# Navigate to backend directory -cd backend - -# Check if virtual environment exists -if [ ! -d "venv" ]; then - echo "📦 Creating virtual environment..." - python -m venv venv -fi - -# Activate virtual environment -echo "🔧 Activating virtual environment..." -source venv/bin/activate - -# Install dependencies -echo "📥 Installing dependencies..." -pip install -r requirements.txt - -# Start the server -echo "🚀 Starting FastAPI server on http://localhost:8000" -echo "📊 API docs available at http://localhost:8000/docs" -uvicorn api.main:app --reload --port 8000 diff --git a/start_frontend.sh b/start_frontend.sh deleted file mode 100755 index 4289745..0000000 --- a/start_frontend.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# No More Jockeys Frontend Startup Script - -echo "🎮 Starting No More Jockeys Frontend..." - -# Check if we're in the right directory -if [ ! -f "frontend/package.json" ]; then - echo "❌ Please run this script from the project root directory" - exit 1 -fi - -# Navigate to frontend directory -cd frontend - -# Check if node_modules exists -if [ ! -d "node_modules" ]; then - echo "📦 Installing dependencies..." - npm install -fi - -# Start the development server -echo "🚀 Starting Next.js development server on http://localhost:3000" -npm run dev From 30b18063ca8bb727f91f002368ff0ecbe7cacfa4 Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 16:51:12 +0100 Subject: [PATCH 2/9] feat: Add comprehensive logging for debugging production 500 errors - Added detailed logging throughout agents.py initialization - Added environment detection and API key status logging - Added error handling with detailed error messages in main.py - Added try/catch blocks around GameOrchestrator creation - Logs show exactly when/where agent initialization fails - Will help identify root cause of Vercel 500 errors --- backend/api/agents.py | 197 ++++++++++++++++++++++++++---------------- backend/api/main.py | 78 ++++++++++++----- 2 files changed, 178 insertions(+), 97 deletions(-) diff --git a/backend/api/agents.py b/backend/api/agents.py index 192497e..e662608 100644 --- a/backend/api/agents.py +++ b/backend/api/agents.py @@ -2,9 +2,11 @@ from langchain_core.messages import SystemMessage, HumanMessage import json import os +import logging from typing import Dict, List, Optional from enum import Enum from dataclasses import dataclass +from datetime import datetime from .prompts import ( PLAYER_SYSTEM_PROMPT, PLAYER_TURN_PROMPT, @@ -13,7 +15,10 @@ VALIDATOR_SYSTEM_PROMPT ) from .game_state import GameState, Move, Player -from datetime import datetime + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) class Environment(Enum): @@ -55,39 +60,57 @@ def create_anthropic_client( player_id: Optional[int] = None ) -> ChatAnthropic: """Create ChatAnthropic client with environment-appropriate configuration.""" - anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") - if not anthropic_api_key: - raise ValueError("ANTHROPIC_API_KEY environment variable is required") + logger.info(f"Creating LLM client for role: {role}, player: {player_id}") - is_production = ProductionDetector.is_production() + anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") helicone_key = os.environ.get('HELICONE_API_KEY') + is_production = ProductionDetector.is_production() - # Production: Helicone required - if is_production: - if not helicone_key or helicone_key == 'your_helicone_key_here': - raise ValueError( - "HELICONE_API_KEY is required in production environment. " - "Please set it in your deployment environment variables." - ) - return LLMClientFactory._create_helicone_client( - model_name, temperature, max_tokens, anthropic_api_key, - helicone_key, role, player_id - ) + logger.info(f"Environment check - Production: {is_production}") + logger.info(f"API Keys - Anthropic: {'SET' if anthropic_api_key else 'MISSING'}, " + f"Helicone: {'SET' if helicone_key else 'MISSING'}") - # Development: Helicone optional - if helicone_key and helicone_key != 'your_helicone_key_here': - return LLMClientFactory._create_helicone_client( - model_name, temperature, max_tokens, anthropic_api_key, - helicone_key, role, player_id - ) + if not anthropic_api_key: + error_msg = "ANTHROPIC_API_KEY environment variable is required" + logger.error(error_msg) + raise ValueError(error_msg) - # Development: Direct Anthropic API - return ChatAnthropic( - model=model_name, - anthropic_api_key=anthropic_api_key, - temperature=temperature, - max_tokens=max_tokens - ) + try: + # Production: Helicone required + if is_production: + logger.info("Production mode: Helicone monitoring required") + if not helicone_key or helicone_key == 'your_helicone_key_here': + error_msg = "HELICONE_API_KEY is required in production environment" + logger.error(error_msg) + raise ValueError(error_msg) + + logger.info("Creating Helicone-enabled client for production") + return LLMClientFactory._create_helicone_client( + model_name, temperature, max_tokens, anthropic_api_key, + helicone_key, role, player_id + ) + + # Development: Helicone optional + if helicone_key and helicone_key != 'your_helicone_key_here': + logger.info("Development mode: Using Helicone monitoring") + return LLMClientFactory._create_helicone_client( + model_name, temperature, max_tokens, anthropic_api_key, + helicone_key, role, player_id + ) + + # Development: Direct Anthropic API + logger.info("Development mode: Using direct Anthropic API") + return ChatAnthropic( + model=model_name, + anthropic_api_key=anthropic_api_key, + temperature=temperature, + max_tokens=max_tokens + ) + + except Exception as e: + logger.error(f"Failed to create LLM client: {str(e)}") + logger.error(f"Environment: {dict(os.environ)}") + raise @staticmethod def _create_helicone_client( @@ -157,15 +180,22 @@ class JockeyAgent: def __init__(self, player_id: int, model_name: str = "claude-3-5-sonnet-20241022"): """Initialize the jockey agent with LLM client and system prompt.""" - self.player_id = player_id - self.llm = LLMClientFactory.create_anthropic_client( - model_name=model_name, - temperature=0.7, - max_tokens=200, - role="player", - player_id=player_id - ) - self.system_prompt = PLAYER_SYSTEM_PROMPT.format(player_id=player_id) + logger.info(f"Initializing JockeyAgent for player {player_id}") + + try: + self.player_id = player_id + self.llm = LLMClientFactory.create_anthropic_client( + model_name=model_name, + temperature=0.7, + max_tokens=200, + role="player", + player_id=player_id + ) + self.system_prompt = PLAYER_SYSTEM_PROMPT.format(player_id=player_id) + logger.info(f"Successfully initialized JockeyAgent for player {player_id}") + except Exception as e: + logger.error(f"Failed to initialize JockeyAgent for player {player_id}: {str(e)}") + raise def take_turn(self, game_state: GameState, feedback: str = None) -> dict: """Generate a move based on current game state. @@ -223,12 +253,19 @@ class ValidatorAgent: def __init__(self, model_name: str = "claude-3-5-sonnet-20241022"): """Initialize the validator agent with LLM client.""" - self.llm = LLMClientFactory.create_anthropic_client( - model_name=model_name, - temperature=0.1, # Low temperature for consistency - max_tokens=300, - role="validator" - ) + logger.info("Initializing ValidatorAgent") + + try: + self.llm = LLMClientFactory.create_anthropic_client( + model_name=model_name, + temperature=0.1, # Low temperature for consistency + max_tokens=300, + role="validator" + ) + logger.info("Successfully initialized ValidatorAgent") + except Exception as e: + logger.error(f"Failed to initialize ValidatorAgent: {str(e)}") + raise def get_person_info(self, person: str) -> dict: """Get comprehensive info about a person""" @@ -289,38 +326,48 @@ def __init__(self, human_player_name: str = None, ai_retry_attempts: int = 2): human_player_name: Name of human player, if any ai_retry_attempts: Number of retry attempts for AI players when invalid moves are made """ - self.human_player_name = human_player_name - self.has_human = human_player_name is not None - self.ai_retry_attempts = ai_retry_attempts # Number of retry attempts for AI players - - if self.has_human: - # Human is player 1, AI agents are 2-4 - self.agents = { - i: JockeyAgent(player_id=i) for i in range(2, 5) - } - self.game_state = GameState( - players=[ - Player(id=1, name=human_player_name, is_human=True), - Player(id=2, name="Claude-2", is_human=False), - Player(id=3, name="Claude-3", is_human=False), - Player(id=4, name="Claude-4", is_human=False) - ], - banned_categories=[], - moves=[] - ) - else: - # All AI agents - self.agents = { - i: JockeyAgent(player_id=i) for i in range(1, 5) - } - self.game_state = GameState( - players=[Player(id=i, name=f"Claude-{i}", is_human=False) for i in range(1, 5)], - banned_categories=[], - moves=[] - ) + logger.info(f"Initializing GameOrchestrator with human player: {human_player_name}") - self.validator = ValidatorAgent() - self.pending_human_turn = False + try: + self.human_player_name = human_player_name + self.has_human = human_player_name is not None + self.ai_retry_attempts = ai_retry_attempts # Number of retry attempts for AI players + + if self.has_human: + logger.info("Setting up game with human player") + # Human is player 1, AI agents are 2-4 + self.agents = { + i: JockeyAgent(player_id=i) for i in range(2, 5) + } + self.game_state = GameState( + players=[ + Player(id=1, name=human_player_name, is_human=True), + Player(id=2, name="Claude-2", is_human=False), + Player(id=3, name="Claude-3", is_human=False), + Player(id=4, name="Claude-4", is_human=False) + ], + banned_categories=[], + moves=[] + ) + else: + logger.info("Setting up AI-only game") + # All AI agents + self.agents = { + i: JockeyAgent(player_id=i) for i in range(1, 5) + } + self.game_state = GameState( + players=[Player(id=i, name=f"Claude-{i}", is_human=False) for i in range(1, 5)], + banned_categories=[], + moves=[] + ) + + self.validator = ValidatorAgent() + self.pending_human_turn = False + logger.info("Successfully initialized GameOrchestrator") + + except Exception as e: + logger.error(f"Failed to initialize GameOrchestrator: {str(e)}") + raise def play_turn(self, human_move: dict = None) -> dict: """Execute one turn of the game""" diff --git a/backend/api/main.py b/backend/api/main.py index 13da52d..c8c0b2d 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -2,12 +2,25 @@ from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import uuid +import logging from dotenv import load_dotenv -from .agents import GameOrchestrator + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) # Load environment variables (development only - production uses system env vars) load_dotenv() +logger.info("Starting FastAPI application...") + +try: + from .agents import GameOrchestrator + logger.info("Successfully imported GameOrchestrator") +except Exception as e: + logger.error(f"Failed to import GameOrchestrator: {str(e)}") + raise + app = FastAPI() # CORS configuration - allow all origins @@ -37,33 +50,54 @@ class HumanMoveRequest(BaseModel): @app.post("/api/game/create") async def create_game(request: CreateGameRequest = CreateGameRequest()): """Create a new game instance""" - game_id = str(uuid.uuid4()) - # Create game orchestrator - orchestrator = GameOrchestrator( - human_player_name=request.human_player_name - ) - orchestrator.game_state.game_id = game_id + logger.info(f"Creating new game with human player: {request.human_player_name}") - games[game_id] = orchestrator - - return { - "game_id": game_id, - "game_state": orchestrator.game_state.to_dict(), - "has_human": orchestrator.has_human - } + try: + game_id = str(uuid.uuid4()) + logger.info(f"Generated game ID: {game_id}") + + # Create game orchestrator + orchestrator = GameOrchestrator( + human_player_name=request.human_player_name + ) + orchestrator.game_state.game_id = game_id + + games[game_id] = orchestrator + logger.info(f"Successfully created game {game_id}") + + return { + "game_id": game_id, + "game_state": orchestrator.game_state.to_dict(), + "has_human": orchestrator.has_human + } + + except Exception as e: + logger.error(f"Failed to create game: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to create game: {str(e)}") @app.post("/api/game/turn") async def play_turn(action: GameAction): """Play one turn of the game""" - if action.game_id not in games: - raise HTTPException(status_code=404, detail="Game not found") - - orchestrator = games[action.game_id] + logger.info(f"Playing turn for game {action.game_id}") - # Play turn using orchestrator - result = orchestrator.play_turn() - - return result + try: + if action.game_id not in games: + logger.error(f"Game {action.game_id} not found") + raise HTTPException(status_code=404, detail="Game not found") + + orchestrator = games[action.game_id] + + # Play turn using orchestrator + result = orchestrator.play_turn() + logger.info(f"Successfully played turn for game {action.game_id}") + + return result + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to play turn for game {action.game_id}: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to play turn: {str(e)}") @app.get("/api/game/{game_id}/state") async def get_game_state(game_id: str): From 31301e7531e232131aa8b69997c51c5b8f85ccca Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 17:03:15 +0100 Subject: [PATCH 3/9] get rid of handler --- backend/api/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/api/main.py b/backend/api/main.py index c8c0b2d..559782f 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -127,5 +127,4 @@ async def make_human_move(request: HumanMoveRequest): return result -# For Vercel -handler = app +# FastAPI app instance exported for Vercel ASGI deployment From a1beec7d9dd3f6b866bf962aedb6c9a6c1b7a796 Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 17:10:59 +0100 Subject: [PATCH 4/9] magnum --- backend/api/main.py | 8 +++++++- backend/requirements.txt | 1 + backend/vercel.json | 12 ++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/backend/api/main.py b/backend/api/main.py index 559782f..35f97cd 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -4,6 +4,7 @@ import uuid import logging from dotenv import load_dotenv +from mangum import Mangum # Configure logging logging.basicConfig(level=logging.INFO) @@ -127,4 +128,9 @@ async def make_human_move(request: HumanMoveRequest): return result -# FastAPI app instance exported for Vercel ASGI deployment +# Create Mangum handler for Vercel/Lambda deployment +handler = Mangum(app, lifespan="off") + +# Also export the FastAPI app for local development +# Export both for maximum compatibility +application = app diff --git a/backend/requirements.txt b/backend/requirements.txt index 282ab01..98fe1fb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,3 +4,4 @@ uvicorn==0.24.0 python-multipart==0.0.6 anthropic>=0.17.0,<1 python-dotenv==1.0.0 +mangum==0.17.0 diff --git a/backend/vercel.json b/backend/vercel.json index f757bc1..f98b22b 100644 --- a/backend/vercel.json +++ b/backend/vercel.json @@ -3,7 +3,10 @@ "builds": [ { "src": "api/main.py", - "use": "@vercel/python" + "use": "@vercel/python", + "config": { + "maxLambdaSize": "15mb" + } } ], "routes": [ @@ -11,5 +14,10 @@ "src": "/(.*)", "dest": "/api/main.py" } - ] + ], + "functions": { + "api/main.py": { + "maxDuration": 60 + } + } } From a2be846e6707c794f431f2602a6a4b2df87ff243 Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 17:14:02 +0100 Subject: [PATCH 5/9] magnum --- backend/vercel.json | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/backend/vercel.json b/backend/vercel.json index f98b22b..f757bc1 100644 --- a/backend/vercel.json +++ b/backend/vercel.json @@ -3,10 +3,7 @@ "builds": [ { "src": "api/main.py", - "use": "@vercel/python", - "config": { - "maxLambdaSize": "15mb" - } + "use": "@vercel/python" } ], "routes": [ @@ -14,10 +11,5 @@ "src": "/(.*)", "dest": "/api/main.py" } - ], - "functions": { - "api/main.py": { - "maxDuration": 60 - } - } + ] } From 1b17bcdfb24d4cef5a9b4145cff9007e1d83b1f9 Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 17:23:54 +0100 Subject: [PATCH 6/9] remove magnum --- backend/api/main.py | 21 +++++++++++++-------- backend/requirements.txt | 1 - 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/backend/api/main.py b/backend/api/main.py index 35f97cd..ca6f563 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -4,7 +4,6 @@ import uuid import logging from dotenv import load_dotenv -from mangum import Mangum # Configure logging logging.basicConfig(level=logging.INFO) @@ -28,11 +27,22 @@ app.add_middleware( CORSMiddleware, allow_origins=["*"], - allow_credentials=True, + allow_credentials=False, # Cannot be True with allow_origins=["*"] allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["*"], ) +# Health check endpoints +@app.get("/") +async def root(): + """Root endpoint for health check""" + return {"message": "FastAPI backend is running", "status": "healthy"} + +@app.get("/api/health") +async def health_check(): + """API health check endpoint""" + return {"message": "API is healthy", "status": "ok"} + # Simple in-memory game storage games = {} @@ -128,9 +138,4 @@ async def make_human_move(request: HumanMoveRequest): return result -# Create Mangum handler for Vercel/Lambda deployment -handler = Mangum(app, lifespan="off") - -# Also export the FastAPI app for local development -# Export both for maximum compatibility -application = app +# FastAPI app is automatically detected by Vercel for ASGI deployment diff --git a/backend/requirements.txt b/backend/requirements.txt index 98fe1fb..282ab01 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,4 +4,3 @@ uvicorn==0.24.0 python-multipart==0.0.6 anthropic>=0.17.0,<1 python-dotenv==1.0.0 -mangum==0.17.0 From 851071b74cc195092b79b9023e59094f75c52028 Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 17:47:55 +0100 Subject: [PATCH 7/9] readme --- README.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7780599..510e415 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # No More Jockeys -Multi-LLM No More Jockeys game with FastAPI backend and Next.js frontend. +Multi-LLM No More Jockeys game implementation with separate backend and frontend deployments. + +## Architecture + +- **Backend**: FastAPI Python application (`/backend`) +- **Frontend**: Next.js React application (`/frontend`) ## Quick Start @@ -10,7 +15,9 @@ cp .env.example .env ./dev-local.sh ``` -## Development Modes +## Development + +### Development Modes ```bash ./dev-local.sh # Frontend + Backend locally @@ -18,7 +25,18 @@ cp .env.example .env ./dev-backend.sh # Backend only ``` -## Environment +### Alternative Development Commands + +```bash +# Install dependencies and start both services +npm run dev + +# Or start individually: +npm run dev:backend # Starts on http://localhost:8000 +npm run dev:frontend # Starts on http://localhost:3000 +``` + +### Environment Edit `.env`: ```bash @@ -29,10 +47,58 @@ HELICONE_API_KEY=optional_dev_key # Required in production ## Deployment -Set in Vercel dashboard: -- **Backend**: `ANTHROPIC_API_KEY`, `HELICONE_API_KEY` (required in production) -- **Frontend**: `NEXT_PUBLIC_API_URL` +This project uses GitHub Actions to automatically deploy to Vercel when changes are pushed to the main branch. + +### Setup GitHub Actions Deployment + +1. **Create Vercel Projects**: + - Create separate Vercel projects for backend and frontend + - Note the project IDs from each project's settings + +2. **Get Vercel Credentials**: + ```bash + # Install Vercel CLI + npm i -g vercel + + # Login and get your tokens + vercel login + vercel --scope your-team-name + ``` + +3. **Add GitHub Secrets**: + Go to your GitHub repository → Settings → Secrets and variables → Actions, and add: + - `VERCEL_TOKEN`: Your Vercel token + - `VERCEL_ORG_ID`: Your Vercel organization/team ID + - `VERCEL_BACKEND_PROJECT_ID`: Backend project ID from Vercel + - `VERCEL_FRONTEND_PROJECT_ID`: Frontend project ID from Vercel + +4. **Manual Deployment** (if needed): + ```bash + # Deploy backend + cd backend && vercel --prod + + # Deploy frontend + cd frontend && vercel --prod + ``` + +### Environment Variables + +For production deployments, set these in your Vercel project settings: + +**Backend**: +- `ANTHROPIC_API_KEY`: Your Anthropic API key (required) +- `HELICONE_API_KEY`: Your Helicone API key (required in production) + +**Frontend**: +- `NEXT_PUBLIC_API_URL`: Your backend Vercel URL ## Game Rules -Players take turns naming a person and declaring a category they belong to. That category becomes banned. Don't name anyone from banned categories or you're eliminated. \ No newline at end of file +No More Jockeys is a game where players take turns naming a person and a category that person belongs to. That category becomes banned. Don't name anyone from banned categories or you're eliminated. + +## Tech Stack + +- **Backend**: FastAPI, Python, Anthropic Claude API +- **Frontend**: Next.js, React +- **Deployment**: Vercel with GitHub Actions +- **Development**: Concurrent local development setup \ No newline at end of file From 017cf9ade79dc4e19bce392c4153c898f7a16d1a Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 17:55:33 +0100 Subject: [PATCH 8/9] make helicone optional --- README.md | 2 +- backend/api/agents.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 510e415..79b074b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ npm run dev:frontend # Starts on http://localhost:3000 Edit `.env`: ```bash ANTHROPIC_API_KEY=your_api_key_here -HELICONE_API_KEY=optional_dev_key # Required in production +HELICONE_API_KEY=optional_monitoring_key # Optional - adds observability # NEXT_PUBLIC_API_URL=optional_override ``` diff --git a/backend/api/agents.py b/backend/api/agents.py index e662608..b5d7cc5 100644 --- a/backend/api/agents.py +++ b/backend/api/agents.py @@ -76,13 +76,20 @@ def create_anthropic_client( raise ValueError(error_msg) try: - # Production: Helicone required + # Production: Helicone optional for now (with warning) if is_production: - logger.info("Production mode: Helicone monitoring required") + logger.info("Production mode: Helicone monitoring recommended") if not helicone_key or helicone_key == 'your_helicone_key_here': - error_msg = "HELICONE_API_KEY is required in production environment" - logger.error(error_msg) - raise ValueError(error_msg) + logger.warning("HELICONE_API_KEY not set in production - monitoring disabled") + logger.warning("For better observability, consider setting HELICONE_API_KEY") + # Fall back to direct Anthropic API + logger.info("Using direct Anthropic API in production (no monitoring)") + return ChatAnthropic( + model=model_name, + anthropic_api_key=anthropic_api_key, + temperature=temperature, + max_tokens=max_tokens + ) logger.info("Creating Helicone-enabled client for production") return LLMClientFactory._create_helicone_client( From 5a38987177de61c7ddaa8fc4cb18a57cecbc729a Mon Sep 17 00:00:00 2001 From: eesanoble Date: Thu, 10 Jul 2025 18:40:03 +0100 Subject: [PATCH 9/9] add be url --- frontend/pages/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/index.js b/frontend/pages/index.js index 5a09c39..f2269b6 100644 --- a/frontend/pages/index.js +++ b/frontend/pages/index.js @@ -23,7 +23,7 @@ export default function Home() { // 2. Auto-detect based on NODE_ENV if (process.env.NODE_ENV === 'production') { - return 'https://backend-pu7w8cumu-set4.vercel.app'; + return 'https://backend-set4.vercel.app'; } // 3. Default to local development