TalatMasood's picture
Working chat with context and history
9700f95
raw
history blame
12.6 kB
# src/main.py
from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
from fastapi.responses import StreamingResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware # Add this import
from typing import List
import uuid
from datetime import datetime
from pathlib import Path
import os
# Import custom modules1
from src.agents.rag_agent import RAGAgent
from src.models.document import AllDocumentsResponse, StoredDocument
from src.utils.document_processor import DocumentProcessor
from src.utils.conversation_summarizer import ConversationSummarizer
from src.utils.logger import logger
from src.utils.llm_utils import get_llm_instance, get_vector_store
from src.db.mongodb_store import MongoDBStore
from src.implementations.document_service import DocumentService
from src.models import (
ChatRequest,
ChatResponse,
DocumentResponse,
BatchUploadResponse,
SummarizeRequest,
SummaryResponse,
FeedbackRequest
)
from config.config import settings
app = FastAPI(title="Chatbot API")
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:8080"], # Add your frontend URL
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)
# Initialize MongoDB
mongodb = MongoDBStore(settings.MONGODB_URI)
# Initialize core components
doc_processor = DocumentProcessor()
summarizer = ConversationSummarizer()
document_service = DocumentService(doc_processor, mongodb)
# Create uploads directory if it doesn't exist
UPLOADS_DIR = Path("uploads")
UPLOADS_DIR.mkdir(exist_ok=True)
# Mount the uploads directory for static file serving
app.mount("/docs", StaticFiles(directory=str(UPLOADS_DIR)), name="documents")
@app.get("/documents")
async def get_all_documents():
"""Get all documents from MongoDB"""
try:
documents = await mongodb.get_all_documents()
formatted_documents = []
for doc in documents:
try:
formatted_doc = {
"document_id": doc.get("document_id"),
"filename": doc.get("filename"),
"content_type": doc.get("content_type"),
"file_size": doc.get("file_size"),
"url_path": doc.get("url_path"),
"upload_timestamp": doc.get("upload_timestamp")
}
formatted_documents.append(formatted_doc)
except Exception as e:
logger.error(f"Error formatting document {doc.get('document_id', 'unknown')}: {str(e)}")
continue
return {
"total_documents": len(formatted_documents),
"documents": formatted_documents
}
except Exception as e:
logger.error(f"Error retrieving documents: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/documents/{document_id}/download")
async def get_document_file(document_id: str):
"""Serve a document file by its ID"""
try:
# Get document info from MongoDB
doc = await mongodb.get_document(document_id)
if not doc:
raise HTTPException(status_code=404, detail="Document not found")
# Extract filename from url_path
filename = doc["url_path"].split("/")[-1]
file_path = UPLOADS_DIR / filename
if not file_path.exists():
raise HTTPException(
status_code=404,
detail=f"File not found on server: {filename}"
)
return FileResponse(
path=str(file_path),
filename=doc["filename"],
media_type=doc["content_type"]
)
except Exception as e:
logger.error(f"Error serving document file: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/documents/upload", response_model=BatchUploadResponse)
async def upload_documents(
files: List[UploadFile] = File(...),
background_tasks: BackgroundTasks = BackgroundTasks()
):
"""Upload and process multiple documents"""
try:
vector_store, _ = await get_vector_store()
response = await document_service.process_documents(
files,
vector_store,
background_tasks
)
return response
except Exception as e:
logger.error(f"Error in document upload: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/documentchunks/{document_id}")
async def get_document_chunks(document_id: str):
"""Get all chunks for a specific document"""
try:
vector_store, _ = await get_vector_store()
chunks = vector_store.get_document_chunks(document_id)
if not chunks:
raise HTTPException(status_code=404, detail="Document not found")
return {
"document_id": document_id,
"total_chunks": len(chunks),
"chunks": chunks
}
except Exception as e:
logger.error(f"Error retrieving document chunks: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/documents/{document_id}")
async def delete_document(document_id: str):
"""Delete document from MongoDB, ChromaDB, and physical storage"""
try:
# First get document details from MongoDB to get file path
document = await mongodb.get_document(document_id)
if not document:
raise HTTPException(status_code=404, detail="Document not found")
# Get vector store instance
vector_store, _ = await get_vector_store()
# Delete physical file using document service
deletion_success = await document_service.delete_document(document_id)
if not deletion_success:
logger.warning(f"Failed to delete physical file for document {document_id}")
# Delete from vector store
try:
vector_store.delete_document(document_id)
except Exception as e:
logger.error(f"Error deleting document from vector store: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Failed to delete document from vector store: {str(e)}"
)
# Delete from MongoDB - don't check return value since document might already be deleted
await mongodb.delete_document(document_id)
return {
"status": "success",
"message": f"Document {document_id} successfully deleted from all stores"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error in delete_document endpoint: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/chat", response_model=ChatResponse)
async def chat_endpoint(
request: ChatRequest,
background_tasks: BackgroundTasks
):
"""Chat endpoint with RAG support"""
try:
vector_store, embedding_model = await get_vector_store()
llm = get_llm_instance(request.llm_provider)
# Initialize RAG agent with required MongoDB
rag_agent = RAGAgent(
llm=llm,
embedding=embedding_model,
vector_store=vector_store,
mongodb=mongodb
)
# Use provided conversation ID or create new one
conversation_id = request.conversation_id or str(uuid.uuid4())
query = request.query + ". The response should be short and to the point. make sure, to not add any irrelevant information. Stick to the point is very very important."
# Generate response
response = await rag_agent.generate_response(
query=query,
conversation_id=conversation_id,
temperature=request.temperature
)
# Store message in chat history
await mongodb.store_message(
conversation_id=conversation_id,
query=request.query,
response=response.response,
context=response.context_docs,
sources=response.sources,
llm_provider=request.llm_provider
)
return ChatResponse(
response=response.response,
context=response.context_docs,
sources=response.sources,
conversation_id=conversation_id,
timestamp=datetime.now(),
relevant_doc_scores=response.scores if hasattr(response, 'scores') else None
)
except Exception as e:
logger.error(f"Error in chat endpoint: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/chat/history/{conversation_id}")
async def get_conversation_history(conversation_id: str):
"""Get complete conversation history"""
history = await mongodb.get_conversation_history(conversation_id)
if not history:
raise HTTPException(status_code=404, detail="Conversation not found")
return {
"conversation_id": conversation_id,
"messages": history
}
@app.post("/chat/summarize", response_model=SummaryResponse)
async def summarize_conversation(request: SummarizeRequest):
"""Generate a summary of a conversation"""
try:
messages = await mongodb.get_messages_for_summary(request.conversation_id)
if not messages:
raise HTTPException(status_code=404, detail="Conversation not found")
summary = await summarizer.summarize_conversation(
messages,
include_metadata=request.include_metadata
)
return SummaryResponse(**summary)
except Exception as e:
logger.error(f"Error generating summary: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/chat/feedback/{conversation_id}")
async def submit_feedback(
conversation_id: str,
feedback_request: FeedbackRequest
):
"""Submit feedback for a conversation"""
try:
# Validate conversation exists
conversation = await mongodb.get_conversation_metadata(conversation_id)
if not conversation:
raise HTTPException(status_code=404, detail="Conversation not found")
# Update feedback
success = await mongodb.update_feedback(
conversation_id=conversation_id,
feedback=feedback_request.feedback,
rating=feedback_request.rating
)
if not success:
raise HTTPException(
status_code=500,
detail="Failed to update feedback"
)
return {
"status": "success",
"message": "Feedback submitted successfully",
"data": {
"conversation_id": conversation_id,
"feedback": feedback_request.feedback,
"rating": feedback_request.format_rating()
}
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error submitting feedback: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/debug/config")
async def debug_config():
"""Debug endpoint to check configuration"""
import os
from config.config import settings
from pathlib import Path
debug_info = {
"environment_variables": {
"OPENAI_API_KEY": "[SET]" if os.getenv('OPENAI_API_KEY') else "[NOT SET]",
"OPENAI_MODEL": os.getenv('OPENAI_MODEL', '[NOT SET]')
},
"settings": {
"OPENAI_API_KEY": "[SET]" if settings.OPENAI_API_KEY else "[NOT SET]",
"OPENAI_MODEL": settings.OPENAI_MODEL,
},
"files": {
"env_file_exists": Path('.env').exists(),
"openai_config_exists": (Path.home() / '.openai' / 'api_key').exists()
}
}
if settings.OPENAI_API_KEY:
key = settings.OPENAI_API_KEY
debug_info["api_key_info"] = {
"length": len(key),
"preview": f"{key[:4]}...{key[-4:]}" if len(key) > 8 else "[INVALID LENGTH]"
}
return debug_info
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {"status": "healthy"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)