MochiNek0/dsh-web-search-free
面向 DeepSeek Harness 的多引擎免费 Web Search 插件,支持 Tavily / Firecrawl / Exa / Jina / Brave 自动 fallback
项目介绍Project Overview
DSH 插件 dsh-web-search-free 把默认 deepseek-official 搜索/抓取通道替换为多引擎加自动 fallback 通道,宿主进程直连 Tavily、Exa、Jina、Firecrawl、Brave 等专用检索端点,不经 LLM、不烧 token,适合第三方 provider 场景或追求零模型计费的用户;卡片里可拖动排序、逐引擎填多 Key 轮换、开关 web_fetch。注意:Brave 无抓取能力,单独配置会导致抓取链空而报错;卸载前需点「清空全部配置」并重启 dsh。
The dsh-web-search-free DSH plugin replaces the default deepseek-official search/fetch channel with a multi-engine, auto-fallback channel. The host process calls dedicated retrieval endpoints (Tavily, Exa, Jina, Firecrawl, Brave) directly, bypassing LLM calls and token costs. Use it when running third-party providers or avoiding per-search model billing. The settings card supports reordering, multiple keys per engine, and toggling web_fetch. Caveats: Brave lacks fetching, so configure at least one fetch-capable engine; clear settings and restart dsh after uninstall.
请帮我了解并安装插件:【dsh-web-search-free】【https://github.com/MochiNek0/dsh-web-search-free】
把上面这条消息直接发给当前会话里的 DSH,让它帮你了解并安装。安装命令不一定准确,发给 DSH 更稳。Send this message to DSH in your current session. CLI install commands may not be accurate across systems — DSH will figure it out for you.
或使用命令行安装(适合开发者)Or use CLI install (for developers)
命令行安装CLI Install
dsh plugin --profile web add dsh-web-search-free
把 MochiNek0/dsh-web-search-free 加入你的 DSH 配置(web profile)即可启用。
READMEREADME
dsh-web-search-free
中文 | English
面向 DeepSeek Harness (dsh) 的免费 Web Search / 网页抓取插件。
它把 dsh 默认的 deepseek-official 搜索/抓取通道,替换成一个多引擎 + 自动 fallback 的通道:你填入哪些引擎的 API Key,它就按你排定的顺序依次尝试,前一个失败(或额度耗尽)会自动落到下一个;同一引擎也可以填多个 Key(每行一个),引擎内 Key 同样按顺序轮换。所有检索请求都由 dsh 的**宿主进程(Node)**直接发往各引擎,不经过官方搜索后端、也不经过任何 LLM;浏览器侧只有那张设置卡片,不发任何网络请求。
- 注册为 dsh 的
web能力通道(同时提供searchProvider与fetchProvider,id 均为web-search-free)。 - 自带一个 Web 设置卡片(设置 → 插件 → Web Search Free),可拖动排序、逐个填 Key。
- 作为 dsh bundle 层安装:装上即接管 web 搜索/抓取,卸载(并重启 dsh)后回落到默认通道,无需手改 profile。
- 可在卡片里开关模型侧的
web_fetch工具——开关即挂载/卸载该工具,不是留着它报错。
为什么是 free 的:与官方通道的计费差异
dsh 默认的官方通道 deepseek-official(由 @deepseek-ai/dsh-web-search-deepseek 提供)不是一个专用搜索端点:每次搜索都会发起一次完整的 Messages 模型调用(POST …/anthropic/v1/messages,带原生 web_search 服务端工具),由 DeepSeek 在服务端执行搜索、返回结构化结果块。因此每次搜索都要烧两份 token:
- 辅助搜索请求本身——一个独立模型收到
Perform a web search for the query: <query>+ 工具定义,产生 input + output token(maxTokens默认 4096、maxUses默认 5);这是一次完整的模型轮次,延迟与生成都按模型调用计。 - 结果回灌对话——解析出的 sources(URL/标题/片段)注入回对话模型上下文,作为对话 token 一直重发直到压缩。
两份都从 DEEPSEEK_API_KEY 余额扣除。
本插件走各引擎的专用检索端点(如 Tavily /search、Exa /search、Jina s.jina.ai),是纯检索,不经任何 LLM:
官方 deepseek-official |
本插件 web-search-free |
|
|---|---|---|
| 检索方式 | 一次完整 LLM 模型调用 + 服务端搜索工具 | 直接调各引擎专用检索端点 |
| 模型 token | 每次搜索都烧(input + output) | 0(纯检索,不碰任何 LLM) |
| 计费来源 | DeepSeek API 余额 | 各搜索 API 自身额度(多数有免费层) |
| 凭据 | 必须 DEEPSEEK_API_KEY |
各引擎各自的 API Key |
| 结果内容 | 只有 sources;snippet 取自模型引用的片段,未被引用的结果没有 snippet | sources + snippet;Tavily 另给一段直接回答 |
这正是插件名为 "free" 的核心理由——官方每次搜索烧一整轮模型 token,这里只走检索、不烧 token。
还有一个容易踩的坑:官方通道强制依赖
DEEPSEEK_API_KEY。如果你的对话模型走的是第三方渠道(自建 provider、中转站等),你很可能根本没配这个 key——而官方 provider 的available()只检查"有没有 key 解析器"(永远有),所以 dsh 会照常选中它,直到模型真的调用web_search才抛WEB_PROVIDER_CREDENTIAL_MISSING,UI 上看不出问题。本插件不依赖任何 LLM 凭据。
支持的引擎
| 引擎 | 搜索 | 抓取 | 结果日期 | 获取 API Key |
|---|---|---|---|---|
| Jina AI | ✓ | ✓ | 部分 | https://jina.ai/api-key |
| Exa (Metaphor) | ✓ | ✓ | 部分 | https://dashboard.exa.ai/ |
| Tavily | ✓ | ✓ | ✗ | https://app.tavily.com/ |
| Firecrawl | ✓ | ✓ | ✗ | https://www.firecrawl.dev/ |
| Brave Search | ✓ | ✗ | 多数 | https://api-dashboard.search.brave.com/register |
关于抓取:Brave Search API 没有 URL 抓取端点,所以它不会进入抓取链(只在搜索链里)。如果只有 Brave 配了 Key,抓取链为空、会以 No web fetch providers configured. 报错——请再给一个支持抓取的引擎配上 Key。
关于结果日期:publishedAt 决定模型能否判断一条结果的时效性,各引擎差别很大(下面是单次查询的实测覆盖率,仅供参考):Brave page_age 18/20、Exa publishedDate 4/10、Jina publishedTime 1~3/10;Tavily 的 published_date 仅在 topic: 'news' 下返回,本插件走通用网页搜索,因此实际为空;Firecrawl 的搜索结果只有 url/title/description,没有日期字段。
如果时效性判断对你重要,可以把 Brave 往调用顺序前面挪——代价是丢掉 Tavily 的直接回答段和较长的摘录。
前置条件
- 已安装 dsh,且
dsh命令可用(在 dsh 源码检出里开发时用pnpm dsh ...代替)。 pnpm在PATH上(dsh plugin通过 pnpm 在 profile 目录里管理依赖)。- 目标 profile 一般是
web(本插件的客户端半边声明platform: web,设置卡片只在 Web 界面出现)。webprofile 首次使用时会从模板自动初始化。
安装
dsh plugin --profile <name> <pnpm args> 会把 pnpm 参数转发到 profile 目录里执行,执行成功后自动对账 dsh.profile.bundles:凡解析到声明了 dsh.bundle 的依赖,都会被自动追加进 bundle 层栈——本插件正好声明了 dsh.bundle.patch,所以装上即接管 web 搜索/抓取,无需手改 profile。
从 npm 安装(推荐)
dsh plugin --profile web add dsh-web-search-free
从本地源码安装(开发 / 二次开发用)
本仓库 dist/ 被 gitignore,安装前需要先构建出产物。
# 1) 在插件仓库目录里构建
cd /path/to/dsh-web-search-free
pnpm install
pnpm build # 生成 dist/index.js 与 dist/client.js
# 2) 装进 web profile(在插件目录里执行,"." 会被锚定到当前目录)
dsh plugin --profile web add .
也可以用绝对路径,从任意目录执行:
dsh plugin --profile web add /absolute/path/to/dsh-web-search-freepnpm 对本地目录默认以链接方式安装,所以之后在源码里重新
pnpm build,profile 会即时拿到新产物,适合二次开发。改完客户端半边后刷新浏览器即可(客户端插件的热更新需要pnpm run dev:web这类重建监听在跑)。
配置
安装后启动 dsh Web 界面:
dsh web # 等价于 dsh --profile web
打开 设置 → 插件 → Web Search Free 卡片:
- 点开卡片,每一行对应一个引擎。
- 在对应引擎的输入框粘贴 API Key,点行内「获取 API Key ↗」可直达各引擎的申请页。每个引擎支持填多个 Key:每行一个,引擎内会按行顺序轮换。
- 拖动每一行调整调用顺序:排在前面的引擎优先调用,失败则按顺序 fallback;同一引擎的多个 Key 也会逐个尝试,任一 (引擎, Key) 成功即返回,全部失败才报错。未填 Key 的引擎不进入调用链。
- 引擎列表上方有 「启用 web_fetch(URL 抓取)」 开关。开启时模型多一个
web_fetch工具,可以对指定 URL 取全文;关闭时该工具会从模型的工具表里移除(不是留着报错),只保留搜索。切换即时生效,无需重启。 - 点「保存」。配置通过 dsh 的设置命名空间(
web-search-free)持久化,下一次搜索即时生效,无需重启。
至少配置一个引擎的 Key,否则搜索/抓取会以 No web search providers configured. 报错。
关于
web_fetch:dsh 官方组合默认把它关掉(模型自选请求目标,抓取 provider 不做 SSRF 防护)。本插件把这个选择交给你,默认开启。若你在意出网面,关掉即可——代价是模型无法读取你贴给它的 URL,也无法精读长文档,只能靠搜索摘要。
验证
dsh web启动 Web 界面。- 在对话里让模型用 Web 搜索/抓取(例如「搜一下今天的新闻」或「抓取 https://example.com 的内容」)。抓取需要卡片里的 「启用 web_fetch」 处于开启状态,否则模型的工具表里没有这个工具。
- 请求会经
web-search-free通道按你排定的引擎顺序执行;某个引擎失败时会在日志里看到Provider <name> ... failed. Trying next provider ...,随后自动尝试下一个。
更新
# 本地源码:重新构建即可(链接安装,无需重装)
cd /path/to/dsh-web-search-free && pnpm build
# npm:升级到新版本
dsh plugin --profile web update dsh-web-search-free
update 同样会触发对账:新版本若新增/移除了 dsh.bundle 声明,bundle 层栈会自动跟进。
卸载
dsh plugin --profile web remove dsh-web-search-free
对账会把本插件从 dsh.profile.bundles 移除,web 搜索/抓取回落到 dsh-base 的 deepseek-official 默认通道——无需手改 profile 或 patch。
两点需要注意:
- 卸载前先点卡片底部的「清空全部配置」。dsh 的卸载流程不会删除设置命名空间里存的东西,你的 API Key 会留在
$DSH_HOME/settings.yaml里。这个按钮会把本插件写入的所有值清掉(需点两次确认)。清空后文件里会剩一个空的web-search-free:键——不含任何值,客户端没有 API 能删掉键本身。 - 卸载后需要重启 dsh 才能真正回落到官方通道。
web行的 provider 选择是启动时组合出来的,卸载只会让本插件的条目在当前进程里失效;重启前搜索会报WEB_PROVIDER_CONFIGURED_MISSING。
工作原理
本插件是一个 dsh bundle 层(package.json 里声明 dsh.bundle.patch: ./cordis.patch.yml)。cordis.patch.yml 做两件事:
insert一行web-search-free,把本插件的宿主半边纳入组合;- 用一条同 id 的
web覆盖层,把searchProvider与fetchProvider都重指到web-search-free,从而盖过dsh-base里钉死的deepseek-official。
宿主半边(src/index.ts,inject: ['web'])向 ctx.web 注册搜索与抓取 provider,内部按 providerOrder 顺序遍历「已配 Key」的引擎做 fallback;每个引擎的 Key 字段可填多个(每行一个),引擎内也会按行顺序逐个轮换。客户端半边(src/client.tsx)在「插件」设置页注册一张 React 卡片,读写同一命名空间 web-search-free 的设置。两层靠这个命名空间字符串对齐。
web_fetch 的挂载由插件自己负责,而不是靠 bundle patch。原因是 tool-web 的工具可见性在挂载时就定了——它的文档写明「Enablement controls tool registration; an enabled tool remains visible when its provider is unavailable」,所以一个只被能力通道读取的开关只能让 web_fetch 报错、不能让它消失。而 bundle patch 层只在启动时读一次、不热更。因此宿主半边用 createRequire(ctx.baseUrl) 从 profile 目录解析出运行中 dsh 自己那一份 @deepseek-ai/dsh-tool-web,以 { search: false, fetch: true } 挂成子 fiber:ctx.plugin 注册进所有 agent 作用域都继承的全局工具层,dispose 时 tool-web 自身的 effect 会把 web_fetch 和它的 prompt section 一并撤掉。search: false 是为了不去碰 web_search 这个名字——组合里已有的所有者(Web 界面上是 agent preset 的作用域行)保持唯一。
解析或挂载失败一律降级为一条 warn,不抛出、不产生 unhandled rejection,搜索链路不受影响。
构建分两步(包声明 "type": "module"):tsconfig.json(module: NodeNext)把宿主半边编成 ESM 产物(dist/index.js 等,与 dsh 运行时同为 ESM,避免 CJS require() 一个 ESM 依赖时触发的加载竞态);tsconfig.client.json(module: CommonJS)单独编出 dist/client.js,再由 wrap-client.cjs 包成 window.__ModuleLoader__.load(...),使其能被 dsh 的浏览器侧模块加载器加载。宿主半边从 @deepseek-ai/schemastery 取 Schema(而非旧版 cordis),Context 仅作类型从 @deepseek-ai/cordis 引入。
插件是装在 profile 旁边的,所有宿主服务必须解析到运行中 dsh 的那一份实例。任何 @deepseek-ai/* 一旦进了 dependencies(或进了非 optional 的 peerDependencies——pnpm 会自动装它),就会在用户的 profile 里多出一份私有副本;hoisted 布局下它平铺到 profile 根目录,遮蔽宿主自己那份,Cordis Service 身份不再相等。这类问题只在别人机器上出现,本地 pnpm build 永远看不见,所以 prepublishOnly 会跑 scripts/check-package.cjs 把它挡在发布之前:禁止宿主包进 dependencies、要求宿主 peer 标 optional、核对 dist 的实际 import 与声明的依赖双向一致、校验 exports 条件顺序与客户端 bundle 是否已包装。改动 package.json 后请跑 pnpm run check。
许可证
MIT,见 LICENSE。
ruvnet/ruflo
amruthpillai/reactive-resume
volcengine/OpenViking
Molunerfinn/PicGo
titanwings/colleague-skill
nocobase/nocobase
Tencent/WeKnora