CoolFace
Apppublic

AhmedShahan/portfolio_knowledge_base

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
api.py206 linesDownload Raw Back to root
1import os2from datetime import datetime, timezone3from dotenv import load_dotenv4from fastapi import FastAPI,HTTPException5from pydantic import BaseModel6from sentence_transformers import SentenceTransformer7from qdrant_client import QdrantClient8from groq import Groq9from groq import RateLimitError10from tavily import TavilyClient11 12import sheet_writer13 14load_dotenv()15 16# ── Config ────────────────────────────────────────────────────────────────────17COLLECTION_NAME = "portfolio"18TOP_K = 519MODEL_NAME = "nomic-ai/nomic-embed-text-v1.5"20GROQ_MODEL = "llama-3.3-70b-versatile"21AI_NEWS_COUNT = 1022 23# ── Init ──────────────────────────────────────────────────────────────────────24app = FastAPI()25 26print("🔧 Loading embedding model...")27embedder = SentenceTransformer(MODEL_NAME, trust_remote_code=True)28 29qdrant = QdrantClient(30    url=os.getenv("QDRANT_URL"),31    api_key=os.getenv("QDRANT_API_KEY"),32)33 34groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))35tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))36 37# ── Schema ────────────────────────────────────────────────────────────────────38class QueryRequest(BaseModel):39    question: str40 41# ── Routes ───────────────────────────────────────────────────────────────────42@app.get("/")43def root():44    return {"status": "ok", "message": "Portfolio RAG API is running"}45 46@app.post("/query")47def query(request: QueryRequest):48    # Step 1: Embed query49    vector = embedder.encode(request.question, normalize_embeddings=True).tolist()50 51    # Step 2: Search Qdrant52    results = qdrant.query_points(53        collection_name=COLLECTION_NAME,54        query=vector,55        limit=TOP_K,56    ).points57 58    # Step 3: Build context59    context = "\n\n---\n\n".join([r.payload.get("text", "") for r in results])60 61    # Step 4: Ask Groq62    prompt = f"""You are an AI assistant for Ahmed Shahan's portfolio.63Answer the user's question using ONLY the context below.64If the answer is not in the context, say "I don't have that information."65 66Context:67{context}68 69Question: {request.question}70"""71 72    try:73        response = groq_client.chat.completions.create(74            model=GROQ_MODEL,75            messages=[{"role": "user", "content": prompt}],76            temperature=0.2,77        )78        return {79            "question": request.question,80            "answer": response.choices[0].message.content,81        }82 83    except RateLimitError as e:84        # Extract retry time from error message85        import re86        match = re.search(r"try again in (.+?)\.", str(e))87        retry_after = match.group(1) if match else "some time"88        raise HTTPException(89            status_code=429,90            detail=f"Daily chat limit hit. Please try again after {retry_after}."91        )92 93 94# ── AI News Endpoint ──────────────────────────────────────────────────────────95@app.get("/ai-news")96def get_ai_news():97    """Fetch today's top AI news, ranked by popularity, with simple explanations."""98    try:99        # Step 1: Fetch AI news from Tavily100        search_result = tavily_client.search(101            query="artificial intelligence AI news today",102            topic="news",103            time_range="day",104            max_results=AI_NEWS_COUNT,105        )106        articles = search_result.get("results", [])107        if not articles:108            return {"articles": [], "message": "No AI news found today."}109 110        # Step 2: Sort by popularity (score descending)111        articles.sort(key=lambda a: a.get("score", 0), reverse=True)112 113        # Step 3: Build a single prompt for Groq to simplify all articles114        news_block = "\n\n".join(115            f"--- Article {i+1} ---\n"116            f"Title: {a['title']}\n"117            f"Content: {a.get('content', 'No summary available.')}"118            for i, a in enumerate(articles)119        )120 121        simplify_prompt = f"""You are a helpful assistant that explains AI news in simple, easy-to-understand language for a general audience.122 123Below are {len(articles)} AI news articles from today. For EACH article, provide:1241. The original title1252. A point-by-point explanation using VERY SIMPLE words that anyone can understand (like you're explaining to your grandmother)126 127Format your response EXACTLY like this (separate each article with a blank line):128 129===== Article 1 =====130Title: <original title>131Key points in simple words:132• <point 1>133• <point 2>134• <point 3>135 136===== Article 2 =====137Title: <original title>138Key points in simple words:139• <point 1>140• <point 2>141 142...and so on for all articles.143 144Rules:145- Use ONLY simple, everyday words. No jargon.146- Each article should have 2-4 bullet points.147- Keep each bullet point short (1-2 sentences max).148- Focus on WHAT happened and WHY it matters.149 150Here are the articles:151 152{news_block}153"""154 155        response = groq_client.chat.completions.create(156            model=GROQ_MODEL,157            messages=[{"role": "user", "content": simplify_prompt}],158            temperature=0.3,159        )160        simplified_text = response.choices[0].message.content161 162        # Step 4: Parse the response back into structured data163        # Each article block starts with ===== Article N =====164        import re165        blocks = re.split(r"=====\s*Article\s+\d+\s*=====", simplified_text)166        blocks = [b.strip() for b in blocks if b.strip()]167 168        retrieved_at = datetime.now(timezone.utc).isoformat()169 170        result_articles = []171        for i, article in enumerate(articles):172            explanation = blocks[i] if i < len(blocks) else "Explanation not available."173            # Clean up the explanation - remove the "Title: ..." line since we have it separately174            explanation_lines = explanation.split("\n")175            # Filter out the "Title:" line if present176            clean_lines = [177                line for line in explanation_lines178                if not line.strip().lower().startswith("title:")179            ]180            clean_explanation = "\n".join(clean_lines).strip()181 182            result_articles.append({183                "title": article["title"],184                "url": article["url"],185                "published_date": article.get("published_date", ""),186                "popularity_score": round(article.get("score", 0), 4),187                "explanation_simple": clean_explanation,188            })189 190        # Write to Google Sheet (non-blocking — failures are logged, never crash the API)191        sheet_writer.append_ai_news_articles(retrieved_at, result_articles)192 193        return {194            "data_retrieved_at": retrieved_at,195            "timezone": "UTC",196            "articles": result_articles,197            "total": len(result_articles),198        }199 200    except Exception as e:201        raise HTTPException(status_code=500, detail=f"Failed to fetch AI news: {str(e)}")202 203 204if __name__ == "__main__":205    import uvicorn206    uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)