Spaces:
Sleeping
Sleeping
| import os | |
| from typing import List, Optional | |
| from fastapi import FastAPI, HTTPException, Depends | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| from pydantic import BaseModel | |
| from pinecone import Pinecone | |
| from sentence_transformers import SentenceTransformer | |
| from openai import OpenAI | |
| # ============================== | |
| # 🔐 CONFIG (Use HF Secrets) | |
| # ============================== | |
| PINECONE_API_KEY = os.getenv("PINECONE_API_KEY") | |
| SARVAM_API_KEY = os.getenv("SARVAM_API_KEY") | |
| API_SECRET_KEY = os.getenv("API_SECRET_KEY") | |
| PINECONE_INDEX = "prathamesh-portfolio" | |
| EMBED_MODEL = "BAAI/bge-m3" | |
| TOP_K = 5 | |
| SARVAM_MODEL = "sarvam-m" | |
| SARVAM_BASE_URL = "https://api.sarvam.ai/v1" | |
| # Validate secrets | |
| if not PINECONE_API_KEY: | |
| raise ValueError("PINECONE_API_KEY not set in environment variables.") | |
| if not SARVAM_API_KEY: | |
| raise ValueError("SARVAM_API_KEY not set in environment variables.") | |
| if not API_SECRET_KEY: | |
| raise ValueError("API_SECRET_KEY must be set in environment variables.") | |
| # ============================== | |
| # 🚀 INITIALIZE COMPONENTS | |
| # ============================== | |
| pc = Pinecone(api_key=PINECONE_API_KEY) | |
| index = pc.Index(PINECONE_INDEX) | |
| embedder = SentenceTransformer(EMBED_MODEL) | |
| sarvam_client = OpenAI( | |
| api_key=SARVAM_API_KEY, | |
| base_url=SARVAM_BASE_URL | |
| ) | |
| # ============================== | |
| # 🏗 FASTAPI APP | |
| # ============================== | |
| app = FastAPI( | |
| title="Prathamesh Portfolio Chatbot", | |
| description="RAG-powered chatbot API with secret key authentication" | |
| ) | |
| # Enable CORS (adjust origin in production) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| security = HTTPBearer() | |
| # ============================== | |
| # 📦 REQUEST MODEL | |
| # ============================== | |
| class ChatRequest(BaseModel): | |
| query: str | |
| tag_filter: Optional[List[str]] = None | |
| # ============================== | |
| # 🔎 RETRIEVAL | |
| # ============================== | |
| def retrieve(query: str, top_k: int = TOP_K, tag_filter: Optional[List[str]] = None): | |
| query_embedding = embedder.encode( | |
| query, | |
| normalize_embeddings=True | |
| ).tolist() | |
| pinecone_filter = None | |
| if tag_filter: | |
| pinecone_filter = {"tags": {"$in": tag_filter}} | |
| results = index.query( | |
| vector=query_embedding, | |
| top_k=top_k, | |
| include_metadata=True, | |
| filter=pinecone_filter | |
| ) | |
| retrieved = [] | |
| for match in results.get("matches", []): | |
| retrieved.append({ | |
| "chunk_id": match["id"], | |
| "score": round(match["score"], 4), | |
| "text": match["metadata"]["text"], | |
| "tags": match["metadata"].get("tags", []), | |
| }) | |
| return retrieved | |
| # ============================== | |
| # 🧠 PROMPT BUILDER | |
| # ============================== | |
| def build_prompt(query: str, retrieved_chunks: list) -> str: | |
| context_blocks = [] | |
| for i, chunk in enumerate(retrieved_chunks, 1): | |
| context_blocks.append( | |
| f"[Context {i} | Relevance: {chunk['score']} | Type: {', '.join(chunk['tags'])}]\n{chunk['text']}" | |
| ) | |
| context_str = "\n\n".join(context_blocks) | |
| return f""" | |
| You are a helpful and professional AI assistant for Prathamesh Raut's portfolio chatbot. | |
| Use ONLY the context provided below to answer. | |
| If the answer isn't present, say: | |
| "I don't have specific information about that in Prathamesh's profile." | |
| Be concise and accurate. | |
| --- | |
| CONTEXT: | |
| {context_str} | |
| --- | |
| QUESTION: {query} | |
| ANSWER: | |
| """ | |
| # ============================== | |
| # 🏷 TAG DETECTION | |
| # ============================== | |
| def detect_tags(query: str) -> Optional[List[str]]: | |
| query_lower = query.lower() | |
| tag_keywords = { | |
| "project": ["project", "built", "developed", "created", "worked on"], | |
| "skills": ["skill", "technologies", "tools", "tech stack"], | |
| "work_experience": ["work", "job", "company", "role", "position"], | |
| "education": ["college", "degree", "university", "cgpa", "gpa"], | |
| "publication": ["publish", "paper", "research", "ieee"], | |
| "certification": ["certified", "harvard", "databricks", "cisco"], | |
| "personal": ["hobbies", "interests", "language"], | |
| } | |
| detected = [] | |
| for tag, keywords in tag_keywords.items(): | |
| if any(keyword in query_lower for keyword in keywords): | |
| detected.append(tag) | |
| return detected if detected else None | |
| # ============================== | |
| # 🤖 ANSWER GENERATION | |
| # ============================== | |
| def answer(query: str, tag_filter: Optional[List[str]] = None) -> str: | |
| chunks = retrieve(query, tag_filter=tag_filter) | |
| if not chunks: | |
| return "I couldn't find relevant information in Prathamesh's knowledge base." | |
| prompt = build_prompt(query, chunks) | |
| response = sarvam_client.chat.completions.create( | |
| model=SARVAM_MODEL, | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "You are a professional portfolio assistant for Prathamesh Raut. " | |
| "Answer only using the provided context." | |
| ), | |
| }, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| temperature=0.3, | |
| max_tokens=600, | |
| ) | |
| return response.choices[0].message.content.strip() | |
| # ============================== | |
| # 🔐 AUTHENTICATED ENDPOINT | |
| # ============================== | |
| async def chat_endpoint( | |
| request: ChatRequest, | |
| credentials: HTTPAuthorizationCredentials = Depends(security), | |
| ): | |
| # Validate secret key | |
| if credentials.credentials != API_SECRET_KEY: | |
| raise HTTPException(status_code=401, detail="Invalid secret key.") | |
| try: | |
| tags = request.tag_filter or detect_tags(request.query) | |
| response = answer(request.query, tag_filter=tags) | |
| return {"answer": response} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ============================== | |
| # ❤️ HEALTH CHECK | |
| # ============================== | |
| async def root(): | |
| return {"message": "Prathamesh Portfolio Chatbot API is running!"} |