SunlitCrack/dsh-web-search-api

dsh-web-search-api

DSH 的 ctx.web 搜索提供方(id: deepseek-search-api):主用 DeepSeek 官方专用检索端点 POST /v1/tools/search(按次计费),失败或空结果时回退到原生(模型)搜索;在「插件」面板提供免重启的配置卡片。

待分类deepseekdeepseek-harnessdsh

dsh-web-search-api

DSH(DeepSeek Harness)ctx.web 的搜索提供方,id = deepseek-search-api,设置页命名空间 = web-search-api。

主路径:DeepSeek 官方专用检索端点 POST {baseURL}/v1/tools/search —— 一次 HTTP,按成功请求计费(5 元 / 1000 次,失败与空结果不计费,无峰谷价)。 兜底路径:Search API 失败或返回空结果时,自动回退到原生(模型)搜索(Anthropic 兼容 Messages + 服务端 web_search 工具)——与官方 @deepseek-ai/dsh-web-search-deepseek 同一请求形状与解析方式,只在主路径不行时才用。

它替换的默认后端 deepseek-official 每次搜索都要跑一个完整模型轮次(官方那个包的 README「开发备注」把"专用检索端点"列为未来工作——现在端点有了,这个包就是补上那一环)。

与社区同类插件的差别

社区已有若干"搜索后端切换/路由"插件(dsh-search-router、dsh-search-hub、dsh-web-search-* 等),但它们的 DeepSeek 选项走的都是同一个原生链路(api.deepseek.com/anthropic/v1 + web_search_20250305),没有一个打新上线的 /v1/tools/search。要"用上这个按次计费的新端点",本插件是那条路。

两条路径的取舍

主路径(Search API)兜底(原生搜索)
请求一次 HTTP POST /v1/tools/search一次 Anthropic Messages 调用 + 服务端 web_search
计费0.005 元 / 次成功请求无请求费,费用 = 该辅助轮次的输入 + 输出 token
延迟一次 HTTP一个完整模型轮次
可控性count(1–50)、need_content、site:/lang:/after: 等检索语法服务端决定条数,只能在返回后截断
结果字段snippet + summary + date + rerank_score + authorityurl/title/page_age,snippet 来自 cited_text 引用
鉴权API Key(Authorization: Bearer)账号 token(x-dsh-auth-token)或 API Key

一次估算(deepseek-flash 价格,空闲时段):主路径 ≈ 0.005 元 + 回填 token;兜底 ≈ 0.012 元(未命中缓存)~0.024 元(高峰)。所以高峰时段主路径便宜约 4~5 倍,真正的收益是延迟低一个数量级。

配置

图形界面(改完免重启)

主侧边栏的「插件」面板 → 标题「网页搜索(Search API)」的卡片 → 点进去是配置表单。

⚠️ 它不在「设置」里。这个 build 的设置侧栏(账号与余额 / 通用设置 / 模型 / 内置插件 / 技能与 MCP / Agent 预设 / 插件市场 / 侧边卡片)没有插件配置页——官方那张「网页搜索」卡片同样挂在「插件」面板里(官方 README 写的 "Settings → Plugins → Plugin configuration" 对应的是这个面板)。本插件用与官方卡片相同的机制注册自己的卡片:客户端半身 lib/client.js 往 plugins.item 槽位注册一条,宿主服务其命名空间时才出现。

卡片可编辑 4 个字段(其余字段只在 composition 里改):

卡片字段我的键默认说明
API KeyapiKey未设置走凭据服务按 apiKeyEnv 的引用名存储,不写进设置文件
检索端点baseURLhttps://api.deepseek.com自动追加 /v1/tools/search
单次返回条数count81–50;不影响单次价格,宁多别少
回退时最多搜索次数maxUses5主路径失败回退到原生搜索时,一次请求最多搜几次

全部字段与 composition

字段默认说明
apiKey未设置字面值密钥(role=secret,不会随远端响应回传)。优先用 apiKeyEnv
apiKeyEnvDEEPSEEK_API_KEY凭据引用名(role=credential-ref):先查凭据服务,再查进程环境
baseURLhttps://api.deepseek.comSearch API 基址,自动追加 /v1/tools/search
count8返回条数 1–50。不影响单次价格,宁多别少
needContentfalse让 Search API 顺带返回正文(不加钱,但回填上下文变贵)
timeoutMs30000单次请求超时
fallbacktrue主路径失败(或空结果)时是否回退原生搜索
fallbackOnEmptytrue空结果是否也算失败
fallbackBaseURLhttps://api.deepseek.com/anthropic/v1回退用的 Anthropic 兼容基址(缺省时回退读 $DEEPSEEK_SEARCH_BASE_URL)
fallbackModeldeepseek-v4-flash回退用的模型名
maxUses5回退时一次请求最多触发几次服务端搜索(卡片也编辑它)
fallbackMaxTokens4096回退请求的 max_tokens

