| # Deployment Guide for Job Application Agent |
|
|
| ## Option 1: LangGraph Cloud (Easiest & Recommended) |
|
|
| ### Prerequisites |
| - LangGraph CLI installed (`langgraph-cli` in requirements.txt) |
| - `langgraph.json` already configured ✅ |
|
|
| ### Steps |
|
|
| 1. **Install LangGraph CLI** (if not already): |
| ```powershell |
| pip install langgraph-cli |
| ``` |
|
|
| 2. **Login to LangGraph Cloud**: |
| ```powershell |
| langgraph login |
| ``` |
|
|
| 3. **Deploy your agent**: |
| ```powershell |
| langgraph deploy |
| ``` |
|
|
| 4. **Get your API endpoint** - LangGraph Cloud provides a REST API automatically |
|
|
| ### Cost |
| - **Free tier**: Limited requests/month |
| - **Paid**: Pay-per-use pricing |
|
|
| ### Pros |
| - ✅ Zero infrastructure management |
| - ✅ Built-in state persistence |
| - ✅ Automatic API generation |
| - ✅ LangSmith integration |
| - ✅ Perfect for LangGraph apps |
|
|
| ### Cons |
| - ⚠️ Vendor lock-in |
| - ⚠️ Limited customization |
|
|
| --- |
|
|
| ## Option 2: Railway.app (Simple & Cheap) |
|
|
| ### Steps |
|
|
| 1. **Create a FastAPI wrapper** (create `api.py`): |
| ```python |
| from fastapi import FastAPI, File, UploadFile |
| from job_writing_agent.workflow import JobWorkflow |
| import tempfile |
| import os |
| |
| app = FastAPI() |
| |
| @app.post("/generate") |
| async def generate_application( |
| resume: UploadFile = File(...), |
| job_description: str, |
| content_type: str = "cover_letter" |
| ): |
| # Save resume temporarily |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: |
| tmp.write(await resume.read()) |
| resume_path = tmp.name |
| |
| try: |
| workflow = JobWorkflow( |
| resume=resume_path, |
| job_description_source=job_description, |
| content=content_type |
| ) |
| result = await workflow.run() |
| return {"result": result} |
| finally: |
| os.unlink(resume_path) |
| ``` |
|
|
| 2. **Create `Procfile`**: |
| ``` |
| web: uvicorn api:app --host 0.0.0.0 --port $PORT |
| ``` |
|
|
| 3. **Deploy to Railway**: |
| - Sign up at [railway.app](https://railway.app) |
| - Connect GitHub repo |
| - Railway auto-detects Python and runs `Procfile` |
|
|
| ### Cost |
| - **Free tier**: $5 credit/month |
| - **Hobby**: $5/month for 512MB RAM |
| - **Pro**: $20/month for 2GB RAM |
|
|
| ### Pros |
| - ✅ Very simple deployment |
| - ✅ Auto-scaling |
| - ✅ Free tier available |
| - ✅ Automatic HTTPS |
|
|
| ### Cons |
| - ⚠️ Need to add FastAPI wrapper |
| - ⚠️ State management needs Redis/Postgres |
|
|
| --- |
|
|
| ## Option 3: Render.com (Similar to Railway) |
|
|
| ### Steps |
|
|
| 1. **Create `render.yaml`**: |
| ```yaml |
| services: |
| - type: web |
| name: job-writer-api |
| env: python |
| buildCommand: pip install -r requirements.txt |
| startCommand: uvicorn api:app --host 0.0.0.0 --port $PORT |
| envVars: |
| - key: OPENROUTER_API_KEY |
| sync: false |
| - key: TAVILY_API_KEY |
| sync: false |
| ``` |
|
|
| 2. **Deploy**: |
| - Connect GitHub repo to Render |
| - Render auto-detects `render.yaml` |
|
|
| ### Cost |
| - **Free tier**: 750 hours/month (sleeps after 15min inactivity) |
| - **Starter**: $7/month (always on) |
|
|
| ### Pros |
| - ✅ Free tier for testing |
| - ✅ Simple YAML config |
| - ✅ Auto-deploy from Git |
|
|
| ### Cons |
| - ⚠️ Free tier sleeps (cold starts) |
| - ⚠️ Need FastAPI wrapper |
|
|
| --- |
|
|
| ## Option 4: Fly.io (Good Free Tier) |
|
|
| ### Steps |
|
|
| 1. **Install Fly CLI**: |
| ```powershell |
| iwr https://fly.io/install.ps1 -useb | iex |
| ``` |
|
|
| 2. **Create `Dockerfile`**: |
| ```dockerfile |
| FROM python:3.12-slim |
| |
| WORKDIR /app |
| COPY requirements.txt . |
| RUN pip install --no-cache-dir -r requirements.txt |
| |
| COPY . . |
| |
| CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8080"] |
| ``` |
|
|
| 3. **Deploy**: |
| ```powershell |
| fly launch |
| fly deploy |
| ``` |
|
|
| ### Cost |
| - **Free tier**: 3 shared-cpu VMs, 3GB storage |
| - **Paid**: $1.94/month per VM |
|
|
| ### Pros |
| - ✅ Generous free tier |
| - ✅ Global edge deployment |
| - ✅ Docker-based (flexible) |
|
|
| ### Cons |
| - ⚠️ Need Docker knowledge |
| - ⚠️ Need FastAPI wrapper |
|
|
| --- |
|
|
| ## Option 5: AWS Lambda (Serverless - Pay Per Use) |
|
|
| ### Steps |
|
|
| 1. **Create Lambda handler** (`lambda_handler.py`): |
| ```python |
| import json |
| from job_writing_agent.workflow import JobWorkflow |
| |
| def lambda_handler(event, context): |
| # Parse event |
| body = json.loads(event['body']) |
| |
| workflow = JobWorkflow( |
| resume=body['resume_path'], |
| job_description_source=body['job_description'], |
| content=body.get('content_type', 'cover_letter') |
| ) |
| |
| result = workflow.run() |
| |
| return { |
| 'statusCode': 200, |
| 'body': json.dumps({'result': result}) |
| } |
| ``` |
|
|
| 2. **Package and deploy** using AWS SAM or Serverless Framework |
|
|
| ### Cost |
| - **Free tier**: 1M requests/month |
| - **Paid**: $0.20 per 1M requests + compute time |
|
|
| ### Pros |
| - ✅ Pay only for usage |
| - ✅ Auto-scaling |
| - ✅ Very cheap for low traffic |
|
|
| ### Cons |
| - ⚠️ 15min timeout limit |
| - ⚠️ Cold starts |
| - ⚠️ Complex setup |
| - ⚠️ Need to handle state externally |
|
|
| --- |
|
|
| ## Recommendation |
|
|
| **For your use case, I recommend:** |
|
|
| 1. **Start with LangGraph Cloud** - Easiest, built for your stack |
| 2. **If you need more control → Railway** - Simple, good free tier |
| 3. **If you need serverless → AWS Lambda** - Cheapest for low traffic |
|
|
| --- |
|
|
| ## Quick Start: FastAPI Wrapper (for Railway/Render/Fly.io) |
|
|
| Create `api.py` in your project root: |
|
|
| ```python |
| from fastapi import FastAPI, File, UploadFile, HTTPException |
| from fastapi.responses import JSONResponse |
| from job_writing_agent.workflow import JobWorkflow |
| import tempfile |
| import os |
| import asyncio |
| |
| app = FastAPI(title="Job Application Writer API") |
| |
| @app.get("/") |
| def health(): |
| return {"status": "ok"} |
| |
| @app.post("/generate") |
| async def generate_application( |
| resume: UploadFile = File(...), |
| job_description: str, |
| content_type: str = "cover_letter" |
| ): |
| """Generate job application material.""" |
| # Save resume temporarily |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: |
| content = await resume.read() |
| tmp.write(content) |
| resume_path = tmp.name |
| |
| try: |
| workflow = JobWorkflow( |
| resume=resume_path, |
| job_description_source=job_description, |
| content=content_type |
| ) |
| |
| # Run workflow (assuming it's async or can be wrapped) |
| result = await asyncio.to_thread(workflow.run) |
| |
| return JSONResponse({ |
| "status": "success", |
| "result": result |
| }) |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=str(e)) |
| finally: |
| # Cleanup |
| if os.path.exists(resume_path): |
| os.unlink(resume_path) |
| |
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run(app, host="0.0.0.0", port=8000) |
| ``` |
|
|
| Then update `requirements.txt` to ensure FastAPI and uvicorn are included (they already are ✅). |
|
|
|
|