Exa 的官方 Python SDK。Exa 是面向 AI 的网页搜索 API。
pip install exa-py
需要 Python 3.9+
from exa_py import Exa
exa = Exa(api_key="your-api-key")
# 搜索网页
results = exa.search(
"blog post about artificial intelligence",
type="auto",
contents={"highlights": True}
)
# 提问
response = exa.answer("What is the capital of France?")
results = exa.search(
"machine learning startups",
contents={"highlights": True}
)
results = exa.search(
"climate tech news",
num_results=20,
start_published_date="2024-01-01",
include_domains=["techcrunch.com", "wired.com"],
contents={"highlights": True}
)
results = exa.search(
"What are the latest battery breakthroughs?",
type="auto",
system_prompt="Prefer official sources and avoid duplicate results",
output_schema={
"type": "object",
"properties": {
"summary": {"type": "string"},
"key_companies": {"type": "array", "items": {"type": "string"}},
},
"required": ["summary", "key_companies"],
},
)
print(results.output.content if results.output else None)
for chunk in exa.stream_search(
"What are the latest battery breakthroughs?",
type="auto",
):
if chunk.content:
print(chunk.content, end="", flush=True)
搜索的 output_schema 模式:
- {"type": "text", "description": "..."}:在 output.content 中返回纯文本
- {"type": "object", ...}:在 output.content 中返回结构化 JSON
所有搜索类型都支持 system_prompt 和 output_schema。
搜索流式输出通过 stream_search(...) 提供,它会逐条产出 OpenAI 风格的 chat completion 分块。
对于 type: "object",搜索目前有以下限制:
- 最大嵌套深度:2
- 最大属性总数:10
支持 additional_queries 的深度搜索变体:
- deep-lite
- deep
- deep-reasoning
使用 extra_body 可按调用覆盖搜索请求体。其他方法也接受该参数,详见请求选项。
result = exa.search("AI research", extra_body={"futureField": True})
results = exa.get_contents(
["https://exa.ai/docs"],
text=True
)
results = exa.get_contents(
["https://arxiv.org/abs/2303.08774"],
highlights=True
)
response = exa.answer("What caused the 2008 financial crisis?")
print(response.answer)
for chunk in exa.stream_answer("Explain quantum computing"):
print(chunk, end="", flush=True)
可以在 OpenAI 或 Anthropic 的调用循环中将 Exa 作为 web_search 工具使用。无参调用 web_search() 可获得 Exa 为智能体搜索推荐的设置(type="auto" 和 contents={"highlights": True})。
from exa_py import Exa
from openai import OpenAI
exa = Exa()
openai_client = OpenAI()
messages = [{"role": "user", "content": "What's the latest on AI chips?"}]
completion = openai_client.chat.completions.create(
model="gpt-5.6",
messages=messages,
tools=[exa.openai.web_search()],
)
message = completion.choices[0].message
messages.append(message)
messages += exa.openai.handle_tool_calls(message)
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=messages,
tools=[exa.anthropic.web_search()],
)
传入 name(以及可选的 description)可重命名工具。Anthropic 要求工具名唯一,因此自定义名称可以让 Exa 工具与 Anthropic 内置的 web_search_20250305 工具共存:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=messages,
tools=[
exa.anthropic.web_search(name="exa_web_search"),
{"type": "web_search_20250305", "name": "web_search", "max_uses": 5},
],
)
get_contents 在同一命名空间中同样可用,可让模型读取它已拥有 URL 的页面。它接收一个 URL 列表,并接受 Exa.get_contents 的所有选项:
completion = openai_client.chat.completions.create(
model="gpt-5.6",
messages=messages,
tools=[
exa.openai.web_search(),
exa.openai.get_contents(summary=True, livecrawl="preferred"),
],
)
对于 OpenAI Responses API,请使用 exa.openai.responses.web_search() 和相同的 handle_tool_calls 辅助函数。这些处理器会应答每一个工具调用:无法解析工具名的调用会得到 Error: unknown tool "<name>" 输出,而不会被丢弃,从而保证后续请求有效。如果你同时使用 Exa 之外的其他工具,请在下次请求前将这些错误输出替换为你自己的结果。
Agent API 无需 beta 请求头即可使用。
run = exa.agent.runs.create(
query="Find engineering leaders at AI infrastructure companies that raised a Series A or B in the last 6 months.",
output_schema={
"type": "object",
"properties": {
"people": {
"type": "array",
"maxItems": 10,
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"contact_email": {"type": "string", "format": "email"},
"linkedin_url": {"type": "string", "format": "uri"},
},
"required": ["name", "linkedin_url"],
},
}
},
"required": ["people"],
},
effort="auto",
)
run = exa.agent.runs.poll_until_finished(run.id)
print(run.output.structured if run.output else None)
如需 Agent Ultra,请设置 effort="ultra":
from exa_py import Exa
exa = Exa()
run = exa.agent.runs.create(
query="Find all companies building browser automation tools in the United States.",
effort="ultra",
budget={"maxCostDollars": 10},
)
Agent 监控器使用 beta 命名空间,需要 beta 标识 AGENT_MONITORS_BETA_HEADER(agent-monitors-2026-08-04)。
Agent 监控器会按固定周期持续刷新一张“实体 × 字段”表格。
from exa_py.agent import AGENT_MONITORS_BETA_HEADER
betas = [AGENT_MONITORS_BETA_HEADER]
# 创建监控器。创建为异步操作:返回时状态为 "creating",
# 首次刷新完成后变为 "active"。
monitor = exa.beta.agent.monitors.create(
betas=betas,
cadence="7d",
entities=[
{"name": "Acme Corp", "domain": "acme.com"},
{"name": "Globex", "domain": "globex.com"},
],
fields=[
{"name": "funding", "description": "New funding rounds"},
{"name": "ceo", "description": "The company's current CEO"},
],
idempotency_key="my-monitor-1", # 安全重试:相同 key 返回同一监控器
)
# 分页获取监控器当前实体及其内容。
for view in exa.beta.agent.monitors.entities.list_all(monitor.id, betas=betas):
print(view.entity.name, view.contents)
# 跟随内容变更流(之后可从页面的 next_cursor 继续)。
changes = exa.beta.agent.monitors.changes.list(
monitor.id,
betas=betas,
since="2026-01-01T00:00:00Z",
)
# 对过去某段新闻窗口做一次性回测——不会持久化监控器。
backtest = exa.beta.agent.monitors.backtests.create_and_wait(
betas=betas,
entities=[{"name": "Acme Corp", "domain": "acme.com"}],
fields=[{"name": "funding", "description": "New funding rounds"}],
start_time="2026-01-01T00:00:00Z",
end_time="2026-01-08T00:00:00Z",
)
print(backtest.data)
# 添加实体、查看刷新进度、清理。
exa.beta.agent.monitors.entities.add(
monitor.id,
betas=betas,
entities=[{"name": "Initech", "domain": "initech.com"}],
)
current = exa.beta.agent.monitors.get(monitor.id, betas=betas)
print(current.status, current.refresh, current.usage)
exa.beta.agent.monitors.delete(monitor.id, betas=betas)
from exa_py import AsyncExa
exa = AsyncExa(api_key="your-api-key")
results = await exa.search("async search example", contents={"highlights": True})
Exa 与 AsyncExa 的每个 API 方法在自身参数之外都接受三个关键字参数:
| 参数 | 作用 |
|---|---|
extra_body |
合并到 JSON 请求体之上的额外字段 |
extra_headers |
额外的请求头 |
extra_query |
额外的查询参数 |
# 发送 SDK 尚未类型化的字段。
exa.get_contents("https://example.com", text=True, extra_body={"futureField": True})
exa.websets.list(
limit=10,
extra_query={"futureParam": "value"},
extra_headers={"X-Request-Source": "nightly"},
)
它们会附加到方法构建的请求上。若与方法自身设置的名称冲突,以你的值为准。
extra_body 会随 POST 和 PATCH 请求发送。其他请求会忽略它,请改用 extra_query。extra_query 会跳过值为 None 的参数,并在两个客户端中都将 True/False 发送为 true/false。User-Agent)无论大小写都会被替换。设为 None 的请求头会被跳过。stream 永远不会从 extra_body 或 extra_query 中获取,额外的 Accept 请求头既不会启动流,也不会替换流式方法的 Accept:请调用流式方法。list_all、get_all、poll_until_finished、create_and_wait、wait_until_idle)会对每次请求应用这些选项,因此 create_and_wait 只在创建请求中发送 extra_body,轮询时不发送。extra_query 中的 cursor 用于选择 list_all 与 get_all 的第一页,辅助方法会从此处继续。exa.tools、exa.openai、exa.anthropic)会将创建工具时传入的选项和 timeout 应用到其发起的每个请求,例如 exa.openai.web_search(timeout=10, extra_headers={"X-Request-Source": "agent"})。run、execute 以及 handle_tool_calls / handle_tool_use 辅助方法不支持按调用传入。同步与异步客户端默认使用 600 秒的网络不活动超时(而非总耗时上限)。可设置客户端级的值,也可在搜索、内容获取、问答或流式调用中覆盖;构造时使用 None 可禁用。按调用传入 None 会继承客户端设置。
from exa_py import Exa
exa = Exa(api_key="your-api-key", timeout=30)
results = exa.search("latest AI developments", timeout=5)
超时的请求会抛出 ExaTimeoutError,同步与异步客户端一致:
from exa_py import Exa, ExaTimeoutError
exa = Exa(api_key="your-api-key")
try:
results = exa.search("latest AI developments", timeout=5)
except ExaTimeoutError:
results = None
该错误同时也是 HTTP 库所抛异常的实例,因此为其编写的处理器仍然有效:
AsyncExa:一个 httpx.TimeoutException。Exa:连接或等待响应开始期间为 requests.exceptions.Timeout;响应开始后停滞时为 requests.exceptions.ConnectionError(Requests 的报告方式)。使用 AsyncExa 时,可通过取消等待该操作的 task 来取消进行中的操作:
import asyncio
from exa_py import AsyncExa
exa = AsyncExa(api_key="your-api-key")
task = asyncio.create_task(exa.search("latest AI developments"))
task.cancel()
提前停止异步搜索/问答流时,请调用 await stream.aclose() 释放 HTTP 连接;在等待下一分块时取消也会关闭连接。同步客户端无法从其他线程取消阻塞读取;超时约束的是每段网络不活动的时间。构造函数与按调用覆盖若非 None,都要求正且有限的超时值。
API 可能返回关于某个请求的 warnings,例如某个被忽略的字段。撰写本文时,搜索响应(包括 stream_search)会携带它们。警告从不会改变状态码或结果。它面向构建请求的代码维护者。请修正请求,不要将警告传给模型。响应会将其保存在 warnings 中。
客户端还会通过 Python 的 warnings 模块将其作为 ExaWarning 发出,归因于发起调用的代码行:
app.py:12: ExaWarning: [exa] POST /search IGNORED_FIELD at /startCrawlDate: startCrawlDate is no longer supported and is ignored. Remove it from the request.
Python 的默认过滤器对每个调用行只显示一次给定警告。可静默它们,或将其转入日志:
import logging
import warnings
from exa_py import ExaWarning
warnings.filterwarnings("ignore", category=ExaWarning) # 静默
logging.captureWarnings(True) # 或发送到 "py.warnings" logger
每个 ExaWarning 都有 warning、endpoint、method 和 request_id 属性。warning 是 ApiWarning,包含其 code、path 和 message。
已弃用的选项会在请求发送前抛出 DeprecationWarning,无论你是作为选项设置还是通过 extra_body 传入。撰写本文时,包括 start_crawl_date、end_crawl_date,以及 highlights 选项 num_sentences 和 highlights_per_url。Python 默认仅对以 __main__ 运行的代码显示 DeprecationWarning。pytest 等测试运行器也会显示它们。
查看完整文档了解包括 websets、过滤器及高级选项在内的所有功能。