dsh-web-search-tavily
面向 DeepSeek Harness(DSH)的 Tavily 搜索提供方插件,让 DSH 内置的 web_search 工具实际走 Tavily Search API。
这是什么
DSH 的 web_search 工具与具体搜索服务解耦,通过中间层 ctx.web 工作:
@deepseek-ai/dsh-tool-web只定义web_search工具的 schema、校验与展示,不碰搜索服务;- 真正执行搜索的是注册到
ctx.web.registerSearchProvider(provider)的「搜索提供方」; - 通过
web段配置searchProvider(或环境变量DSH_WEB_SEARCH_PROVIDER)选择提供方。
DSH 开箱只带一个搜索提供方 @deepseek-ai/dsh-web-search-deepseek(走 DeepSeek 的 Anthropic 兼容 Messages API),没有内置 Tavily。本项目补齐了这块:安装并接线后,即可把 web_search 切换到 Tavily。
特性
- 原生
fetch直连POST https://api.tavily.com/search results[] → sources[]、answer → content的规范化映射- 复用 DSH 的 credential seam(
credentialRef),密钥只以引用形式出现、绝不明文写死 - 完整错误路径(缺失 key / 网络失败 / 取消),遵循
WebError错误码规范 - 全程转发
AbortSignal用于取消 - 纯 JS 实现、零构建步骤,函数/命名空间插件(
inject: ['web'])
架构
| 层 | 包 | 职责 |
|---|---|---|
| 工具层 | @deepseek-ai/dsh-tool-web |
模型侧 web_search 工具的 schema、校验与展示 |
| 能力 seam | @deepseek-ai/dsh-web |
提供方注册表、选择策略、maxResults 兜底截断、WebError |
| 提供方(本项目) | @deepseek-ai/dsh-web-search-tavily |
把一次搜索请求翻译成 Tavily API 调用并规范化结果 |
本项目实现的是 seam 定义的 WebSearchProvider 接口:
interface WebSearchProvider {
readonly id: string // 本项目注册为 'tavily'
available(): boolean // 廉价本地检查,禁止发网络请求
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
}
安装
前置条件
- 已安装 DSH,并有一个 profile(下文以
webprofile 为例) - Node.js(DSH 运行环境自带)
步骤 1:安装插件
本地源码 / GitHub clone 后
# clone 本仓库后,把本地路径装进 profile
dsh plugin --profile web add file:D:/path/to/dsh-web-search-tavily
dsh plugin底层就是转发给 pnpm;若未安装 pnpm,可等价地直接执行npx pnpm add file:D:/path/to/dsh-web-search-tavily(在 profile 目录下)。
步骤 2:接线
编辑 profile 的补丁层(不是 node_modules 里的配置):
- 路径:
$DSH_HOME/profiles/web/cordis.patch.yml - Windows 默认:
C:\Users\<用户名>\.dsh\profiles\web\cordis.patch.yml
# 新增 Tavily 搜索提供方
- insert:
- id: web-search-tavily
name: '@deepseek-ai/dsh-web-search-tavily'
config:
apiKeyEnv: TAVILY_API_KEY
# 把 web 段的 searchProvider 从 deepseek-official 改为 tavily
- id: web
name: '@deepseek-ai/dsh-web'
config:
searchProvider: tavily
步骤 3:配置 API Key
三种方式任选其一(优先级:启动环境变量 > credentials 文件 > .env):
A(推荐):写入 DSH 凭据文件 $DSH_HOME/.credentials.yaml(Windows 默认 C:\Users\<你>\.dsh\.credentials.yaml):
TAVILY_API_KEY: tvly-你的key
B(持久环境变量,最高优先级):
setx TAVILY_API_KEY "tvly-你的key"
C(临时,仅当前终端):
$env:TAVILY_API_KEY = "tvly-你的key"
步骤 4:重启并验证
重启 DSH(关闭运行中的控制台窗口 / Ctrl+C,再按原方式启动),然后让模型执行一次 web_search(例如问「今天有什么新闻」):
- 正常:结果来源来自 Tavily(返回
answer+sources[]) - 故意不设 key:报
WEB_PROVIDER_CREDENTIAL_MISSING—— 这恰好证明选择逻辑已切到 Tavily,而非静默回退 DeepSeek
配置项
| Key | 默认 | 说明 |
|---|---|---|
apiKeyEnv |
TAVILY_API_KEY |
credential 引用,每次 search 经 ctx.credentials 解析;无 seam 时回退启动环境变量 |
apiKey |
省略 | 可选字面量 key(role('secret')),非空时优先;建议用 apiKeyEnv 避免密钥进配置 |
请求与映射
- 端点:
POST https://api.tavily.com/search - 请求头:
Content-Type: application/json、Authorization: Bearer <TAVILY_API_KEY> - 请求体:
{
"query": "<query>",
"search_depth": "advanced",
"max_results": 8
}
响应映射:
| Tavily 字段 | 目标字段 | 规则 |
|---|---|---|
results[].url |
sources[].url |
必填;缺失/空串的项被跳过 |
results[].title |
sources[].title |
缺失/空则省略 |
results[].content |
sources[].snippet |
缺失/空则省略 |
results[].published_date |
sources[].publishedAt |
缺失/空则省略 |
answer |
content |
非空则放入,否则省略 |
truncated 恒为 false:provider 不主动截断,maxResults 由 seam 统一截断并置位。max_results 在请求层设置仅是成本/延迟优化。
错误码
| 情况 | 错误码 |
|---|---|
| 无 key(字面量、credentials、环境均无) | WEB_PROVIDER_CREDENTIAL_MISSING |
| 网络失败 / 非 2xx / 响应无法解析 | WEB_PROVIDER_ERROR |
| 调用方取消 | WEB_ABORTED |
| 配置了 id 但未注册 / 不可用 / 多提供方歧义 | WEB_PROVIDER_CONFIGURED_MISSING / WEB_PROVIDER_CONFIGURED_UNAVAILABLE / WEB_PROVIDER_AMBIGUOUS(由 seam 抛出) |
项目结构
dsh-web-search-tavily/
├── package.json
├── README.md
└── lib/
├── index.js # 插件入口 + provider + 映射 + 错误/取消助手
└── types/
├── index.d.ts # 插件导出 + Config 接口
├── provider.d.ts # TavilySearchProvider + Options
└── types.d.ts # Tavily 线上响应/错误类型
No comments yet. Be the first to write one.