-
Notifications
You must be signed in to change notification settings - Fork 0
Engine: Task changes #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Engine: Task changes #365
Changes from all commits
76b6b64
980859d
1394741
8e6d1c1
c784a27
2a14009
1a55a09
831cfe6
544fd76
0ec27ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,18 @@ | ||
| # Bolt's Journal | ||
| This journal documents critical performance learnings for the codebase. | ||
|
|
||
| ## 2024-05-23 - Lazy Loading Search Providers | ||
| **Learning:** Moving heavy SDK imports (like `google.genai`) inside methods significantly improves startup time and robustness. | ||
| **Action:** Use local imports inside factory methods for optional integrations or heavy dependencies. When testing, patch the class at its *source definition*, not the module namespace. | ||
|
|
||
| ## 2024-05-24 - [Initial Entry] | ||
| **Learning:** Initial setup of Bolt's journal. | ||
| **Action:** Use this file to record specific performance insights. | ||
|
|
||
| ## 2025-05-18 - [Optimization of Fuzzy Matching] | ||
| **Learning:** `difflib.SequenceMatcher.real_quick_ratio()` provides an O(1) upper bound check based on length, which is significantly faster than `quick_ratio()` (O(N)) or `ratio()` (O(N*M)). Using this as a first-pass filter for fuzzy matching large datasets can dramatically improve performance. | ||
| **Action:** Always check for `real_quick_ratio()` when implementing fuzzy matching loops with `difflib`. | ||
|
|
||
| ## 2026-01-25 - [Memory vs Reality Discrepancy] | ||
| **Learning:** The memory claimed `format_search_output` was already using list join, but the actual code on disk was using O(N^2) string concatenation. | ||
| **Action:** Always verify code state with `read_file` before trusting memory or documentation about optimizations. |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import math | ||
| import os | ||
| import time | ||
| import math | ||
| from collections import defaultdict | ||
| from typing import List, Set | ||
|
|
||
|
|
@@ -266,6 +267,7 @@ async def dispatch(self, request: Request, call_next): | |
| # 🛡️ Sentinel: Support X-Forwarded-For for proxies (Render/Load Balancers) | ||
| # Use trust-bound extraction to prevent IP spoofing attacks. | ||
| forwarded = request.headers.get("X-Forwarded-For") | ||
| <<<<<<< HEAD | ||
| fallback_ip = request.client.host if request.client else "unknown" | ||
|
|
||
| if forwarded and self.trust_proxy_headers: | ||
|
Comment on lines
+270
to
273
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Unresolved merge conflict around X-Forwarded-For handling and rate limit responses. This file currently contains unresolved Git conflict markers in |
||
|
|
@@ -276,6 +278,27 @@ async def dispatch(self, request: Request, call_next): | |
| ) | ||
| if client_ip is None: | ||
| client_ip = fallback_ip | ||
| ======= | ||
| if forwarded and self.trust_proxy_headers: | ||
| # 🛡️ Sentinel: Prevent spoofing by traversing from the end (trusted proxies) | ||
| # Proxies (like Render) append the verified client IP to the end. | ||
| # We traverse backwards to find the first non-private IP to avoid blocking the proxy itself. | ||
| try: | ||
| ips = [ip.strip() for ip in forwarded.split(",")] | ||
| client_ip = ips[-1] # Default to last IP | ||
| for ip in reversed(ips): | ||
| try: | ||
| # Check if IP is public (not private, not loopback) | ||
| ip_obj = ipaddress.ip_address(ip) | ||
| if not ip_obj.is_private and not ip_obj.is_loopback: | ||
| client_ip = ip | ||
| break | ||
| except ValueError: | ||
| continue # Skip invalid IPs | ||
| except Exception: | ||
| # Fallback to simple extraction if parsing fails | ||
| client_ip = forwarded.split(",")[-1].strip() | ||
| >>>>>>> dc0cbcc (feat(security): add logging for rejected requests in middleware) | ||
|
|
||
| # Truncate to 100 chars to prevent memory exhaustion attacks | ||
| client_ip = client_ip[:100] | ||
|
|
@@ -297,17 +320,36 @@ async def dispatch(self, request: Request, call_next): | |
| self.requests[client_key] = active_requests | ||
|
|
||
| # Calculate retry_after | ||
| <<<<<<< HEAD | ||
| oldest_request_time = active_requests[0] | ||
| reset_time = oldest_request_time + self.window | ||
| retry_after = max(1, int(math.ceil(reset_time - now))) | ||
|
|
||
| logger.warning(f"Rate limit exceeded for {client_key} on {path}") | ||
| logger.warning(f"Rate limit exceeded for client {client_key} on path {path}") | ||
|
|
||
| return JSONResponse( | ||
| status_code=429, | ||
| content={"detail": "Too Many Requests", "retry_after": retry_after}, | ||
| headers={"Retry-After": str(retry_after)}, | ||
| ) | ||
| ======= | ||
| if active_requests: | ||
| oldest_request_time = active_requests[0] | ||
| reset_time = oldest_request_time + self.window | ||
| retry_after = max(1, int(math.ceil(reset_time - now))) | ||
| else: | ||
| retry_after = self.window | ||
|
|
||
| logger.warning( | ||
| f"Rate limit exceeded for client {client_key} on path {path}" | ||
| ) | ||
|
|
||
| return JSONResponse( | ||
| status_code=429, | ||
| content={"detail": "Too Many Requests", "retry_after": retry_after}, | ||
| headers={"Retry-After": str(retry_after)}, | ||
| ) | ||
| >>>>>>> dc0cbcc (feat(security): add logging for rejected requests in middleware) | ||
|
|
||
| active_requests.append(now) | ||
|
|
||
|
|
@@ -355,6 +397,9 @@ async def dispatch(self, request: Request, call_next): | |
| ): # This was a new entry (or re-entry after expiry) | ||
| # Safe delete using pop to avoid KeyErrors in race conditions | ||
| self.requests.pop(client_key, None) | ||
| logger.warning( | ||
| f"Server busy (max clients exceeded) for client {client_key} on path {path}" | ||
| ) | ||
| return Response("Server Busy", status_code=503) | ||
|
|
||
| self.requests[client_key] = active_requests | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| """Search router module for handling multiple search providers.""" | ||
| import logging | ||
| import threading | ||
| from enum import Enum | ||
|
|
@@ -11,6 +12,7 @@ | |
|
|
||
|
|
||
| class SearchProviderType(Enum): | ||
| """Enumeration of supported search providers.""" | ||
| GOOGLE = "google" | ||
| DUCKDUCKGO = "duckduckgo" | ||
| BRAVE = "brave" | ||
|
|
@@ -25,9 +27,11 @@ def __init__(self, app_config: AppConfig = config): | |
| """Initialize router with config.""" | ||
| self.config = app_config | ||
| self.providers: Dict[str, SearchProvider] = {} | ||
| <<<<<<< HEAD | ||
| self._providers_lock = threading.Lock() | ||
|
|
||
| def _get_provider(self, name: str) -> SearchProvider | None: | ||
| """Lazily initialize and return a search provider instance.""" | ||
| # Quick check without lock | ||
| if name in self.providers: | ||
| return self.providers[name] | ||
|
Comment on lines
+30
to
37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Resolve the merge conflict in There are unresolved merge markers around the provider initialization ( |
||
|
|
@@ -40,23 +44,18 @@ def _get_provider(self, name: str) -> SearchProvider | None: | |
| try: | ||
| if name == SearchProviderType.GOOGLE.value: | ||
| from .providers.google_adapter import GoogleSearchAdapter | ||
|
|
||
| self.providers[name] = GoogleSearchAdapter() | ||
| elif name == SearchProviderType.BRAVE.value: | ||
| from .providers.brave_adapter import BraveSearchAdapter | ||
|
|
||
| self.providers[name] = BraveSearchAdapter() | ||
| elif name == SearchProviderType.DUCKDUCKGO.value: | ||
| from .providers.duckduckgo_adapter import DuckDuckGoAdapter | ||
|
|
||
| self.providers[name] = DuckDuckGoAdapter() | ||
| elif name == SearchProviderType.TAVILY.value: | ||
| from .providers.tavily_adapter import TavilyAdapter | ||
|
|
||
| self.providers[name] = TavilyAdapter() | ||
| elif name == SearchProviderType.BING.value: | ||
| from .providers.bing_adapter import BingAdapter | ||
|
|
||
| self.providers[name] = BingAdapter() | ||
| except Exception as e: | ||
| logger.debug(f"Provider {name} failed to init: {e}") | ||
|
|
@@ -70,6 +69,42 @@ def _get_provider(self, name: str) -> SearchProvider | None: | |
| ) | ||
|
|
||
| return self.providers.get(name) | ||
| ======= | ||
|
|
||
| def _load_provider(self, name: str) -> Optional[SearchProvider]: | ||
| """Lazily load provider class and instantiate.""" | ||
| if name in self.providers: | ||
| return self.providers[name] | ||
|
|
||
| provider = None | ||
| try: | ||
| if name == SearchProviderType.GOOGLE.value: | ||
| from .providers.google_adapter import GoogleSearchAdapter | ||
| provider = GoogleSearchAdapter() | ||
| elif name == SearchProviderType.DUCKDUCKGO.value: | ||
| from .providers.duckduckgo_adapter import DuckDuckGoAdapter | ||
| provider = DuckDuckGoAdapter() | ||
| elif name == SearchProviderType.BRAVE.value: | ||
| from .providers.brave_adapter import BraveSearchAdapter | ||
| provider = BraveSearchAdapter() | ||
| elif name == SearchProviderType.TAVILY.value: | ||
| from .providers.tavily_adapter import TavilyAdapter | ||
| provider = TavilyAdapter() | ||
| elif name == SearchProviderType.BING.value: | ||
| from .providers.bing_adapter import BingAdapter | ||
| provider = BingAdapter() | ||
|
|
||
| if provider: | ||
| self.providers[name] = provider | ||
|
|
||
| except Exception as e: | ||
| logger.debug(f"Failed to lazy load provider {name}: {e}") | ||
|
|
||
| return provider | ||
|
|
||
| def _get_provider(self, name: str) -> Optional[SearchProvider]: | ||
| return self._load_provider(name) | ||
| >>>>>>> 5d045b0 (Refactor SearchRouter to lazily load provider adapters on first use for faster startup.) | ||
|
|
||
| def search( | ||
| self, | ||
|
|
@@ -96,6 +131,8 @@ def search( | |
| provider = self._get_provider(primary_name) | ||
|
|
||
| if not provider: | ||
| # Fallback was also unavailable or failed to init | ||
| logger.error("No valid search provider available.") | ||
| raise ValueError("No valid search provider available.") | ||
|
|
||
| # Execute with reliability-first logic | ||
|
|
@@ -117,8 +154,6 @@ def search( | |
| fallback_provider = self._get_provider(fallback_name) | ||
| if fallback_provider: | ||
| # Fallback gets the same retry logic or just a single shot? | ||
| # For simplicity, fallback is usually single shot untuned or standard. | ||
| # Let's try standard (tuned=True default) | ||
| return fallback_provider.search(query, max_results=max_results) | ||
|
|
||
| # If we get here, all attempts failed | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Remove merge markers around the
RateLimitMiddlewareinstantiation.The remaining conflict markers around
trust_proxy_headerswill cause a syntax error and prevent the app from starting. Resolve the merge and keep a singletrust_proxy_headersargument in the call.