Spaces:
Sleeping
Sleeping
| """ | |
| Chat API Endpoints | |
| Chat sessions and AI conversations with RAG | |
| """ | |
| import logging | |
| import uuid | |
| from datetime import datetime | |
| from typing import List, Optional | |
| from fastapi import APIRouter, Depends, HTTPException, Query | |
| from sqlalchemy.orm import Session, selectinload | |
| from app.api.deps import get_current_user_id | |
| from app.core.exceptions import NotFoundError | |
| from app.db.session import get_db | |
| from app.models.audit import AuditAction, create_audit_log | |
| from app.models.chat import ChatMessage, ChatSession | |
| from app.schemas.chat import ( | |
| ChatMessageResponse, | |
| ChatRequest, | |
| ChatResponse, | |
| ChatSessionCreate, | |
| ChatSessionDetailResponse, | |
| ChatSessionResponse, | |
| ChatSessionUpdateRequest, | |
| ) | |
| from app.services.guardrails import MiningGuardrails | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter() | |
| async def list_chat_sessions( | |
| limit: int = Query(50, ge=1, le=100), | |
| user_id: str = Depends(get_current_user_id), | |
| db: Session = Depends(get_db), | |
| ): | |
| """ | |
| List user's chat sessions ordered by most recent. | |
| """ | |
| sessions = ( | |
| db.query(ChatSession) | |
| .filter(ChatSession.user_id == user_id) | |
| .order_by(ChatSession.updated_at.desc()) | |
| .limit(limit) | |
| .options(selectinload(ChatSession.messages)) | |
| .all() | |
| ) | |
| result = [] | |
| for session in sessions: | |
| last_msg = session.messages[-1] if session.messages else None | |
| result.append( | |
| ChatSessionResponse( | |
| id=str(session.id), | |
| title=session.title, | |
| message_count=len(session.messages), | |
| document_context=session.document_context or [], | |
| created_at=session.created_at, | |
| updated_at=session.updated_at, | |
| last_message=last_msg.content[:100] if last_msg else None, | |
| last_message_at=last_msg.created_at if last_msg else None, | |
| ) | |
| ) | |
| return result | |
| async def create_chat_session( | |
| request: ChatSessionCreate, | |
| user_id: str = Depends(get_current_user_id), | |
| db: Session = Depends(get_db), | |
| ): | |
| """ | |
| Create a new chat session. | |
| """ | |
| session = ChatSession( | |
| user_id=user_id, | |
| title=request.title or "New Chat", | |
| document_context=request.document_ids or [], | |
| system_prompt=request.system_prompt, | |
| ) | |
| db.add(session) | |
| db.commit() | |
| db.refresh(session) | |
| # Audit log | |
| audit = create_audit_log( | |
| action=AuditAction.CHAT_CREATE.value, | |
| user_id=user_id, | |
| resource_type="chat_session", | |
| resource_id=str(session.id), | |
| ) | |
| db.add(audit) | |
| db.commit() | |
| return ChatSessionResponse( | |
| id=str(session.id), | |
| title=session.title, | |
| message_count=0, | |
| document_context=session.document_context, | |
| created_at=session.created_at, | |
| updated_at=session.updated_at, | |
| ) | |
| async def get_chat_session( | |
| session_id: uuid.UUID, | |
| user_id: str = Depends(get_current_user_id), | |
| db: Session = Depends(get_db), | |
| ): | |
| """ | |
| Get chat session with all messages. | |
| """ | |
| session = ( | |
| db.query(ChatSession) | |
| .filter(ChatSession.id == session_id, ChatSession.user_id == user_id) | |
| .options(selectinload(ChatSession.messages)) | |
| .first() | |
| ) | |
| if not session: | |
| raise NotFoundError("Chat session", session_id) | |
| messages = [ | |
| ChatMessageResponse( | |
| id=str(msg.id), | |
| role=msg.role, | |
| content=msg.content, | |
| sources=msg.sources or [], | |
| created_at=msg.created_at, | |
| model_used=msg.model_used, | |
| response_time_ms=msg.response_time_ms, | |
| ) | |
| for msg in session.messages | |
| ] | |
| return ChatSessionDetailResponse( | |
| id=str(session.id), | |
| title=session.title, | |
| document_context=session.document_context or [], | |
| system_prompt=session.system_prompt, | |
| messages=messages, | |
| created_at=session.created_at, | |
| updated_at=session.updated_at, | |
| ) | |
| async def update_chat_session( | |
| session_id: uuid.UUID, | |
| request: ChatSessionUpdateRequest, | |
| user_id: str = Depends(get_current_user_id), | |
| db: Session = Depends(get_db), | |
| ): | |
| """ | |
| Update chat session title or document context. | |
| """ | |
| session = ( | |
| db.query(ChatSession) | |
| .filter(ChatSession.id == session_id, ChatSession.user_id == user_id) | |
| .options(selectinload(ChatSession.messages)) | |
| .first() | |
| ) | |
| if not session: | |
| raise NotFoundError("Chat session", session_id) | |
| if request.title is not None: | |
| session.title = request.title | |
| if request.document_ids is not None: | |
| session.document_context = request.document_ids | |
| session.updated_at = datetime.utcnow() | |
| db.commit() | |
| db.refresh(session) | |
| return ChatSessionResponse( | |
| id=str(session.id), | |
| title=session.title, | |
| message_count=len(session.messages), | |
| document_context=session.document_context, | |
| created_at=session.created_at, | |
| updated_at=session.updated_at, | |
| ) | |
| async def delete_chat_session( | |
| session_id: uuid.UUID, | |
| user_id: str = Depends(get_current_user_id), | |
| db: Session = Depends(get_db), | |
| ): | |
| """ | |
| Delete a chat session and all messages. | |
| """ | |
| session = ( | |
| db.query(ChatSession) | |
| .filter(ChatSession.id == session_id, ChatSession.user_id == user_id) | |
| .first() | |
| ) | |
| if not session: | |
| raise NotFoundError("Chat session", session_id) | |
| # Audit log | |
| audit = create_audit_log( | |
| action=AuditAction.CHAT_DELETE.value, | |
| user_id=user_id, | |
| resource_type="chat_session", | |
| resource_id=session_id, | |
| ) | |
| db.add(audit) | |
| db.delete(session) | |
| db.commit() | |
| return {"success": True, "message": "Chat session deleted"} | |
| async def send_message( | |
| request: ChatRequest, | |
| user_id: str = Depends(get_current_user_id), | |
| db: Session = Depends(get_db), | |
| ): | |
| """ | |
| Send a message and get AI response. | |
| Uses RAG to find relevant document context. | |
| """ | |
| start_time = datetime.utcnow() | |
| # ── Input Guardrails ──────────────────────────────────────────────────── | |
| validated_query = MiningGuardrails.validate_input(request.content) | |
| # Get or create session | |
| if request.session_id: | |
| session = ( | |
| db.query(ChatSession) | |
| .filter( | |
| ChatSession.id == request.session_id, ChatSession.user_id == user_id | |
| ) | |
| .first() | |
| ) | |
| if not session: | |
| raise NotFoundError("Chat session", request.session_id) | |
| else: | |
| # Create new session | |
| session = ChatSession(user_id=user_id, title="New Chat") | |
| db.add(session) | |
| db.commit() | |
| db.refresh(session) | |
| # Save user message (but don't commit yet - wait for successful response) | |
| user_message = ChatMessage( | |
| session_id=session.id, role="user", content=request.content | |
| ) | |
| db.add(user_message) | |
| # Generate AI response with RAG | |
| from app.services.chat_service import ChatService | |
| chat_service = ChatService() | |
| try: | |
| ai_response, sources, tokens_used = await chat_service.generate_response( | |
| query=validated_query, | |
| user_id=user_id, | |
| document_ids=request.document_ids, | |
| db=db, | |
| ) | |
| # Calculate response time | |
| end_time = datetime.utcnow() | |
| response_time_ms = int((end_time - start_time).total_seconds() * 1000) | |
| # Save assistant message | |
| assistant_message = ChatMessage( | |
| session_id=session.id, | |
| role="assistant", | |
| content=ai_response, | |
| sources=sources if request.include_sources else [], | |
| model_used="gemini-2.5-flash", | |
| response_time_ms=response_time_ms, | |
| tokens_used=tokens_used, | |
| ) | |
| db.add(assistant_message) | |
| # Get fresh message count from database | |
| message_count = ( | |
| db.query(ChatMessage).filter(ChatMessage.session_id == session.id).count() | |
| ) | |
| # Update session title if first/second message | |
| if message_count <= 2: | |
| # Auto-generate title from first user message | |
| session.title = request.content[:50] + ( | |
| "..." if len(request.content) > 50 else "" | |
| ) | |
| session.updated_at = datetime.utcnow() | |
| # Audit log | |
| audit = create_audit_log( | |
| action=AuditAction.CHAT_MESSAGE.value, | |
| user_id=user_id, | |
| resource_type="chat_session", | |
| resource_id=str(session.id), | |
| details={"message_length": len(request.content)}, | |
| ) | |
| db.add(audit) | |
| # Commit all changes atomically | |
| db.commit() | |
| db.refresh(assistant_message) | |
| except Exception as e: | |
| db.rollback() | |
| raise | |
| return ChatResponse( | |
| message=ChatMessageResponse( | |
| id=str(assistant_message.id), | |
| role="assistant", | |
| content=ai_response, | |
| sources=sources if request.include_sources else [], | |
| created_at=assistant_message.created_at, | |
| model_used="gemini-2.5-flash", | |
| response_time_ms=response_time_ms, | |
| ), | |
| session_id=str(session.id), | |
| session_title=session.title, | |
| ) | |