Spaces:
Running
Running
| import os | |
| import uuid | |
| from typing import Optional | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from fastapi.responses import StreamingResponse | |
| from fastapi.security import OAuth2PasswordBearer | |
| from src.core.domain.schemas import ChatRequest, ChatResponse, SearchResponse | |
| from src.core.use_cases.search_use_case import SearchUseCase | |
| from src.core.use_cases.rag_chat_use_case import RagChatUseCase | |
| from src.core.use_cases.agent_router_use_case import AgentRouterUseCase | |
| from src.api.dependencies import get_search_use_case, get_rag_chat_use_case, get_agent_router_use_case | |
| from src.core.security import get_current_user | |
| from src.core.domain.db_models import User | |
| from jose import jwt, JWTError | |
| from src.core.config import settings | |
| from src.infrastructure.database import get_db | |
| from sqlalchemy.orm import Session | |
| router = APIRouter() | |
| # Optional bearer — doesn't raise if token is missing | |
| _optional_bearer = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login", auto_error=False) | |
| def get_optional_user( | |
| token: Optional[str] = Depends(_optional_bearer), | |
| db: Session = Depends(get_db) | |
| ) -> Optional[User]: | |
| """Returns the authenticated user or None for guests.""" | |
| if not token: | |
| return None | |
| try: | |
| payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) | |
| if payload.get("type") != "access": | |
| return None | |
| email = payload.get("sub") | |
| if not email: | |
| return None | |
| user = db.query(User).filter(User.email == email).first() | |
| return user if user and user.is_active else None | |
| except JWTError: | |
| return None | |
| def direct_search( | |
| request: ChatRequest, | |
| search_use_case: SearchUseCase = Depends(get_search_use_case), | |
| current_user: User = Depends(get_current_user) | |
| ): | |
| """Lightning-fast hybrid search bypassing the LLM.""" | |
| try: | |
| results = search_use_case.execute( | |
| query=request.query, | |
| limit=request.top_k, | |
| source_filter=request.source_filter, | |
| language_filter=request.language_filter, | |
| days_back=getattr(request, 'days_back', None) | |
| ) | |
| hits = [{"content": r.content, "metadata": r.metadata, "score": r.score, "doc_id": r.doc_id} for r in results] | |
| return {"results": hits, "query": request.query} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def chat_test( | |
| request: ChatRequest, | |
| agent_router_use_case: AgentRouterUseCase = Depends(get_agent_router_use_case) | |
| ): | |
| """Test RAG chat endpoint without authentication for debugging""" | |
| # Get endpoint from environment variable or use default | |
| os.getenv("RAG_ENDPOINT", "/rag/chat/test") | |
| result = await agent_router_use_case.execute_chat(request) | |
| return result | |
| async def chat_with_rag_stream( | |
| request: ChatRequest, | |
| router_use_case: AgentRouterUseCase = Depends(get_agent_router_use_case), | |
| current_user: Optional[User] = Depends(get_optional_user) | |
| ): | |
| """Streaming RAG chat. Works for both authenticated users and guests.""" | |
| try: | |
| if current_user is None and not request.session_id: | |
| request.session_id = f"guest_{uuid.uuid4().hex[:12]}" | |
| user_id = current_user.id if current_user else None | |
| # X-Accel-Buffering: no tells HF nginx NOT to buffer — fixes ERR_HTTP2_PROTOCOL_ERROR | |
| # NOTE: Do NOT set Transfer-Encoding: chunked — it is prohibited in HTTP/2 (RFC 7540 §8.1.2.2) | |
| headers = { | |
| "X-Accel-Buffering": "no", | |
| "Cache-Control": "no-cache, no-transform", | |
| } | |
| return StreamingResponse( | |
| router_use_case.execute_stream(request, is_guest=(current_user is None), user_id=user_id), | |
| media_type="text/event-stream", | |
| headers=headers, | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |