Spaces:
Running
Running
| """ | |
| Chatbot API routes. | |
| """ | |
| import logging | |
| from uuid import uuid4 | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from psycopg import Connection | |
| from langchain_core.runnables.history import RunnableWithMessageHistory | |
| from langchain_tavily import TavilySearch | |
| from app.core.chatbot.config import CHAT_HISTORY_TABLE | |
| from app.core.chatbot.chains import general_chain, news_chain | |
| from app.core.chatbot.database import db_connection_dependency | |
| from app.schemas.chat import ChatRequest | |
| from app.services.chatbot.history import delete_guest_session, get_message_history | |
| from app.services.chatbot.search import ( | |
| extract_search_query, | |
| format_search_results, | |
| get_tavily_search, | |
| should_use_search, | |
| ) | |
| logger = logging.getLogger(__name__) | |
| chat = APIRouter() | |
| async def chat_endpoint( | |
| request: ChatRequest, | |
| tavily_search: TavilySearch = Depends(get_tavily_search), | |
| ): | |
| """ | |
| Chat endpoint with intelligent query processing. | |
| Flow: | |
| 1. LLM classifies if query needs web search | |
| 2. If NO search needed -> respond directly with LLM knowledge | |
| 3. If search needed -> extract optimized search query -> search with Tavily -> respond with sources | |
| Returns: | |
| response: The LLM's response | |
| session_id: Session ID for conversation history | |
| used_search: Whether web search was used | |
| search_reason: Why search was/wasn't used | |
| """ | |
| try: | |
| # Ensure every session has an id for history tracking (DB or in-memory) | |
| session_id = request.session_id or str(uuid4()) | |
| save_to_db = request.save_to_db | |
| # Step 1: LLM decides if search is needed | |
| needs_search, search_reason = should_use_search(request.query) | |
| logger.info("Search needed: %s, Reason: %s", needs_search, search_reason) | |
| def get_history(sid: str): | |
| return get_message_history(sid, save_to_db) | |
| # Step 2: Take appropriate path | |
| if not needs_search: | |
| # Respond directly without search | |
| chain_with_history = RunnableWithMessageHistory( | |
| general_chain, | |
| get_history, | |
| input_messages_key="question", | |
| history_messages_key="chat_history", | |
| ) | |
| response = chain_with_history.invoke( | |
| {"question": request.query}, | |
| config={"configurable": {"session_id": session_id}}, | |
| ) | |
| else: | |
| # Step 2a: Extract optimized search query | |
| optimized_query = extract_search_query(request.query) | |
| logger.info("Optimized search query: %s", optimized_query) | |
| # Step 2b: Search with optimized query | |
| search_results = tavily_search.invoke(optimized_query) | |
| formatted_results = format_search_results(search_results) | |
| logger.debug("Search results: %s", search_results) | |
| # Step 2c: Respond with sources | |
| chain_with_history = RunnableWithMessageHistory( | |
| news_chain, | |
| get_history, | |
| input_messages_key="question", | |
| history_messages_key="chat_history", | |
| ) | |
| response = chain_with_history.invoke( | |
| { | |
| "question": request.query, | |
| "search_results": formatted_results, | |
| }, | |
| config={"configurable": {"session_id": session_id}}, | |
| ) | |
| logger.debug("Response: %s", response.content) | |
| return { | |
| "response": { | |
| "content": response.content, | |
| }, | |
| "session_id": session_id, | |
| "used_search": needs_search, | |
| "search_reason": search_reason, | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as exc: # pragma: no cover - defensive server guard | |
| logger.exception("Unhandled error in chat_endpoint") | |
| raise HTTPException(status_code=500, detail=str(exc)) | |
| async def delete_chat_history(session_id: str, conn: Connection = Depends(db_connection_dependency)): | |
| """Delete chat history for a specific session.""" | |
| try: | |
| # Delete from in-memory guest sessions | |
| delete_guest_session(session_id) | |
| # Delete from database | |
| with conn.cursor() as cursor: | |
| cursor.execute( | |
| f'DELETE FROM "{CHAT_HISTORY_TABLE}" WHERE session_id = %s', | |
| (session_id,), | |
| ) | |
| deleted_count = cursor.rowcount | |
| conn.commit() | |
| return { | |
| "success": True, | |
| "deleted_count": deleted_count, | |
| "session_id": session_id, | |
| } | |
| except Exception as e: | |
| logger.exception("Error in delete_chat_history") | |
| raise HTTPException(status_code=500, detail=str(e)) | |