Download api/routes/chat.py from MAWB/MCP_todo: direct link, hf CLI and curl.
- Browser
- Download file 8.91 kB
-
https://huggingface.co/spaces/MAWB/MCP_todo/resolve/main/api/routes/chat.py
- Command line
-
hf download hf://spaces/MAWB/MCP_todo/api/routes/chat.py
-
curl -L -o chat.py https://huggingface.co/spaces/MAWB/MCP_todo/resolve/main/api/routes/chat.py
8.91 kB
| """ | |
| Chat API endpoints for Todo AI Chatbot. | |
| Per @specs/001-chatbot-mcp/contracts/openapi.yaml and @specs/001-chatbot-mcp/plan.md | |
| """ | |
| from fastapi import APIRouter, Depends, HTTPException, Request, status | |
| from sqlmodel import Session | |
| from uuid import UUID | |
| from datetime import datetime | |
| import time | |
| import logging | |
| from config import engine | |
| from api.dependencies import get_current_user | |
| from api.schemas.chat import ChatRequest, ChatResponse, Message, ChatError | |
| from services.chat import ChatService | |
| from agents.todo_agent import create_todo_agent | |
| from models.message import MessageRole | |
| logger = logging.getLogger(__name__) | |
| # Create router | |
| router = APIRouter(prefix="/api", tags=["chat"]) | |
| async def chat_endpoint( | |
| user_id: str, | |
| request: ChatRequest, | |
| http_request: Request, | |
| current_user: str = Depends(get_current_user) | |
| ): | |
| """ | |
| Chat endpoint for AI-powered task management. | |
| Processes user messages through OpenAI agent with MCP tool integration. | |
| Creates new conversations or continues existing ones. | |
| Per @specs/001-chatbot-mcp/plan.md: | |
| - Stateless architecture: history loaded from DB each request | |
| - MCP First: all task operations through MCP tools | |
| - Data isolation: all queries filter by user_id | |
| Per @specs/001-chatbot-mcp/contracts/openapi.yaml | |
| """ | |
| start_time = time.time() | |
| # Verify user ownership | |
| if current_user != user_id: | |
| logger.warning(f"User {current_user} attempted to access user {user_id} chat") | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Access forbidden: You can only access your own chat" | |
| ) | |
| # Parse user_id as UUID | |
| try: | |
| user_uuid = UUID(user_id) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail="Invalid user ID format" | |
| ) | |
| with Session(engine) as session: | |
| try: | |
| # Get or create conversation | |
| conversation = None | |
| conversation_id = request.conversation_id | |
| if conversation_id: | |
| # Validate user owns this conversation | |
| conversation = ChatService.get_conversation(session, conversation_id, user_uuid) | |
| if not conversation: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Conversation not found or access denied" | |
| ) | |
| else: | |
| # Create new conversation for first message | |
| conversation = ChatService.create_conversation( | |
| session=session, | |
| user_id=user_uuid, | |
| title="New Chat" # Can be updated based on first message content | |
| ) | |
| conversation_id = conversation.id | |
| logger.info(f"Created new conversation {conversation_id} for user {user_id}") | |
| # Sanitize user input | |
| sanitized_message = ChatService.sanitize_user_input(request.message) | |
| # Store user message | |
| user_message = ChatService.store_message( | |
| session=session, | |
| conversation_id=conversation_id, | |
| role=MessageRole.USER, | |
| content=sanitized_message | |
| ) | |
| # Load conversation history | |
| history = ChatService.get_conversation_history( | |
| session=session, | |
| conversation_id=conversation_id, | |
| user_id=user_uuid | |
| ) | |
| # Format for OpenAI (exclude the message we just added) | |
| formatted_history = ChatService.format_messages_for_openai(history[:-1]) | |
| # Create agent and process message | |
| agent = create_todo_agent(user_uuid) | |
| # Collect agent response | |
| response_parts = [] | |
| async for chunk in agent.process_message(sanitized_message, formatted_history): | |
| response_parts.append(chunk) | |
| assistant_response = "".join(response_parts) | |
| # Store assistant response | |
| assistant_message = ChatService.store_message( | |
| session=session, | |
| conversation_id=conversation_id, | |
| role=MessageRole.ASSISTANT, | |
| content=assistant_response, | |
| metadata={"processing_time": time.time() - start_time} | |
| ) | |
| # Calculate processing time | |
| processing_time = time.time() - start_time | |
| # Log request | |
| logger.info( | |
| f"Chat processed: user={user_id}, " | |
| f"conversation={conversation_id}, " | |
| f"processing_time={processing_time:.2f}s, " | |
| f"message_length={len(request.message)}" | |
| ) | |
| # Build response | |
| return ChatResponse( | |
| conversation_id=conversation_id, | |
| message=Message( | |
| id=assistant_message.id, | |
| role="assistant", | |
| content=assistant_response, | |
| created_at=assistant_message.created_at | |
| ), | |
| tasks=None # Could be populated with affected tasks if needed | |
| ) | |
| except HTTPException: | |
| # Re-raise HTTP exceptions as-is | |
| raise | |
| except Exception as e: | |
| # Log error for debugging | |
| logger.error(f"Error processing chat request: {e}", exc_info=True) | |
| # Return user-friendly error message | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail={ | |
| "error": "Failed to process chat message", | |
| "message": "I encountered an error processing your request. Please try again.", | |
| "conversation_id": str(conversation_id) if conversation_id else None | |
| } | |
| ) | |
| async def list_conversations( | |
| user_id: str, | |
| current_user: str = Depends(get_current_user) | |
| ): | |
| """ | |
| List all conversations for a user. | |
| Returns conversations ordered by most recently updated. | |
| """ | |
| # Verify user ownership | |
| if current_user != user_id: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Access forbidden: You can only access your own conversations" | |
| ) | |
| try: | |
| user_uuid = UUID(user_id) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail="Invalid user ID format" | |
| ) | |
| with Session(engine) as session: | |
| conversations = ChatService.get_user_conversations(session, user_uuid) | |
| return { | |
| "conversations": [ | |
| { | |
| "id": str(conv.id), | |
| "title": conv.title, | |
| "created_at": conv.created_at.isoformat(), | |
| "updated_at": conv.updated_at.isoformat() | |
| } | |
| for conv in conversations | |
| ], | |
| "count": len(conversations) | |
| } | |
| async def get_conversation_messages( | |
| user_id: str, | |
| conversation_id: str, | |
| current_user: str = Depends(get_current_user) | |
| ): | |
| """ | |
| Get all messages in a conversation. | |
| Requires user owns the conversation. | |
| """ | |
| # Verify user ownership | |
| if current_user != user_id: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Access forbidden" | |
| ) | |
| try: | |
| user_uuid = UUID(user_id) | |
| conv_uuid = UUID(conversation_id) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail="Invalid ID format" | |
| ) | |
| with Session(engine) as session: | |
| # Verify user owns the conversation | |
| conversation = ChatService.get_conversation(session, conv_uuid, user_uuid) | |
| if not conversation: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Conversation not found" | |
| ) | |
| # Get messages | |
| messages = ChatService.get_conversation_history( | |
| session=session, | |
| conversation_id=conv_uuid, | |
| user_id=user_uuid | |
| ) | |
| return { | |
| "conversation_id": conversation_id, | |
| "messages": [ | |
| { | |
| "id": str(msg.id), | |
| "role": msg.role.value, | |
| "content": msg.content, | |
| "created_at": msg.created_at.isoformat() | |
| } | |
| for msg in messages | |
| ], | |
| "count": len(messages) | |
| } | |