CrewAI BraveSearchTool 实战解析:从安装配置到源码级请求流程
本篇围绕 CrewAI 工具包中的 BraveSearchTool 展开,说明如何通过 Brave Web Search API 为 Agent 接入实时网页搜索能力:包括安装 crewai[tools]、配置 BRAVE_API_KEY、调用参数与结果解析的完整流程。读完本文,你不仅能直接复制运行该工具,还能从源码层面理解它的参数映射、限流、结果精化与错误处理机制,并了解如何平滑迁移到新版 BraveWebSearchTool 等细分工具。
工具定位:Brave Web Search API 的 Agent 封装
BraveSearchTool 是 CrewAI 官方工具包(crewai-tools)内置的网页搜索工具,其设计目标是:针对给定查询词,通过 Brave Web Search API 这个 REST 接口查询互联网并返回 JSON 格式的搜索结果,供 Agent 在推理过程中直接消费。其官方说明位于 brave_search_tool 模块 README。
同一模块目录下还组织了完整的 Brave 工具家族(Web / News / Image / Video / Local POIs / LLM Context),官方文档 Brave Search Tools 文档 明确标注:BraveSearchTool 属于遗留(legacy)类,仅为向后兼容保留;新代码推荐直接按端点选择 BraveWebSearchTool、BraveNewsSearchTool 等更细粒度的工具。本文以 README 所描述的 BraveSearchTool 为主体,同时在结尾给出迁移路径。
安装与准备工作
按 README 给出的三步完成接入:
- 安装依赖包:确认 Python 环境中安装了带 tools 附加依赖的 crewai:
pip install 'crewai[tools]'
-
获取 API Key:到 Brave Search API 官方站点申请一个订阅密钥。
-
配置环境变量:把密钥存入名为
BRAVE_API_KEY的环境变量,工具在初始化时会读取它:
export BRAVE_API_KEY="your-key-here"
这一点在源码中有直接印证:BraveSearchTool 构造函数 在 __init__ 中检查环境变量,缺失时立即抛出异常,属于"快速失败"设计:
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
if "BRAVE_API_KEY" not in os.environ:
raise ValueError(
"BRAVE_API_KEY environment variable is required for BraveSearchTool"
)
最小可运行示例
README 给出的初始化示例:
from crewai_tools import BraveSearchTool
# Initialize the tool for internet searching capabilities
tool = BraveSearchTool()
在此基础上执行一次搜索(run 参数名支持 q / query / search_query 三种写法,源码中有兼容映射,见下文解析),典型调用如下:
tool = BraveSearchTool()
results = tool.run(query="CrewAI agent framework")
print(results)
返回值为一个 JSON 字符串,内容是经过精简的结构化结果列表,例如:
[
{
"url": "https://example.com/crewai",
"title": "CrewAI — ...",
"description": "..."
}
]
源码解析:一次搜索的完整处理链路
以下分析基于 brave_search_tool.py 的 _run 方法(L60-L172)。
1. 内置限流:至少 1 秒间隔
工具在每次执行前做了一次简单的速率检查(L64-L69):若距上次请求不足 1 秒(_min_request_interval = 1.0,ClassVar),就 sleep 补齐间隔,避免触发 Brave 侧的按秒滑动窗口限流(429 RATE_LIMITED)。
current_time = time.time()
if (current_time - self._last_request_time) < self._min_request_interval:
time.sleep(
self._min_request_interval - (current_time - self._last_request_time)
)
BraveSearchTool._last_request_time = time.time()
需要留意:这是类级别的全局时钟(ClassVar),同一进程内所有 BraveSearchTool 实例共享 1 秒间隔;而新版基类已改为实例级限流且速率可配置(见后文)。
2. 参数解析与请求体组装
_run 从 kwargs 中逐项提取参数并组装 payload,关键的兼容逻辑如下:
| 源码行为 | 说明 |
|---|---|
kwargs.get("q") or kwargs.get("query") or kwargs.get("search_query") |
三种查询参数写法等价,缺失则抛出 ValueError("Query is required") |
country |
地理定向(ISO 两位国家码,如 "US") |
search_lang / search_language |
结果语言码(如 "en"),旧名 search_language 自动映射 |
count |
显式结果数;未提供时回落到构造参数 n_results(默认 10) |
offset |
跳过的结果页数,采用 is not None 判断以兼容 0 值 |
safesearch |
内容过滤:off / moderate / strict |
spellcheck、text_decorations、extra_snippets、operators |
布尔开关,均为 is not None 时透传 |
freshness |
时效过滤:pd / pw / pm / py 或 YYYY-MM-DDtoYYYY-MM-DD 区间 |
此外源码强制注入 payload["result_filter"] = "web"(L124-L126),注释说明当前仅限制 web 结果类型,因为该工具还没有 news/videos/locations 的独立处理分支——这正是后续拆分为多个细分工具的动因。
请求最终发往 search_url = "https://api.search.brave.com/res/v1/web/search",携带 X-Subscription-Token(API 密钥)与 Accept: application/json 请求头,HTTP 超时固定 30 秒:
headers = {
"X-Subscription-Token": os.environ["BRAVE_API_KEY"],
"Accept": "application/json",
}
response = requests.get(
self.search_url, headers=headers, params=payload, timeout=30
)
3. 响应精化与 save_file
拿到 JSON 后,工具只提取 results["web"]["results"] 中同时具有 url 和 title 的条目,并压缩为 url / title / description / snippets(来自 extra_snippets)四个字段,最后 json.dumps 成字符串返回(L140-L164)。
save_file=True 时,结果会额外通过 base.py 中的 _save_results_to_file 写入本地文件,文件名带时间戳:
def _save_results_to_file(content: str) -> None:
"""Saves the search results to a file."""
filename = f"search_results_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt"
with open(filename, "w") as file:
file.write(content)
即每次保存生成形如 search_results_2026-09-05_04-26-51.txt 的文件,注意它会写到当前工作目录。
4. 错误处理:以字符串返回而非抛异常
遗留实现把网络异常"软化"为对 LLM 友好的文本(L165-L168):requests.RequestException(连接失败、超时等)返回 "Error performing search: ...",KeyError 返回 "Error parsing search results: ..."。这种设计的意图是让 Agent 能读到错误信息并自行决策(例如换个查询词重试),而不是让整个 Crew 直接崩溃。
新版基类 BraveSearchToolBase:可靠性机制的升级
README 描述的 BraveSearchTool 是单文件实现,而同目录下的 base.py 为新一代工具族提供了统一基类 BraveSearchToolBase。对照阅读可以清楚看到官方在哪些方面补强了可靠性,这些机制也代表了这个模块当前推荐的使用形态:
构造参数全表
所有 Brave 工具共享以下构造参数(来自 BraveSearchToolBase.__init__,L113-L138,并有 测试用例 test_custom_constructor_args 验证):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
api_key |
str | None |
None |
显式传入密钥,优先于环境变量 |
headers |
dict | None |
None |
附加 HTTP 头,会经 schema 校验并合并 |
requests_per_second |
float |
1.0 |
实例级限流速率;<= 0 时关闭限流 |
save_file |
bool |
False |
每次响应写入时间戳 .txt 文件 |
raw |
bool |
False |
True 时跳过响应精化,返回完整 API JSON |
timeout |
int |
30 |
HTTP 请求超时(秒) |
country |
str | None |
None |
遗留快捷参数,作为 country 查询参数缺省值 |
n_results |
int |
10 |
遗留快捷参数,作为 count 查询参数缺省值 |
API 密钥解析顺序为:显式 api_key 参数 > BRAVE_API_KEY 环境变量,两者皆无则抛出 ValueError("BRAVE_API_KEY environment variable is required")。
重试与退避策略
_make_request(L179-L243)实现了完整的重试状态机:
- 可重试条件(
_is_retryable):429且错误码不是配额耗尽类,或5xx服务端错误; - 终态错误:
QUOTA_LIMITED、USAGE_LIMIT_EXCEEDED两种配额错误码被显式排除在重试之外——注释解释得很直白:计费周期未重置前重试永远不会成功;401、422(如OPTION_NOT_IN_PLAN)等也直接抛出; - 等待策略(
_retry_delay):优先读服务端Retry-After响应头,缺失时按2^attempt指数退避(1s、2s、4s); - 默认最多 3 次尝试,重试用尽后把完整 JSON 错误体(含
code、detail、meta)拼进RuntimeError消息,非 JSON 错误体则回落到前 500 字符的文本。
测试文件 对这些路径逐一做了覆盖,例如:test_429_rate_limited_retries_then_succeeds 验证带 Retry-After: 2 的 429 会精确 sleep 2 秒后第二次成功;test_quota_limited_429_raises_immediately 验证配额错误只发 1 次 HTTP 请求;test_5xx_is_retried 验证 502 后重试成功。
请求头校验与实例级限流
_build_and_validate_headers会把请求头统一小写化,补上x-subscription-token与accept: application/json,并用 Pydantic 的header_schema校验合法性(例如给Accept传text/xml会抛ValueError: Invalid headers,测试见test_invalid_header_value_raises);set_headers()支持链式调用并在初始化后追加热头,Web 工具还支持x-loc-lat/x-loc-long/x-loc-city等地理位置头做本地化检索;- 限流时钟改为每实例独立(
_last_request_time+threading.Lock),源码注释指出:进程总速率等于各实例限额之和。
空值剥离:适配 LLM 严格模式的一个细节
_common_payload_refinement(base.py L284-L318)有一段很有意思的防御性逻辑:crewAI 的 schema 管道会为兼容 OpenAI strict-mode 结构化输出把所有属性标记为 required,副作用是 LLM 会把真正可选的参数也填上 None、""、"null" 或 [] 之类的占位值。基类会把这些空值从可选字段中剔除,并把 query / search_query 统一归一为 q;同时把构造期传入的 n_results / country 作为 count / country 的缺省值注入(调用时显式传入则优先)。test_common_refinement_strips_null_like_values 专门验证了这一行为。
从 BraveSearchTool 迁移到 BraveWebSearchTool
结合官方文档的迁移指南,从遗留类切换到新工具族只需替换导入并调整参数风格:
# Before (legacy)
from crewai_tools import BraveSearchTool
tool = BraveSearchTool(country="US", n_results=5, save_file=True)
results = tool.run(search_query="AI agents")
# After (recommended)
from crewai_tools import BraveWebSearchTool
tool = BraveWebSearchTool(save_file=True)
results = tool.run(q="AI agents", country="US", count=5)
关键差异归纳:
- 导入:
BraveWebSearchTool(或 News/Image/Video 变体)替代BraveSearchTool; - 查询参数:推荐
q=(query/search_query仍被兼容接受); - 结果数与地域:由构造期
n_results/country改为调用期count/country查询参数; - 密钥:除环境变量外还支持
api_key=直传; - 可靠性:
requests_per_second可调,且内置 429/5xx 自动重试。
以 BraveWebSearchTool 为例,子类只需声明 search_url、args_schema、header_schema 并实现 _refine_request_payload / _refine_response——Web 工具的精化逻辑是把 web.results 收敛为 {url, title, snippets} 列表,snippets 优先取 extra_snippets,缺失时回落到 description。查询参数约束则定义在 schemas.py 的 WebSearchParams:q 最长 400 字符、count 范围 1–20、offset 范围 0–9、country 必须是两位大写国家码等,请求前都会经过 Pydantic 校验(例如 count=999 会抛 Invalid parameters,测试见 test_invalid_params_raises_value_error)。
一个 Agent 集成示例(引自官方文档):
from crewai import Agent
from crewai.project import agent
from crewai_tools import BraveWebSearchTool, BraveNewsSearchTool
web_search = BraveWebSearchTool()
news_search = BraveNewsSearchTool()
@agent
def researcher(self) -> Agent:
return Agent(
config=self.agents_config["researcher"],
tools=[web_search, news_search],
)
小结
BraveSearchTool是 CrewAI 接入 Brave Web Search API 的遗留工具:安装crewai[tools]、设置BRAVE_API_KEY后即可使用;它的run支持q/query/search_query三种查询写法,内置 1 秒类级限流、30 秒超时、web 结果强制过滤与save_file落盘能力,网络错误以文本形式返回以便 Agent 容错。- 同模块的新版工具族基于
BraveSearchToolBase,补齐了实例级可调限流、429/5xx 指数退避重试(配额错误不重试)、Pydantic 请求头/参数校验、LLM 空值剥离等可靠性机制,并有完整的单元测试覆盖。 - 新代码建议直接使用
BraveWebSearchTool等细分工具;涉及enable_snippets、Local POIs 等能力时需注意部分参数/工具要求付费订阅计划,以 Brave 官方 API 文档为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00