Madras1 commited on
Commit
2b115d1
Β·
verified Β·
1 Parent(s): 86c9738

Upload 47 files

Browse files
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
- async def search(request: SearchRequest) -> SearchResponse:
 
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
- async def deep_research(request: DeepResearchRequest):
 
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
- async def heavy_search(request: SearchRequest):
 
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
- async def image_search(query: str, max_results: int = 6):
 
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]