全部插件

DSH / BUNDLE / BUNDLES

@gausszhou/dsh-web-search-local

v0.2.1gausszhou / dsh-web-search-local195bccfddf

可安装组合包组合包与其他模块社区 · Topic 自动分析

概览

@gausszhou/dsh-web-search-local

Keyless multi-engine web search + page fetch providers for the dsh web seam (no DeepSeek dependency).

README / ZH

插件文档

dsh-web-search-local

DeepSeek Harness(dsh)ctx.web 接口提供的无密钥多引擎网页搜索与抓取 provider。适用于任何模型后端——包括完全本地模型无需 API Key,也不依赖 DeepSeek 的服务端搜索

为什么需要它

dsh 内置的 web_search 工具与模型无关:它只调用 ctx.web.search()。依赖 DeepSeek 的是它的默认搜索 providerdsh-web-search-deepseek),该 provider 会把每次查询带上 DEEPSEEK_API_KEY 发给 DeepSeek 的 web_search_20250305 服务端工具。一旦切换到本地模型(如 Ollama),这个 provider 没有 key,搜索就失效了。

本包注册两个由插件自己发 HTTP 请求的 provider:

provider id 能力 引擎
local-multi web_search 三层顺序执行——SearXNG(配置时)→ Google/DuckDuckGo/Mojeek(国外层)→ Bing/Baidu/Sogou/360(国内层);同层引擎并行请求round-robin 合并结果;一层无结果则降级到下一层
local-fetch web_fetch 直接 GET,字符集感知解码(含 gbk),返回 html/text 正文

代理 / VPN 支持

Node 进程不会自动使用操作系统/浏览器的代理。如果 DuckDuckGo 等引擎在你的网络环境下不可达,provider 会自动解析代理:

  1. proxyUrl 配置(显式指定,或设为 'off' 强制直连)
  2. HTTPS_PROXY / HTTP_PROXY / ALL_PROXY 环境变量
  3. 探测常见本地 HTTP 代理端口(7890 Clash、789710809 v2rayN、1080、……)

代理只作用于国外层引擎googleduckduckgomojeek)——它们才是大陆网络下需要隧道的一方。国内层引擎(bingbaidusogou360)和私有 SearXNG 实例始终直连(只有国外层引擎走 CONNECT 隧道;隧道在传输层挂掉时这些请求同样回退直连)。这个拆分很关键:把国内引擎塞进国外节点 IP 会触发百度的安全验证墙、搜狗的反爬验证码和 360 的 302 跳转;把自己的 SearXNG 实例也走 VPN 节点则可能拿到陈旧或空的结果集。

安装

通过 npm

已发布至 npm registry

npm install @gausszhou/dsh-web-search-local

或者用 dsh CLI 一步注册并激活插件:

dsh plugin add @gausszhou/dsh-web-search-local

本包内置 dsh.bundle patch,因此 dsh plugin add 会安装并自动激活插件: web profile 会自动切换为 searchProvider: local-multi / fetchProvider: local-fetch, 并禁用内置的 web-search-deepseek provider,无需手工编辑 cordis.patch.yml

如需调整 bundle 应用的默认值,可在你的 profile 的 cordis.patch.yml (web profile 即 $DSH_HOME/profiles/web/cordis.patch.yml,位于 bundle 层之后)中覆盖:

- id: web
  config:
    searchProvider: local-multi
    fetchProvider: local-fetch

- id: web-search-deepseek
  disabled: true

- insert:
    - id: web-search-local
      name: '@gausszhou/dsh-web-search-local'
      config:
        engines: [searxng, google, duckduckgo, mojeek, bing, baidu, sogou, 360]

本地目录 / 文件路径

把本包放到 dsh 进程可读的任意位置,例如 $DSH_HOME/profiles/web/plugins/web-search-local/(Windows:C:\Users\<you>\.dsh\profiles\web\plugins\web-search-local\)。然后在你的 profile 的 cordis.patch.yml(web profile 即 $DSH_HOME/profiles/web/cordis.patch.yml)中加入:

- id: web
  config:
    searchProvider: local-multi
    fetchProvider: local-fetch

- id: web-search-deepseek
  disabled: true

