dsh-tavily-firecrawl
把 DeepSeek Harness 的联网能力换成 Tavily 搜索 + Firecrawl 抓取 的组合,并支持多 API Key 轮询——一个 bundle 包,dsh plugin add 一条命令完成安装。
Tavily search + Firecrawl fetch providers for DeepSeek Harness, with a rotating multi-key pool so several free-tier accounts add up to a usable quota.
web_search → Tavily(POST /search)
web_fetch → Firecrawl(POST /v1/scrape,返回 markdown 正文)
- 自动禁用内置的 DeepSeek 原生搜索(
web-search-deepseek),无需 DEEPSEEK_API_KEY
- 多 Key 轮询:一个请求被某个 key 拒绝(额度、限流、失效)时自动换下一个 key 继续,而不是让整次工具调用失败
- 纯 JavaScript ESM,无需编译;peer 依赖从 dsh 安装自带的 fallback node_modules 解析,接收者不需要额外安装依赖
安装
前置:已安装 dsh CLI(≥ 0.1.0-rc.5)和 pnpm(dsh plugin 需要)。
方式 A:官方 bundle 机制(推荐)
dsh plugin --profile web add dsh-tavily-firecrawl # 从 npm
# 或锁定 GitHub tag:
dsh plugin --profile web add github:skillre/dsh-tavily-firecrawl#v0.2.0
方式 B:本地 tgz
npm pack # 生成 dsh-tavily-firecrawl-0.2.0.tgz
dsh plugin --profile web add ./dsh-tavily-firecrawl-0.2.0.tgz
方式 C:./install.sh(无需 pnpm / dsh CLI)
./install.sh # 装进 $DSH_HOME/profiles/web
./install.sh --enable-fetch-default # 同时把 standard-web 预设设为新会话默认
脚本幂等:复制 bundle 到 profile vendor/ → 建 node_modules 符号链接 → 合并 wiring patch(带起止标记)→ 复制 standard-web 预设 → 生成 .env 模板。卸载用 ./uninstall.sh [--profile <名字>] [--keep-preset]。
方式 D:--patch 临时叠加(不落盘、可随时去掉)
dsh web --patch ./tavily-firecrawl.patch.yml
装完填 Key、重启 dsh,新会话即生效:
# $DSH_HOME/.env
TAVILY_API_KEY=tvly-xxxx
FIRECRAWL_API_KEY=fc-xxxx
dsh web
多 Key 轮询(v0.2.0)
免费版额度很小,注册多个账号后把 Key 列出来即可,插件会自动轮询:
# 逗号、分号、空白(含换行)都支持
TAVILY_API_KEYS=tvly-aaaa,tvly-bbbb,tvly-cccc
FIRECRAWL_API_KEYS=fc-aaaa;fc-bbbb
或在组合配置里显式列出(优先级更高):
- id: web-tavily-firecrawl
config:
search:
apiKeys: ['tvly-aaaa', 'tvly-bbbb']
fetch:
apiKeys: ['fc-aaaa', 'fc-bbbb']
轮询与失败处理
| 情况 | 行为 |
|---|
| 正常调用 | 在可用 Key 之间轮流使用(不是永远打第一个);一次 web_search 里的多个并发查询会落在不同 Key 上 |
| HTTP 401(Key 失效) | 该 Key 本次进程内永久剔除,剩余 Key 继续 |
| HTTP 429(限流) | 该 Key 冷却 60s(rateLimitCooldownMs),其余 Key 继续 |
| HTTP 402/403/432/433(额度/套餐) | 该 Key 冷却并从 30 分钟起指数退避(quotaCooldownMs,上限 24h quotaCooldownMaxMs) |
| HTTP 5xx | 换下一个 Key 重试,但不记该 Key 的问题 |
| HTTP 400 等请求级错误 | 与 Key 无关,直接报错,不再消耗其余 Key |
| 所有 Key 都不可用 | 明确报出每个 Key 的状态与预计恢复时间,而不是静默失败 |
一次调用最多尝试多少个 Key 由 maxAttempts 控制(默认 = Key 数量)。冷却状态保存在内存里,重启进程即清空。
Key 解析顺序(每侧独立):apiKeys(配置)→ apiKey(配置)→ *_API_KEYS(环境)→ *_API_KEY(环境)。
环境变量的三个层级:启动进程的环境 → <调用目录>/.env → $DSH_HOME/.env,前者优先。
⚠️ 凭据在插件加载时读取一次(启动环境快照),改 Key / 改配置后必须重启 dsh。
配置
patch 行的 config: 里覆盖(全部可选):
- insert:
- id: web-tavily-firecrawl
name: 'dsh-tavily-firecrawl'
config:
search: # Tavily 侧
apiKeys: [tvly-aaaa, tvly-bbbb] # 多 Key 轮询(推荐)
apiKey: tvly-xxx # 单 Key(旧写法,仍兼容)
searchDepth: basic # basic(1 credit)| advanced(2 credits)
includeAnswer: true # 让 Tavily 生成摘要(成为结果的 answer)
maxResults: 8 # 无 maxResults 时的默认条数
timeoutMs: 30000 # 每次尝试的超时
maxAttempts: 3 # 一次调用最多试几个 Key
rateLimitCooldownMs: 60000 # 429 后的冷却
quotaCooldownMs: 1800000 # 额度类错误的首次冷却
quotaCooldownMaxMs: 86400000
baseURL: https://api.tavily.com
fetch: # Firecrawl 侧(同样支持 apiKeys / 冷却参数)
timeoutMs: 30000
maxBodyChars: 200000 # 正文上限,超出截断并标记
onlyMainContent: true
baseURL: https://api.firecrawl.dev
searchEnabled: true # false 则不注册搜索提供方
fetchEnabled: true # false 则不注册抓取提供方
Credit 成本:Tavily basic 每次搜索 1 credit、advanced 2 credits(本包默认 basic);想更深检索就在上面的 searchDepth 改 advanced。多 Key 轮询下建议先跑 basic。
web_fetch 工具与预设
web_search / web_fetch 这两个工具由 agent 预设里的 tool-web.fetch 决定,不是由本插件决定;本插件只负责提供 provider。
- 当前 dsh(0.1.5 起)自带的
标准模式 预设已经打开 fetch: true,装完直接可用;
- 较老的 dsh(约 0.1.0-rc.5)自带预设没有开 fetch,此时两种做法:
- 设置 → 通用 → agent 预设 里选 standard-web(本包附带,= 官方 standard +
fetch: true)。npm 安装不会自动把它放进 $DSH_HOME/.agent-presets/,需手动复制:
cp -R node_modules/dsh-tavily-firecrawl/presets/standard-web "$DSH_HOME/.agent-presets/"
(用方式 C 安装的由 install.sh 自动完成)
- 或把它设为新会话默认:
dsh web --patch ./enable-web-fetch-default.patch.yml(要求 standard-web 预设确实存在)
验证
node --test "test/**/*.test.mjs" # 45 个单元/集成测试,本地 mock,不联网
node tools/live-smoke.mjs --env ~/.dsh/.env # 用真实 Key 跑一次(只打印脱敏信息)
或直接让 agent 试:「搜一下 <关键词>」→ 走 Tavily;「抓取 https://example.com」→ 走 Firecrawl。
常见问题
- 搜索报
HTTP 432? 该 Tavily 账号超了套餐额度,报错消息里会直接带 Tavily 的原文(如 This request exceeds your plan's set usage limit)。加更多 Key 用 TAVILY_API_KEYS 轮询,或升级套餐。
- 搜索报
has no usable API key? 所有 Key 都在冷却中或已失效,消息里会给出每个 Key 的状态和下次可用时间;重启可清空冷却状态。
- 改完配置不生效? 插件与 patch 在启动时加载,必须重启 dsh 进程。
web_fetch 工具不存在? 见上一节——工具注册由预设的 tool-web.fetch 控制。
- 想换回 DeepSeek 搜索?
dsh plugin remove dsh-tavily-firecrawl 后,把 profile 的 cordis.patch.yml 里标记块删掉(或跑 ./uninstall.sh)。
- patch 优先级? bundle 层 < profile
cordis.patch.yml < $DSH_HOME/cordis.patch.yml < --patch 叠加层,后层覆盖前层。
开发者
pnpm test # node --test(无需网络)
npm run smoke # 真实 API 冒烟(需要 Key)
目录:lib/key-pool.js(轮询与冷却)、lib/search.js / lib/fetch.js(两个 provider)、lib/index.js(配置与注册)、cordis.patch.yml(bundle 补丁)、presets/(可选的 standard-web 预设)、tools/live-smoke.mjs。
发布:
git tag v0.2.0 && git push origin main --tags
npm publish # package.json 已无 private;确保包名全局唯一
兼容性
- macOS / Linux(Windows 建议 WSL 或用方式 B/C)
- 需要带 web seam(
ctx.web)的 DSH 版本(官方 dsh-base + dsh-web-app 组合均满足)
- Node ≥ 20(用到
AbortSignal.any / AbortSignal.timeout)
更新日志
- 0.2.0(当前)
- 新增多 Key 轮询:
apiKeys / TAVILY_API_KEYS / FIRECRAWL_API_KEYS,失败分类(失效 / 限流 / 额度)与冷却退避
- 修复错误消息映射:Tavily 把原因放在
detail.error,之前只读 error 导致只剩一个 HTTP 状态码
- 默认
searchDepth 由 advanced 改为 basic(1 credit 而非 2)
- Firecrawl:API 自身的错误(401/402/429…)不再伪装成"空正文的成功结果",改为带原因的
WEB_PROVIDER_ERROR
- 新增
search.timeoutMs;README 纠正"改 Key 无需重启"的错误说法
- 0.1.0:首个版本,Tavily 搜索 + Firecrawl 抓取
License
MIT