tools.brave_search module
Web search via the Brave Search API with rate limiting and key rotation.
- class tools.brave_search.BraveAPIKeyManager[source]
Bases:
objectRound-robin manager for the pool of shared Brave Search API keys.
Holds the list of default/pool Brave subscription tokens and hands them out in rotation so that the per-key free-tier quota is spread across all configured keys. Rotation is advanced both on every normal request (via
get_next_key()) and on demand when a key returns HTTP 429/402, lettingBraveSearchRateLimiterfail over to a fresh key without aborting the search. A single module-level instance (_key_manager) is shared by the rate limiter and the public helpers; state is purely in-memory and not persisted.- __init__()[source]
Construct an empty key manager with no keys loaded.
Keys are populated later by
load_keys()once a config object is available; the round-robin cursor starts at zero and the asyncio lock guardingget_next_key()is created lazily on first use.
- load_keys(config)[source]
Populate the key pool from the bot configuration.
Reads
config.API_KEYS["brave"](which may be a single string or a list), discards blank/whitespace-only entries, and stores the cleaned result onself.keys; the outcome is logged at INFO when keys are found and WARNING when none are. Any exception while reading the config is swallowed and leaves the pool empty so a misconfiguration degrades gracefully into “no shared key”. Called once (guarded by the module-level_keys_loadedflag) fromrun()andsearch_with_key()the first time a search is performed.- Parameters:
config – Bot configuration object exposing an
API_KEYSmapping.
- async get_next_key()[source]
Return the next pool key and advance the round-robin cursor.
Hands back the key at the current index, then moves the cursor forward (wrapping modulo the pool size) so consecutive callers spread load across keys. The read-and-advance is performed under the lazily created lock from
_ensure_lock()so concurrent searches never receive the same index or race the cursor update. Called byBraveSearchRateLimiter._brave_search()when the request is not using a caller-supplied user key.
- rotate_key()[source]
Advance to the next pool key and return it, for failover.
Unlike
get_next_key(), this advances the cursor before reading so it can be called mid-request to deliberately switch away from a key that just failed, and it logs the new position at INFO. It is synchronous (no lock) because it is only invoked from inside the rate limiter worker’s single-threaded retry loop. Called byBraveSearchRateLimiter._brave_search()after an HTTP 429 or a network error to retry on a fresh key.
- get_current_key()[source]
Return the key at the current cursor without advancing it.
A read-only peek at the active pool key; the round-robin cursor is left untouched. Provided for completeness alongside the rotation helpers; it is not currently called elsewhere in the repo.
- get_key_count()[source]
Return how many keys are in the pool.
Used to decide whether failover is even possible (rotating is pointless with a single key) and to render the
key N/Mprogress string in the search logs. Called byBraveSearchRateLimiter._brave_search().- Returns:
The number of configured pool keys.
- Return type:
- class tools.brave_search.BraveSearchTask(query, count, country, search_lang, ui_lang, safesearch, future, user_api_key=None, user_id=None, redis_client=None, config=None)[source]
Bases:
objectA single queued Brave search request awaiting rate-limited execution.
Bundles everything
BraveSearchRateLimiterneeds to run one search (the query plus its locale/safesearch options, the resolved key and caller identity for quota accounting, and a Redis handle and config for privilege checks) together with anasyncio.Futurethe worker resolves with the result. Instances are created byBraveSearchRateLimiter.search(), placed on the limiter’s internal queue, and consumed by its background worker.- Parameters:
- future: Future
- class tools.brave_search.BraveSearchRateLimiter(calls_per_interval=0.5, interval_seconds=1.0)[source]
Bases:
objectProcess-wide serialiser that throttles all Brave API traffic.
Every search is enqueued as a
BraveSearchTaskand executed by a single background worker coroutine, guaranteeing the configured minimum gap between outbound Brave calls regardless of how many concurrent tool invocations are in flight. The worker performs the Brave request (with retries and key rotation), applies the Tavily fallback on failure, and resolves each task’s future with the result. A single module-level instance (_rate_limiter) backs bothrun()andsearch_with_key().- __init__(calls_per_interval=0.5, interval_seconds=1.0)[source]
Configure the throttle rate; defer all async setup until first use.
Stores the rate parameters but does not create the queue, lock, or worker task here (no event loop is guaranteed at construction time); those are built lazily by
_ensure_initialized(). The default of 0.5 calls per 1.0 second yields a minimum spacing of two seconds between Brave requests.
- async search(query, count=10, country=None, search_lang=None, ui_lang=None, safesearch='moderate', user_api_key=None, user_id=None, redis_client=None, config=None)[source]
Submit a search to the rate-limited worker and await its result.
The public entry point on the limiter. It rejects an empty query up front, ensures the worker is running via
_ensure_initialized(), wraps the arguments in aBraveSearchTaskcarrying a fresh future, enqueues it, and awaits the future the worker resolves. Any exception surfaced through the future is logged and converted into a JSON error payload so callers always get a string. Called byrun()(the tool entry point) andsearch_with_key()(the internal programmatic helper).
- async tools.brave_search.search_with_key(query, count=3, api_key=None)[source]
Execute a Brave search through the shared rate limiter.
This is the entry point for internal callers (e.g.
WebSearchContextManager) that already have a resolved API key and don’t need the tool-context ceremony.
- async tools.brave_search.run(query, count=10, country=None, search_lang=None, ui_lang=None, safesearch='off', ctx=None)[source]
Entry point for the
brave_web_searchtool: authorize, then search.Dispatched by
tool_loaderunder the single-toolTOOL_NAME/runconvention when the LLM invokesbrave_web_search. It first enforces the WEB_SEARCH privilege via_check_web_search_access(), lazily loads the shared key pool fromctx.config(guarded by the module_keys_loadedflag), and looks up the caller’s own stored Brave key from Redis throughtools.manage_api_keys.get_user_api_key()so personal keys bypass the pool quota. It then forwards everything toBraveSearchRateLimiter.search()on the shared_rate_limiterand returns its JSON result string.- Parameters:
query (
str) – Search query, optionally using Brave search operators.count (
int) – Number of results to return (clamped to 1-20).country (
str) – Country code for localised results (e.g. US, GB).search_lang (
str) – Language code for search results (e.g. en, es).ui_lang (
str) – Language code for UI elements.safesearch (
str) – Safe-search level (off, moderate, or strict).ctx – Tool execution context exposing
user_id,redis,channel_id, andconfig.
- Returns:
A JSON string of search results, or a JSON error payload.
- Return type: