@ expandai/ sdk
正式类型SDK 扩展AI API (英语).
建造在 效果,同时拥有一个基于阶级的客户端,用于承诺式使用和 Effect.Service 实现全面一体化.
安装
需要节点20.3或更新.
npm install @expandai/sdk
# or
bun add @expandai/sdk
# or
pnpm add @expandai/sdk
在不改变生产的情况下可以使用内部中转建筑 latest 标签 :
npm install @expandai/sdk@staging
通过客户端的 API URL 传递 baseUrl; 选择 npm 标记不会改变默认的生产终点.
认证
设定 EXPAND_API_KEY 在你的环境中,或者通过 apiKey 在构建客户端时明确.
export EXPAND_API_KEY=sk_...
快开始
保证API
import { ExpandClient } from '@expandai/sdk'
const expand = new ExpandClient()
try {
const result = await expand.fetch({ url: 'https://example.com' })
console.log(result)
} finally {
await expand.close()
}
重新使用客户端用于您的应用程序的寿命, 然后调用 close() 在关闭期间。 关闭终止活动请求,结束 Crawl 延时器和投票,并释放这个客户端的连接而不关闭另一个客户端的池. 重复关闭是安全的;以后对一个被关闭的客户端请求拒绝. 效应服务消费者保留现有的范围层清理.
构造器选项 :
new ExpandClient({
apiKey: 'xpnd_...', // defaults to process.env.EXPAND_API_KEY
baseUrl: 'https://api...', // defaults to https://api.expand.ai
timeoutMs: 60_000, // total wall-clock budget incl. retries
maxRetries: 2, // retries transient failures; see "What gets retried"
})
出错 :
ExpandClientError无效选项( 缺少 API 密钥, 为空)baseUrl(中文(简体) )ExpandClientApiError——非-2xxHTTP响应ExpandClientConnectionError——网络故障ExpandClientTimeoutError——请求超时
效力服务
import { NodeRuntime } from '@effect/platform-node'
import { Effect } from 'effect'
import { ExpandService } from '@expandai/sdk'
const program = Effect.gen(function* () {
const expand = yield* ExpandService
const result = yield* expand.fetch({ url: 'https://example.com' })
yield* Effect.log(result)
})
NodeRuntime.runMain(program.pipe(Effect.provide(ExpandService.layer({ apiKey: process.env.EXPAND_API_KEY }))))
图层选项( 形状与 ExpandClientOptions:
ExpandService.layer({
apiKey: 'xpnd_...', // defaults to process.env.EXPAND_API_KEY
baseUrl: 'https://api...', // defaults to https://api.expand.ai
timeoutMs: 60_000, // total wall-clock budget incl. retries
maxRetries: 2, // retries transient failures; see "What gets retried"
})
输入错误 Effect 频道 :
ExpandSdkError无效选项( 缺少 API 密钥, 为空)baseUrl(中文(简体) )ExpandApiError——非-2xxHTTP响应ExpandConnectionError——网络故障ExpandTimeoutError——请求超时
本地页面和文件
在节点.js,通过 { local: true } 改为 fetch() 或者说 fetchJson() 有本地主机 HTTP URL 或文件或文件夹路径。 同一个选项在 ExpandService。 。 。 .
const markdown = await expand.fetch({ url: 'http://localhost:3000' }, { local: true })
const page = await expand.fetchJson({ url: './dist' }, { local: true })
本地模式需要一个 API 密钥并临时公开分享所选内容. 保存的快照之后仍可共享 。 永远不要为未信任的用户提供的 URL 或路径启用 。 见 本地页面和文件 用于设置、文件夹边界、支持输入和出错.
搜索
search() 在您还没有 URL 时找到回答问题的页面 。 同样的方法 ExpandService。 。 。 .
const { results } = await expand.search({ query: 'how do I rotate a Stripe API key', maxResults: 3 })
发送一个 query 最多2000个字符 maxResults 从 1 改为 10,默认为 10。每种结果都有: url, (中文(简体) ), title, (中文(简体) ), excerpt,以及 score最好先说 答复还有搜索的 id数字 : getSearch(id) 免费读回来,不再搜索。 搜索费1.5分 见 搜索。 。 。 .
爬行和捕捉预测
创建和运行命令重放需要调用者所持的一能键. 一次性爬行完成后永久关闭, 失败或取消; 创建一个新的爬行以再次运行 。 活动由自己的简历抵消.
重放创建密钥返回原命令的 Crawl ID, 即使一个成员已经过期 。 读取已过期成员返回404;再播放从不再创建,或开始替换运行.
爬行如何找到页面
爬行从每个源 URL 开始,并停留在该 URL 的可注册域及其子域上.
maxDepth从您的 URL 中计数.0仅是 URL.1添加网站链接的页面和网站地图中的页面.2增加这些链接的页等。 在网站映射中或搜索中发现的页数为一击,因为它们不在您的列表中 。 A 类kind: 'sitemap'源是例外: 它列出的 URL 是您的, 因此它们从深度 0 开始 。 默认的,50,是整个网站直到maxPages。 。 。 .include决定最后的结果 而不是爬行者去的地方. 达到/p/**从主页上,爬行者打开链接那里的页面,例如/财务报告和审定财务报表/damen,并遵循其链接。 它不保存这些页, 他们不计算maxPages。 。 。 .exclude让爬行者离开. 匹配的页面从未被打开,所以其后面没有通过它找到.sync.freshness是否检查每一页的更改次数,例如24h。每张支票是如何做的,由爬行者决定.
changePolicy.ignore 从变化比较中删除匹配子树; changePolicy.volatile 用稳定的标记代替它们。 这两段保存的页面内容不变。 选择器支持静态标记、类、ID、属性、组合器和结构伪类,例如 :not, (中文(简体) ), :has 财务报告和审定财务报表 :nth-child。相互作用的国家,例如: :hover,伪元素,命名命名空间前缀,嵌入 :has 财务报告和审定财务报表 :nth-child(... of ...) 拒绝。 匹配包括被捕获的阴影子树并使用对大小写敏感的类和ID值,甚至在夸克-模版页面上使用.
import { Expand } from '@expandai/sdk'
const expand = new Expand({ apiKey: process.env.EXPAND_API_KEY })
const [crawl] = await expand.crawls.create(
{ sources: ['https://example.com/products'], formats: ['markdown', 'stateJson'] },
{ idempotencyKey: 'catalog-import-2026-09' },
)
if (!crawl) throw new Error('Expected a Crawl')
for await (const event of crawl.events()) {
if (event.type !== 'page.added' && event.type !== 'page.changed') continue
const capture = expand.captures(event.captureId)
const state = event.page.stateJson ?? await capture.stateJson()
for (const evidence of state.evidence) {
const body = evidence.body !== undefined ? evidence.body : (evidence.bodyUrl ? await expand.fetchJson(evidence.bodyUrl) : undefined)
// Process body, then persist event.offset in your application.
}
}
captures(id) 还揭露 get(), (中文(简体) ), markdown(), (中文(简体) ), html(), (中文(简体) ), evidence(key) 财务报告和审定财务报表 playgroundUrl()。这些读取存储的捕获,而不是新鲜的页面。 JSON正体 URL 必须属于所配置的 API 源, 并且不能重定向 。 投影读取接受 signal 财务报告和审定财务报表 timeoutMs;它们不进行自动重试.
目录和版本列表支持单个项目、页面或等待的一页 :
const list = crawl.pages({ limit: 100, status: 'live' })
const first = await list // { items, nextCursor? }
for await (const page of list.pages()) { /* page.items */ }
for await (const item of list) { /* item.id, item.url */ }
const bounded = await list.collect({ limit: 200 })
const versions = expand.pages('page-id').versions({ limit: 20 })
const diff = await expand.pages('page-id').diff({ from: 'capture-before', to: 'capture-after' })
列表实例缓存其首页 。 停止重复为 break,或取消读取 signal。 。 。 . crawl.page(id) 财务报告和审定财务报表 crawl.pageByUrl(url) 读取当前目录投影。 页面版本列表使用 before 或者说 at 历史读取.
events({ from: '-1' }) 重放保留历史,包括更改、去除和失败。 它不重建当前目录 。 用于完整目录导入或回收 CrawlOffsetExpiredError,使用被钉住的快照:
for await (const batch of crawl.snapshot().pages()) {
// Apply batch.items, then persist batch.nextOffset.
// When batch.upToDate is true, that offset is the boundary to resume with crawl.events({ from }).
}
snapshot({ from: savedSnapshotCursor }) 恢复未完成的快照。 快照光标属于 /snapshot; 向 /events 返回 410。 事件抵消是不透明的和可分类的:准确复制而不是将其转换成数字。 一次性事件和活动传动器停在EOF溪口. 重复读取被夹时返回,除非 live: true 已经设定.
事件读取也暴露了限定的服务器页面. 等待一个事件读取返回一页, 包括空接页, 并关闭读取的页 。 继续使用其 nextOffset。事件读取时不会在不同的引用中缓存第一页.
每个非活事件请求和控制请求都尊重客户的 timeoutMs 它的复习和 maxRetries 限制。 明确 live: true 读取在瞬间失效时无限期重联,直到取消、从客户端返回或关闭。 标记服务器拒绝 retryable: false 停止任一模式. done() 民意调查直至完成或取消 pollIntervalMs 不得大于2147483647,50以下的值被夹到50ms.
爬行 API 拒绝延长 ExpandClientApiError 财务报告和审定财务报表 ExpandClientError,与 status, (中文(简体) ), body, (中文(简体) ), code,以及 retryable 可用. CrawlOffsetExpiredError 仍然是具体的410个错误。 本地验证错误延伸 ExpandClientError 结束 CrawlClientError。 。 。 .
效果用户可以调用 yield* expand.crawls.create(request, { idempotencyKey }) 结束 ExpandService,然后使用 status, (中文(简体) ), update, (中文(简体) ), cancel,或 run 返回的资源。 这些控制方法共享服务的 HTTP 客户端, 重试, 并输入 ExpandError 频道. 游行者们, done(),并抓取预测当前使用“承诺”接口;“效果”服务尚未映射这些帮助者.
因为这些读物是可以的, Promise.resolve, (中文(简体) ), Promise.all,或从一个 async 函数还开始一页读取。 活页迭代曝光出空长波超时页;项目迭代隐藏.
const batch = await crawl.events({ from: savedOffset })
if (batch) {
for (const event of batch.items) { /* process event */ }
// Persist batch.nextOffset after processing the whole batch.
}
for await (const batch of crawl.events({ live: true }).pages()) {
// batch.crawlId identifies the partition, including in merged reads.
}
合并页面读取 from: { [crawlId]: offset },就像合并项目读。 每回一批属于一个Crawl 并携带了Crawl的 nextOffset; 永远不要为不同的爬行重用.
断接取取
batched 踢出一个工作,并立即返回 id. Poll (英语) getBatched(id) 直至 batchedStatus 已经不再 RUNNING 或者说 QUEUED。 。 。 .
保证API
import { ExpandClient } from '@expandai/sdk'
const expand = new ExpandClient()
const run = await expand.batched({ urls: ['https://example.com'] })
let status = await expand.getBatched(run.id)
while (status.batchedStatus === 'RUNNING' || status.batchedStatus === 'QUEUED') {
await new Promise((resolve) => setTimeout(resolve, 1000))
status = await expand.getBatched(run.id)
}
console.log(status.results)
成功结果包括: data.markdown jSON国家 data.json 当提取成功时默认.
每一个结果都存在 status (单位:千美元)QUEUED, (中文(简体) ), RUNNING, (中文(简体) ), SUCCEEDED, (中文(简体) ), FAILED,或 CANCELLED并与其 data。 。 。 .
效力服务
import { NodeRuntime } from '@effect/platform-node'
import { Duration, Effect, Schedule } from 'effect'
import { ExpandService, type GetBatchedResponse } from '@expandai/sdk'
const program = Effect.gen(function* () {
const expand = yield* ExpandService
const run = yield* expand.batched({ urls: ['https://example.com'] })
const final = yield* expand.getBatched(run.id).pipe(
Effect.repeat(
Schedule.recurUntil(
(status: GetBatchedResponse) => status.batchedStatus !== 'RUNNING' && status.batchedStatus !== 'QUEUED',
).pipe(Schedule.addDelay(() => Duration.seconds(1))),
),
)
yield* Effect.log(final.results)
})
NodeRuntime.runMain(program.pipe(Effect.provide(ExpandService.layer({ apiKey: process.env.EXPAND_API_KEY }))))
SDK生成一个等效键,并在省略时再用于自动重试. 供应一个稳定 idempotencyKey 以复制进程重新启动后分别拨打或拨打的电话。 使用不同有效载荷的密钥返回 409 BatchedIdempotencyConflict。 。 。 .
重审什么
不是每5xx是短暂的。 其中一些是对您要求的页面的判断——浏览器自己输入了错误文件,或者该页面无法被捕获——再重复请求会重新运行一个完全的捕获以达到相同的答案,在您的超时预算和配额上.
因此服务器声明答案,而不是把它留给一个状态码规则. 地位不明的错误机构(429, (中文(简体) ), 500, (中文(简体) ), 502, (中文(简体) ), 503, (中文(简体) ), 504, (中文(简体) ), 529携带 retryable 布尔,SDK跟着它:
| 回应 | 复试 |
|---|---|
| 超时, 连接失败 | 对 |
retryable: true (例如:. 429 TooManyRequests, (中文(简体) ), 503 ServiceUnavailable, (中文(简体) ), 529 FetchCapacityTimeout(中文(简体) ) | 对 |
retryable: false (例如:. 502 FetchNavigationFailed, (中文(简体) ), 502 FetchCaptureFailed, (中文(简体) ), 504 FetchCaptureTimeout(中文(简体) ) | 没有 |
裁决标记为无 retryable 字段 - 409 BatchedIdempotencyConflict, (中文(简体) ), 409 BatchedCancellationConflict, (中文(简体) ), 502 FetchNavigationFailed, (中文(简体) ), 502 FetchCaptureFailed, (中文(简体) ), 504 FetchCaptureTimeout | 没有 |
408, (中文(简体) ), 429,其他 5xx 没有 retryable 字段 | 对 |
| 任何其他非-2xx | 没有 |
因为判决在身体中进行,所以新式错误课不需要SDK升级——这个未识别错误说 retryable: false 至今仍未重审.
当回复时 Retry-After,下一次尝试至少要等那么长的时间——重新激起,所以一批被一并拒绝的客户不会被锁住而回来. timeoutMs 仍然限制整个操作, 重复和等待包含.