Spaces:
Sleeping
Sleeping
| # """ | |
| # Chat API Endpoints (WITH AUTHENTICATION) | |
| # RESTful API for the Banking RAG Chatbot | |
| # NOW REQUIRES JWT TOKEN FOR ALL ENDPOINTS! | |
| # Endpoints: | |
| # - POST /chat - Send a message and get response (PROTECTED) | |
| # - GET /chat/history/{conversation_id} - Get conversation history (PROTECTED) | |
| # - POST /chat/conversation - Create new conversation (PROTECTED) | |
| # - GET /chat/conversations - List user's conversations (PROTECTED) | |
| # - DELETE /chat/conversation/{conversation_id} - Delete conversation (PROTECTED) | |
| # - GET /chat/health - Health check (PUBLIC) | |
| # """ | |
| # from fastapi import APIRouter, HTTPException, status, Depends | |
| # from pydantic import BaseModel, Field | |
| # from typing import List, Dict, Optional | |
| # from datetime import datetime | |
| # from app.services.chat_service import chat_service | |
| # from app.db.repositories.conversation_repository import ConversationRepository | |
| # from app.utils.dependencies import get_current_user # AUTH DEPENDENCY | |
| # from app.models.user import TokenData # USER DATA FROM TOKEN | |
| # # ============================================================================ | |
| # # CREATE ROUTER | |
| # # ============================================================================ | |
| # router = APIRouter() | |
| # # ============================================================================ | |
| # # DEPENDENCY: Get ConversationRepository instance | |
| # # ============================================================================ | |
| # def get_conversation_repo() -> ConversationRepository: | |
| # """ | |
| # Dependency that provides ConversationRepository instance. | |
| # This ensures MongoDB is connected before repository is used. | |
| # """ | |
| # return ConversationRepository() | |
| # # ============================================================================ | |
| # # PYDANTIC MODELS (Request/Response schemas) | |
| # # ============================================================================ | |
| # class ChatRequest(BaseModel): | |
| # """Request model for chat endpoint""" | |
| # query: str = Field(..., description="User query text", min_length=1, max_length=1000) | |
| # conversation_id: Optional[str] = Field(None, description="Optional conversation ID") | |
| # class Config: | |
| # json_schema_extra = { | |
| # "example": { | |
| # "query": "What is my account balance?", | |
| # "conversation_id": "conv-123" | |
| # } | |
| # } | |
| # class ChatResponse(BaseModel): | |
| # """Response model for chat endpoint""" | |
| # response: str = Field(..., description="Generated response text") | |
| # conversation_id: str = Field(..., description="Conversation ID") | |
| # policy_action: str = Field(..., description="Policy decision: FETCH or NO_FETCH") | |
| # policy_confidence: float = Field(..., description="Policy confidence score (0-1)") | |
| # documents_retrieved: int = Field(..., description="Number of documents retrieved") | |
| # top_doc_score: Optional[float] = Field(None, description="Best document similarity score") | |
| # total_time_ms: float = Field(..., description="Total processing time in milliseconds") | |
| # timestamp: str = Field(..., description="Response timestamp (ISO format)") | |
| # class ConversationCreateResponse(BaseModel): | |
| # """Response after creating a conversation""" | |
| # conversation_id: str = Field(..., description="Created conversation ID") | |
| # created_at: str = Field(..., description="Creation timestamp") | |
| # class MessageModel(BaseModel): | |
| # """Single message in conversation history""" | |
| # role: str = Field(..., description="Message role: user or assistant") | |
| # content: str = Field(..., description="Message content") | |
| # timestamp: str = Field(..., description="Message timestamp") | |
| # metadata: Optional[Dict] = Field(None, description="Optional metadata") | |
| # class ConversationHistoryResponse(BaseModel): | |
| # """Response containing conversation history""" | |
| # conversation_id: str | |
| # messages: List[MessageModel] | |
| # message_count: int | |
| # # ============================================================================ | |
| # # ENDPOINTS (ALL PROTECTED WITH JWT) | |
| # # ============================================================================ | |
| # @router.post("/", response_model=ChatResponse, status_code=status.HTTP_200_OK) | |
| # async def chat( | |
| # request: ChatRequest, | |
| # current_user: TokenData = Depends(get_current_user), | |
| # repo: ConversationRepository = Depends(get_conversation_repo) # ← INJECT REPO | |
| # ): | |
| # """ | |
| # Main chat endpoint - Send a query and get a response. | |
| # **REQUIRES AUTHENTICATION** - JWT token must be provided in Authorization header. | |
| # """ | |
| # try: | |
| # # Get user_id from token | |
| # user_id = current_user.user_id | |
| # # If no conversation_id provided, create a new conversation | |
| # conversation_id = request.conversation_id | |
| # if not conversation_id: | |
| # conversation_id = await repo.create_conversation(user_id=user_id) | |
| # else: | |
| # # Verify user owns this conversation | |
| # conversation = await repo.get_conversation(conversation_id) | |
| # if not conversation: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_404_NOT_FOUND, | |
| # detail="Conversation not found" | |
| # ) | |
| # if conversation["user_id"] != user_id: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_403_FORBIDDEN, | |
| # detail="Access denied - you don't own this conversation" | |
| # ) | |
| # # Get conversation history | |
| # history = await repo.get_conversation_history( | |
| # conversation_id=conversation_id, | |
| # max_messages=10 | |
| # ) | |
| # # Save user message | |
| # await repo.add_message( | |
| # conversation_id=conversation_id, | |
| # message={ | |
| # 'role': 'user', | |
| # 'content': request.query, | |
| # 'timestamp': datetime.now() | |
| # } | |
| # ) | |
| # # Process query through RAG pipeline | |
| # result = await chat_service.process_query( | |
| # query=request.query, | |
| # conversation_history=history, | |
| # user_id=user_id | |
| # ) | |
| # # Save assistant message | |
| # await repo.add_message( | |
| # conversation_id=conversation_id, | |
| # message={ | |
| # 'role': 'assistant', | |
| # 'content': result['response'], | |
| # 'timestamp': datetime.now(), | |
| # 'metadata': { | |
| # 'policy_action': result['policy_action'], | |
| # 'policy_confidence': result['policy_confidence'], | |
| # 'documents_retrieved': result['documents_retrieved'], | |
| # 'top_doc_score': result['top_doc_score'] | |
| # } | |
| # } | |
| # ) | |
| # # Log retrieval data for RL training | |
| # await repo.log_retrieval({ | |
| # 'conversation_id': conversation_id, | |
| # 'user_id': user_id, | |
| # 'query': request.query, | |
| # 'policy_action': result['policy_action'], | |
| # 'policy_confidence': result['policy_confidence'], | |
| # 'should_retrieve': result['should_retrieve'], | |
| # 'documents_retrieved': result['documents_retrieved'], | |
| # 'top_doc_score': result['top_doc_score'], | |
| # 'response': result['response'], | |
| # 'retrieval_time_ms': result['retrieval_time_ms'], | |
| # 'generation_time_ms': result['generation_time_ms'], | |
| # 'total_time_ms': result['total_time_ms'], | |
| # 'retrieved_docs_metadata': result.get('retrieved_docs_metadata', []), | |
| # 'timestamp': datetime.now() | |
| # }) | |
| # # Return response | |
| # return ChatResponse( | |
| # response=result['response'], | |
| # conversation_id=conversation_id, | |
| # policy_action=result['policy_action'], | |
| # policy_confidence=result['policy_confidence'], | |
| # documents_retrieved=result['documents_retrieved'], | |
| # top_doc_score=result['top_doc_score'], | |
| # total_time_ms=result['total_time_ms'], | |
| # timestamp=result['timestamp'] | |
| # ) | |
| # except HTTPException: | |
| # raise | |
| # except Exception as e: | |
| # print(f"❌ Chat endpoint error: {e}") | |
| # import traceback | |
| # traceback.print_exc() | |
| # raise HTTPException( | |
| # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # detail=f"Failed to process chat request: {str(e)}" | |
| # ) | |
| # @router.post("/conversation", response_model=ConversationCreateResponse, status_code=status.HTTP_201_CREATED) | |
| # async def create_conversation( | |
| # current_user: TokenData = Depends(get_current_user), | |
| # repo: ConversationRepository = Depends(get_conversation_repo) | |
| # ): | |
| # """Create a new conversation""" | |
| # try: | |
| # conversation_id = await repo.create_conversation(user_id=current_user.user_id) | |
| # return ConversationCreateResponse( | |
| # conversation_id=conversation_id, | |
| # created_at=datetime.now().isoformat() | |
| # ) | |
| # except Exception as e: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # detail=f"Failed to create conversation: {str(e)}" | |
| # ) | |
| # @router.get("/history/{conversation_id}", response_model=ConversationHistoryResponse) | |
| # async def get_conversation_history( | |
| # conversation_id: str, | |
| # current_user: TokenData = Depends(get_current_user), | |
| # repo: ConversationRepository = Depends(get_conversation_repo) | |
| # ): | |
| # """Get conversation history by ID""" | |
| # try: | |
| # conversation = await repo.get_conversation(conversation_id) | |
| # if not conversation: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_404_NOT_FOUND, | |
| # detail=f"Conversation {conversation_id} not found" | |
| # ) | |
| # if conversation["user_id"] != current_user.user_id: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_403_FORBIDDEN, | |
| # detail="Access denied - you don't own this conversation" | |
| # ) | |
| # messages = [] | |
| # for msg in conversation.get('messages', []): | |
| # messages.append(MessageModel( | |
| # role=msg['role'], | |
| # content=msg['content'], | |
| # timestamp=msg['timestamp'].isoformat() if isinstance(msg['timestamp'], datetime) else msg['timestamp'], | |
| # metadata=msg.get('metadata') | |
| # )) | |
| # return ConversationHistoryResponse( | |
| # conversation_id=conversation_id, | |
| # messages=messages, | |
| # message_count=len(messages) | |
| # ) | |
| # except HTTPException: | |
| # raise | |
| # except Exception as e: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # detail=f"Failed to fetch conversation history: {str(e)}" | |
| # ) | |
| # @router.get("/conversations") | |
| # async def list_user_conversations( | |
| # limit: int = 10, | |
| # skip: int = 0, | |
| # current_user: TokenData = Depends(get_current_user), | |
| # repo: ConversationRepository = Depends(get_conversation_repo) | |
| # ): | |
| # """List all conversations for the authenticated user""" | |
| # try: | |
| # conversations = await repo.get_user_conversations( | |
| # user_id=current_user.user_id, | |
| # limit=limit, | |
| # skip=skip | |
| # ) | |
| # return { | |
| # "user_id": current_user.user_id, | |
| # "user_email": current_user.email, | |
| # "conversations": [ | |
| # { | |
| # "conversation_id": conv['conversation_id'], | |
| # "created_at": conv['created_at'].isoformat() if isinstance(conv['created_at'], datetime) else conv['created_at'], | |
| # "updated_at": conv['updated_at'].isoformat() if isinstance(conv['updated_at'], datetime) else conv['updated_at'], | |
| # "message_count": len(conv.get('messages', [])) | |
| # } | |
| # for conv in conversations | |
| # ], | |
| # "total": len(conversations) | |
| # } | |
| # except Exception as e: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # detail=f"Failed to fetch conversations: {str(e)}" | |
| # ) | |
| # @router.delete("/conversation/{conversation_id}") | |
| # async def delete_conversation( | |
| # conversation_id: str, | |
| # current_user: TokenData = Depends(get_current_user), | |
| # repo: ConversationRepository = Depends(get_conversation_repo) | |
| # ): | |
| # """Delete a conversation""" | |
| # try: | |
| # conversation = await repo.get_conversation(conversation_id) | |
| # if not conversation: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_404_NOT_FOUND, | |
| # detail=f"Conversation {conversation_id} not found" | |
| # ) | |
| # if conversation["user_id"] != current_user.user_id: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_403_FORBIDDEN, | |
| # detail="Access denied - you don't own this conversation" | |
| # ) | |
| # success = await repo.delete_conversation(conversation_id) | |
| # if success: | |
| # return { | |
| # "message": "Conversation deleted successfully", | |
| # "conversation_id": conversation_id | |
| # } | |
| # else: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # detail="Failed to delete conversation" | |
| # ) | |
| # except HTTPException: | |
| # raise | |
| # except Exception as e: | |
| # raise HTTPException( | |
| # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # detail=f"Failed to delete conversation: {str(e)}" | |
| # ) | |
| # @router.get("/health") | |
| # async def chat_health(): | |
| # """Health check for chat service (PUBLIC)""" | |
| # try: | |
| # health = await chat_service.health_check() | |
| # return { | |
| # "status": "healthy", | |
| # "service": "chat", | |
| # "components": health['components'], | |
| # "timestamp": datetime.now().isoformat() | |
| # } | |
| # except Exception as e: | |
| # return { | |
| # "status": "unhealthy", | |
| # "service": "chat", | |
| # "error": str(e), | |
| # "timestamp": datetime.now().isoformat() | |
| # } | |
| # # ============================================================================ | |
| # # """ | |
| # # Chat API Endpoints (WITH AUTHENTICATION) | |
| # # RESTful API for the Banking RAG Chatbot | |
| # # NOW REQUIRES JWT TOKEN FOR ALL ENDPOINTS! | |
| # # Endpoints: | |
| # # - POST /chat - Send a message and get response (PROTECTED) | |
| # # - GET /chat/history/{conversation_id} - Get conversation history (PROTECTED) | |
| # # - POST /chat/conversation - Create new conversation (PROTECTED) | |
| # # - GET /chat/conversations - List user's conversations (PROTECTED) | |
| # # - DELETE /chat/conversation/{conversation_id} - Delete conversation (PROTECTED) | |
| # # - GET /chat/health - Health check (PUBLIC) | |
| # # """ | |
| # # from fastapi import APIRouter, HTTPException, status, Depends | |
| # # from pydantic import BaseModel, Field | |
| # # from typing import List, Dict, Optional | |
| # # from datetime import datetime | |
| # # from app.services.chat_service import chat_service | |
| # # from app.db.repositories.conversation_repository import ConversationRepository | |
| # # from app.utils.dependencies import get_current_user # AUTH DEPENDENCY | |
| # # from app.models.user import TokenData # USER DATA FROM TOKEN | |
| # # # ============================================================================ | |
| # # # CREATE ROUTER | |
| # # # ============================================================================ | |
| # # router = APIRouter() | |
| # # # Initialize repository | |
| # # conversation_repo = ConversationRepository() | |
| # # # ============================================================================ | |
| # # # PYDANTIC MODELS (Request/Response schemas) | |
| # # # ============================================================================ | |
| # # class ChatRequest(BaseModel): | |
| # # """ | |
| # # Request model for chat endpoint. | |
| # # NOTE: user_id is now extracted from JWT token, not from request body! | |
| # # Example: | |
| # # { | |
| # # "query": "What is my account balance?", | |
| # # "conversation_id": "abc-123" | |
| # # } | |
| # # """ | |
| # # query: str = Field(..., description="User query text", min_length=1, max_length=1000) | |
| # # conversation_id: Optional[str] = Field(None, description="Optional conversation ID") | |
| # # class Config: | |
| # # json_schema_extra = { | |
| # # "example": { | |
| # # "query": "What is my account balance?", | |
| # # "conversation_id": "conv-123" | |
| # # } | |
| # # } | |
| # # class ChatResponse(BaseModel): | |
| # # """ | |
| # # Response model for chat endpoint. | |
| # # Contains the generated response plus metadata about the RAG pipeline. | |
| # # """ | |
| # # response: str = Field(..., description="Generated response text") | |
| # # conversation_id: str = Field(..., description="Conversation ID") | |
| # # policy_action: str = Field(..., description="Policy decision: FETCH or NO_FETCH") | |
| # # policy_confidence: float = Field(..., description="Policy confidence score (0-1)") | |
| # # documents_retrieved: int = Field(..., description="Number of documents retrieved") | |
| # # top_doc_score: Optional[float] = Field(None, description="Best document similarity score") | |
| # # total_time_ms: float = Field(..., description="Total processing time in milliseconds") | |
| # # timestamp: str = Field(..., description="Response timestamp (ISO format)") | |
| # # class ConversationCreateRequest(BaseModel): | |
| # # """Request to create a new conversation (no user_id needed - from token)""" | |
| # # pass # Empty - user_id comes from JWT token | |
| # # class ConversationCreateResponse(BaseModel): | |
| # # """Response after creating a conversation""" | |
| # # conversation_id: str = Field(..., description="Created conversation ID") | |
| # # created_at: str = Field(..., description="Creation timestamp") | |
| # # class MessageModel(BaseModel): | |
| # # """Single message in conversation history""" | |
| # # role: str = Field(..., description="Message role: user or assistant") | |
| # # content: str = Field(..., description="Message content") | |
| # # timestamp: str = Field(..., description="Message timestamp") | |
| # # metadata: Optional[Dict] = Field(None, description="Optional metadata") | |
| # # class ConversationHistoryResponse(BaseModel): | |
| # # """Response containing conversation history""" | |
| # # conversation_id: str | |
| # # messages: List[MessageModel] | |
| # # message_count: int | |
| # # # ============================================================================ | |
| # # # ENDPOINTS (ALL PROTECTED WITH JWT) | |
| # # # ============================================================================ | |
| # # @router.post("/", response_model=ChatResponse, status_code=status.HTTP_200_OK) | |
| # # async def chat( | |
| # # request: ChatRequest, | |
| # # current_user: TokenData = Depends(get_current_user) # ← REQUIRES AUTH! | |
| # # ): | |
| # # """ | |
| # # Main chat endpoint - Send a query and get a response. | |
| # # **REQUIRES AUTHENTICATION** - JWT token must be provided in Authorization header. | |
| # # This endpoint: | |
| # # 1. Extracts user_id from JWT token | |
| # # 2. Processes the query through the RAG pipeline | |
| # # 3. Saves messages to MongoDB | |
| # # 4. Logs retrieval data for RL training | |
| # # 5. Returns response with metadata | |
| # # Args: | |
| # # request: ChatRequest with query and optional conversation_id | |
| # # current_user: Authenticated user data from JWT token | |
| # # Returns: | |
| # # ChatResponse: Generated response with metadata | |
| # # Raises: | |
| # # HTTPException: If processing fails or user not authenticated | |
| # # """ | |
| # # try: | |
| # # # Get user_id from token (NOT from request body!) | |
| # # user_id = current_user.user_id | |
| # # # If no conversation_id provided, create a new conversation | |
| # # conversation_id = request.conversation_id | |
| # # if not conversation_id: | |
| # # conversation_id = await conversation_repo.create_conversation( | |
| # # user_id=user_id | |
| # # ) | |
| # # else: | |
| # # # Verify user owns this conversation | |
| # # conversation = await conversation_repo.get_conversation(conversation_id) | |
| # # if not conversation: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_404_NOT_FOUND, | |
| # # detail="Conversation not found" | |
| # # ) | |
| # # if conversation["user_id"] != user_id: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_403_FORBIDDEN, | |
| # # detail="Access denied - you don't own this conversation" | |
| # # ) | |
| # # # Get conversation history | |
| # # history = await conversation_repo.get_conversation_history( | |
| # # conversation_id=conversation_id, | |
| # # max_messages=10 # Last 5 turns (10 messages) | |
| # # ) | |
| # # # Save user message to database | |
| # # await conversation_repo.add_message( | |
| # # conversation_id=conversation_id, | |
| # # message={ | |
| # # 'role': 'user', | |
| # # 'content': request.query, | |
| # # 'timestamp': datetime.now() | |
| # # } | |
| # # ) | |
| # # # Process query through RAG pipeline | |
| # # result = await chat_service.process_query( | |
| # # query=request.query, | |
| # # conversation_history=history, | |
| # # user_id=user_id | |
| # # ) | |
| # # # Save assistant message to database | |
| # # await conversation_repo.add_message( | |
| # # conversation_id=conversation_id, | |
| # # message={ | |
| # # 'role': 'assistant', | |
| # # 'content': result['response'], | |
| # # 'timestamp': datetime.now(), | |
| # # 'metadata': { | |
| # # 'policy_action': result['policy_action'], | |
| # # 'policy_confidence': result['policy_confidence'], | |
| # # 'documents_retrieved': result['documents_retrieved'], | |
| # # 'top_doc_score': result['top_doc_score'] | |
| # # } | |
| # # } | |
| # # ) | |
| # # # Log retrieval data for RL training | |
| # # await conversation_repo.log_retrieval({ | |
| # # 'conversation_id': conversation_id, | |
| # # 'user_id': user_id, | |
| # # 'query': request.query, | |
| # # 'policy_action': result['policy_action'], | |
| # # 'policy_confidence': result['policy_confidence'], | |
| # # 'should_retrieve': result['should_retrieve'], | |
| # # 'documents_retrieved': result['documents_retrieved'], | |
| # # 'top_doc_score': result['top_doc_score'], | |
| # # 'response': result['response'], | |
| # # 'retrieval_time_ms': result['retrieval_time_ms'], | |
| # # 'generation_time_ms': result['generation_time_ms'], | |
| # # 'total_time_ms': result['total_time_ms'], | |
| # # 'retrieved_docs_metadata': result.get('retrieved_docs_metadata', []), | |
| # # 'timestamp': datetime.now() | |
| # # }) | |
| # # # Return response | |
| # # return ChatResponse( | |
| # # response=result['response'], | |
| # # conversation_id=conversation_id, | |
| # # policy_action=result['policy_action'], | |
| # # policy_confidence=result['policy_confidence'], | |
| # # documents_retrieved=result['documents_retrieved'], | |
| # # top_doc_score=result['top_doc_score'], | |
| # # total_time_ms=result['total_time_ms'], | |
| # # timestamp=result['timestamp'] | |
| # # ) | |
| # # except HTTPException: | |
| # # raise # Re-raise HTTP exceptions | |
| # # except Exception as e: | |
| # # print(f"❌ Chat endpoint error: {e}") | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # detail=f"Failed to process chat request: {str(e)}" | |
| # # ) | |
| # # @router.post("/conversation", response_model=ConversationCreateResponse, status_code=status.HTTP_201_CREATED) | |
| # # async def create_conversation( | |
| # # current_user: TokenData = Depends(get_current_user) # ← REQUIRES AUTH! | |
| # # ): | |
| # # """ | |
| # # Create a new conversation. | |
| # # **REQUIRES AUTHENTICATION** - User ID is extracted from JWT token. | |
| # # Args: | |
| # # current_user: Authenticated user data from JWT token | |
| # # Returns: | |
| # # ConversationCreateResponse: Created conversation ID | |
| # # """ | |
| # # try: | |
| # # conversation_id = await conversation_repo.create_conversation( | |
| # # user_id=current_user.user_id | |
| # # ) | |
| # # return ConversationCreateResponse( | |
| # # conversation_id=conversation_id, | |
| # # created_at=datetime.now().isoformat() | |
| # # ) | |
| # # except Exception as e: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # detail=f"Failed to create conversation: {str(e)}" | |
| # # ) | |
| # # @router.get("/history/{conversation_id}", response_model=ConversationHistoryResponse) | |
| # # async def get_conversation_history( | |
| # # conversation_id: str, | |
| # # current_user: TokenData = Depends(get_current_user) # ← REQUIRES AUTH! | |
| # # ): | |
| # # """ | |
| # # Get conversation history by ID. | |
| # # **REQUIRES AUTHENTICATION** - User can only access their own conversations. | |
| # # Args: | |
| # # conversation_id: Conversation ID | |
| # # current_user: Authenticated user data from JWT token | |
| # # Returns: | |
| # # ConversationHistoryResponse: List of messages | |
| # # Raises: | |
| # # HTTPException: If conversation not found or user doesn't own it | |
| # # """ | |
| # # try: | |
| # # # Get conversation | |
| # # conversation = await conversation_repo.get_conversation(conversation_id) | |
| # # if not conversation: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_404_NOT_FOUND, | |
| # # detail=f"Conversation {conversation_id} not found" | |
| # # ) | |
| # # # Verify user owns this conversation | |
| # # if conversation["user_id"] != current_user.user_id: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_403_FORBIDDEN, | |
| # # detail="Access denied - you don't own this conversation" | |
| # # ) | |
| # # # Format messages | |
| # # messages = [] | |
| # # for msg in conversation.get('messages', []): | |
| # # messages.append(MessageModel( | |
| # # role=msg['role'], | |
| # # content=msg['content'], | |
| # # timestamp=msg['timestamp'].isoformat() if isinstance(msg['timestamp'], datetime) else msg['timestamp'], | |
| # # metadata=msg.get('metadata') | |
| # # )) | |
| # # return ConversationHistoryResponse( | |
| # # conversation_id=conversation_id, | |
| # # messages=messages, | |
| # # message_count=len(messages) | |
| # # ) | |
| # # except HTTPException: | |
| # # raise | |
| # # except Exception as e: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # detail=f"Failed to fetch conversation history: {str(e)}" | |
| # # ) | |
| # # @router.get("/conversations") | |
| # # async def list_user_conversations( | |
| # # limit: int = 10, | |
| # # skip: int = 0, | |
| # # current_user: TokenData = Depends(get_current_user) # ← REQUIRES AUTH! | |
| # # ): | |
| # # """ | |
| # # List all conversations for the authenticated user. | |
| # # **REQUIRES AUTHENTICATION** - User ID is extracted from JWT token. | |
| # # Args: | |
| # # limit: Maximum conversations to return (default: 10) | |
| # # skip: Number to skip for pagination (default: 0) | |
| # # current_user: Authenticated user data from JWT token | |
| # # Returns: | |
| # # dict: List of conversations for current user | |
| # # """ | |
| # # try: | |
| # # conversations = await conversation_repo.get_user_conversations( | |
| # # user_id=current_user.user_id, # From JWT token! | |
| # # limit=limit, | |
| # # skip=skip | |
| # # ) | |
| # # # Format response | |
| # # return { | |
| # # "user_id": current_user.user_id, | |
| # # "user_email": current_user.email, | |
| # # "conversations": [ | |
| # # { | |
| # # "conversation_id": conv['conversation_id'], | |
| # # "created_at": conv['created_at'].isoformat() if isinstance(conv['created_at'], datetime) else conv['created_at'], | |
| # # "updated_at": conv['updated_at'].isoformat() if isinstance(conv['updated_at'], datetime) else conv['updated_at'], | |
| # # "message_count": len(conv.get('messages', [])) | |
| # # } | |
| # # for conv in conversations | |
| # # ], | |
| # # "total": len(conversations) | |
| # # } | |
| # # except Exception as e: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # detail=f"Failed to fetch conversations: {str(e)}" | |
| # # ) | |
| # # @router.delete("/conversation/{conversation_id}") | |
| # # async def delete_conversation( | |
| # # conversation_id: str, | |
| # # current_user: TokenData = Depends(get_current_user) # ← REQUIRES AUTH! | |
| # # ): | |
| # # """ | |
| # # Delete a conversation. | |
| # # **REQUIRES AUTHENTICATION** - User can only delete their own conversations. | |
| # # Args: | |
| # # conversation_id: Conversation ID to delete | |
| # # current_user: Authenticated user data from JWT token | |
| # # Returns: | |
| # # dict: Success message | |
| # # Raises: | |
| # # HTTPException: If conversation not found or user doesn't own it | |
| # # """ | |
| # # try: | |
| # # # Get conversation | |
| # # conversation = await conversation_repo.get_conversation(conversation_id) | |
| # # if not conversation: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_404_NOT_FOUND, | |
| # # detail=f"Conversation {conversation_id} not found" | |
| # # ) | |
| # # # Verify user owns this conversation | |
| # # if conversation["user_id"] != current_user.user_id: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_403_FORBIDDEN, | |
| # # detail="Access denied - you don't own this conversation" | |
| # # ) | |
| # # # Delete conversation | |
| # # success = await conversation_repo.delete_conversation(conversation_id) | |
| # # if success: | |
| # # return { | |
| # # "message": "Conversation deleted successfully", | |
| # # "conversation_id": conversation_id | |
| # # } | |
| # # else: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # detail="Failed to delete conversation" | |
| # # ) | |
| # # except HTTPException: | |
| # # raise | |
| # # except Exception as e: | |
| # # raise HTTPException( | |
| # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # detail=f"Failed to delete conversation: {str(e)}" | |
| # # ) | |
| # # @router.get("/health") | |
| # # async def chat_health(): | |
| # # """ | |
| # # Health check for chat service. | |
| # # **PUBLIC ENDPOINT** - No authentication required. | |
| # # Returns: | |
| # # dict: Health status of chat service components | |
| # # """ | |
| # # try: | |
| # # health = await chat_service.health_check() | |
| # # return { | |
| # # "status": "healthy", | |
| # # "service": "chat", | |
| # # "components": health['components'], | |
| # # "timestamp": datetime.now().isoformat() | |
| # # } | |
| # # except Exception as e: | |
| # # return { | |
| # # "status": "unhealthy", | |
| # # "service": "chat", | |
| # # "error": str(e), | |
| # # "timestamp": datetime.now().isoformat() | |
| # # } | |
| # # # ============================================================================ | |
| # # # USAGE DOCUMENTATION | |
| # # # ============================================================================ | |
| # # """ | |
| # # === API USAGE EXAMPLES (WITH AUTHENTICATION) === | |
| # # ALL ENDPOINTS (except /health) NOW REQUIRE JWT TOKEN IN AUTHORIZATION HEADER! | |
| # # 1. Register user: | |
| # # POST /api/v1/auth/register | |
| # # Body: { | |
| # # "email": "user@example.com", | |
| # # "password": "SecurePass123", | |
| # # "full_name": "John Doe" | |
| # # } | |
| # # Response: { "access_token": "eyJ...", "user": {...} } | |
| # # 2. Login: | |
| # # POST /api/v1/auth/login | |
| # # Body: { | |
| # # "email": "user@example.com", | |
| # # "password": "SecurePass123" | |
| # # } | |
| # # Response: { "access_token": "eyJ...", "user": {...} } | |
| # # 3. Send chat message (WITH TOKEN): | |
| # # POST /api/v1/chat/ | |
| # # Headers: { "Authorization": "Bearer eyJ..." } | |
| # # Body: { | |
| # # "query": "What is my account balance?", | |
| # # "conversation_id": "conv_abc" // optional | |
| # # } | |
| # # 4. Get conversation history (WITH TOKEN): | |
| # # GET /api/v1/chat/history/conv_abc | |
| # # Headers: { "Authorization": "Bearer eyJ..." } | |
| # # 5. List conversations (WITH TOKEN): | |
| # # GET /api/v1/chat/conversations?limit=10 | |
| # # Headers: { "Authorization": "Bearer eyJ..." } | |
| # # === TESTING WITH CURL === | |
| # # # 1. Register | |
| # # TOKEN=$(curl -X POST "http://localhost:8000/api/v1/auth/register" \ | |
| # # -H "Content-Type: application/json" \ | |
| # # -d '{"email":"test@test.com","password":"test123","full_name":"Test User"}' \ | |
| # # | jq -r '.access_token') | |
| # # # 2. Send chat message with token | |
| # # curl -X POST "http://localhost:8000/api/v1/chat/" \ | |
| # # -H "Content-Type: application/json" \ | |
| # # -H "Authorization: Bearer $TOKEN" \ | |
| # # -d '{"query": "What is my balance?"}' | |
| # # """ | |
| # # # ============================================================================ | |
| # # # ====================================================================================================== | |
| # # # OLD CODE | |
| # # # ====================================================================================================== | |
| # # # """ | |
| # # # Chat API Endpoints | |
| # # # RESTful API for the Banking RAG Chatbot | |
| # # # Endpoints: | |
| # # # - POST /chat - Send a message and get response | |
| # # # - GET /chat/history/{conversation_id} - Get conversation history | |
| # # # - POST /chat/conversation - Create new conversation | |
| # # # - GET /chat/conversations - List user's conversations | |
| # # # - GET /chat/health - Health check for chat service | |
| # # # """ | |
| # # # from fastapi import APIRouter, HTTPException, status | |
| # # # from pydantic import BaseModel, Field | |
| # # # from typing import List, Dict, Optional | |
| # # # from datetime import datetime | |
| # # # from app.services.chat_service import chat_service | |
| # # # from app.db.repositories.conversation_repository import ConversationRepository | |
| # # # # ============================================================================ | |
| # # # # CREATE ROUTER | |
| # # # # ============================================================================ | |
| # # # router = APIRouter() | |
| # # # # Initialize repository | |
| # # # conversation_repo = ConversationRepository() | |
| # # # # ============================================================================ | |
| # # # # PYDANTIC MODELS (Request/Response schemas) | |
| # # # # ============================================================================ | |
| # # # class ChatRequest(BaseModel): | |
| # # # """ | |
| # # # Request model for chat endpoint. | |
| # # # Example: | |
| # # # { | |
| # # # "query": "What is my account balance?", | |
| # # # "conversation_id": "abc-123", | |
| # # # "user_id": "user_456" | |
| # # # } | |
| # # # """ | |
| # # # query: str = Field(..., description="User query text", min_length=1, max_length=1000) | |
| # # # conversation_id: Optional[str] = Field(None, description="Optional conversation ID") | |
| # # # user_id: str = Field(..., description="User ID") | |
| # # # class Config: | |
| # # # json_schema_extra = { | |
| # # # "example": { | |
| # # # "query": "What is my account balance?", | |
| # # # "conversation_id": "conv-123", | |
| # # # "user_id": "user-456" | |
| # # # } | |
| # # # } | |
| # # # class ChatResponse(BaseModel): | |
| # # # """ | |
| # # # Response model for chat endpoint. | |
| # # # Contains the generated response plus metadata about the RAG pipeline. | |
| # # # """ | |
| # # # response: str = Field(..., description="Generated response text") | |
| # # # conversation_id: str = Field(..., description="Conversation ID") | |
| # # # policy_action: str = Field(..., description="Policy decision: FETCH or NO_FETCH") | |
| # # # policy_confidence: float = Field(..., description="Policy confidence score (0-1)") | |
| # # # documents_retrieved: int = Field(..., description="Number of documents retrieved") | |
| # # # top_doc_score: Optional[float] = Field(None, description="Best document similarity score") | |
| # # # total_time_ms: float = Field(..., description="Total processing time in milliseconds") | |
| # # # timestamp: str = Field(..., description="Response timestamp (ISO format)") | |
| # # # class ConversationCreateRequest(BaseModel): | |
| # # # """Request to create a new conversation""" | |
| # # # user_id: str = Field(..., description="User ID") | |
| # # # class ConversationCreateResponse(BaseModel): | |
| # # # """Response after creating a conversation""" | |
| # # # conversation_id: str = Field(..., description="Created conversation ID") | |
| # # # created_at: str = Field(..., description="Creation timestamp") | |
| # # # class MessageModel(BaseModel): | |
| # # # """Single message in conversation history""" | |
| # # # role: str = Field(..., description="Message role: user or assistant") | |
| # # # content: str = Field(..., description="Message content") | |
| # # # timestamp: str = Field(..., description="Message timestamp") | |
| # # # metadata: Optional[Dict] = Field(None, description="Optional metadata") | |
| # # # class ConversationHistoryResponse(BaseModel): | |
| # # # """Response containing conversation history""" | |
| # # # conversation_id: str | |
| # # # messages: List[MessageModel] | |
| # # # message_count: int | |
| # # # # ============================================================================ | |
| # # # # ENDPOINTS | |
| # # # # ============================================================================ | |
| # # # @router.post("/", response_model=ChatResponse, status_code=status.HTTP_200_OK) | |
| # # # async def chat(request: ChatRequest): | |
| # # # """ | |
| # # # Main chat endpoint - Send a query and get a response. | |
| # # # This endpoint: | |
| # # # 1. Processes the query through the RAG pipeline | |
| # # # 2. Saves messages to MongoDB | |
| # # # 3. Logs retrieval data for RL training | |
| # # # 4. Returns response with metadata | |
| # # # Args: | |
| # # # request: ChatRequest with query, conversation_id, user_id | |
| # # # Returns: | |
| # # # ChatResponse: Generated response with metadata | |
| # # # Raises: | |
| # # # HTTPException: If processing fails | |
| # # # """ | |
| # # # try: | |
| # # # # If no conversation_id provided, create a new conversation | |
| # # # conversation_id = request.conversation_id | |
| # # # if not conversation_id: | |
| # # # conversation_id = await conversation_repo.create_conversation( | |
| # # # user_id=request.user_id | |
| # # # ) | |
| # # # # Get conversation history | |
| # # # history = await conversation_repo.get_conversation_history( | |
| # # # conversation_id=conversation_id, | |
| # # # max_messages=10 # Last 5 turns (10 messages) | |
| # # # ) | |
| # # # # Save user message to database | |
| # # # await conversation_repo.add_message( | |
| # # # conversation_id=conversation_id, | |
| # # # message={ | |
| # # # 'role': 'user', | |
| # # # 'content': request.query, | |
| # # # 'timestamp': datetime.now() | |
| # # # } | |
| # # # ) | |
| # # # # Process query through RAG pipeline | |
| # # # result = await chat_service.process_query( | |
| # # # query=request.query, | |
| # # # conversation_history=history, | |
| # # # user_id=request.user_id | |
| # # # ) | |
| # # # # Save assistant message to database | |
| # # # await conversation_repo.add_message( | |
| # # # conversation_id=conversation_id, | |
| # # # message={ | |
| # # # 'role': 'assistant', | |
| # # # 'content': result['response'], | |
| # # # 'timestamp': datetime.now(), | |
| # # # 'metadata': { | |
| # # # 'policy_action': result['policy_action'], | |
| # # # 'policy_confidence': result['policy_confidence'], | |
| # # # 'documents_retrieved': result['documents_retrieved'], | |
| # # # 'top_doc_score': result['top_doc_score'] | |
| # # # } | |
| # # # } | |
| # # # ) | |
| # # # # Log retrieval data for RL training | |
| # # # await conversation_repo.log_retrieval({ | |
| # # # 'conversation_id': conversation_id, | |
| # # # 'user_id': request.user_id, | |
| # # # 'query': request.query, | |
| # # # 'policy_action': result['policy_action'], | |
| # # # 'policy_confidence': result['policy_confidence'], | |
| # # # 'should_retrieve': result['should_retrieve'], | |
| # # # 'documents_retrieved': result['documents_retrieved'], | |
| # # # 'top_doc_score': result['top_doc_score'], | |
| # # # 'response': result['response'], | |
| # # # 'retrieval_time_ms': result['retrieval_time_ms'], | |
| # # # 'generation_time_ms': result['generation_time_ms'], | |
| # # # 'total_time_ms': result['total_time_ms'], | |
| # # # 'retrieved_docs_metadata': result.get('retrieved_docs_metadata', []), | |
| # # # 'timestamp': datetime.now() | |
| # # # }) | |
| # # # # Return response | |
| # # # return ChatResponse( | |
| # # # response=result['response'], | |
| # # # conversation_id=conversation_id, | |
| # # # policy_action=result['policy_action'], | |
| # # # policy_confidence=result['policy_confidence'], | |
| # # # documents_retrieved=result['documents_retrieved'], | |
| # # # top_doc_score=result['top_doc_score'], | |
| # # # total_time_ms=result['total_time_ms'], | |
| # # # timestamp=result['timestamp'] | |
| # # # ) | |
| # # # except Exception as e: | |
| # # # print(f"❌ Chat endpoint error: {e}") | |
| # # # raise HTTPException( | |
| # # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # # detail=f"Failed to process chat request: {str(e)}" | |
| # # # ) | |
| # # # @router.post("/conversation", response_model=ConversationCreateResponse, status_code=status.HTTP_201_CREATED) | |
| # # # async def create_conversation(request: ConversationCreateRequest): | |
| # # # """ | |
| # # # Create a new conversation. | |
| # # # Args: | |
| # # # request: ConversationCreateRequest with user_id | |
| # # # Returns: | |
| # # # ConversationCreateResponse: Created conversation ID | |
| # # # """ | |
| # # # try: | |
| # # # conversation_id = await conversation_repo.create_conversation( | |
| # # # user_id=request.user_id | |
| # # # ) | |
| # # # return ConversationCreateResponse( | |
| # # # conversation_id=conversation_id, | |
| # # # created_at=datetime.now().isoformat() | |
| # # # ) | |
| # # # except Exception as e: | |
| # # # raise HTTPException( | |
| # # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # # detail=f"Failed to create conversation: {str(e)}" | |
| # # # ) | |
| # # # @router.get("/history/{conversation_id}", response_model=ConversationHistoryResponse) | |
| # # # async def get_conversation_history(conversation_id: str): | |
| # # # """ | |
| # # # Get conversation history by ID. | |
| # # # Args: | |
| # # # conversation_id: Conversation ID | |
| # # # Returns: | |
| # # # ConversationHistoryResponse: List of messages | |
| # # # """ | |
| # # # try: | |
| # # # # Get conversation | |
| # # # conversation = await conversation_repo.get_conversation(conversation_id) | |
| # # # if not conversation: | |
| # # # raise HTTPException( | |
| # # # status_code=status.HTTP_404_NOT_FOUND, | |
| # # # detail=f"Conversation {conversation_id} not found" | |
| # # # ) | |
| # # # # Format messages | |
| # # # messages = [] | |
| # # # for msg in conversation.get('messages', []): | |
| # # # messages.append(MessageModel( | |
| # # # role=msg['role'], | |
| # # # content=msg['content'], | |
| # # # timestamp=msg['timestamp'].isoformat() if isinstance(msg['timestamp'], datetime) else msg['timestamp'], | |
| # # # metadata=msg.get('metadata') | |
| # # # )) | |
| # # # return ConversationHistoryResponse( | |
| # # # conversation_id=conversation_id, | |
| # # # messages=messages, | |
| # # # message_count=len(messages) | |
| # # # ) | |
| # # # except HTTPException: | |
| # # # raise | |
| # # # except Exception as e: | |
| # # # raise HTTPException( | |
| # # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # # detail=f"Failed to fetch conversation history: {str(e)}" | |
| # # # ) | |
| # # # @router.get("/conversations") | |
| # # # async def list_user_conversations(user_id: str, limit: int = 10, skip: int = 0): | |
| # # # """ | |
| # # # List all conversations for a user. | |
| # # # Args: | |
| # # # user_id: User ID | |
| # # # limit: Maximum conversations to return (default: 10) | |
| # # # skip: Number to skip for pagination (default: 0) | |
| # # # Returns: | |
| # # # dict: List of conversations | |
| # # # """ | |
| # # # try: | |
| # # # conversations = await conversation_repo.get_user_conversations( | |
| # # # user_id=user_id, | |
| # # # limit=limit, | |
| # # # skip=skip | |
| # # # ) | |
| # # # # Format response | |
| # # # return { | |
| # # # "user_id": user_id, | |
| # # # "conversations": [ | |
| # # # { | |
| # # # "conversation_id": conv['conversation_id'], | |
| # # # "created_at": conv['created_at'].isoformat() if isinstance(conv['created_at'], datetime) else conv['created_at'], | |
| # # # "updated_at": conv['updated_at'].isoformat() if isinstance(conv['updated_at'], datetime) else conv['updated_at'], | |
| # # # "message_count": len(conv.get('messages', [])) | |
| # # # } | |
| # # # for conv in conversations | |
| # # # ], | |
| # # # "total": len(conversations) | |
| # # # } | |
| # # # except Exception as e: | |
| # # # raise HTTPException( | |
| # # # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| # # # detail=f"Failed to fetch conversations: {str(e)}" | |
| # # # ) | |
| # # # @router.get("/health") | |
| # # # async def chat_health(): | |
| # # # """ | |
| # # # Health check for chat service. | |
| # # # Returns: | |
| # # # dict: Health status of chat service components | |
| # # # """ | |
| # # # try: | |
| # # # health = await chat_service.health_check() | |
| # # # return { | |
| # # # "status": "healthy", | |
| # # # "service": "chat", | |
| # # # "components": health['components'], | |
| # # # "timestamp": datetime.now().isoformat() | |
| # # # } | |
| # # # except Exception as e: | |
| # # # return { | |
| # # # "status": "unhealthy", | |
| # # # "service": "chat", | |
| # # # "error": str(e), | |
| # # # "timestamp": datetime.now().isoformat() | |
| # # # } | |
| # # # # ============================================================================ | |
| # # # # USAGE DOCUMENTATION | |
| # # # # ============================================================================ | |
| # # # """ | |
| # # # === API USAGE EXAMPLES === | |
| # # # 1. Send a chat message: | |
| # # # POST /api/v1/chat/ | |
| # # # Body: { | |
| # # # "query": "What is my account balance?", | |
| # # # "user_id": "user_123", | |
| # # # "conversation_id": "conv_abc" // optional | |
| # # # } | |
| # # # 2. Create new conversation: | |
| # # # POST /api/v1/chat/conversation | |
| # # # Body: { | |
| # # # "user_id": "user_123" | |
| # # # } | |
| # # # 3. Get conversation history: | |
| # # # GET /api/v1/chat/history/conv_abc | |
| # # # 4. List user's conversations: | |
| # # # GET /api/v1/chat/conversations?user_id=user_123&limit=10&skip=0 | |
| # # # 5. Check health: | |
| # # # GET /api/v1/chat/health | |
| # # # === TESTING WITH CURL === | |
| # # # # Send chat message | |
| # # # curl -X POST "http://localhost:8000/api/v1/chat/" \ | |
| # # # -H "Content-Type: application/json" \ | |
| # # # -d '{ | |
| # # # "query": "What is my balance?", | |
| # # # "user_id": "user_123" | |
| # # # }' | |
| # # # # Get history | |
| # # # curl "http://localhost:8000/api/v1/chat/history/conv_123" | |
| # # # === TESTING WITH SWAGGER UI === | |
| # # # After starting the server, visit: | |
| # # # http://localhost:8000/docs | |
| # # # Interactive API documentation with "Try it out" buttons! | |
| # # # """ |