CoolFace
Apppublic

jessejohnson/plg4-dev-server

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
app.py194 linesDownload Raw Back to backend
1from backend.utils.request_dto.chat_response import ChatResponse2from backend.utils.request_dto.scrape_request import ScrapeRequest3from backend.utils.types import ChatMessage4from fastapi import FastAPI, HTTPException, BackgroundTasks, Header5from fastapi.middleware.cors import CORSMiddleware6import os7from typing import Type8from fastapi.middleware.cors import CORSMiddleware9from data_minning.dto.stream_opts import StreamOptions10from data_minning.base_scrapper import BaseRecipeScraper, JsonArraySink, MongoSink11from data_minning.all_nigerian_recipe_scraper import AllNigerianRecipesScraper12from data_minning.yummy_medley_scraper import YummyMedleyScraper13from backend.config.settings import settings14from backend.config.logging_config import setup_default_logging, get_logger15from backend.utils.sanitization import sanitize_user_input16from backend.services.vector_store import vector_store_service17# Setup logging first, before importing services18setup_default_logging()19logger = get_logger("app")20 21# Import services after logging is configured22from backend.services.llm_service import llm_service23 24SCRAPERS: dict[str, Type[BaseRecipeScraper]] = {25    "yummy": YummyMedleyScraper,26    "anr": AllNigerianRecipesScraper,27}28 29app = FastAPI(30    title="Recipe Recommendation Bot API",31    description="AI-powered recipe recommendation system with RAG capabilities",32    version="1.0.0"33)34 35logger.info("๐Ÿš€ Starting Recipe Recommendation Bot API")36logger.info(f"Environment: {settings.ENVIRONMENT}")37logger.info(f"Provider: {settings.get_llm_config()['provider']} (LLM + Embeddings)")38 39# Add CORS middleware40app.add_middleware(41    CORSMiddleware,42    allow_origins=settings.CORS_ORIGINS or ["*"],43    allow_credentials=settings.CORS_ALLOW_CREDENTIALS or True,44    allow_methods=settings.CORS_ALLOW_METHODS or ["*"],45    allow_headers=settings.CORS_ALLOW_HEADERS or ["*"],46)47 48# Remove OpenAI direct setup - now handled by LLM service49# if settings.OPENAI_API_KEY:50#     openai.api_key = settings.OPENAI_API_KEY51 52@app.get("/")53def index():54    logger.info("๐Ÿ“ก Root endpoint accessed")55    return {56        "message": "Recipe Recommendation Bot API",57        "version": "1.0.0",58        "status": "running"59    }60 61@app.get("/health")62def health_check():63    logger.info("๐Ÿฅ Health check endpoint accessed")64    return {65        "status": "healthy",66        "environment": settings.ENVIRONMENT,67        "llm_service_initialized": llm_service is not None68    }69 70@app.post("/chat", response_model=ChatResponse)71async def chat(chat_message: ChatMessage):72    """Main chatbot endpoint - Recipe recommendation with ConversationalRetrievalChain"""73    try:74        # Message is already sanitized by the Pydantic validator75        # Find the last user message in the messages list76        last_user_message = chat_message.get_latest_message()77        if not last_user_message:78            raise ValueError("No valid user message found")79        user_text = last_user_message.parts[0].text80 81        response_text = llm_service.ask_question(user_text)82        return ChatResponse(response=response_text)83        84    except ValueError as e:85        # Handle validation/sanitization errors86        logger.warning(f"โš ๏ธ Invalid input received: {str(e)}")87        raise HTTPException(status_code=400, detail=f"Invalid input: {str(e)}")88        89    except Exception as e:90        logger.error(f"โŒ Chat service error: {str(e)}", exc_info=True)91        raise HTTPException(status_code=500, detail=f"Chat service error: {str(e)}")92 93@app.get("/demo")94def demo(prompt: str = "What recipes do you have?"):95    """Demo endpoint - uses simple chat completion without RAG"""96    logger.info(f"๐ŸŽฏ Demo request: '{prompt[:50]}...'")97    98    try:99        # Sanitize the demo prompt using the same sanitization method100        sanitized_prompt = sanitize_user_input(prompt)        101        response_text = llm_service.simple_chat_completion(sanitized_prompt)102        return {"prompt": sanitized_prompt, "reply": response_text}103        104    except ValueError as e:105        # Handle validation/sanitization errors106        logger.warning(f"โš ๏ธ Invalid demo prompt: {str(e)}")107        return {"error": f"Invalid prompt: {str(e)}", "prompt": prompt}108        109    except Exception as e:110        logger.error(f"โŒ Demo endpoint error: {str(e)}", exc_info=True)111        return {"error": f"Failed to get response: {str(e)}"}112 113@app.post("/clear-memory")114def clear_conversation_memory():115    """Clear conversation memory"""116    logger.info("๐Ÿงน Memory clear request received")117    118    try:119        success = llm_service.clear_memory()120        121        if success:122            logger.info("โœ… Conversation memory cleared successfully")123            return {"status": "success", "message": "Conversation memory cleared"}124        else:125            logger.warning("โš ๏ธ Memory clear operation failed")126            return {"status": "failed", "message": "Failed to clear conversation memory"}127            128    except Exception as e:129        logger.error(f"โŒ Memory clear error: {str(e)}", exc_info=True)130        return {"status": "error", "message": str(e)}131 132 133 134def run_job(job_id: str, site: str, limit: int, output_type: str):135    '''136    Background job to run the scraper137    Uses global JOBS dict to track status138    Outputs to JSON file or MongoDB based on output_type139    '''140    s = SCRAPERS[site]()141    s.embedder = vector_store_service._create_sentence_transformer_wrapper("sentence-transformers/all-MiniLM-L6-v2")142    s.embedding_fields = [(("title", "ingredients", "instructions"), "recipe_emb")]143    sink = None144    if output_type == "json":145        sink = JsonArraySink("./data/recipes_unified.json")146    elif output_type == "mongo":147        sink = MongoSink() if os.getenv("MONGODB_URI") else None148 149    stream_opts = StreamOptions(150            delay=0.3,151            limit=500,152            batch_size=limit,153            resume_file="recipes.resume",154            progress_callback=make_progress_cb(job_id),155        )156    try:157        JOBS[job_id] = {"status": "running", "count": 0}158        s.stream( sink=sink, options=stream_opts)159        JOBS[job_id]["status"] = "done"160    except Exception as e:161        JOBS[job_id] = {"status": "error", "error": str(e)}162 163def make_progress_cb(job_id: str):164    ''' Create a progress callback to update JOBS dict 165    '''166    def _cb(n: int):167        JOBS[job_id]["count"] = n168    return _cb169 170 171 172 173 174# super-lightweight in-memory job store (reset on restart)175JOBS: dict[str, any] = {}176 177@app.post("/scrape")178def scrape(body: ScrapeRequest, background: BackgroundTasks, x_api_key: str = Header(None)):179    if body.site not in SCRAPERS:180        raise HTTPException(status_code=400, detail="Unknown site")181 182    job_id = f"{body.site}-{os.urandom(4).hex()}"183    # use thread via BackgroundTasks to avoid blocking the request184    background.add_task(run_job, job_id, body.site, body.limit, body.output_type)185    return {"job_id": job_id, "status": "queued"}186 187@app.get("/jobs/{job_id}")188def job_status(job_id: str):189    return JOBS.get(job_id, {"status": "unknown"})190 191@app.get("/jobs")192def list_jobs():193    return JOBS194