Skip to content

Commit a806a77

Browse files
Merge pull request #20 from debabratamishra/feat/web_search
Web Search Integration
2 parents 31618a6 + 8a99ea5 commit a806a77

File tree

15 files changed

+1769
-91
lines changed

15 files changed

+1769
-91
lines changed

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ NUMEXPR_NUM_THREADS=4
2424
# vLLM Configuration (Optional)
2525
# HF_TOKEN=your-huggingface-token-here
2626

27+
# Web Search Configuration
28+
# Get your API key from https://serpapi.com/
29+
SERP_API_KEY=your-serpapi-key-here
30+
# Optional: Maximum number of search results to retrieve (default: 10)
31+
WEB_SEARCH_MAX_RESULTS=10
32+
# Optional: Timeout for web search requests in seconds (default: 30)
33+
WEB_SEARCH_TIMEOUT=30
34+
2735
# Cache directories (automatically configured by docker-compose)
2836
# HUGGINGFACE_CACHE_DIR=${HOME}/.cache/huggingface
2937
# OLLAMA_CACHE_DIR=${HOME}/.ollama

README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ A robust, production-ready web interface for Large Language Models (LLMs) featur
3737
-**High-Performance API** - Async FastAPI backend for scalable LLM processing
3838
- 🧠 **Dual Backend Support** - Seamlessly switch between Ollama (local) and vLLM (Hugging Face) backends
3939
- 📚 **RAG Integration** - Upload documents (PDFs, DOCX, TXT) with enhanced extraction and query with context-aware responses
40+
- 🔍 **Web Search Integration** - Optional real-time web search powered by SerpAPI with AI-driven synthesis
4041
- 🔄 **Auto-Failover** - Intelligent backend detection with graceful fallbacks
4142
- 🤖 **Multi-Model Support** - Access to popular models through vLLM or local Ollama models
4243

@@ -231,6 +232,8 @@ mkdir -p uploads .streamlit
231232
| `/models` | GET | Available Ollama models |
232233
| `/api/chat` | POST | Process chat messages (supports both Ollama and vLLM backends) |
233234
| `/api/chat/stream` | POST | Streaming chat responses (supports both backends) |
235+
| `/api/chat/web-search` | POST | Process chat with optional web search integration |
236+
| `/api/chat/serp-status` | GET | Check SerpAPI token configuration status |
234237
| `/api/rag/upload` | POST | Upload documents for RAG processing |
235238
| `/api/rag/query` | POST | Query uploaded documents with context-aware responses |
236239
| `/api/rag/documents` | GET | List uploaded documents |
@@ -246,7 +249,8 @@ mkdir -p uploads .streamlit
246249
3. **Configure Models:**
247250
- For Ollama: Select from locally installed models
248251
- For vLLM: Choose from popular models or enter custom model names
249-
4. Enter your message and receive AI responses
252+
4. **Optional Web Search:** Enable the web search toggle to enhance responses with real-time internet data
253+
5. Enter your message and receive AI responses
250254

251255
### Document Q\&A (RAG)
252256