- insert:
    - id: web-search-local
      name: 'file:///C:/Users/<you>/.dsh/profiles/web/plugins/web-search-local/index.js'
      config:
        engines: [searxng, google, duckduckgo, mojeek, bing, baidu, sogou, 360]
  1. 重启 dsh。web_search 现在返回纯来源列表(无服务端摘要),且适用于任何模型。

配置

config:
  engines: [searxng, google, duckduckgo, mojeek, bing, baidu, sogou, 360]  # 成员列表(执行按层:searxng → 国外 → 国内,层内并行)
  skipWithoutProxy: [google, duckduckgo, mojeek] # 无代理时直接跳过的引擎([] = 总是尝试)
  searxngBaseUrl: 'http://127.0.0.1:8080'   # 可选;设置后优先执行
  proxyUrl: ''                              # '' 自动 | 'off' 直连 | 'http://host:port' 显式指定
  searchTimeoutMs: 12000
  fetchTimeoutMs: 20000
  maxFetchBytes: 1048576
  maxSources: 12
  cacheTtlMs: 300000                        # 内存结果缓存
  engineMinIntervalMs: 1500                 # 引擎请求最小间隔(防限流)
  engineCooldownMs: 600000                  # 验证墙/验证码后熔断冷却(0 = 关闭)
  engineRetryCooldownMs: 60000              # 普通失败后冷却(0 = 关闭)
  userAgent: '<浏览器风格的 UA>'

