dsh-tavily
让 DSH 内置的 web_search 和 web_fetch 改走 Tavily,替代随附的 DeepSeek 搜索通道与本地 HTTP 抓取通道。
- 原生设置页:DSH 设置弹窗新增 Settings → Tavily 一节,直接编辑搜索/抓取参数与 API Key;保存后立即生效,密钥保存在 DSH 凭据库。
- 不改工具契约:接的是 DSH 的 web 能力 seam(
ctx.web),模型侧仍然是原来的web_search/web_fetch,参数、结果卡片、引用格式全部不变。 - 两个能力可分开启用:只想换搜索、保留本地抓取时,把
fetchProvider改回http即可(见 6)。 - 可审计:
package.json声明dsh.seams: ["web"],离线自测锁定它与inject一致。 - 离线自测:
npm test不需要网络、不需要安装依赖、不需要装 DSH;npm run test:schema在npm install后校验设置 schema。 - 零构建:浏览器半边是手写脚本,没有打包步骤。
免责声明:这是个人/本地插件,与 DeepSeek 官方、Tavily 官方均无关。 DSH 目前是 developer preview,本插件针对
0.2.1-rc.3的 seam API 编写; 它依赖的ctx.web.registerSearchProvider/registerFetchProvider、设置槽位与客户端 primitives 可能随 DSH 版本变化。
兼容性
| 项 | 要求 |
|---|---|
| DSH | 已在 0.2.1-rc.3 上核对接口(peerDependencies 声明为 ^0.2.0-rc.2) |
| Node | >= 20.3.0 —— 用到 AbortSignal.any() |
| 运行依赖 | @deepseek-ai/schemastery ~3.18.5(设置页/schema 用;缺失时插件仍可搜索,但设置页不出现) |
| 安装 | 本地 link: 安装需在插件目录执行 npm install;发布包由包管理器自动安装依赖 |
| 构建步骤 | 无(client.js 手写) |
目录
1. 原理
DSH 的 web 访问是一个可插拔的 seam(@deepseek-ai/dsh-web,即 ctx.web):
| 组件 | 角色 |
|---|---|
@deepseek-ai/dsh-tool-web |
面向模型的 web_search / web_fetch 工具 |
@deepseek-ai/dsh-web |
seam 本身:注册搜索/抓取提供方,并按 id 选择提供方 |
@deepseek-ai/dsh-web-search-deepseek |
随附的搜索提供方(id:deepseek-official) |
@deepseek-ai/dsh-web-fetch-http |
随附的抓取提供方(id:http) |
DSH 没有内置 Tavily 提供方,所以接入只需要两件事:
- 挂载一个注册
tavily搜索/抓取提供方的插件(本包); - 把
web行的searchProvider/fetchProvider改成tavily。
这两件事都在本包的 cordis.patch.yml 里完成(组合包 patch 层在 dsh-base / dsh-web-app 之后应用,同一行最后写入者生效,因此可以覆盖默认值)。
⚠️ 必须显式固定 provider id:
deepseek-official只要存在 key 解析器就报告「可用」, 如果两个提供方同时可用且没配置 id,web_search会以WEB_PROVIDER_AMBIGUOUS失败。 本包的 patch 已经替你固定好了。
2. 准备 API key
- 在 Tavily 控制台 注册,拿到
tvly-开头的 key; - 注意额度:一次
web_search消耗 1 次搜索额度(includeAnswer会额外消耗), 一次web_fetch消耗 1 次 extract 额度(extractDepth: advanced更多)。
key 有三种放法,推荐设置页或环境变量,避免把密钥写进文件:
| 方式 | 存储位置 | 说明 |
|---|---|---|
| Settings → Tavily(推荐) | DSH 凭据库(~/.dsh/.credentials.yaml) |
页面写入 TAVILY_API_KEY;可显示已配置/只读状态;不写进 profile patch |
| 环境变量 | 启动 DSH 的进程环境 | 与凭据库同名引用;页面会显示为只读 |
字面 apiKey |
cordis.patch.yml |
优先级最高,适合临时/overlay;会明文落盘 |
优先级:字面 apiKey > 凭据库 > 环境变量。
# 当前 shell(临时)
$env:TAVILY_API_KEY = 'tvly-...'
# 用户级(需要完全重启 DSH 才继承到)
[Environment]::SetEnvironmentVariable('TAVILY_API_KEY', 'tvly-...', 'User')
凭据库的引用名由 apiKeyEnv 控制,默认 TAVILY_API_KEY;一般不需要改。页面检测到 patch 里的字面 apiKey 时会禁用密钥输入并提示优先级,避免“保存了 key 却不生效”。
3. 配置项
配置在 cordis.patch.yml 中 web-tavily 行的 config 里;标 是 的字段也可以在 Settings → Tavily 页面里编辑(保存后立即生效,不需要重启)。
| 字段 | 默认 | 设置页 | 说明 |
|---|---|---|---|
apiKey |
无 | 否 | 字面 key(tvly- 开头),优先级最高;页面检测到它会禁用凭据输入 |
apiKeyEnv |
TAVILY_API_KEY |
否(高级) | 凭据库引用名;页面按它读写密钥 |
baseURL |
https://api.tavily.com |
是 | Tavily 地址;可指向网关/代理,末尾不要斜杠 |
searchPath |
/search |
否 | 搜索端点路径,仅当网关改写了路径才改 |
extractPath |
/extract |
否 | 抓取端点路径,同上 |
timeoutMs |
15000 |
是 | 单次搜索超时 |
fetchTimeoutMs |
30000 |
是 | 单次抓取超时 |
maxResults |
8 |
是 | 本提供方上限(≤ 20);调用方只能往下压,不能往上抬 |
searchDepth |
basic |
是 | basic / advanced / fast / ultra-fast |
topic |
general |
是 | general / news / finance |
timeRange |
不发送 | 是 | day / week / month / year |
days |
不发送 | 是 | 仅 topic: news 有意义,最近 N 天 |
country |
不发送 | 是 | 两位国家代码,例如 us / cn |
includeAnswer |
false |
是 | false / true / basic / advanced;开启后模型会直接拿到一段答案(消耗额外额度) |
includeDomains |
[] |
是 | 只搜这些域名;页面用逗号/空格分隔,patch 里可写数组 |
excludeDomains |
[] |
是 | 排除这些域名;同上 |
extractDepth |
basic |
是 | basic / advanced |
extractFormat |
markdown |
是 | markdown / text |
maxBodyChars |
100000 |
是 | 抓取正文字符上限,超出截断并标记 truncated |
userAgent |
dsh-tavily/<包版本> |
否 | 请求 UA,默认值跟随 package.json 的 version |
headers |
{} |
否 | 额外请求头(网关鉴权用);同名时覆盖默认的 authorization |
页面保存的 volatile 字段写入当前 profile 的 cordis.patch.yml,Host 的下一次请求就会读到,不需要重启。直接改 patch 文件里的非 volatile 字段(searchPath / extractPath / userAgent / headers)仍需让组合包重新加载:桌面端完全重启 DSH;dsh web / dsh tui / dsh headless 下次启动生效。
配置 schema 与设置页
Host 半边现在声明 Config(@deepseek-ai/schemastery),换来:
- 加载时校验:字段类型/枚举/范围写错会在组合时被拒绝,而不是第一次请求才报错;
- 原生设置页:
web-tavilynamespace 被dsh-settings提供给浏览器半边,Settings → Tavily里出现表单; - volatile 立即生效:页面保存后
apply()里的 options thunk 下次请求就重读,不需要重启; - 密钥不进 patch:页面通过 DSH 凭据库写
TAVILY_API_KEY。
代价与注意事项:
- 多了一个包依赖
@deepseek-ai/schemastery ~3.18.5(DSH 自身同款版本)。本地link:安装需要在插件目录npm install;发布包由包管理器安装。 - 缺少该依赖时
index.js会动态 import 失败并降级:搜索/抓取照常,设置页不出现,控制台给出警告。这样不会因为漏装依赖导致整行加载失败。 - DSH 升级 schemastery 时 schema 可能被判定不兼容,需要回归
npm run test:schema;DSH 升级客户端 primitives 时设置页可能需要跟着调整。 searchPath/extractPath/userAgent/headers仍是 patch-only(高级字段,页面不渲染)。
4. 安装
本包已通过 link: 安装进桌面 profile;下面是通用的安装方式。
方式 A:插件管理器 + 本地路径(桌面端)
- 在插件目录执行
npm install(安装设置页/schema 所需的@deepseek-ai/schemastery); - 打开 DSH Web 界面 → 侧栏 插件 → 添加插件;
- 粘贴本仓库所在目录的绝对路径,例如
D:\path\to\dsh-tavily; - 点 安装,等 pnpm 跑完;
- 完全重启 DSH(关窗口不算,要确认进程退出)。
方式 B:命令行装到某个 profile
npm install # 先装本包自己的依赖
dsh plugin --profile web add "D:/path/to/dsh-tavily"
profile 之间互不相干:装进哪个 profile,就只有那个 surface 生效(桌面端是 desktop,只能由 App 管理器安装)。
方式 C:用 --patch overlay 临时覆盖
$env:TAVILY_API_KEY = 'tvly-...'
dsh web --patch ./examples/dsh-tavily.overlay.yml
overlay 只重述已存在的 row,前提是该 profile 里已装本包。
装完必做
- 在插件目录执行
npm install(本地link:安装不会自动装 link 目标的依赖); - 配好
TAVILY_API_KEY(或apiKey,或安装后在 Settings → Tavily 里保存); - 完全退出并重启 DSH。
新装/改动组合包后不重启,运行中的 DSH 仍然在用旧组合——表现就是「插件卡片显示已装,但 web_search 还在走 DeepSeek」。新增 dsh.client 声明也必须完全重启,浏览器半边才会被加载。
回退
把 profile 里 web 行的 searchProvider / fetchProvider 改回 deepseek-official / http(或禁用 web-tavily 行),重启即可。
5. 结果映射
搜索(POST /search):
| Tavily | DSH |
|---|---|
results[].url / title / content |
sources[].url / title / snippet(按 url 去重,无 url 丢弃) |
results[].published_date |
sources[].publishedAt |
answer |
WebSearchResult.content(模型优先读的一段答案) |
抓取(POST /extract):
| Tavily | DSH |
|---|---|
results[].raw_content |
body { kind: 'text', content }(Tavily 已转成 markdown/text) |
failed_results[].error |
不抛错,返回 statusCode: 502 的结果,正文写明原因 |
6. web_fetch 的取舍
用 Tavily 接管抓取有三个实际影响,装之前请确认:
- 每次抓取都消耗 Tavily 额度,而且比搜索更容易吃额度(
extractDepth: advanced更贵); - 目标 URL 会发送给 Tavily(第三方),抓取由 Tavily 侧执行;
- 抓不到内网/本机地址——Tavily 从自己的基础设施访问,
http://192.168.x.x、http://localhost之类不可用。
如果这三点里任何一条不能接受,就只换搜索:把 cordis.patch.yml 里 web 行的 fetchProvider: tavily 改成 fetchProvider: http,web_fetch 继续走随附的本地 HTTP 提供方(带私网拦截、同源重定向限制等安全策略)。
7. 验证
7.1 离线自测(不依赖 DSH、不需要网络)
npm test # smoke + 浏览器半边注册检查,不需要 node_modules
npm run test:schema # 需要先 npm install,校验设置 schema
npm run test:all # 两者一起跑
test/smoke.mjs 覆盖:注册 id、可用性判断、环境变量/凭据库解析、字面 key 优先、缺 key 错误、URL/路径规范化、请求体构造与上限夹逼、结果映射/去重/截断、真实 HTTP 往返(内置 mock Tavily)、401/429/432 报错、取消、超时、不可达、抓取截断、failed_results 降级为结果、URL 策略、dsh.seams 与 inject 一致、dsh.client 清单、patch 结构、发布文件无 UTF-8 BOM。
test/client.mjs 用假的 window.__ModuleLoader__ + mini React/primitives/store 桩执行 client.js,覆盖:模块 id、浏览器半边 inject、settings.section 注册参数(id/order/locale)、whileServed('web-tavily')、页面 hook/actions、字面 key 检测。
test/schema.mjs 覆盖:schema 可被 Schemastery 重建、secret/credential-ref/volatile 元数据、默认值与 normalizeConfig 一致、非法值被拒、覆盖值通过 ref 读出。
7.2 在 DSH 里验证
- 完全重启 DSH,打开 Settings:左侧出现 Tavily;
- 页面显示当前生效值;修改
maxResults等字段并保存,检查~/.dsh/profiles/<profile>/cordis.patch.yml出现/更新web-tavily行; - 在页面保存 API Key,检查
~/.dsh/.credentials.yaml出现TAVILY_API_KEY条目(不要打印值),页面显示「已配置」; - 让模型调用一次
web_search(例如「搜索 deepseek harness」),看来源是否来自 Tavily; - 再调用一次
web_fetch(给一个公网 URL),看是否返回 markdown 正文; - 禁用
web-tavily行后重启/刷新,Settings → Tavily 应消失;重新启用后恢复。
缺 key 时现在会报 WEB_PROVIDER_CREDENTIAL_MISSING(不再是 WEB_PROVIDER_CONFIGURED_UNAVAILABLE),见 8。
8. 排错
| 现象 / 错误码 | 原因与处理 |
|---|---|
WEB_PROVIDER_CONFIGURED_MISSING |
组合包没启用/没装成功,tavily 提供方没注册;确认已重启 DSH |
WEB_PROVIDER_CONFIGURED_UNAVAILABLE |
已注册但 available() 为 false:baseURL 不合法或超时预算非法。缺 key 现在报 WEB_PROVIDER_CREDENTIAL_MISSING,不再走这个错误 |
WEB_PROVIDER_CREDENTIAL_MISSING |
已注册但没有可用 key:在 Settings → Tavily 保存、或设 TAVILY_API_KEY、或在 patch 里写 apiKey |
| Settings 里没有 Tavily 一节 | ① 没 npm install(缺 schemastery,控制台有警告);② web-tavily 行被禁用;③ 没完全重启(新 dsh.client 需要重启);④ 该 profile 没装本包 |
| 页面显示「未配置」但搜索可用 | key 来自环境变量或 patch 的字面 apiKey;字面 apiKey 优先级最高,页面检测到后会禁用输入 |
页面提示字面 apiKey 但实际用环境变量 |
patch 里有一行空的 apiKey: ''(设置服务把“存在”视为已设置)。删掉这行,或填入非空 key |
| 页面显示只读 | 当前 profile 的设置为只读,或密钥由环境变量提供(describe 报 writable=false) |
| 页面保存失败 | 设置文档 revision 冲突或 Host 拒绝;草稿保留,刷新后重试 |
| 凭据服务不可用 | credentials 服务没挂载:key 字段禁用,改用环境变量或字面 apiKey |
WEB_PROVIDER_AMBIGUOUS |
web 行的 provider id 没被钉住(patch 层顺序被更高层覆盖)。在 profile 的 cordis.patch.yml 里显式写 searchProvider: tavily |
Tavily search returned HTTP 401 |
key 无效或过期:核对设置页 / apiKey / TAVILY_API_KEY |
HTTP 429 |
速率限制:降低请求频率后重试 |
HTTP 432 / HTTP 433 |
计划额度 / PAYGO 额度用尽:去 Tavily 控制台加额度或升级 |
HTTP 400 |
参数被拒:检查 searchDepth / topic / timeRange / days / 域名列表 |
timed out after ...ms |
提高 timeoutMs / fetchTimeoutMs,确认 DSH 主机到 api.tavily.com 的网络与代理 |
request to ... failed |
端点不可达:检查 baseURL、代理、DNS |
web_fetch 返回 502 + could not extract ... |
Tavily 抓不到该页(404/反爬/PDF 等):换 URL 或用本地 fetchProvider: http |
web_fetch 报 WEB_INVALID_URL / WEB_BLOCKED_URL |
只接受 http(s) 且 URL 里不能带用户名密码 |
装了但 web_search 还走 DeepSeek |
没完全重启 DSH;或该 profile 里 web 行仍是 deepseek-official |
| 插件卡片显示「已关闭」 | 运行中的 DSH 是在装插件之前启动的。完全重启 |
9. 已知限制
- 未做真实 API 联调:创建这个插件时,本机 DNS 把
api.tavily.com/docs.tavily.com解析到198.18.0.0/15(代理 fake-IP 段),DSH 自带的web_fetch会以「解析到非公网 IP」拒绝; 插件的fetch走 Node 原生实现、通常会经系统代理,但没有真实 key 就没有验证过。 安装后第一次调用请按 7.2 现场确认。 - 字段与错误码来源:Tavily 请求/响应字段(
search_depth、include_answer、raw_content、failed_results等)与401/429/432/433语义按公开文档与官方 SDK 行为实现;文档站不可达时 无法二次核对。 - 可用性是乐观的:配置了凭据引用(默认
TAVILY_API_KEY)后available()就为 true, 真正的 key 在每次请求时解析;缺 key 时由WEB_PROVIDER_CREDENTIAL_MISSING报出,而不是 让 seam 报WEB_PROVIDER_CONFIGURED_UNAVAILABLE。这是为了支持凭据库/环境变量在运行时变化。 - 设置页依赖 DSH 客户端 primitives:
SettingsFormModel/SettingsForm/SettingsValueField/SettingsSecretField是0.2.1-rc.3的基线模块 API;DSH 升级后 可能需要按新版本调整client.js(与dsh-cloud-control的浏览器半边同类风险)。 - 设置页对所有启用本 bundle 的 profile 可见:它是 profile 级设置,不做“只有用 Tavily 的人 才显示”的额外门控;不想看到它就把 bundle 从 profile 移除。
- 字面
apiKey优先:保留兼容;页面检测到后会禁用凭据输入并提示,避免“保存了 key 却不生效”。 - Web/桌面 surface 的
web_search/web_fetch工具来自 agent preset(tool-web); 若某个 preset 没挂tool-web,seam 里注册了提供方也没有工具去调它。
10. 开发与测试
| 文件 | 作用 |
|---|---|
index.js |
两个 provider 的实现:apply / inject、配置规整、凭据解析、请求体构造、结果映射、错误码;可选 schema 的动态加载 |
lib/defaults.js |
默认值、枚举、凭据引用名;host 与 schema 共用,避免漂移 |
lib/schema.js |
Config(@deepseek-ai/schemastery),驱动设置页与加载时校验 |
client.js |
浏览器半边:settings.section 注册、表单、凭据状态、清除密钥 |
cordis.patch.yml |
组合包 patch:固定 web 的两个 provider id + 挂载 web-tavily 行 |
package.json |
组合包清单(dsh.bundle.patch、dsh.client、dsh.seams、依赖、展示元信息、icon 导出) |
locale/zh.json、locale/en.json |
插件管理器卡片标题与描述 |
icon.svg |
插件图标(原创) |
examples/dsh-tavily.overlay.yml |
dsh --patch 用的 overlay |
test/smoke.mjs |
离线 provider 自测(内置 mock Tavily) |
test/client.mjs |
离线浏览器半边注册检查(mini React/primitives/store 桩) |
test/schema.mjs |
设置 schema 校验(需要 npm install) |
npm install # 设置页/schema 依赖(cosmokit、standard-schema 会一起装)
npm test # 离线:smoke + client,不需要 node_modules
npm run test:schema # 需要上面的 npm install
npm run test:all # 两者一起
npm test 不依赖 node_modules:index.js 对 schema 用动态 import,缺依赖时降级为“无设置页”,测试里用 DSH_TAVILY_QUIET=1 静音该警告。
修改 lib/defaults.js 里的 PACKAGE_VERSION 时记得同步 package.json 的 version——
test/smoke.mjs 会检查默认 User-Agent 与包版本是否一致。修改 schema 时同时跑 npm run test:schema。
No comments yet. Be the first to write one.