也可以直接写 composition(profile 的 cordis.patch.yml),注意一行的 config 是整体替换,写全:

- id: web-search-api
  config:
    count: 5
    fallback: true
    apiKeyEnv: DEEPSEEK_API_KEY

安装

本机(桌面版)已接好:软链进 profile、bundles 已登记、pnpm-lock.yaml 已同形补条目。重启 DSH 生效。

换机/重装时三步:

  1. 源码放到 ~/.dsh/plugin-src/dsh-web-search-api,建软链到 ~/.dsh/profiles/<profile>/node_modules/(形状与既有 dsh-kb 一致);或者用 CLI:dsh plugin --profile <profile> add link:/path/to/dsh-web-search-api。
  2. 补依赖解析路径:node scripts/link-deps.mjs。 link: 装的包不会被 pnpm 安装它自己的依赖,而 Node 从导入文件的真实路径往上找 —— 源码在 plugin-src/ 下,天然找不到 harness 侧的 @deepseek-ai/*。本脚本把 @deepseek-ai/schemastery 链进来;缺了它插件照常工作,只是设置页没有表单(Config 不导出)。
  3. 重启 DSH。切换搜索后端这件事由插件自带的 cordis.patch.yml 负责(dsh.bundle.patch 声明),不需要动 profile 的 patch。

为什么不用手改 profile 的 cordis.patch.yml:bundle 层按 dsh.profile.bundles 顺序在 profile 层之前应用,本插件排在 @deepseek-ai/dsh-base 之后,所以它自带的 patch 能覆盖 base 的 web 条目(config 整体替换)。这样卸载插件即自动还原成 base 默认的 deepseek-official。

验证

node scripts/cordis-load.mjs        # 用 DSH 本体那份真 cordis 加载 + 替身 fetch 跑 20 项断言
node scripts/verify-profile-wiring.mjs

cordis-load.mjs 覆盖:主路径成功 / HTTP 500 回退 / 空结果回退 / 两边皆空 / fallback=false / 无 Key 时直接走原生 / 账号路由鉴权 / 双失败聚合错误 / 请求形状 / 设置表单 schema。 verify-profile-wiring.mjs 覆盖:依赖解析、profile 链接、package.json(依赖 + bundles + 顺序安全)、lock 形状、composition patch 结构、残留覆盖、备份。

运行期怎么判断走的哪条路:

  • 不再出现 web/deepseek-search-llm-request 会话事件 = 主路径在跑(那是原生链路的标记事件)。
  • 回退发生时插件会打一条 [dsh-web-search-api] Search API ... 已回退原生搜索 的 warn 日志。
  • 设置页能看到 web-search-api 表单 = bundle 已加载、schema 已投影。

回滚

  • 改回原后端:删掉或注释插件自带 cordis.patch.yml 里的 - id: web 段,重启;或在 profile 的 patch 里写一段 - id: web 覆盖成 searchProvider: deepseek-official(覆盖是整体替换,两个键都要写)。
  • 卸载:dsh plugin --profile <profile> remove dsh-web-search-api;手工装的话删 bundles 那行、删 lock 条目、删软链。
  • 彻底还原本次改动:用 profile 下的 {package.json,pnpm-lock.yaml,cordis.patch.yml}.bak-before-web-search-api 覆盖回去。

已知限制

  • 第三方插件目录解析不到 @deepseek-ai/*,所以错误用带 code 的普通 Error(WEB_PROVIDER_ERROR / WEB_PROVIDER_CREDENTIAL_MISSING / WEB_ABORTED)而不是 harness 的 WebError;node scripts/link-deps.mjs 只补 schemastery 这一条解析路径,不为错误类型引入依赖。
  • needContent=true 时 Search API 返回的正文会被丢弃:seam 的来源形状只有 url/title/snippet/publishedAt,装不下正文。想让模型读正文,用 web_fetch。
  • 主路径只用 API Key(官方文档的鉴权方式);账号登录态只在兜底的原生路径生效。
  • 未做端到端联网验证(作者环境里 Key 存在凭据服务中,取不到),联网部分靠真 cordis + 替身 fetch 的 20 项断言覆盖到请求形状与响应解析层。

MIT。