import os import re import requests from fastapi import FastAPI from pydantic import BaseModel from typing import List, Optional from fastapi.middleware.cors import CORSMiddleware from huggingface_hub import InferenceClient app = FastAPI(title="Yuyu - LibraryLuxe AI") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ================== CONFIG ================== HF_TOKEN = os.getenv("HF_TOKEN") client = InferenceClient(api_key=HF_TOKEN) # Better model (try these in order) MODEL = "Qwen/Qwen2.5-7B-Instruct" # Best quality # MODEL = "mistralai/Mistral-7B-Instruct-v0.3" # MODEL = "microsoft/Phi-3.5-mini-instruct" # =========================================== class ChatHistoryItem(BaseModel): role: str content: str class ChatRequest(BaseModel): message: str history: List[ChatHistoryItem] = [] class BookEnrichment(BaseModel): title: Optional[str] = None author: Optional[str] = None cover_url: Optional[str] = None description: Optional[str] = None isbn: Optional[str] = None class ChatResponse(BaseModel): response: str book_data: Optional[BookEnrichment] = None # ================== MASTER LIBRARIAN KNOWLEDGE BASE ================== SYSTEM_PROMPT = """ You are Yuyu, the Master Librarian at LibraryLuxe. ### CORE RULES: - ALL prices and balances must be mentioned in MMK (Myanmar Kyat). - Use the 'Actual Library Plans' and 'User Context' provided in the message for precision. ### 1. MEMBERSHIP & SUBSCRIPTIONS - We have different plans (Basic, Premium, Basic yearly, Premium yearly.) with different book limits and prices. (Basic: maxed books=3, Borrow days=14 days, Premium: maxed books=5, Borrow days=21days) - Users can subscribe using their Wallet or other payment methods. - IMPORTANT: When a user subscribes, it might stay "Pending" until an Admin approves it. - To Renew/Upgrade: Go to the 'Membership' page and click 'Renew' or 'Upgrade'. ### 2. WALLET & PAYMENTS - Users have a digital Wallet. They can 'Top Up' money into it. - Wallet balance can be used to buy subscriptions instantly. - To Top Up: Go to the 'Wallet' section in the profile menu. ### 3. LOYALTY & REWARDS - Users earn 'Loyalty Points' for borrowing books and buying membership plans. - Points can be redeemed for 'Rewards' in 'Rewards' page. - To check points: Visit the 'Rewards' page. ### 4. BORROWING & RETURNS - Each plan has a 'Max Books' limit and 'Borrowing Days' limit. - To Return: Users must 'Request Return' from their 'My Books' page OR go to the library and return it. A librarian will then verify the physical return. - Overdue books might prevent new borrowings. ### 5. PERSONALITY & TONE - Be warm, helpful, and professional. - Use the user's name if provided. - ALWAYS use the 'Actual Library Plans' and 'User Context' provided in the message for precision. - If you don't know a specific detail about their account, ask them to check their 'Dashboard' or email aiyurina1610@gmail.com. Keep responses helpful but concise (max 2 paragraphs). """ def get_book_details(isbn: str) -> Optional[BookEnrichment]: try: url = f"https://openlibrary.org/api/books?bibkeys=ISBN:{isbn}&format=json&jscmd=data" resp = requests.get(url, timeout=6) data = resp.json() key = f"ISBN:{isbn}" if key in data: b = data[key] return BookEnrichment( title=b.get("title"), author=", ".join([a.get("name", "") for a in b.get("authors", [])]), cover_url=b.get("cover", {}).get("large"), description=b.get("notes") or b.get("description"), isbn=isbn ) except: pass return None @app.get("/") def home(): return {"status": "online", "message": "Yuyu is ready!"} @app.post("/chat", response_model=ChatResponse) async def chat(request: ChatRequest): # Extract ISBN isbn_match = re.search(r"\b(?:97[89])?\d{9}[\dX]\b", request.message) book_data = get_book_details(isbn_match.group(0)) if isbn_match else None # Build messages messages = [{"role": "system", "content": SYSTEM_PROMPT}] # Add recent history for item in request.history[-6:]: messages.append({"role": item.role, "content": item.content}) messages.append({"role": "user", "content": request.message}) try: response = client.chat.completions.create( model=MODEL, messages=messages, max_tokens=450, temperature=0.75, top_p=0.9, ) ai_reply = response.choices[0].message.content.strip() except Exception as e: ai_reply = "I'm a bit overloaded right now... Please try again in a few seconds." return ChatResponse(response=ai_reply, book_data=book_data)