低 Token 联网搜索:只把结果裁成 {title, url, snippet} 三个字段返回,厂商附带的 answer / score / raw_content 等一律不进上下文。后端可在 Tavily、Brave、SearXNG、Google CSE、Exa 五种之间配置,主后端失败时按 fallback / chain 顺序自动切换到下一个可用后端(同一后端去重)。返回的 URL 与摘要经清洗,除去跟踪参数。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-plugin-tlsearch
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:ltl0312/my-dsh-plugins#path:/packages/tlsearch
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
面向 DeepSeek Harness 的低 Token 消耗联网搜索插件。
把「一次搜索」塞进模型上下文的东西压到最少:只返回清洗过的 { title, url, snippet },
没有提供方散文、没有 Raw HTML、没有埋点参数、没有每轮重复的引用指引。
后端可插拔(Tavily / Brave / SearXNG),端点可自建(SearXNG、new-api 等中转网关)。
✨ 核心特性
- 结构化最小输出:每条结果最多两行(
N. 标题 — URL+ 缩进摘要),零装饰词、零填充句。 - 摘要硬上限:
maxSnippetChars(默认 250)逐条截断并尽量落在词边界,Token 成本可预算、可封顶。 - 结果数硬上限:
maxResults(默认 5,上限 10)——原生web_search默认 8 条。 - 清洗是确定性的:剥离 HTML 标签(
<strong>、<p>、<style>正文整块丢弃)、 解码 HTML 实体('6 个字符 →'1 个字符)、折叠换行/制表/NBSP/零宽字符。 - URL 减负:剔除
utm_*/fbclid/gclid/srsltid等纯埋点参数, 保留路径、其余查询参数与#hash;解析失败的 URL 逐字节原样返回,绝不改写。 - 先去重再截断:按归一化后的 URL 去重,避免同一篇文章的多个镜像先占满名额再被去重。
- 超时 + 熔断:每次请求挂
AbortController超时;连续失败 3 次即熔断 60 秒, 冷却期内不发网络请求直接快速失败。调用方主动中断不计入熔断。 - 零运行时依赖:网络层直接用 Node 原生
fetch/AbortSignal,不引入 axios 等重库。 - 错误可据以行动:缺凭据、端点错、非 JSON、限流、超时各有分类错误码与修复指引。
💰 为什么更省 Token(可复现的度量)
用同一批真实脏数据(8 条 Brave 风格原始结果:带 <strong> 高亮、HTML 实体、
埋点参数、均值 372 字符的长摘要)走真实管线对比:
| 指标 | 原生 web_search 风格 |
tlsearch 默认 |
差异 |
|---|---|---|---|
| 结果条数 | 8 | 5 | −37% |
| 单条摘要字符(均值) | 372(未清洗、无上限) | 248(清洗 + 250 上限) | −33% |
| 单条 URL 字符(均值) | 105(含 utm_*/fbclid) |
34 | −68% |
| 总字符数 | 4346 | 1608 | 2.70× |
| 粗估 Token(英文 ≈ 4 字符/token) | ≈ 1087 | ≈ 402 | 2.70× |
节省来自六个确定性机制,其中贡献最大的是摘要上限、结果数上限与实体/标签清洗:
- 摘要按
maxSnippetChars硬截断(原生工具没有这个旋钮); - 结果数默认 5 条(原生默认 8 条);
- 剥离 HTML 标签并解码实体(
'→'即 6 字符省到 1 字符); - 剔除 URL 埋点参数;
- 不返回
age/score/engine/raw_content/ 图片等元数据; - 「引用来源」的整句指引放在工具描述里只付一次,而不是每次调用都重复一遍。
复现方式:
packages/tlsearch构建后,用normalizeHits+renderOutcome处理同一批载荷即可 得到上表数字(本插件导出这两个函数,便于自行核对与调参)。
🔌 支持的后端
| 后端 | 密钥 | 默认端点 | 免费额度 | 说明 |
|---|---|---|---|---|
tavily(默认) |
必需 | https://api.tavily.com |
1000 credits/月(1 credit = 1 次搜索) | 结构化 JSON、字段干净;密钥同时走 Authorization 头与请求体,兼容自建/中转网关 |
searxng |
不需要 | 无(必须自配) | 无限(自建) | 自托管元搜索,聚合 Google/Bing/DDG;baseUrl 必填,并需在实例 settings.yml 的 search.formats 中启用 json |
google |
必需(key + cx) |
https://www.googleapis.com |
⚠️ 新客户已无法开通 | Google Custom Search JSON API。自 2026 年起不再向新客户开放(官方文档:"This API is not available for new customers"),并将于 2027-01-01 停服;新建的 Programmable Search Engine 只能做站点搜索(≤50 域名),无法搜整个网络。适配器保留仅供存量老 CSE 在停服前使用;新项目请勿投入。实测不具备资格的项目会返回 403 This project does not have the access to Custom Search JSON API(本插件归类为 config 错误并如实说明) |
brave |
必需 | https://api.search.brave.com |
以官方定价页为准(条款多次调整) | 请求时关闭 text_decorations(源头不产生 <strong>)并限定 result_filter=web |
exa |
必需 | https://api.exa.ai |
$10 credits/月 ≈ 1400 次 | 语义/神经检索,适合「找概念、找相似」;搜索结果默认不含正文,本插件显式请求 contents.text 取摘要(Exa 对 contents 单独计费 $1/1k 页) |
计费口径差异很大,务必分清:Tavily 与 Google 按请求计费(与
maxResults无关, 调大条数不花额度只花 Token);Exa 的搜索与 contents 分开计费。
🔀 调度规则(四个服务分别在什么时候用)
插件维护一条有序后端链:[0] 是主后端,其后按 chain 数组顺序排列。
搜索时从链首开始依次尝试,第一个成功的就返回。
一句话规则
按链的顺序依次降级。前面的不可用(失败 / 熔断 / 配额耗尽 / 缺凭据),就落到下一个。
推荐顺序
- id: dsh-plugin-tlsearch
config:
provider: tavily # 1
apiKey: "tvly-…"
chain:
- provider: exa # 2
apiKey: "…"
- provider: searxng # 3
baseUrl: "http://localhost:8080"
| 顺位 | 后端 | 什么时候真正被使用 | 额度 | 为什么排在这里 |
|---|---|---|---|---|
| 1 | tavily |
每次搜索的首选 | 1000 credits/月 | 按请求计费、字段干净、质量稳定,是这三家里最适合当主力的一档 |
| 2 | exa |
Tavily 不可用时 | ≈833/月 | 语义检索,检索方法与前一级不同 —— 前一级失效时它仍有独立价值 |
| 3 | searxng |
商业额度都耗尽 / 全部失效时的永久地板 | 无限 | 唯一不会用完的,放最后保证「永远还能搜」 |
为什么把有额度的放前面、无限免费的放最后? 因为免费月度额度按月过期,不用就是浪费; 而 SearXNG 是自建的、不会枯竭。所以正确策略是「先把会过期的额度烧完,把无限的留在最后兜底」, 而不是反过来「为了省钱先用免费的」—— 后者会让你每月白扔近 2000 次商业额度,还一直用质量最不稳定的那一层。
关于 Google CSE:曾经的「3000/月、真实 Google 结果」是最优选择,但 Custom Search JSON API 已不再向新客户开放(官方文档明确写着 "This API is not available for new customers"),并将于 2027-01-01 停服;新引擎只能做站点搜索(≤50 域名), 无法搜整个网络。因此不建议把它放进调度链 —— 适配器保留只为存量老 CSE 过渡用。
四条运行期规则
- 失败即降级。除「调用方主动中断」外,任何失败都改试下一级 —— 包括缺凭据、配额耗尽、 正在熔断中。这几项恰恰是最需要下一级救场的场景。
- 每级一个独立熔断器(连续失败 3 次 → 冷却 60 秒)。共用是错的:SearXNG 挂掉会把 健康的 Tavily 一起熔断,正好摧毁分级互补的全部价值。 副产品:Google 的日额度用尽后会连续失败,从而被熔断 60 秒,于是同一批搜索会直接 跳过它 —— 不必等你自己发现「今天已经搜了 100 次」。
- 调用方中断不降级。模型换了目标或宿主超时,用户已经不想要这次搜索了,不该再去打下一级。
- 只有全部失败才报错。此时抛合并错误,把每一级的原因都摆出来
(
all configured search backends failed — Google [http]: … | Tavily [timeout]: …); 只配了一级时则原样上抛,保留精确的错误码与文案。
其它细节:链上重复的 provider 只保留首次出现;fallback 是「单个备用」的简写,
等价于 chain 的第一个元素(兼容旧配置,两者同时存在时顺序为 fallback 在前、chain 在后)。
这套规则长什么样
宿主日志里有一行就是调度规则的可执行说明:
[tlsearch] 装配: 主=google@https://www.googleapis.com → 备1=tavily@https://api.tavily.com
→ 备2=exa@https://api.exa.ai → 备3=searxng@http://localhost:8080
而每一次降级都会留痕,否则你永远查不出「为什么这次结果风格变了」:
[warn] [tlsearch] Google 搜索失败(http),改试备用后端: google quota exhausted (HTTP 403) …
[info] [tlsearch] Tavily 返回 5 条结果 [via Tavily](query="…")
链上某一级配置不完整时只记
info(搜索仍能工作,只是少一层兜底); 只有全部不可用才升级为warn,以保证那条告警的信噪比。
📊 额度自查工具 tlsearch_usage
配置里存在 Tavily 后端时,会额外注册一个无入参的 tlsearch_usage 工具,
返回一行额度信息:
Tavily "Researcher": 137/1000 credits used this cycle; 863 remaining (each search costs 1 credit).
用途:让 agent 在搜索开始出现配额/限流错误时(或准备发起大批搜索前)自己查一下余量, 据此收敛搜索频率,而不是把额度打空后才发现。
设计取舍:只在配置了 Tavily 时注册。其余后端(Brave / SearXNG / Google / Exa) 没有机器可读的额度接口,注册一个永远只会说「请去控制台看」的工具纯粹是每轮 Token 浪费。 被调用时若后端确实不支持,会如实说明而不是编一个看起来合理的数字—— 一个假的剩余额度比没有额度信息更糟。
📦 安装
本包是 bundle 形态(package.json 的 dsh.bundle.patch 指向包内自带的
cordis.patch.yml)。因此只要被列进 profile 的 dsh.profile.bundles 就会自动激活,
不需要再往 profile 的 cordis.patch.yml 写挂载条目。
方式一:一条命令安装并激活(推荐)
dsh plugin --profile web add dsh-plugin-tlsearch
dsh plugin add 会自动装依赖并把该包归并进 dsh.profile.bundles(幂等)。bundle 形态
无需额外补丁条目,这正是 dsh plugin 分类器认可的形态。dsh plugin --profile web list
可核对激活状态,dsh plugin --profile web remove dsh-plugin-tlsearch 可一键摘除。
方式二:手工安装(等价)
cd ~/.dsh/profiles/web
pnpm add dsh-plugin-tlsearch
然后把它加进 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 数组:
"dsh": { "profile": { "bundles": ["…", "dsh-plugin-tlsearch"] } }
用本地源码调试时,依赖写成
"dsh-plugin-tlsearch": "file:D:/Code/my-dsh-plugins/packages/tlsearch", 并且同样必须出现在dsh.profile.bundles里才会生效 —— 只装依赖不激活是不会生效的。
⚙️ 配置
配置有两条路径:宿主 GUI 的 Settings > Plugins > Plugin configuration > tlsearch,
或在 Profile 目录下的 ~/.dsh/profiles/web/cordis.patch.yml 里按 id 覆盖
(注意是覆盖,不是再插一条 - insert: —— 挂载已由 bundle 完成,重复插入会挂载两次):
- id: dsh-plugin-tlsearch
config:
provider: searxng # tavily | searxng | google | brave | exa
baseUrl: "http://localhost:8080" # SearXNG 必填;也可指向自建代理
# apiKey: "…" # SearXNG 不需要;其余后端必填
# cx: "…" # 仅 provider=google 需要(CSE 引擎 ID)
fallback: # 可选:主后端失败/熔断时自动改用备用后端
provider: tavily
apiKey: "tvly-xxxxxxxxxxxx"
maxResults: 5 # 1-10,默认 5
maxSnippetChars: 250 # 单条摘要字符上限,默认 250(控制 Token 的主旋钮)
timeoutMs: 15000 # 单次请求超时(毫秒),默认 15000
outputFormat: markdown # markdown(最省)| json
language: "" # 如 zh / en;留空由后端判定
includeAnswer: false # 是否附带后端的一句话答案(默认 false 以省 Token)
也可以完全不在这里写
apiKey,改用环境变量(见下节),配置层就不必碰密钥。
配置项
| 配置项 | 默认值 | 说明 |
|---|---|---|
provider |
tavily |
主后端:tavily / searxng / google / brave / exa |
apiKey |
'' |
主后端密钥;留空时回退环境变量(SearXNG 可留空) |
baseUrl |
后端默认端点 | 主后端端点根地址;SearXNG 必填 |
cx |
'' |
Google CSE 引擎 ID;provider=google 时必填(与 apiKey 缺一不可) |
fallback |
none |
单个备用后端(简写,等价于 chain 的第一个元素)。字段同 chain 的元素 |
chain[] |
[] |
后备后端链:主后端失败/熔断时按数组顺序依次尝试。每项含 provider / apiKey / baseUrl / cx。重复的 provider 只保留首次出现 |
maxResults |
5 |
返回条数上限(钳制在 1-10) |
maxSnippetChars |
250 |
单条摘要字符上限(钳制在 40-2000) |
timeoutMs |
15000 |
单次请求超时(钳制在 1000-60000) |
language |
'' |
检索语言(Tavily 不发送该字段) |
includeAnswer |
false |
是否附带一句话答案(Tavily answer / SearXNG answers) |
outputFormat |
markdown |
markdown(最省 Token)或 json(结构化,便于精确解析) |
越界值一律钳制而非报错;
null/ 空串等「未提供」形态退回默认值而不是被压成下限。 配置不完整不会导致插件装载失败——工具照常注册,调用时返回分类错误, 便于模型把「去哪儿补配置」原样转述给你。
环境变量回退
配置留空时按顺序回退(通用变量优先于后端专用变量):
| 用途 | 变量 |
|---|---|
| 通用密钥 | TLSEARCH_API_KEY |
| 通用端点 | TLSEARCH_BASE_URL |
| 通用 Google 引擎 ID | TLSEARCH_CX |
| Tavily | TAVILY_API_KEY、TAVILY_BASE_URL |
| Brave | BRAVE_SEARCH_API_KEY、BRAVE_API_KEY、BRAVE_BASE_URL |
| SearXNG | SEARXNG_BASE_URL |
GOOGLE_CSE_API_KEY、GOOGLE_API_KEY、GOOGLE_CSE_ID、GOOGLE_CSE_CX |
|
| Exa | EXA_API_KEY、EXA_BASE_URL |
与原生 web_search 的关系
两者可以并存(模型按需选用)。若要替代原生搜索,在 dsh-tool-web 的配置里关掉它的搜索工具:
- id: tool-web
config:
search: false # 保留 web_fetch,仅关闭原生 web_search
🧾 输出格式
模型看到的只有结果本身,没有任何前言后语。
outputFormat: markdown(默认,最省)
1. DeepSeek Harness 文档 — https://example.com/docs
紧凑的 agent 运行时,支持工具注册与会话事件流。
2. 插件开发指南 — https://example.com/plugins
摘要为空时整行省略,不会渲染出只有空白的缩进行。零命中时返回
No results for "查询串".(回显查询串且截断到 120 字符)。
outputFormat: json
[{"title":"DeepSeek Harness 文档","url":"https://example.com/docs","snippet":"紧凑的 agent 运行时…"}]
恒为 [{ title, url, snippet }] 裸数组;开启 includeAnswer 时答案单独成块
({"answer":"…"}),结果块的形状不会漂移,解析方无需分情况处理。
🛡️ 超时、熔断与错误分类
- 超时:每次请求挂
AbortController,timeoutMs到点即中止; 同时向宿主声明timeoutMs + 5s的工具超时——先由本插件给出分类错误,宿主再兜底硬停。 - 熔断:连续失败 3 次即熔断,冷却 60 秒。冷却期内不发网络请求,
直接返回
circuit-open并提示「不要立即重试」。冷却期满放行一次试探, 试探失败立即重新熔断(而不是退回闭合并重新数满阈值)。 - 不计入熔断:调用方主动中断(
aborted)属于用户/宿主行为,不算后端故障。 - 体积上限:响应超过 2,000,000 字符直接拒绝,避免异常端点拖垮内存与 JSON 解析。
| 错误码 | 含义 | 处置 |
|---|---|---|
config |
配置不合法(缺 baseUrl、查询为空) |
按错误信息补齐配置 |
credential |
缺密钥或密钥被拒(HTTP 401/403) | 检查 apiKey 或环境变量 |
timeout |
后端未在 timeoutMs 内响应 |
调大超时或换后端;会计入熔断 |
aborted |
调用方主动中断 | 无需处理;不计入熔断 |
http |
其他 HTTP 错误(含 429 限流) | 看错误信息中的响应摘要 |
network |
传输层失败(DNS/连接/TLS) | 检查网络与端点可达性 |
response |
响应不是合法 JSON 或超体积 | SearXNG 需启用 json 格式 |
circuit-open |
熔断中 | 等待冷却,或换后端 |
🔧 后端配置要点
SearXNG(最常见的坑):默认只对浏览器提供 HTML,必须在其 settings.yml 中启用 JSON:
search:
formats:
- html
- json
未启用时本插件会返回明确提示(而不是把 HTML 页面当成结果解析)。
自建 / 中转网关:baseUrl 支持带路径前缀,例如 https://proxy.example/tavily;
Tavily 的密钥同时以请求头与请求体两种形式携带,因此 new-api 这类只读请求体的网关也能直接用。
Brave:需要有效的订阅令牌(X-Subscription-Token);本插件已在请求侧关闭文本装饰
并限定 result_filter=web,避免 <strong> 标记与 infobox/FAQ 噪声进入上下文。
🛠️ 开发
本仓库统一使用 pnpm(见根目录 AGENTS.md):
# 类型检查(tsc --noEmit,零错误)
pnpm --filter dsh-plugin-tlsearch run typecheck
# 构建产物:dist/index.js (ESM) + dist/index.cjs (CJS) + dist/index.d.ts
pnpm --filter dsh-plugin-tlsearch run build
# 单元测试(112 个用例,零网络:全部使用假 fetch 与假 Cordis 上下文)
pnpm --filter dsh-plugin-tlsearch run test
# 全工作区递归构建
pnpm run build
源码结构
| 文件 | 职责 |
|---|---|
src/index.ts |
插件入口:Config schema、apply 装配、生命周期与可逆注销 |
src/tool.ts |
模型可见工具:定义、参数校验、熔断判定、在途请求登记、错误分类 |
src/providers.ts |
三个后端适配器 + 配置解析 + 归一化管线(清洗/去重/截断) |
src/http.ts |
超时、体积上限、熔断状态机、错误分类 |
src/sanitize.ts |
HTML/实体/空白清洗、词边界截断、URL 埋点参数剔除 |
src/format.ts |
工具返回契约收敛层 + 紧凑渲染(markdown/json) |
src/types.ts |
领域模型与 Cordis 上下文类型合并 |
两个值得记录的宿主事实
- 工具返回必须是
{ content: [{ type: 'text', text }] }。 宿主的工具流水线会按output.schema校验返回值,再以(args, value)双参调用output.render,随后对结果执行.some(...)。若render返回裸字符串, 宿主会抛content.some is not a function且工具链不可恢复。 本插件对畸形输入做兜底归一,任何情况下都返回合法非空块数组。 - Cordis 3.0.0 会丢弃
apply的返回值(ensure(async () => plugin.apply(...)))。 因此真正的卸载路径绑定在ctx上:工具注销器由ctx.tools.register返回(本身即 effect), 在途请求中止与熔断复位挂在ctx.on('dispose', …)。同时返回幂等的dispose函数, 对会消费返回值的宿主与单元测试同样可用。
🤝 贡献
欢迎 Issue 与 PR:https://github.com/ltl0312/my-dsh-plugins
- 新增搜索后端:在
src/providers.ts实现ProviderAdapter(build+parse)并注册进PROVIDERS;清洗、去重、截断、熔断、限流由共享管线负责,无需重复实现。 - 提交前请确保
typecheck/build/test三项全绿。 - 本仓库一律使用 pnpm(
package.json的packageManager+devEngines与.npmrc的engine-strict=true会拒绝 npm)。
📄 License
MIT License
本项目是 DeepSeek Harness 的第三方插件,与 DeepSeek 官方无隶属关系。
链接
同类插件
Tencent/WeKnora#dsh-weknora★ 34412
把 WeKnora 知识库接入 dsh 的四个只读工具:列出知识库、混合检索原文片段、按顺序还原单篇文档,以及直接取用 WeKnora 自己带引用的 RAG 或 ReAct agent 回答(含可续聊的 session id)。
superdesigndev/treg★ 5565
给 Agent 的工具目录:按「要做的事」检索约 2,600 个外部接口(SEO 与 SERP、外链、社交、人物与公司信息补全、广告库、抓取),查看参数与单次调用价格后直接调用,凭据由服务端注入。附带技能,MCP 行在未设置 TREG_TOKEN 前保持禁用。
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1138
把腾讯云 CloudBase 后端接入 DeepSeek Harness——在对话里搭好并部署全栈应用,查询结果渲染为表格卡片(分页、排序、导出 CSV),部署后可预览真实域名,并提供 CloudBase MCP 工具集(`mcp__cloudbase__*`),登录走 device-code 流程。
gitroomhq/postiz-agent#dsh-postiz★ 520
通过 MCP 将 DeepSeek Harness 连接到 Postiz:列出已连接的社交媒体渠道、获取各平台发帖规则,并向 X、LinkedIn、Instagram、Facebook、Threads、TikTok、YouTube、Reddit、Bluesky、Mastodon、Discord、Slack、Telegram 等平台排期、存草稿或发布帖子;附带 postiz 工作流技能。
EthanYoQ/Invoice-Downloader#dsh-invoice-downloader★ 509
面向 DeepSeek Harness 的本地 IMAP 发票下载、OCR 识别、归档与 Excel 报销汇总。
anysearch-team/anysearch-dsh★ 504
基于 AnySearch 的实时网页与垂直搜索插件,为 DeepSeek Harness 提供搜索工具。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。