Spaces:
Sleeping
Sleeping
Ali Hashhash
feat: implement chat API routes, note generation logic, and model loading utilities
3bc6c02 | """ | |
| Chat routes โ Document-specific Q&A powered by local Qwen model. | |
| """ | |
| from typing import List, Optional | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from pydantic import BaseModel, Field | |
| from src.auth.dependencies import get_current_user | |
| from src.db.models import User | |
| from src.summarization.note_generator import NoteGenerator | |
| from src.utils.logger import setup_logger | |
| from src.utils.model_loader import INFERENCE_MODE, get_groq_client | |
| logger = setup_logger(__name__) | |
| router = APIRouter(tags=["Chat"]) | |
| # โโโ Schemas โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class ChatMessage(BaseModel): | |
| role: str = Field(..., description="Either 'user' or 'assistant'") | |
| content: str = Field(..., description="Message content") | |
| class ChatRequest(BaseModel): | |
| note_content: str = Field( | |
| ..., | |
| description="The full text of the note to use as context", | |
| ) | |
| question: str = Field( | |
| ..., | |
| min_length=1, | |
| description="The user's question about the note", | |
| ) | |
| history: Optional[List[ChatMessage]] = Field( | |
| default=None, | |
| description="Previous conversation turns for multi-turn context", | |
| ) | |
| class ChatResponse(BaseModel): | |
| answer: str = Field(..., description="AI-generated answer") | |
| # โโโ Endpoint โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def chat_with_note( | |
| request: ChatRequest, | |
| current_user: User = Depends(get_current_user), | |
| ): | |
| """Ask a question about a specific note. Answers are grounded in the note content.""" | |
| logger.info( | |
| "Chat request from user %s โ question length: %d, context length: %d", | |
| current_user.id, | |
| len(request.question), | |
| len(request.note_content), | |
| ) | |
| if not request.note_content.strip(): | |
| raise HTTPException(status_code=400, detail="Note content cannot be empty.") | |
| history_dicts = None | |
| if request.history: | |
| history_dicts = [msg.model_dump() for msg in request.history] | |
| if INFERENCE_MODE == "groq": | |
| logger.info("๐ข Chat request routed directly to Groq API (llama-3.3-70b-versatile)...") | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "You are a helpful study assistant. " | |
| "Answer the user's question based ONLY on the note content provided below. " | |
| "If the answer is not in the note, say so honestly. " | |
| "Reply in the same language the user uses.\n\n" | |
| f"--- NOTE CONTENT ---\n{request.note_content[:4000]}\n--- END NOTE ---" | |
| ), | |
| }, | |
| ] | |
| if history_dicts: | |
| for msg in history_dicts[-6:]: | |
| role = msg.get("role", "user") | |
| content = msg.get("content", "") | |
| if role in ("user", "assistant") and content: | |
| messages.append({"role": role, "content": content}) | |
| messages.append({"role": "user", "content": request.question}) | |
| groq_client = get_groq_client() | |
| try: | |
| chat_completion = groq_client.chat.completions.create( | |
| model="llama-3.3-70b-versatile", | |
| messages=messages, | |
| max_tokens=300, | |
| temperature=0.0, | |
| ) | |
| answer = chat_completion.choices[0].message.content or "" | |
| answer = answer.strip() | |
| if not answer or len(answer) < 3: | |
| answer = "ุนุฐุฑูุงุ ูู ุฃุชู ูู ู ู ุชูููุฏ ุฅุฌุงุจุฉ. ูุฑุฌู ุฅุนุงุฏุฉ ุตูุงุบุฉ ุงูุณุคุงู." | |
| except Exception as e: | |
| logger.error("โ Groq chat error: %s", e, exc_info=True) | |
| answer = "ุญุฏุซ ุฎุทุฃ ุฃุซูุงุก ู ุนุงูุฌุฉ ุณุคุงูู. ูุฑุฌู ุงูู ุญุงููุฉ ู ุฑุฉ ุฃุฎุฑู." | |
| else: | |
| logger.info("๐ค Chat request routed to local Qwen pipeline...") | |
| note_gen = NoteGenerator() | |
| answer = note_gen.chat_with_note( | |
| note_content=request.note_content, | |
| question=request.question, | |
| history=history_dicts, | |
| ) | |
| return ChatResponse(answer=answer) | |