Exa 搜索 API | SandBase
用一个 REST API 运行 Exa 神经网络搜索、网页正文抓取和带引用的答案。无需单独 SDK——一个 SandBase 密钥,为 Agent 和 RAG 工作流而生。

Exa 是一个为 AI 而非为”人点蓝色链接”而建的搜索引擎——它做基于语义的神经检索,让 Agent 能按意图找到网页、抓它们的正文、拿到一个带引用的答案。这让它天然契合检索增强生成(RAG)、研究型 Agent,以及把 LLM 输出锚定在真实来源上。可要自己接,仍意味着再管一个 API 客户端和它的鉴权。
SandBase 的 Exa 搜索 API 把这些收进一套约定。它通过普通 REST 运行 Exa 的搜索、正文和答案能力——一个 SandBase API 密钥,不需要单独的 SDK。端点 API 参考是每个参数和响应信封的权威来源;下面的字段名来自我实际跑的调用(测试于 2026-09-27,UTC),仅供观测——并非文档保证——请以真实响应为准核对,因为负载会随时间变化。想上手?获取 SandBase API 密钥,然后浏览 Exa 端点。
先说结论
- 三个端点覆盖整个循环:
search(找网页)、contents(抓正文)、answer(带引用的答案)。- 每次调用都是
POST /v1/api/exa/<path>——只传该端点的参数,无 SDK,一个SANDBASE_API_KEY。- 信封可能不同:参考记录的是
outputs[0].data,但在我的调用里负载常出现在一个顶层output对象下——两种结构都读。- 为 RAG 和 Agent 而生:按语义搜索、抓网页正文,或拿一个锚定在引用上的答案。
关于这个端点响应结构的说明
SandBase 端点通常返回一个 outputs 数组,其首个元素在 data 下承载负载;Exa 参考记录的是这个 outputs[0].data 结构。而在我自己的调用里,负载常出现在单个顶层 output 对象下(search 和 contents 是 results,answer 是 answer 加 citations)。因为信封可能不同,写代码时两种结构都读:优先读顶层 output,再回退到 outputs[0].data。动手前请以你所调用端点的线上参考核对确切信封。
你需要哪个 Exa 端点?
| 你的需求 | 端点 | 输入 |
|---|---|---|
| 按语义找相关网页 | exa/search | query |
| 抓取特定网页的正文 | exa/contents | ids |
| 拿一个带引用的直接答案 | exa/answer | query |
SandBase vs. 直接用 Exa API
| 你的情况 | 用 |
|---|---|
| 想在众多 provider 间用一个密钥、一套约定 | SandBase Exa API(本文) |
| 需要 Exa 特有功能或直接计费 | Exa 自己的 API 和控制台 |
SandBase 在 Exa 的能力之上提供一层统一的 Model API;当你需要厂商特有控制或直接契约时,用 Exa 自己的 API。
Exa API 能取到什么
- 神经搜索 —
exa/search接一个query,返回排名结果,每条带url、title和publishedDate。 - 正文 —
exa/contents接ids(网页 URL,Exa 用它作标识),返回每个网页的正文。 - 答案 —
exa/answer接一个query,返回一个合成的answer加citations。
动手前请以每个端点的线上 API 参考为准核对参数;可用性因端点而异。
SandBase 上的 Exa API 页面——带标签的概览和端点列表,每个端点都标了路径。
快速上手:第一次调用
SandBase 暴露不止一个 API 面。目录里可能显示 /apis/v1/... 下的 GET 路径;本文使用每个端点 API 参考上标注的带厂商前缀的 Model API 路径。不要擅自改动 HTTP 方法或 URL——以你所选端点的参考为准。
运行一次神经搜索:
import os
import requests
resp = requests.post(
"https://api.sandbase.ai/v1/api/exa/search",
headers={
"Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
"Content-Type": "application/json",
},
json={"query": "latest advances in AI agents"},
)
resp.raise_for_status()
body = resp.json()
if body.get("status") != "completed":
error = body.get("error", {})
raise RuntimeError(error.get("message", "Exa 请求未完成"))
# 信封可能不同:优先读顶层 output,否则回退到文档的 outputs[0].data。
output = body.get("output")
if output is None and body.get("outputs"):
output = body["outputs"][0].get("data", {})
output = output or {}
for r in output.get("results", [])[:5]:
print(r.get("title"), "-", r.get("url"))
curl -X POST https://api.sandbase.ai/v1/api/exa/search \
-H "Authorization: Bearer $SANDBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "latest advances in AI agents"}'
响应带一个 id、一个 status、model 名称。参考记录负载在 outputs[0].data 下;在我的调用里它常出现在一个顶层 output 对象下——所以两种结构都读。failed 或 timeout 的运行则带 error,所以先按 status 分支判断。下面的块是示意性的、并非文档保证——我那次搜索调用的一次抓取(搜索 run id 67700c0e-9398-4f89-954d-42a434ed2706,测试于 2026-09-27,UTC);参考的业务负载示例是刻意留空的,所以请把这些字段名当作仅供观测,并对照真实响应核对:
{
"id": "67700c0e-9398-4f89-954d-42a434ed2706",
"model": "exa/search",
"status": "completed",
"output": {
"results": [ { "id": "…", "title": "…", "url": "…", "publishedDate": "…" } ],
"searchTime": 0.0,
"resolvedSearchType": "…"
}
}
响应结构因端点而异——请检查一次真实响应,并逐端点确定精确的字段路径。
端点 API 参考是每个参数名和响应路径的事实来源。
串起 search → contents → answer
因为每个端点共享同一套鉴权和一致的 output 信封,Agent 可以把经典的 RAG 循环串起来,而不用为每个面单独写特例:
- 按语义搜索。 用一个
query调用exa/search;读results,每条带一个url(也可作它的id)。 - 抓正文。 用
ids(网页 URL 或 Exa 结果 ID)调用exa/contents抓网页正文;注意text是一个可选请求项,请对照参考确认你请求和收到的内容。 - 或拿直接答案。 用一个
query调用exa/answer拿一个合成的answer加citations。
每一步都返回相同的 { id, status, model, output } 结构,所以你的 Agent 只需按 status 分支一次,并在每一步复用同一段读 JSON 的代码。
Exa 端点列表的一角。
常见用例
Exa 搜索 API 做 RAG 检索
用一个 query 运行 exa/search 按语义找相关网页,再用它们的 URL 调 exa/contents 抓正文喂进你的上下文窗口。输入:一个 query,再网页 URL。输出:排名结果,再网页正文。端点:search、contents。
Exa 答案 API 做有据回答
用一个问题调用 exa/answer 拿一个带引用的合成答案,你可以呈现给用户或去核实。输入:一个 query。输出:一个 answer 加 citations。端点:answer。
Exa 正文 API 做文本抽取
把一组网页 URL 作为 ids 传给 exa/contents,一次调用抓它们的正文。输入:ids(URL)。输出:每个网页的正文。端点:contents。
为什么放在 API 层来做
通过一层统一 API 来读 Exa,意味着你的代码依赖的是有名字的 JSON 字段和单个响应信封,而不是又一个厂商 SDK。鉴权是一个密钥,而且因为每个端点都返回相同的 { id, status, model, output } 结构,重试、日志和错误处理都可以收进一个你写一次、处处复用的辅助函数里。换一个 query、换一组 URL,代码路径完全一样——这正是让一个 RAG 或研究型 Agent 可组合的关键。你的时间花在”检索到的来源对你的任务意味着什么”上,而不是花在把 SDK 粘一起。当你需要的不止这些读取时,在线上列表里查到合适的端点,并在接入前确认它的参数。
局限与边界
- 信封可能不同。 参考记录的是
outputs[0].data;在我的调用里负载常出现在一个顶层output对象下。两种结构都读(优先output,回退outputs[0].data)并按status分支。 contents接ids。ids参数接受网页 URL 或 Exa 结果 ID(通常来自上一次search)。网页正文(text)是一个可选请求项——请对照参考确认你请求和收到的内容。- 字段仅供观测。 参考保证的是信封;把
results、text、answer、citations当作观测到的、以真实响应核对。 - 速率与量级。 把响应当作尽力而为的读取;作为客户端侧的韧性措施,遇到 HTTP 429 等瞬时错误时按退避策略重试。
- 以线上参考核对端点。 可用性和字段可能变化;在依赖某个具体端点前先确认。
常见问题
我需要单独的 Exa API 密钥或 SDK 吗?
不需要。你用自己的 SANDBASE_API_KEY 向 SandBase 鉴权,通过普通 REST 调用 Exa 端点——你这边不需要单独的 SDK。
为什么这个面返回 output 而不是 outputs?
参考记录负载在 outputs[0].data 下;在我的调用里它常出现在一个顶层 output 对象下,里面是端点的负载。因为信封可能不同,两种结构都读(优先 output,回退 outputs[0].data),并以线上参考核对。
search 和 answer 有什么区别?
search 返回你自己去抓取和处理的排名网页;answer 返回一个带引用的合成答案。做 RAG 用 search(+ contents),想要直接的有据回答用 answer。
我能用它做 RAG 吗?
能。search → contents 这一对是天然的 RAG 检索步:按语义找网页、抓它们的正文、喂给你的模型。answer 覆盖你想直接要有据回答的场景。
从一次搜索开始
创建一个 SandBase API 密钥,用一个 query 调用 search,在串进 contents 或 answer 之前先检查 output。准备好后: