File size: 7,669 Bytes
b07a1e9 f051da6 b07a1e9 f051da6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | import os
import sys
import time
from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException, Header, Request
from fastapi.responses import JSONResponse
import logging
from qdrant_client import QdrantClient
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from services.rag_service import RAGService
from auth.jwt_utils import get_current_user_id_from_token
router = APIRouter()
# Configure OpenRouter and RAG service
openrouter_api_key = os.getenv("OPENAI_API_KEY")
qdrant_url = os.getenv("QDRANT_URL")
qdrant_api_key = os.getenv("QDRANT_API_KEY")
collection_name = os.getenv("QDRANT_COLLECTION", "project_documents")
if openrouter_api_key and openrouter_api_key != "your_openrouter_api_key_here":
# Initialize Qdrant client
try:
# Check if cloud Qdrant is configured
qdrant_host = os.getenv("QDRANT_HOST")
if qdrant_api_key and qdrant_host and "qdrant.io" in qdrant_host:
# Use Qdrant Cloud
qdrant_client = QdrantClient(
url=qdrant_host, # Use QDRANT_HOST which has the full URL
api_key=qdrant_api_key,
prefer_grpc=False
)
else:
# Use local Qdrant - extract host from URL if needed
if qdrant_url and qdrant_url.startswith("http"):
# Extract host and port from URL
from urllib.parse import urlparse
parsed = urlparse(qdrant_url)
host = parsed.hostname or "localhost"
port = parsed.port or 6333
else:
host = qdrant_url or "localhost"
port = int(os.getenv("QDRANT_PORT", 6333))
qdrant_client = QdrantClient(host=host, port=port)
except Exception as e:
logger.error(f"Failed to initialize Qdrant client: {str(e)}")
# Create a dummy client to allow service to work without Qdrant
qdrant_client = None
# Initialize RAG service with OpenRouter
rag_service = RAGService(openrouter_api_key, qdrant_client, collection_name)
else:
rag_service = None
logger = logging.getLogger(__name__)
# JWT verification dependency
def verify_jwt_token(authorization: str = Header(None)) -> dict:
"""Verify JWT token from Authorization header"""
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization format")
token = authorization[7:] # Remove "Bearer " prefix
user_id = get_current_user_id_from_token(token)
if not user_id:
raise HTTPException(status_code=401, detail="Invalid or expired token")
return {"user_id": user_id}
@router.post("/chat")
async def chat(payload: dict, user_data: dict = Depends(verify_jwt_token)):
user_msg = payload["message"]
selected_text = payload.get("selected_text", "")
# If selected text is provided, try to use RAG service to answer based only on that text
if selected_text and rag_service:
try:
# Use the RAG service to answer based on selected text only (with OpenRouter)
answer = rag_service.query_rag(selected_text, user_msg)
return {"answer": answer}
except Exception as e:
logger.error(f"RAG service failed: {str(e)}")
# Fall through to fallback response below
elif selected_text and not rag_service:
logger.warning("RAG service not available, using fallback")
# Fallback response when API is unavailable or not configured
fallback_responses = {
"hello": "Hello! I'm your AI textbook assistant. Feel free to ask questions about the content you're studying!",
"hi": "Hi there! I'm here to help you understand the AI and robotics concepts in your textbook. What would you like to know?",
"help": "I can help explain concepts from your AI and robotics textbook! Please select some text and ask questions about it.",
"default": f"I'm currently unable to process your request about '{user_msg}'. This might be because the AI service is temporarily unavailable or needs to be configured with a valid API key. The system is working properly but requires a valid OPENROUTER_API_KEY to provide AI-generated responses."
}
response_text = fallback_responses.get(user_msg.lower().strip(), fallback_responses["default"])
result = {"answer": response_text}
if not rag_service:
result["setup_needed"] = "Please configure a valid OPENROUTER_API_KEY in the .env file to enable AI responses"
return result
# ============================================================
# OpenAI ChatKit SDK Compatible Endpoint
# Provides OpenAI Chat Completions API format for ChatKit React
# ============================================================
@router.post("/chatkit/session")
async def chatkit_session(request: Request, user_data: dict = Depends(verify_jwt_token)):
"""
Create a ChatKit session. Returns a client_secret for the ChatKit React component.
This is a simplified session endpoint for self-hosted ChatKit.
"""
session_id = str(uuid4())
return {
"client_secret": f"chatkit_{session_id}_{user_data['user_id']}",
"session_id": session_id,
"user_id": user_data["user_id"],
}
@router.post("/chatkit/chat")
async def chatkit_chat(payload: dict, user_data: dict = Depends(verify_jwt_token)):
"""
ChatKit-compatible chat endpoint.
Accepts OpenAI Chat Completions format and routes through RAG service.
Used by the @openai/chatkit-react frontend component.
"""
messages = payload.get("messages", [])
selected_text = payload.get("selected_text", "")
# Extract the last user message
user_msg = ""
for msg in reversed(messages):
if msg.get("role") == "user":
user_msg = msg.get("content", "")
break
if not user_msg:
user_msg = payload.get("message", "")
# Process through RAG service
answer = ""
if selected_text and rag_service:
try:
answer = rag_service.query_rag(selected_text, user_msg)
except Exception as e:
logger.error(f"ChatKit RAG service failed: {str(e)}")
answer = "Is sawal ka jawab provided data me mojood nahi hai."
elif rag_service:
try:
answer = rag_service.query_rag(user_msg, user_msg)
except Exception as e:
logger.error(f"ChatKit RAG service failed: {str(e)}")
answer = "Please select text from the textbook to ask questions."
else:
answer = "AI service is not configured. Please set up OPENROUTER_API_KEY."
# Return in OpenAI Chat Completions format (ChatKit compatible)
return {
"id": f"chatcmpl-{uuid4().hex[:12]}",
"object": "chat.completion",
"created": int(time.time()),
"model": "openai/gpt-3.5-turbo",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": answer,
},
"finish_reason": "stop",
}
],
"usage": {
"prompt_tokens": len(user_msg.split()),
"completion_tokens": len(answer.split()),
"total_tokens": len(user_msg.split()) + len(answer.split()),
},
} |