@@ -316,6 +320,59 @@ export OLLAMA_BASE_URL="http://localhost:11434"
316320
export UPLOAD_FOLDER="./uploads"
317321
```
318322

323+
### Web Search Configuration (Optional)
324+
325+
LiteMindUI supports optional web search integration powered by SerpAPI. When enabled, the system can retrieve real-time information from the internet and synthesize it with your local LLM.
326+
327+
#### Setting up SerpAPI
328+
329+
1. **Get your API key:**
330+
- Visit [https://serpapi.com/](https://serpapi.com/)
331+
- Sign up for a free account (100 searches/month on free tier)
332+
- Navigate to your dashboard to find your API key
333+
334+
2. **Configure the API key:**
335+
336+
Add to your `.env` file:
337+
```bash
338+
SERP_API_KEY=your-serpapi-key-here
339+
```
340+
341+
Or export as environment variable:
342+
```bash
343+
export SERP_API_KEY="your-serpapi-key-here"
344+
```
345+
346+
3. **Optional settings:**
347+
```bash
348+
# Maximum number of search results to retrieve (default: 10)
349+
WEB_SEARCH_MAX_RESULTS=10
350+
351+
# Timeout for web search requests in seconds (default: 30)
352+
WEB_SEARCH_TIMEOUT=30
353+
```
354+
355+
#### Using Web Search
356+
357+
1. Navigate to the **Chat** tab in the UI
358+
2. Look for the **Web Search** toggle in the prompt area
359+
3. Enable the toggle before submitting your query
360+
4. The system will:
361+
- Retrieve top 10 search results from the web
362+
- Synthesize the information using your selected LLM
363+
- Display a comprehensive response with web context
364+
365+
**Status Indicators:**
366+
- The sidebar displays your SerpAPI token status (valid/missing/invalid)
367+
- During search: "Searching web..." status message
368+
- During synthesis: "Synthesizing results..." status message
369+
370+
**Fallback Behavior:**
371+
- If the SerpAPI token is missing or invalid, the system automatically falls back to local LLM processing
372+
- An error message will be displayed: "SerpAPI token is required to perform Web search. Defaulting to local results"
373+
374+
**Note:** Web search is currently available in the Chat interface. RAG integration is planned for a future release.
375+
319376
## 🎯 Advanced Features
320377

321378
- **Backend Detection:** Automatic FastAPI availability checking with local fallback

app/backend/api/chat.py

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22
Chat API endpoints
33
"""
44
import logging
5+
from dotenv import load_dotenv
56
from fastapi import APIRouter, HTTPException
67
from fastapi.responses import StreamingResponse
78

8-
from app.backend.models.api_models import ChatRequestEnhanced, ChatResponse
9+
# Ensure environment variables are loaded before importing services
10+
load_dotenv()
11+
12+
from app.backend.models.api_models import ChatRequestEnhanced, ChatResponse, SerpTokenStatus
913
from app.services.ollama import stream_ollama
1014
from app.services.vllm_service import vllm_service
15+
from app.services.web_search_service import WebSearchService
16+
from app.services.web_search_crew import WebSearchOrchestrator
1117

1218
logger = logging.getLogger(__name__)
1319

14-
router = APIRouter(prefix="/api/chat", tags=["chat"])
20+
router = APIRouter(tags=["chat"])
1521

1622

17-
@router.post("/", response_model=ChatResponse)
23+
@router.post("/api/chat", response_model=ChatResponse)
1824
async def chat_endpoint(request: ChatRequestEnhanced):
1925
"""Single chat message processing"""
2026
logger.info(f"Chat request - Backend: {request.backend}, Model: {request.model}")
@@ -30,7 +36,7 @@ async def chat_endpoint(request: ChatRequestEnhanced):
3036
raise HTTPException(status_code=500, detail=str(e))
3137

3238

33-
@router.post("/stream")
39+
@router.post("/api/chat/stream")
3440
async def chat_stream(request: ChatRequestEnhanced):
3541
"""Stream chat responses"""
3642
logger.info(f"Streaming chat - Backend: {request.backend}, Model: {request.model}")
@@ -105,3 +111,140 @@ async def _stream_ollama_chat(request: ChatRequestEnhanced):
105111
messages = [{"role": "user", "content": request.message}]
106112
async for chunk in stream_ollama(messages, model=request.model, temperature=request.temperature):
107113
yield chunk
114+
115+
116+
@router.post("/api/chat/web-search")
117+
async def chat_web_search(request: ChatRequestEnhanced):
118+
"""Process chat with optional web search integration"""
119+
logger.info(f"Web search chat request - use_web_search: {request.use_web_search}, Model: {request.model}")
120+
121+
try:
122+
# Validate that web search is requested
123+
if not request.use_web_search:
124+
logger.info("Web search not requested, routing to standard chat endpoint")
125+
return await chat_stream(request)
126+
127+
# Check if SerpAPI token is configured
128+
web_search_service = WebSearchService()
129+
token_validation = web_search_service.validate_token()
130+
131+
if not token_validation["valid"]:
132+
logger.warning(f"SerpAPI token invalid: {token_validation['message']}")
133+
logger.info("Falling back to standard chat due to invalid token")
134+
135+
# Return error message and fallback to standard chat
136+
async def error_and_fallback():
137+
error_msg = "SerpAPI token is required to perform Web search. Defaulting to local results.\n\n"
138+
yield error_msg
139+
140+
# Stream standard chat response
141+
async for chunk in _stream_ollama_chat(request):
142+
yield chunk
143+
144+
return StreamingResponse(error_and_fallback(), media_type="text/plain")
145+
146+
# Route to web search handler
147+
return await _handle_web_search_chat(request)
148+
149+
except Exception as e:
150+
logger.error(f"Web search endpoint error: {e}", exc_info=True)
151+
logger.info("Falling back to standard chat due to error")
152+
153+
# Fallback to standard chat on any error
154+
async def error_fallback():
155+
error_msg = f"Web search error: {str(e)}. Defaulting to local results.\n\n"
156+
yield error_msg
157+
158+
async for chunk in _stream_ollama_chat(request):
159+
yield chunk
160+
161+
return StreamingResponse(error_fallback(), media_type="text/plain")
162+
163+
164+
@router.get("/api/chat/serp-status", response_model=SerpTokenStatus)
165+
async def get_serp_token_status():
166+
"""Get SerpAPI token validation status"""
167+
logger.info("Checking SerpAPI token status")
168+
169+
try:
170+
web_search_service = WebSearchService()
171+
validation = web_search_service.validate_token()
172+
173+
if validation["valid"]:
174+
return SerpTokenStatus(
175+
status="valid",
176+
message=validation["message"]
177+
)
178+
else:
179+
return SerpTokenStatus(
180+
status="invalid",
181+
message=validation["message"]
182+
)
183+
184+
except Exception as e:
185+
logger.error(f"Error checking SerpAPI token status: {e}")
186+
return SerpTokenStatus(
187+
status="error",
188+
message=f"Error checking token status: {str(e)}"
189+
)
190+
191+
192+
async def _handle_web_search_chat(request: ChatRequestEnhanced):
193+
"""Handle web search chat request by routing to orchestrator"""
194+
logger.info("Routing to web search orchestrator")
195+
196+
async def event_generator():
197+
try:
198+
# Initialize orchestrator
199+
orchestrator = WebSearchOrchestrator()
200+
201+
# Build conversation history from request if available
202+
conversation_history = []
203+
# Note: ChatRequestEnhanced currently only has 'message', not full history
204+
# If history is needed, it would be added to the model
205+
206+
# Process query through orchestrator with streaming
207+
async for chunk in orchestrator.process_query(
208+
query=request.message,
209+
conversation_history=conversation_history,
210+
stream=True
211+
):
212+
yield chunk + "\n"
213+
214+
except Exception as e:
215+
logger.error(f"Web search orchestrator error: {e}", exc_info=True)
216+
yield f"Error during web search: {str(e)}\n"
217+
yield "Falling back to local results...\n\n"
218+
219+
# Fallback to standard chat
220+
async for chunk in _stream_ollama_chat(request):
221+
yield chunk + "\n"
222+
223+
return StreamingResponse(event_generator(), media_type="text/plain")
224+
225+
226+
async def _stream_web_search_chat(request: ChatRequestEnhanced):
227+
"""Stream web search chat responses (helper for streaming)"""
228+
try:
229+
# Initialize orchestrator
230+
orchestrator = WebSearchOrchestrator()
231+
232+
# Build conversation history from request if available
233+
conversation_history = []
234+
235+
# Process query through orchestrator with streaming
236+
async for chunk in orchestrator.process_query(
237+
query=request.message,
238+
conversation_history=conversation_history,
239+
stream=True
240+
):
241+
yield chunk
242+
243+
except Exception as e:
244+
logger.error(f"Web search streaming error: {e}", exc_info=True)
245+
yield f"Error during web search: {str(e)}\n"
246+
yield "Falling back to local results...\n\n"
247+
248+
# Fallback to standard chat
249+
async for chunk in _stream_ollama_chat(request):
250+
yield chunk

app/backend/models/api_models.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ChatRequestEnhanced(BaseModel):
1111
temperature: Optional[float] = 0.7
1212
backend: Optional[str] = "ollama"
1313
hf_token: Optional[str] = None
14+
use_web_search: Optional[bool] = False
1415

1516

1617
class RAGQueryRequestEnhanced(BaseModel):
@@ -105,3 +106,26 @@ class ErrorResponse(BaseModel):
105106
error: str
106107
detail: Optional[str] = None
107108
path: Optional[str] = None
109+
110+
111+
class WebSearchRequest(BaseModel):
112+
query: str
113+
num_results: Optional[int] = 10
114+
115+
116+
class WebSearchResult(BaseModel):
117+
title: str
118+
link: str
119+
snippet: str
120+
position: int
121+
122+
123+
class WebSearchResponse(BaseModel):
124+
query: str
125+
results: List[WebSearchResult]
126+
total_results: int
127+
128+
129+
class SerpTokenStatus(BaseModel):
130+
status: str
131+
message: str
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
# Components package
2+
3+
from .web_search_toggle import WebSearchToggle, get_web_search_toggle
4+
5+
__all__ = ['WebSearchToggle', 'get_web_search_toggle']

0 commit comments

Comments
 (0)