← 返回列表
未验证
一个原生 DeepSeek Harnessdsh插件,将 harness 内置的单提供商 websearch…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/25 · 已提供中文文档
综合分
27.4
GitHub 分
27.4
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add takasurazeem/websearch-dsh该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/19(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-credentials@deepseek-ai/dsh-settings@deepseek-ai/dsh-tools@deepseek-ai/schemastery用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
websearch-dsh
一个原生 DeepSeek Harness(dsh)插件,将 harness 内置的单提供商 web_search 后端替换为具备弹性的多提供商回退链——因此 web_search 在没有 DEEPSEEK_API_KEY 的情况下也能工作,能够承受单个提供商的速率限制 / 配额耗尽 / 服务中断,并且——当完全没有配置任何密钥时——仍可通过无需密钥的 DuckDuckGo 作答。
MIT 许可。没有 MCP 服务器,没有 sidecar 进程:它注册在 harness 的 web 接缝上,内置的 web_search 工具仍是模型面向的唯一工具。姊妹插件:gmail-dsh。
目录
- 你将获得什么
- 链式回退如何工作
- 开/关切换
- 设置
- 提供商参考
- 故障分类
- DuckDuckGo 环节
- 验证
- 配置参考
- 开发插件
- 常见问题
- 故障排查
- 还原 / 卸载
- 许可证
你将获得什么
| 界面 | 作用 |
| --- | --- |
| web_search 工具 | 从 harness 的视角看保持不变——同一个工具、同一个模型,现在由回退链提供服务。 |
| websearch_status 工具 | 只读:哪些提供商有密钥、当前生效的链、最近提供服务的提供商、各提供商近期故障、切换状态。 |
| /websearch status 命令 | 相同信息,以适合人类阅读的方式在聊天中呈现。 |
| 模型上下文段落 | 一段稳定的文字(在需要之前不消耗任何 token):什么为 web_search 提供服务、按什么顺序,以及用户是否已启用它。每次渲染时重新读取实时状态。 |
| GUI 切换卡片 | dsh Web 客户端中的 Settings → Plugins → Configurable → Web Search:一键切换,无需重启。 |
| Bundle 补丁层 | 将 harness web 行的 searchProvider 指向此插件,因此即使完全没有 DeepSeek 密钥,搜索也能工作。 |
链式回退如何工作
web_search "…"
│
├─ 切换已关闭? ──▶ 立即拒绝(不调用任何提供商,不消耗额度)
│
├─ 每次调用时解析密钥 ──▶ 优先凭据存储引用,然后是进程环境变量
│
├─ Tavily ──失败──▶ Exa ──失败──▶ Firecrawl ──失败──▶ Brave ──失败──▶ Serper
│ (只有具有非空密钥的提供商才在链中;按优先级顺序)
│
└─ DuckDuckGo(始终最后,无需密钥)
└─ 按顺序 3 条路径:d.js JSON → lite HTML → Instant API
关键特性:
- 每次搜索时都会解析密钥——优先来自 DSH 凭据存储(Web Credentials 界面写入的引用),然后是 process.env。启动后添加到 ~/.dsh/.env 或在 Credentials 界面中存储的密钥会在下一次搜索时被采用,无需重启。插件从不存储密钥。
- 任何失败都会继续向下传递。 认证、配额、速率限制、超时、网络和解析失败都会将调用链推进到下一个提供商。只有调用方中止(模型/框架取消搜索)才会提前终止遍历。
- 仅当所有环节都失败时,才会报告一个合并错误,并列出每个提供商及其失败原因:All web search providers failed for "…": tavily: auth — …; serper: quota — …; duckduckgo: network — …。
- 来源标注(默认 on-fallback):当由无密钥的 DuckDuckGo 环节提供服务时,结果会附带一条模型可见的说明——例如 Served by DuckDuckGo (keyless fallback) after tavily (rate), exa (auth) failed. always 会为每个提供商都添加标注;off 则保持静默。
- 钳制:maxResults 会被钳制到 1–50(默认 10);空查询会在任何网络 I/O 之前被拒绝。
开/关切换
该插件通过实时监视的 settings 服务,在框架的设置文档(~/.dsh/settings.yaml)中注册一个 websearch-dsh 命名空间:
created on first GUI click (or by hand):
websearch-dsh:
enabled: false
- 默认开启(注册的 base 层为 {enabled: true};缺少该节即表示启用)。
- 搜索链在每次调用时都会检查该标志。 关闭 → web_search 会在联系任何提供商之前,以对模型友好的消息拒绝(“disabled by the user … do not call web_search until it is re-enabled”)——因此关闭期间不会消耗任何 API 额度。websearch_status、/websearch 命令以及模型上下文部分都会反映该状态。
- GUI 卡片(一个以 client.js 形式提供的纯 JS 包——无需构建步骤)渲染在 Settings → Plugins → Configurable 上。点击一次复选框会对 enabled 执行一次带修订隔离的写入;提交的更改由设置服务发布,客户端镜像将其并入,服务器端会在下一次调用时看到它。永远无需重启。
- 外部编辑同样有效:设置文件会被监视,因此手动编辑 ~/.dsh/settings.yaml 会实时生效。
- 仅限回环:dsh 的设置传输服务于本地浏览器。远程浏览器看到的卡片是只读的(该标志在机器上仍然是全局的)。
- 容错:没有设置服务的组合就没有切换开关;搜索保持启用。该插件从不硬依赖它。
设置
1. API 密钥(任意子集——全部可选)
dsh 在启动时加载两个环境变量层:项目 /.env 和用户层 ~/.dsh/.env(当项目目录就是你的主目录时跳过)。.env 中的值只会填充尚未在启动 shell 中导出的名称——shell 导出优先。带引号或不带引号的值解析方式相同(Node 内置的环境变量解析器);# 开始注释。
~/.dsh/.env (chmod 600 recommended)
TAVILY_API_KEY="tvly-…" # best all-round; generous free tier
EXA_API_KEY="…" # neural/keyword search
FIRECRAWL_API_KEY="fc-…" # search + scrape
BRAVE_API_KEY="BSA…" # Brave Search API
SERPER_API_KEY="…" # Google SERP 数据
或者将它们以相同的名称存储在 dsh Web GUI 的 Credentials 界面中——凭据引用会先于环境变量被检查,并在每次搜索时重新读取。
如果未配置任何密钥,一切都将通过 DuckDuckGo 工作(参见 The DuckDuckGo leg)。
2. 将其接入你的 profile
在你的 profile 的 package.json 中(例如 ~/.dsh/profiles/web/package.json):
{
"dependencies": {
"websearch-dsh": "file:/path/to/websearch-dsh"
},
"dsh": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"websearch-dsh"
]
}
}
然后在 profile 目录中运行 pnpm install。对于实时开发,请使用符号链接指向检出目录,而不是复制:
ln -sfn /path/to/websearch-dsh node_modules/websearch-dsh
该插件的 bundle 补丁层(cordis.patch.yml)会完成其余工作:
- 插入一行 search-multi,携带部署开关(超时、来源、上下文),并且
- 重新指向 web 行:searchProvider: websearch-dsh。
它有意只携带部署开关——绝不携带密钥名称或优先级,这些保留在代码中,以免过时。
3. 重启 dsh
dsh web
组合配置需要重启一次。此后:密钥、开关以及所有其他运行时更改都会实时生效。
Provider 参考
| # | Provider | Endpoint | Auth | 免费额度(撰写时) | 备注 |
| --- | --- | --- | --- | --- | --- |
| 1 | Tavily | POST api.tavily.com/search | Authorization: Bearer(也接受旧版 api_key) | 约 1,000 积分/月 | 适合研究类查询;使用 search_depth: advanced;结果包含摘要 + 相关性评分。 |
| 2 | Exa | POST api.exa.ai/search | x-api-key 请求头 | 约 1,000 积分/月 | 神经/关键词/自动搜索;将 text 映射为摘要,携带 publishedDate。 |
| 3 | Firecrawl | POST api.firecrawl.dev/v1/search | Authorization: Bearer | 约 500 积分 | 也是一个抓取 API;带内 success:false 响应会被分类(配额文本 → quota)。 |
| 4 | Brave Search | GET api.search.brave.com/res/v1/web/search | X-Subscription-Token 请求头 | 约 2,000 次查询/月 | 使用查询字符串的 GET;映射 web.results 块。 |
| 5 | Serper | POST google.serper.dev/search | X-API-KEY 请求头 | 约 2,500 积分 | Google SERP 数据;映射 organic(跳过广告/答案)。 |
| 6 | DuckDuckGo | 3 条路径,见下文 | 无 | 无限(受速率限制) | 无密钥的最后手段;在并发搜索之间串行化。 |
每个 provider 模块(src/providers/.js)都是 (query, {key, maxResults, timeoutMs, legTimeoutMs, signal, fetch}) → {sources, truncated} 的纯函数——可轻松替换或扩展。要添加一个 provider:实现它,将其加入 src/providers/index.js,并在 src/config.js 中扩展 DEFAULT_PRIORITY/DEFAULT_ENV_KEYS。
失败分类
src/http.js 会对每个失败进行分类,以便调用链(以及状态工具)能够报告原因:
| 代码 | 触发条件 | 链行为 |
| --- | --- | --- |
| auth | 401 / 403(无配额文本) | 跳过该 provider,尝试下一个 |
| quota | 402,或 403/429 且带有类似配额的文本("quota"、"credits"、"exhausted"、……) | 跳过该 provider,尝试下一个 |
| rate | 429(裸) | 跳过该 provider,尝试下一个 |
| timeout | 超出每个 provider 的预算(默认 10 秒;DDG 各分支也各自上限 8 秒) | 跳过该 provider,尝试下一个 |
| network | fetch 被拒绝(DNS、连接重置、TLS) | 跳过该 provider,尝试下一个 |
| parse | 2xx 但响应体不是预期结构 | 跳过该 provider,尝试下一个 |
| aborted | 调用方的信号被中止 | 停止整个遍历并重新抛出 |
失败会按 provider 记录在 websearch_status 中(最近的在前),并在全部失败时组合错误中列出名称。
DuckDuckGo 分支
每次搜索按顺序进行三次尝试(某个供应商的 API 已在脚下发生变化——npm 的 duckduckgo-search 包针对当前端点已损坏,因此本插件自带一个固定的、无依赖的移植版本):
1. d.js —— 从 HTML 搜索页面获取 vqd 令牌,然后查询 links.duckduckgo.com/d.js 获取 JSON 结果集(去重,去除 tag/entity)。
2. Lite HTML —— lite.duckduckgo.com/lite 结果页面;结果链接经过 uddg 解析并解码。
3. Instant API —— api.duckduckgo.com/?q=…&format=json(标题 + 第一段 + 相关主题)。
并发在多次搜索之间是串行化的(同一时间只有一个分支在途),作为一种礼貌措施。当前状态(2026-08):正常服务;在某些网络/IP 下,DuckDuckGo 会间歇性地返回反机器人 202 挑战而不是结果——这三个分支通常能通过,而当挑战确实发生时,你会得到明确命名该挑战的组合错误,而不是挂起。
验证
| 检查 | 应观察到的内容 |
| --- | --- |
| 在聊天中使用 web_search,未设置 DEEPSEEK_API_KEY | 正常工作:先使用带密钥的 provider,DDG 最后。 |
| websearch_status 工具 / /websearch status | Web search: enabled.(或 DISABLED)、实时链、每个 provider 的密钥存在情况、最近服务的 provider、最近的失败。 |
| Web GUI 中的 Settings → Plugins → Configurable | 带有开/关复选框的 Web Search 卡片。 |
| 点击后的 ~/.dsh/settings.yaml | 出现 websearch-dsh: { enabled: … } 部分。 |
| 在插件目录中运行 node test.mjs | 66/66(一个网络测试在离线时自行跳过)。 |
配置参考
所有字段均为可选;默认值如下所示。(这些是 bundle 补丁所插入的 search-multi 行的字段;完整的自定义组合也可以在任何插件行上设置它们。)
| 字段 | 默认值 | 含义 |
| --- | --- | --- |
| envKeys | { TAVILY_API_KEY, EXA_API_KEY, FIRECRAWL_API_KEY, BRAVE_API_KEY, SERPER_API_KEY } | 每个提供商的 Env/凭据名称。 |
| priority | [tavily, exa, firecrawl, brave, serper] | 链式顺序;未知 id 会被丢弃,顺序保持不变。 |
| providerTimeoutMs | 10000 | 每次提供商调用的总体预算(每个 DDG 分支也限制在 8 秒)。 |
| provenance | on-fallback | off — 从不标注 · on-fallback — 当无密钥 DDG 提供服务时标注 · always — 标注每个提供服务的提供商。 |
| context.enabled | true | 是否完全输出模型上下文部分。 |
| context.order | 118 | 在模型上下文部分中的排序。 |
开发插件
node test.mjs # 离线测试套件,纯 Node,无框架
该测试套件涵盖:配置规范化/~standard 验证、HTTP + 分类层、每次调用的密钥解析、每个提供商的请求结构和响应映射(伪造的 fetch 路由)、回退链(逐级下探、来源标注、钳制、中止语义、DDG 序列化、实时密钥变更、开关)、状态工具,以及设置/客户端契约(伪造的 settings 服务 + 一个伪造的 window.__ModuleLoader__ 测试装置,用于运行真实的 client.js)。请从可解析 dsh 包的检出目录(例如你实时符号链接的 profile 的 node_modules)运行它,以便针对真实的 dsh-tools/dsh-credentials/dsh-settings/schemastery 运行完整变体;从裸克隆运行时它会优雅降级,并且仍会测试所有不依赖这些包的内容。
使用临时 DSH_HOME 进行组合检查:
dsh --profile --dump-config # 预期出现 websearch-dsh 行,无错误
常见问题
为什么用一个复合提供商而不是五个? 测试装置的 web 接缝在构造时只选择一个 searchProvider(dsh-web 将其固定——不存在运行时重新选择的接缝)。单一复合提供商是实现故障转移并将内置 web_search 工具保留为唯一面向模型表面的唯一方式。
为什么不用 MCP 服务器? 原生插件跳过了边车进程及其自身的生命周期:它直接注册在测试装置接缝上(工具、设置、凭据、系统提示),因此开/关切换、审批语义和实时配置的行为与第一方功能一致。
它在 Web GUI 之外能工作吗? 能——链、工具、命令和上下文部分都在服务器端,在 dsh chat/无头模式下也能工作。只有切换卡片是 Web 客户端功能(settings.yaml 文件在任何地方都能工作)。
它会在我只是聊天时消耗我的额度吗? 只有在实际调用 web_search 且开关打开时才会。模型上下文部分只是一个静态段落。
我可以重新排序提供方吗? 可以——在行配置中设置 priority(例如把 serper 放在最前面)。未知的 id 会被丢弃并给出验证警告,绝不会导致崩溃。
故障排查
| 症状 | 修复方法 |
| --- | --- |
| web_search 提示“已被用户禁用” | 开关处于关闭状态——在 GUI 中将其打开,或在 ~/.dsh/settings.yaml 中设置 websearch-dsh.enabled: true。 |
| 状态显示某个提供方没有密钥,尽管你已设置环境变量 | 检查你是否在 dsh 会读取的层中设置了它(~/.dsh/.env 或启动它的 shell),并重启 dsh 一次;或使用凭据界面(实时生效)。 |
| 每次搜索都由 DuckDuckGo 提供并带有来源说明 | 带密钥的提供方正在失败——查看 websearch_status → failures(auth = 密钥错误,quota = 免费额度已耗尽,rate = 被限流)。 |
| 组合错误“所有网络搜索提供方均失败” | 网络层面问题:该机器无法访问任何提供方 API(或 DDG 正在对你发起质询)。错误会列出每个提供方的代码——修复与你的设置相匹配的那个。 |
| GUI 卡片缺失 | 你使用的是 0.1.2 之前的 GUI 构建,或使用的是远程浏览器(仅限回环地址的功能)——或者该插件不在配置文件的 dsh.bundles 中。 |
| 安装后 web_search 仍提示“没有 DEEPSEEK_API_KEY 的 API 密钥” | 自接入配置文件以来你还没有重启 dsh web,或者 bundle 补丁层未应用(检查 dsh --dump-config 中是否有 searchProvider: websearch-dsh)。 |
还原 / 卸载
在配置文件级别的 bundle 补丁中重新指向 web 行(searchProvider: deepseek-official),或者直接从配置文件的 dsh.bundles 中移除 websearch-dsh,然后重启。该插件所做的其他任何事情都不依赖它;没有该插件时,设置部分(如果有)是不起作用的。
许可证
MIT——参见 LICENSE。同作者(takasurazeem)的其他插件
扫码进群