默认引擎列表分三层、按序执行,同层引擎并行请求并合并

  1. searxng——配置了 searxngBaseUrl 的私有 SearXNG 实例本身就是元搜索聚合,有结果就直接返回,跳过下面两层
  2. 国外层——Google、DuckDuckGo、Mojeek(大陆需要代理;无代理时整体直接跳过,见 skipWithoutProxy
  3. 国内层——Bing、Baidu、Sogou、360(直连可用,无需 VPN/代理)

一层无结果(空、被墙或跳过的引擎)就降级到下一层,所以国外层永远不会拖垮可直连的国内层。google 引擎易被反爬(consent 墙、sorry/ 机器人检测、enablejs JS 墙);要稳定拿到 Google 结果,使用启用了 google 引擎的 SearXNG 实例。在开放网络(全球引擎可直连)上,把 skipWithoutProxy 设为 []

私有 SearXNG 实例(Docker:docker run -p 8080:8080 searxng/searxng)是所有引擎中最稳健的:元搜索聚合、JSON API、无需逐引擎爬取。

限流韧性

搜索引擎(尤其是 DuckDuckGo)会限流脚本。以下三个机制让单引擎配置也能稳定使用:

  • 节流——按引擎计:同一引擎在 engineMinIntervalMs 内不会被调用两次(防限流),同层不同引擎则一起启动。
  • 熔断——引擎出现机器人墙(blocked by captcha / anomaly check / 百度的 verification wall,或 HTTP 403/429)时,在 engineCooldownMs(默认 10 分钟)内跳过;普通失败(传输、HTTP 错误)只触发更短的 engineRetryCooldownMs(默认 60 秒)。冷却期间引擎被跳过,原因会聚合进错误信息。
  • DuckDuckGo lite 兜底——html.duckduckgo.com 端点被机器人墙拦截时,同一查询会改走 lite.duckduckgo.com/lite/ 重试一次(该端点对脚本更宽容)。若 lite 端点也被墙,引擎会报告 blocked by anomaly check (html and lite) 并触发长冷却 engineCooldownMs,而不是每次搜索都反复冲击两个端点。

被墙的引擎不会让整个搜索失败(前提是还有其他引擎);单引擎模式下会快速失败并给出 "cooling down" 原因,而不是反复冲击被墙端点。

模型指定引擎

模型可以在每次搜索时指定用哪个引擎,两种途径:

  1. 工具——在官方 web_search 之外,本插件注册了 web_search_engine,带两个可选参数:
    • engine:单个引擎——searxnggoogleduckduckgomojeekbingbaidusogou360
    • engines:有序的引擎优先级列表 两者都不传时,调用降级为配置的默认三层引擎链,与 web_search 完全一致。
  2. provider 请求——任何直接调用 ctx.web.search({ query, engine })ctx.web.search({ query, engines }) 的调用方都获得同样的覆盖;未知引擎名会抛 WEB_PROVIDER_ERROR 并列出合法 id。

显式覆盖会完全替换配置的引擎链(包括 SearXNG 自动前置)——模型的明确选择优先。指定的引擎同样按 searxng / 国外 / 国内三层分组、层内并行合并(与默认链一致);只指定一个引擎就是单跑。节奏控制、熔断和 skipWithoutProxy 对指定引擎同样生效,所以指定了但不可达的引擎会快速失败,而不会拖垮整个搜索。

回退到 DeepSeek 搜索

cordis.patch.yml 中移除 web 覆盖项、web-search-deepseek 禁用项以及插入的那一行即可。

注意事项

  • 引擎靠正则抓取纯 HTML;上游改版可能导致某个引擎失效——链路会自动落到下一个引擎。所有引擎的错误会聚合进抛出的异常信息。搜狗的 /link?url= 加密跳转会在服务端解析(跳转页正文内嵌真实地址);360 的跳转链接在锚点的 data-mdurl 属性里直接暴露真实地址,解析器直接读取。
  • google 引擎用双布局解析器抓取 HTML 结果页(基础 gbv=1 标记与现代 JS 时代标记),并发送 CONSENT/SOCS Cookie 绕过欧盟 consent 墙。Google 经常对脚本返回"需要启用 JavaScript"墙(/httpservice/retry/enablejs)或 sorry/ 验证码而不是结果——两者都会被检测并触发长熔断冷却(带明确原因),国外层随即降级到国内层。要稳定拿到 Google 结果,使用启用了 google 引擎的 SearXNG 实例。
  • 返回结构与官方 provider 一致:web_search 返回 { sources: [{ url, title?, snippet?, publishedAt? }], truncated }。层内引擎并行请求,来源round-robin 合并、去重、截断到 maxSources(合并结果超过上限时 truncated 置 true);一层无结果则降级到下一层。publishedAt 是尽力而为的 YYYY-MM-DD 日期,当引擎页面出现日期时填充(SearXNG 的 publishedDate,或 Bing/Baidu/Sogou/360 结果块中的日期文本),否则省略该字段——与官方 page_age 相同的可选语义。
  • 无第三方运行时依赖:只用 fetch + node:http/https/net/tls,外加 dsh 自带的 @deepseek-ai/dsh-web(以 peerDependency 声明;每个 dsh profile 都已内置)。
  • 错误遵循 seam 的 provider 契约:失败时抛 WebError,code 为 WEB_PROVIDER_ERROR(引擎/传输/超时,引擎错误会聚合进 message)或 WEB_ABORTED(调用方取消)——与官方 provider 使用同一套错误词汇。
  • web_fetch 需要 tool-webfetch: true;自带的 standard agent 预设默认是 fetch: false——把预设复制到 $DSH_HOME/.agent-presets/ 并在那里打开开关。

配置与设置集成

插件声明了一个 schemastery Config 架构(字段与 defaultConfig() 一一对应)并通过 dsh 的 settings 服务注册了一个设置命名空间(web-search-local)——内置的 web-search-deepseekshellagent-loop 等插件用的就是这套机制。这带来:

  • 配置经过校验与规范化,可通过 dsh 的 settings 服务持久化。
  • provider 每次搜索/抓取时读取实时生效的配置段:通过设置 UI 改动的值会在下一次调用立即生效,无需重启。(ConfigSETTINGS_NAMESPACE 均作为具名导出。)
  • 在没有 settings 服务的 profile 里,行为与原来完全一致:仍走 cordis.patch.yml 传给 apply(ctx, config) 的组合配置。

说明:在“插件配置”面板中看得见的那张卡片是一个客户端 React 组件——内置插件(终端 / Agent 循环 / 网页搜索)的卡片都硬编码在 dsh-client-ui-settings-plugins 这个打包客户端包里。要让本插件在这套面板里出现可编辑卡片,还需要随包提供一个 ./client 半端来注册 settings.plugin.item 卡片(见后文)。本插件的配置界面 = 上面的服务端 schema 集成 + 客户端卡片两部分。

许可证

MIT