CoolFace
Apppublic

Aryan2301/YouTube_RAG_Intelligence

sourceHugging Faceupdated 13h agoView on Hugging Face
0likes
api.py116 linesDownload Raw Back to root
1from fastapi import FastAPI, HTTPException2from fastapi.responses import StreamingResponse3from pydantic import BaseModel4from typing import List, Dict, Any5import json6import asyncio7import logging8from langgraph.checkpoint.memory import MemorySaver9from contextlib import asynccontextmanager10 11from core.graph import build_langgraph12from core.vectorstore import build_retrievers13from services.transcript_service import fetch_transcript14 15logger = logging.getLogger(__name__)16 17@asynccontextmanager18async def lifespan(app: FastAPI):19    # In-memory checkpointer for conversation memory20    app.state.checkpointer = MemorySaver()21    app.state.rag_graph = build_langgraph(checkpointer=app.state.checkpointer)22    yield23 24app = FastAPI(title="YouTube RAG Intelligence API", lifespan=lifespan)25 26# Simple in-memory cache for vector stores and retrievers27# In production, use Redis or a persistent ChromaDB path28app.state.video_stores = {}29 30class InitRequest(BaseModel):31    video_id: str32    transcript_segments: List[Dict[str, Any]] = []33 34class ChatRequest(BaseModel):35    video_id: str36    chat_id: str37    query: str38    chat_history: List[Dict[str, str]] = []39 40@app.post("/init")41async def init_video(req: InitRequest):42    """Initialize a video: fetch transcript and build vector store."""43    try:44        if req.video_id in app.state.video_stores:45            return {"status": "already initialized"}46            47        if not req.transcript_segments:48            transcript_text, transcript_segments = await asyncio.to_thread(fetch_transcript, req.video_id)49        else:50            transcript_segments = req.transcript_segments51            52        vector_store, bm25_retriever = await asyncio.to_thread(build_retrievers, req.video_id, transcript_segments)53        54        app.state.video_stores[req.video_id] = {55            "vector_store": vector_store,56            "bm25_retriever": bm25_retriever57        }58        59        return {"status": "success", "segments": len(transcript_segments)}60    except Exception as e:61        logger.error(f"Error initializing video {req.video_id}: {str(e)}")62        raise HTTPException(status_code=500, detail=str(e))63 64@app.post("/chat/stream")65async def chat_stream(req: ChatRequest):66    """Stream chat response and graph events using Server-Sent Events (SSE)."""67    68    if req.video_id not in app.state.video_stores:69        return {"error": "Video not initialized. Call /init first."}70        71    stores = app.state.video_stores[req.video_id]72    73    inputs = {74        "query": req.query,75        "chat_history": req.chat_history,76        "video_id": req.video_id,77        "retrieval_attempt": 0,78        "context": ""79    }80    81    thread_id = f"{req.video_id}_{req.chat_id}"82    config = {83        "configurable": {84            "thread_id": thread_id,85            "vector_store": stores["vector_store"],86            "bm25_retriever": stores["bm25_retriever"],87            # LLM needs to be initialized. We can import here to avoid global init issues.88            "llm": __import__("core.llm", fromlist=["load_llm"]).load_llm()89        }90    }91 92    async def event_generator():93        # Yield meaningful Server-Sent Events (SSE) by streaming the graph execution94        try:95            async for event in app.state.rag_graph.astream_events(inputs, config=config, version="v1"):96                kind = event["event"]97                name = event["name"]98                99                # Stream graph node transitions (e.g., hybrid_retrieve, evaluate_retrieval)100                if kind == "on_chain_start" and name in ["hybrid_retrieve", "evaluate_retrieval", "correct_query", "web_search", "generate_answer"]:101                    yield f"event: status\ndata: {json.dumps({'message': f'Starting node: {name}'})}\n\n"102                    103                # Stream the actual tokens from the LLM inside generate_answer104                elif kind == "on_chat_model_stream":105                    chunk = event["data"]["chunk"].content106                    if isinstance(chunk, list):107                        chunk = "".join(p.get("text", "") if isinstance(p, dict) else str(p) for p in chunk)108                    if chunk:109                        yield f"event: token\ndata: {json.dumps({'token': chunk})}\n\n"110                        111            yield "event: end\ndata: {}\n\n"112        except Exception as e:113            yield f"event: error\ndata: {json.dumps({'error': str(e)})}\n\n"114 115    return StreamingResponse(event_generator(), media_type="text/event-stream")116