Perplexa is a Retrieval Augmented Generation (RAG) application that combines web search, document retrieval, and multiple LLM APIs to provide intelligent answers to user queries. This document provides comprehensive API documentation, performance metrics, and operational details.
┌─────────────────────────────────────────────────────────────────┐
│ USER QUERY INPUT │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DATA RETRIEVAL LAYER │
│ ┌────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ SerpAPI │ │ BeautifulSoup │ │ Content │ │
│ │ Google Search │→ │ Web Scraping │→ │ Extraction │ │
│ └────────────────┘ └──────────────────┘ └─────────────────┘ │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ INFERENCE/EMBEDDING LAYER │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ SentenceTransformer (all-MiniLM-L6-v2) │ │
│ │ • Query Embedding │ │
│ │ • Document Embeddings │ │
│ │ • Cosine Similarity Calculation │ │
│ │ • Top-K Document Selection (k=3) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ GENERATION LAYER │
│ ┌────────────┐ ┌──────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Gemini │ │ Mistral │ │ Command R+ │ │OpenRouter │ │
│ │ API │ │ API │ │ (Cohere) │ │ API │ │
│ └────────────┘ └──────────┘ └──────────────┘ └───────────┘ │
│ │ │ │ │ │
│ └──────────────┴───────────────┴──────────────────┘ │
│ │ │
│ RAG Prompt │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SERVING LAYER │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Streamlit Web Application │ │
│ │ • User Authentication (Auth0) │ │
│ │ • Chat Interface │ │
│ │ • MongoDB (User & Chat Storage) │ │
│ │ • Session Management │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────┐
│ USER RESPONSE │
└───────────────────┘
| Metric | Value | Notes |
|---|---|---|
| P50 Latency | ~8-12 seconds | Typical query processing time |
| P95 Latency | ~20-25 seconds | Including web scraping & API calls |
| P99 Latency | ~30-35 seconds | Complex queries with slow web sources |
| Embedding Generation | ~100-200ms | Cached model, batch processing |
| Document Retrieval | ~3-8 seconds | SerpAPI + web scraping (5 URLs) |
| LLM API Call | ~2-10 seconds | Varies by model and prompt size |
| Component | Cost | Details |
|---|---|---|
| SerpAPI | $0.002-0.005 | Per search query (5 results) |
| Gemini API | $0.001-0.003 | Per 1K tokens (~2-3K tokens/query) |
| Mistral API | $0.002-0.006 | Per 1K tokens |
| Command R+ (Cohere) | $0.003-0.008 | Per 1K tokens |
| OpenRouter | $0.000-0.005 | Varies by model (free tier available) |
| MongoDB Atlas | $0.08/hr | Shared cluster (~$60/month) |
| Streamlit Cloud | Free | Community deployment |
| Total Est. Cost/Request | $0.005-0.015 | Average across all components |
| Metric | Target | Measurement Method |
|---|---|---|
| Answer Relevance | >85% | Manual evaluation of top-K document selection |
| Citation Accuracy | >90% | Verification of source URLs in responses |
| Model Uptime | >99% | API availability monitoring |
| User Satisfaction | >4.0/5.0 | User feedback (when implemented) |
| Context Retrieval Precision | >80% | Cosine similarity threshold (>0.5) |
- Application Host: Streamlit Cloud (Community Tier)
- Deployment Method: GitHub webhook-triggered auto-deployment
- SSL/TLS: Automatic HTTPS via Streamlit Cloud
- Database: MongoDB Atlas (Cloud)
- Collections:
users: User profiles and authentication datachats: Chat history and session data
- Connection: TLS-enabled with connection string in environment variables
- Purpose: Google search results retrieval
- Rate Limit: 100 searches/month (free), scalable plans
- Endpoint:
https://serpapi.com/search
Gemini API
- Provider: Google
- Model: gemini-1.5-flash-latest
- Endpoint:
https://generativelanguage.googleapis.com/v1beta/models/ - Rate Limit: 60 requests/minute
Mistral API
- Provider: Mistral AI
- Model: mistral-small-latest
- Endpoint:
https://api.mistral.ai/v1/chat/completions - Rate Limit: 100 requests/minute
Cohere (Command R+)
- Provider: Cohere
- Model: command-r-plus
- SDK: Cohere Python SDK v2
- Rate Limit: 100 requests/minute
OpenRouter
- Provider: OpenRouter (Multi-model gateway)
- Models:
- deepseek/deepseek-r1-zero:free
- microsoft/phi-3-medium-128k-instruct:free
- nvidia/llama-3.1-nemotron-70b-instruct:free
- meta-llama/llama-3.3-70b-instruct:free
- qwen/qwq-32b:free
- Endpoint:
https://openrouter.ai/api/v1 - Rate Limit: Varies by model
- Purpose: SentenceTransformer model hosting
- Model: sentence-transformers/all-MiniLM-L6-v2
- Token Required: For private models (optional for public)
- Provider: Auth0 (via Streamlit)
- Method: OAuth 2.0
- Session Management: Streamlit session state
Core Framework:
- streamlit==1.42.0 # Web application framework
- Authlib==1.5.1 # Authentication library
Database:
- pymongo[srv] # MongoDB driver
NLP & ML:
- sentence-transformers==2.7.0 # Embedding generation
- transformers==4.49.0 # Base transformers library
- scikit-learn # Cosine similarity calculation
- numpy # Numerical operations
Web Scraping:
- requests # HTTP client
- beautifulsoup4 # HTML parsing
- google-search-results # SerpAPI client
- serpapi # Alternative SerpAPI client
LLM APIs:
- openai # OpenAI-compatible API client
- cohere # Cohere API client
Configuration:
- python-dotenv # Environment variable management
Version Control: GitHub
- Repository: https://github.com/sayan112207/Perplexa
- Branching Strategy: Main branch with feature branches
Continuous Integration
- Dependency Review: GitHub Actions workflow (
.github/workflows/dependency-review.yml)- Scans for vulnerable dependencies on pull requests
- Automated security checks
Continuous Deployment
- Trigger: Git push to main branch
- Process:
- Developer pushes code to GitHub
- GitHub webhook notifies Streamlit Cloud
- Streamlit Cloud pulls latest commit
- Automatic build and deployment
- HTTPS-enabled app goes live
- Rollback: Manual revert via GitHub + redeploy
- Embedding Model: Cached via
@st.cache_resourcedecorator- Loaded once per application instance
- Persists across user sessions
- Reduces latency and memory usage
- Application Logs: Streamlit Cloud logs
- Error Tracking: Try-catch blocks with Streamlit error displays
- API Monitoring: Manual monitoring of API responses
- Implement structured logging (e.g., Python
loggingmodule) - Add API latency tracking and metrics dashboard
- Implement A/B testing for different models
- Set up automated model version updates
- Add user feedback loop for answer quality
- Implement caching layer for repeated queries
- Set up alerts for API failures or high latency
Load Testing
- Tool: Locust (defined in
tests/lucustfile.py) - Usage:
locust -f tests/locust_test.py --host=http://localhost:5500 - Purpose: Simulate concurrent users and measure performance
Unit Testing
- Test files:
tests/test_app.py,tests/test_ui.py - Framework: (To be determined based on test file contents)
While Perplexa is primarily a Streamlit application without exposed REST endpoints, the following internal functions serve as the application's "API":
def aggregate_documents(query: str) -> tuple[list[dict], list[str]]Purpose: Fetch and aggregate documents from web search results
Parameters:
query(string): User search query
Returns:
- Tuple containing:
documents: List of dicts withurlandcontentkeysfetched_urls: List of fetched URLs for citation
Process:
- Calls SerpAPI with query
- Extracts top 5 URLs
- Scrapes content from each URL using BeautifulSoup
- Falls back to snippet if scraping fails
Example Flow:
query = "What is machine learning?"
documents, urls = aggregate_documents(query)
# documents: [{"url": "...", "content": "..."}, ...]
# urls: ["https://...", "https://...", ...]def get_top_k_documents(query: str, documents: list[dict], k: int = 3) -> dictPurpose: Select top-K most relevant documents using semantic similarity
Parameters:
query(string): User querydocuments(list): List of document dictsk(int): Number of top documents to return (default: 3)
Returns:
- Dict with
contextkey containing concatenated top documents
Process:
- Generate embeddings for all documents
- Generate embedding for query
- Calculate cosine similarity
- Sort and select top K documents
- Concatenate content
Example:
top_docs = get_top_k_documents(query, documents, k=3)
# top_docs: {"context": "Document 1 content\n\nDocument 2 content\n\n..."}def build_rag_prompt(query: str, context: str, references: list[str]) -> strPurpose: Build structured prompt for LLM with context and citations
Parameters:
query(string): User querycontext(string): Concatenated top documentsreferences(list): List of source URLs
Returns:
- Formatted prompt string
Prompt Structure:
You are an expert assistant. Using the following retrieved context,
provide a detailed explanation for the query: '{query}'.
Context:
{context}
Your response should include:
1. An introduction defining the topic.
2. Key points in bullet format.
3. A references section listing the source URLs.
References:
- {url1}
- {url2}
- {url3}
Make sure your answer is clear, concise, and accessible to beginners.
4a. Gemini API
def call_gemini_api(prompt: str) -> strAPI Details:
- Endpoint:
https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent - Method: POST
- Headers:
Content-Type: application/json - Request Body:
{
"contents": [
{
"parts": [
{"text": "prompt_text"}
]
}
]
}- Response: JSON with nested structure
- Error Handling: Returns error message on failure
4b. Mistral API
def call_mistral_api(prompt: str) -> strAPI Details:
- Endpoint:
https://api.mistral.ai/v1/chat/completions - Method: POST
- Headers:
Authorization: Bearer {MISTRAL_API_KEY}Content-Type: application/json
- Request Body:
{
"model": "mistral-small-latest",
"messages": [
{"role": "user", "content": "prompt_text"}
]
}- Response: Standard OpenAI-compatible format
4c. Command R+ (Cohere)
def call_cmd_r_plus(prompt: str) -> strAPI Details:
- Uses Cohere Python SDK v2
- Model: command-r-plus
- Method:
client.chat() - Parameters:
{
"model": "command-r-plus",
"messages": [{"role": "user", "content": prompt}]
}4d. OpenRouter (Multi-Model)
def call_openrouter_api(prompt: str, model_choice: str) -> strAPI Details:
- Endpoint:
https://openrouter.ai/api/v1 - Method: Uses OpenAI-compatible client
- Model Mapping:
- Deepseek R1 →
deepseek/deepseek-r1-zero:free - Phi 3 →
microsoft/phi-3-medium-128k-instruct:free - Nemotron →
nvidia/llama-3.1-nemotron-70b-instruct:free - Meta Llama →
meta-llama/llama-3.3-70b-instruct:free - Qwen 32B →
qwen/qwq-32b:free
- Deepseek R1 →
5a. User Management
def save_user_to_mongo(user: dict) -> NonePurpose: Save or update user profile in MongoDB
Parameters:
user: Dict with keys:name,email,picture
Database Operation: upsert (update or insert)
5b. Chat History
def load_chats_from_mongo(user_email: str) -> list[dict]Purpose: Load all chat sessions for a user
Returns: List of chat documents
def save_chat_to_mongo(user_email: str, chat_id: str,
chat_title: str, messages: list) -> strPurpose: Save or update chat session
Returns: Chat ID (string)
def delete_chat_from_mongo(chat_id: str) -> NonePurpose: Delete a specific chat session
def generate_people_also_ask(query: str) -> list[str]Purpose: Fetch related questions from Google search results
Returns: List of related question strings
Source: SerpAPI's related_questions field
Create a .env file in the project root with the following:
# Database
MONGO_URI=mongodb+srv://username:[email protected]/database
# Search API
SERPAPI_API_KEY=your_serpapi_key_here
# LLM APIs
GEMINI_API_KEY=your_gemini_api_key
MISTRAL_API_KEY=your_mistral_api_key
COMMAND_R_PLUS=your_cohere_api_key
OPENROUTER_API_KEY=your_openrouter_api_key
# Optional: Hugging Face (for private models)
HF_API_KEY=your_huggingface_tokenFor deployment on Streamlit Cloud:
- Go to app settings → Secrets
- Add all environment variables in TOML format:
MONGO_URI = "mongodb+srv://..."
SERPAPI_API_KEY = "..."
GEMINI_API_KEY = "..."
MISTRAL_API_KEY = "..."
COMMAND_R_PLUS = "..."
OPENROUTER_API_KEY = "..."
HF_API_KEY = "..."While Perplexa doesn't expose traditional REST APIs, here are testing approaches:
Location: tests/lucustfile.py
Usage:
# Start Streamlit app
streamlit run bot.py
# Run Locust in another terminal
locust -f tests/lucustfile.py --host=http://localhost:8501What it tests:
- Concurrent user simulation
- Response time measurement
- Throughput analysis
Recommended Structure:
# tests/test_api_functions.py
import pytest
from app import (
get_top_k_documents,
build_rag_prompt,
compute_embeddings
)
def test_top_k_documents():
documents = [
{"url": "url1", "content": "Machine learning content"},
{"url": "url2", "content": "Deep learning content"},
{"url": "url3", "content": "Unrelated content"}
]
query = "What is machine learning?"
result = get_top_k_documents(query, documents, k=2)
assert "context" in result
assert len(result["context"]) > 0
def test_build_rag_prompt():
query = "Test query"
context = "Test context"
refs = ["http://url1.com", "http://url2.com"]
prompt = build_rag_prompt(query, context, refs)
assert query in prompt
assert context in prompt
assert refs[0] in promptTest External APIs Directly:
# Test Gemini API
curl -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"parts": [{"text": "Explain machine learning"}]
}]
}'
# Test Mistral API
curl -X POST \
"https://api.mistral.ai/v1/chat/completions" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-small-latest",
"messages": [{"role": "user", "content": "Hello"}]
}'
# Test SerpAPI
curl "https://serpapi.com/search?q=machine+learning&api_key=YOUR_KEY"Use Streamlit's built-in testing utilities:
# tests/test_ui.py
from streamlit.testing.v1 import AppTest
def test_app_loads():
at = AppTest.from_file("bot.py")
at.run()
assert not at.exceptionSymptom: Application hangs on startup, embedding model fails to load
Root Cause: Hugging Face model download timeout or memory limits
Fix Applied:
- Implemented
@st.cache_resourcedecorator for model caching - Added timeout handling in model loading
- Set
token=HF_API_KEYfor authenticated downloads
Prevention:
- Monitor Streamlit Cloud memory usage
- Consider using smaller embedding models for constrained environments
Symptom: Intermittent errors when calling LLM APIs during high traffic
Root Cause: Exceeded free tier rate limits on various APIs
Fix Applied:
- Added try-catch blocks around all API calls
- Display user-friendly error messages
- Implemented fallback to alternative models
Prevention:
- Implement request queuing
- Add exponential backoff retry logic
- Monitor API usage dashboards
Symptom: Connection failed errors, users unable to save chats
Root Cause:
- TLS certificate validation issues
- Connection string misconfiguration
Fix Applied:
client = MongoClient(
MONGO_URI,
tls=True,
tlsAllowInvalidCertificates=True # For self-signed certs
)Prevention:
- Use proper SSL certificates in production
- Implement connection pooling
- Add health check endpoint for database
Symptom: aggregate_documents() takes too long, some URLs timeout
Root Cause: Slow or unresponsive websites in search results
Fix Applied:
response = requests.get(url, timeout=5) # 5-second timeoutFallback: Use snippet from SerpAPI if scraping fails
Prevention:
- Implement async scraping with concurrent requests
- Add URL blacklist for known slow sites
- Increase timeout for slow networks
Symptom: RuntimeError: Tried to instantiate class '__path__._path'
Root Cause: PyTorch internal issue with certain operations
Fix Applied:
import torch
torch.classes.__path__ = [] # Workaround for torch bugStatus: Temporary fix, monitor PyTorch updates for permanent solution
Symptom: Chat history gets corrupted, messages duplicate
Root Cause: Multiple reruns causing race conditions in session state
Fix Applied:
- Implemented
is_same_chat()comparison function - Structured message processing to avoid duplicate appends
- Used
st.rerun()strategically
Prevention:
- Minimize reruns
- Use session state flags to track processing status
Enable debug logging:
import logging
logging.basicConfig(level=logging.DEBUG)# Check Streamlit version
streamlit --version
# Run with verbose output
streamlit run bot.py --logger.level=debug
# Clear Streamlit cache
streamlit cache clear
# Test MongoDB connection
python -c "from pymongo import MongoClient; print(MongoClient('YOUR_URI').server_info())"
# Test API keys
python -c "import os; from dotenv import load_dotenv; load_dotenv(); print('Keys loaded:', bool(os.getenv('GEMINI_API_KEY')))"- ✅ Keys stored in
.envfile (not committed to Git) - ✅ Streamlit Cloud secrets for production
⚠️ Consider using key rotation policies⚠️ Implement rate limiting per user
- ✅ Auth0 integration via Streamlit
- ✅ User data stored securely in MongoDB
⚠️ Implement session timeout⚠️ Add CSRF protection for future API endpoints
- ✅ User queries not logged externally
- ✅ MongoDB connection encrypted (TLS)
⚠️ Add data retention policies⚠️ Implement GDPR compliance measures
- Implement Redis caching for repeated queries
- Add CDN for static assets
- Use connection pooling for database
- Implement async API calls
- Export chat history to PDF
- Voice input support
- Multi-language support
- Custom embedding model fine-tuning
- Set up Prometheus metrics
- Add Grafana dashboards
- Implement ELK stack for log aggregation
- Set up PagerDuty alerts
For issues, questions, or contributions:
- GitHub: https://github.com/sayan112207/Perplexa
- Issues: https://github.com/sayan112207/Perplexa/issues
Document Version: 1.0
Last Updated: 2025
Maintained By: Perplexa Development Team