Upload 47 files
Browse files- app/api/routes/search.py +10 -5
- app/main.py +8 -0
- app/middleware/__init__.py +1 -0
- app/middleware/rate_limiter.py +45 -0
- pyproject.toml +1 -0
app/api/routes/search.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
| 4 |
import time
|
| 5 |
from datetime import datetime
|
| 6 |
|
| 7 |
-
from fastapi import APIRouter, HTTPException
|
| 8 |
from fastapi.responses import StreamingResponse
|
| 9 |
|
| 10 |
from app.api.schemas import (
|
|
@@ -23,6 +23,7 @@ from app.sources.tavily import search_tavily
|
|
| 23 |
from app.sources.duckduckgo import search_duckduckgo
|
| 24 |
from app.reranking.pipeline import rerank_results
|
| 25 |
from app.agents.synthesizer import synthesize_answer, synthesize_answer_stream
|
|
|
|
| 26 |
|
| 27 |
router = APIRouter()
|
| 28 |
|
|
@@ -34,7 +35,8 @@ router = APIRouter()
|
|
| 34 |
summary="Search with AI synthesis",
|
| 35 |
description="Perform a search with temporal intelligence and return an AI-synthesized answer.",
|
| 36 |
)
|
| 37 |
-
|
|
|
|
| 38 |
"""
|
| 39 |
Perform an intelligent search with:
|
| 40 |
- Temporal intent detection
|
|
@@ -262,7 +264,8 @@ async def search_stream(request: SearchRequest):
|
|
| 262 |
summary="Deep research with multi-dimensional analysis",
|
| 263 |
description="Decompose a query into dimensions, search each in parallel, and generate a comprehensive report.",
|
| 264 |
)
|
| 265 |
-
|
|
|
|
| 266 |
"""
|
| 267 |
Run deep research with streaming progress updates.
|
| 268 |
|
|
@@ -295,7 +298,8 @@ async def deep_research(request: DeepResearchRequest):
|
|
| 295 |
summary="Heavy search with content scraping",
|
| 296 |
description="Search with full content extraction from top sources for richer answers.",
|
| 297 |
)
|
| 298 |
-
|
|
|
|
| 299 |
"""
|
| 300 |
Heavy search with content scraping.
|
| 301 |
|
|
@@ -325,7 +329,8 @@ async def heavy_search(request: SearchRequest):
|
|
| 325 |
summary="Search for images",
|
| 326 |
description="Search for images related to a query using Brave Image Search.",
|
| 327 |
)
|
| 328 |
-
|
|
|
|
| 329 |
"""
|
| 330 |
Search for images related to a query.
|
| 331 |
|
|
|
|
| 4 |
import time
|
| 5 |
from datetime import datetime
|
| 6 |
|
| 7 |
+
from fastapi import APIRouter, HTTPException, Request
|
| 8 |
from fastapi.responses import StreamingResponse
|
| 9 |
|
| 10 |
from app.api.schemas import (
|
|
|
|
| 23 |
from app.sources.duckduckgo import search_duckduckgo
|
| 24 |
from app.reranking.pipeline import rerank_results
|
| 25 |
from app.agents.synthesizer import synthesize_answer, synthesize_answer_stream
|
| 26 |
+
from app.middleware.rate_limiter import limiter
|
| 27 |
|
| 28 |
router = APIRouter()
|
| 29 |
|
|
|
|
| 35 |
summary="Search with AI synthesis",
|
| 36 |
description="Perform a search with temporal intelligence and return an AI-synthesized answer.",
|
| 37 |
)
|
| 38 |
+
@limiter.limit("30/minute")
|
| 39 |
+
async def search(request: SearchRequest, req: Request) -> SearchResponse:
|
| 40 |
"""
|
| 41 |
Perform an intelligent search with:
|
| 42 |
- Temporal intent detection
|
|
|
|
| 264 |
summary="Deep research with multi-dimensional analysis",
|
| 265 |
description="Decompose a query into dimensions, search each in parallel, and generate a comprehensive report.",
|
| 266 |
)
|
| 267 |
+
@limiter.limit("5/minute")
|
| 268 |
+
async def deep_research(request: DeepResearchRequest, req: Request):
|
| 269 |
"""
|
| 270 |
Run deep research with streaming progress updates.
|
| 271 |
|
|
|
|
| 298 |
summary="Heavy search with content scraping",
|
| 299 |
description="Search with full content extraction from top sources for richer answers.",
|
| 300 |
)
|
| 301 |
+
@limiter.limit("10/minute")
|
| 302 |
+
async def heavy_search(request: SearchRequest, req: Request):
|
| 303 |
"""
|
| 304 |
Heavy search with content scraping.
|
| 305 |
|
|
|
|
| 329 |
summary="Search for images",
|
| 330 |
description="Search for images related to a query using Brave Image Search.",
|
| 331 |
)
|
| 332 |
+
@limiter.limit("60/minute")
|
| 333 |
+
async def image_search(req: Request, query: str, max_results: int = 6):
|
| 334 |
"""
|
| 335 |
Search for images related to a query.
|
| 336 |
|
app/main.py
CHANGED
|
@@ -5,9 +5,12 @@ from datetime import datetime
|
|
| 5 |
|
| 6 |
from fastapi import FastAPI
|
| 7 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
| 8 |
|
| 9 |
from app.api.routes import search
|
| 10 |
from app.config import get_settings
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
@asynccontextmanager
|
|
@@ -18,6 +21,7 @@ async def lifespan(app: FastAPI):
|
|
| 18 |
print(f"π Lancer API starting...")
|
| 19 |
print(f" LLM Provider: {settings.llm_provider}")
|
| 20 |
print(f" LLM Model: {settings.llm_model}")
|
|
|
|
| 21 |
yield
|
| 22 |
# Shutdown
|
| 23 |
print("π Lancer API shutting down...")
|
|
@@ -30,6 +34,10 @@ app = FastAPI(
|
|
| 30 |
lifespan=lifespan,
|
| 31 |
)
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
# CORS middleware
|
| 34 |
app.add_middleware(
|
| 35 |
CORSMiddleware,
|
|
|
|
| 5 |
|
| 6 |
from fastapi import FastAPI
|
| 7 |
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
+
from slowapi import _rate_limit_exceeded_handler
|
| 9 |
+
from slowapi.errors import RateLimitExceeded
|
| 10 |
|
| 11 |
from app.api.routes import search
|
| 12 |
from app.config import get_settings
|
| 13 |
+
from app.middleware.rate_limiter import limiter
|
| 14 |
|
| 15 |
|
| 16 |
@asynccontextmanager
|
|
|
|
| 21 |
print(f"π Lancer API starting...")
|
| 22 |
print(f" LLM Provider: {settings.llm_provider}")
|
| 23 |
print(f" LLM Model: {settings.llm_model}")
|
| 24 |
+
print(f" Rate limiting: enabled")
|
| 25 |
yield
|
| 26 |
# Shutdown
|
| 27 |
print("π Lancer API shutting down...")
|
|
|
|
| 34 |
lifespan=lifespan,
|
| 35 |
)
|
| 36 |
|
| 37 |
+
# Rate limiting
|
| 38 |
+
app.state.limiter = limiter
|
| 39 |
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 40 |
+
|
| 41 |
# CORS middleware
|
| 42 |
app.add_middleware(
|
| 43 |
CORSMiddleware,
|
app/middleware/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Middleware package."""
|
app/middleware/rate_limiter.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Rate limiting middleware using SlowAPI.
|
| 2 |
+
|
| 3 |
+
Provides IP-based rate limiting for all API endpoints.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from slowapi import Limiter
|
| 7 |
+
from slowapi.util import get_remote_address
|
| 8 |
+
from slowapi.errors import RateLimitExceeded
|
| 9 |
+
from slowapi.middleware import SlowAPIMiddleware
|
| 10 |
+
from fastapi import Request
|
| 11 |
+
from fastapi.responses import JSONResponse
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# Create limiter instance with IP-based key
|
| 15 |
+
limiter = Limiter(
|
| 16 |
+
key_func=get_remote_address,
|
| 17 |
+
default_limits=["100/minute"],
|
| 18 |
+
storage_uri="memory://", # Use memory storage (OK for single instance on HF Spaces)
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded):
|
| 23 |
+
"""Custom handler for rate limit exceeded errors."""
|
| 24 |
+
return JSONResponse(
|
| 25 |
+
status_code=429,
|
| 26 |
+
content={
|
| 27 |
+
"error": "rate_limit_exceeded",
|
| 28 |
+
"message": f"Rate limit exceeded: {exc.detail}",
|
| 29 |
+
"retry_after": getattr(exc, "retry_after", 60),
|
| 30 |
+
},
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# Rate limit decorators for different endpoints
|
| 35 |
+
LIMITS = {
|
| 36 |
+
"search": "30/minute",
|
| 37 |
+
"heavy": "10/minute",
|
| 38 |
+
"deep": "5/minute",
|
| 39 |
+
"images": "60/minute",
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_limiter():
|
| 44 |
+
"""Get the limiter instance for dependency injection."""
|
| 45 |
+
return limiter
|
pyproject.toml
CHANGED
|
@@ -20,6 +20,7 @@ dependencies = [
|
|
| 20 |
"numpy>=2.0.0",
|
| 21 |
"python-dotenv>=1.0.0",
|
| 22 |
"tenacity>=9.0.0",
|
|
|
|
| 23 |
]
|
| 24 |
|
| 25 |
[project.optional-dependencies]
|
|
|
|
| 20 |
"numpy>=2.0.0",
|
| 21 |
"python-dotenv>=1.0.0",
|
| 22 |
"tenacity>=9.0.0",
|
| 23 |
+
"slowapi>=0.1.9",
|
| 24 |
]
|
| 25 |
|
| 26 |
[project.optional-dependencies]
|