Spaces:
Sleeping
Sleeping
File size: 4,393 Bytes
8ed954c 19d2204 8ed954c 19d2204 8ed954c 19d2204 8ed954c 19d2204 8ed954c 19d2204 8ed954c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import os
import random
import time
import requests
from .logger import get_logger
logger = get_logger(__name__)
_BRAVE_URL = "https://api.search.brave.com/res/v1/web/search"
_MAX_RETRIES = 3
def _brave_request(
params: dict,
api_key: str,
accept: str = "application/json",
) -> requests.Response | None:
"""Send a Brave Search request with retry + exponential backoff on 429.
A small random jitter (0-2 s) is added before the first attempt to
spread out concurrent requests from parallel region fan-out.
"""
headers = {
"Accept": accept,
"X-Subscription-Token": api_key,
}
time.sleep(random.uniform(0, 2.0))
for attempt in range(_MAX_RETRIES):
try:
resp = requests.get(
_BRAVE_URL, headers=headers, params=params, timeout=15,
)
if resp.status_code == 429:
wait = (2 ** (attempt + 1)) + random.uniform(0, 1)
logger.info(
"Brave 429 (attempt %d/%d), retrying in %.1fs…",
attempt + 1, _MAX_RETRIES, wait,
)
time.sleep(wait)
continue
resp.raise_for_status()
return resp
except requests.exceptions.HTTPError:
raise
except requests.exceptions.RequestException as exc:
if attempt < _MAX_RETRIES - 1:
wait = (2 ** (attempt + 1)) + random.uniform(0, 1)
logger.info("Brave network error, retrying in %.1fs: %s", wait, exc)
time.sleep(wait)
else:
raise
return None
def brave_search(query: str, count: int = 5, freshness: str = "") -> str:
"""Single implementation of Brave Search used across the whole project.
Args:
query: The search query string.
count: Number of results to request (max 20).
freshness: Optional freshness filter (e.g. ``"pw"`` for past week,
``"pm"`` for past month).
Returns:
A newline-joined string of ``"- title: description"`` lines, or an
error message.
"""
api_key = os.getenv("BRAVE_API_KEY")
if not api_key:
logger.warning("BRAVE_API_KEY not set – search skipped")
return "No Brave API key found."
params: dict = {"q": query, "count": count}
if freshness:
params["freshness"] = freshness
try:
resp = _brave_request(params, api_key)
if resp is None:
logger.warning("Brave rate-limit exhausted for query: %s", query)
return "Rate limit hit – try again shortly."
results = resp.json().get("web", {}).get("results", [])
if not results:
logger.info("Brave returned 0 results for: %s", query)
return "No results found."
return "\n".join(
f"- {r.get('title', '')}: {r.get('description', '')}"
for r in results
)
except requests.exceptions.HTTPError as exc:
if exc.response is not None and exc.response.status_code == 429:
logger.warning("Brave rate-limit hit for query: %s", query)
return "Rate limit hit – try again shortly."
logger.error("Brave HTTP error: %s", exc)
return f"Search HTTP error: {exc}"
except requests.exceptions.RequestException as exc:
logger.error("Brave network error: %s", exc)
return f"Search error: {exc}"
def brave_search_raw(query: str, count: int = 10, freshness: str = "pm") -> str:
"""Return the raw combined text of titles + descriptions.
Useful for regex-based ticker extraction in niche_hunter / scanner.
"""
api_key = os.getenv("BRAVE_API_KEY")
if not api_key:
logger.warning("BRAVE_API_KEY not set – raw search skipped")
return ""
params: dict = {"q": query, "count": count}
if freshness:
params["freshness"] = freshness
try:
resp = _brave_request(params, api_key)
if resp is None:
logger.warning("Brave rate-limit exhausted for raw query")
return ""
results = resp.json().get("web", {}).get("results", [])
return " ".join(f"{r['title']} {r['description']}" for r in results)
except requests.exceptions.RequestException as exc:
logger.error("Brave raw-search error: %s", exc)
return ""
|