trae本地部署大模型并接入deepseek harness第二章 搜索引擎的使用
给 DeepSeek Harness 写一个免 Key 联网搜索插件360 Bing 双引擎实战上篇[博客-用Trae本地部署MiniCPM5-2B并接入DeepSeek-Harness.md](file:///d:/ai/1/博客-用Trae本地部署MiniCPM5-2B并接入DeepSeek-Harness.md)硬件/环境不变RTX 5050 8GB | Windows | 成本0 元 | 代码仍然全部由 Trae 编写我负责实测、选型拍板、复制报错。一、上篇留下的尾巴上篇结束时本地 2B 小模型已经能在 DeepSeek Harness 里当 Agent 用了但有一件事很别扭模型是本地的联网搜索却是租来的。Harness 自带的web_search工具默认走 DeepSeek 官方搜索通道需要DEEPSEEK_API_KEY。也就是说每搜一次东西我的搜索词都要经过 DeepSeek 的云端服务Key 欠费、断网、或者纯粹不想被记录搜索就废了。这一篇要做的事给 Harness 写一个新的搜索提供方插件不申请任何 Key直接抓取公开搜索页解析结果让原生web_search工具整条链路落在本地。最终效果项目结果插件deepseek-ai/dsh-web-search-bing本地 workspace 包version0.1.0-local引擎360 搜索www.so.com优先cn.bing.com 兜底单引擎结果不足 3 条自动换引擎Key不需要零配置即可用DeepSeek Key 留着也只作备用切换方式profile 的cordis.patch.yml一行searchProvider: bing验证单元直测 headless 端到端泛中文查询返回真实新闻结果插件源码在 [web-search-bing](file:///d:/ai/1/harness/deepseek-harness/packages/web/web-search-bing) 目录。二、先搞懂 Harness 的插件机制五分钟版Harness 基于 Cordis 插件框架。不用读全文档写搜索插件只需要记住三个概念1. Seam接缝。Harness 把可替换能力做成注册表。Web 能力的接缝在ctx.web谁都可以往里注册搜索提供方。核心接口定义在 [types.ts](file:///d:/ai/1/harness/deepseek-harness/packages/web/web/src/types.ts)interfaceWebSearchProvider{id:string// 提供方唯一标识profile 里用它选择available():boolean// 同步判断不许发网络请求search(request:WebSearchRequest,signal?:AbortSignal):Promise{sources:WebSearchSource[];truncated:boolean}}interfaceWebSearchRequest{query:string;maxResults?:number}interfaceWebSearchSource{url:string;title?:string;snippet?:string;publishedAt?:string}2. 插件 一个带apply(ctx, config)的模块声明自己inject: [web]在apply里调ctx.web.registerSearchProvider(new 我的Provider())就完事。官方的 Exa 搜索包 [web-search-exa](file:///d:/ai/1/harness/deepseek-harness/packages/web/web-search-exa) 是最好的结构模板我的包目录结构照抄它。3. 两处 YAML 决定生死bundle 的 [cordis.patch.yml](file:///d:/ai/1/harness/deepseek-harness/packages/bundle/base/cordis.patch.yml)把插件包登记进加载清单- id: web-search-bing / name: deepseek-ai/dsh-web-search-bing用户 profile 的补丁%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml选择用哪个提供方-id:webconfig:searchProvider:bing# 对应插件注册时的 id改回 deepseek-official 即恢复官方通道登记和选择分离意味着插件可以常驻、随时切换不用删代码。三、引擎选型这件事必须人来拍板免 Key 抓搜索页的方案有好几个候选Trae 把每个站点都实测了一遍我来根据结果做决定候选国内可达泛中文查询强实体查询结论DuckDuckGo否被墙——排除搜狗是返回 5KB 反爬验证页—排除百度是反爬严重结构不稳定—排除cn.bing.com是差搜2026年 国内 大模型 开源 最新消息返回节假日页面只匹配上2026年极好“MiniCPM5 面壁智能”、DeepSeek 融资全部准确兜底引擎www.so.com360是好返回财经网、36氪、光明网等真实新闻好默认引擎Bing 的反差很有意思它的分词对一长串泛中文描述处理得很烂对专有名词 限定词几乎完美。360 则相反泛查询的相关性明显更懂中文语境。最终决定双引擎360 优先、Bing 兜底某引擎返回不足 3 条或直接失败就自动尝试下一个。代码上的策略只有几行for(constengineofthis.options.engines){// 默认 [360, bing]constsourcesengine360?awaitthis.searchSo360(query,signal):awaitthis.searchBing(query,count,signal)if(sources.lengthMIN_GOOD_RESULTS||engine最后一个引擎){return{sources:sources.slice(0,count),truncated:false}}lastError...// 不足 3 条记下来继续试}这个3 条的阈值MIN_GOOD_RESULTS也是实测拍的2B 小模型做 Agent 时拿到 3 条以上相关链接基本就能组织回答少于 3 条说明引擎没理解查询换一个比硬凑强。四、插件实现要点4.1 包结构照抄 exa 包的布局一共就四个实质文件packages/web/web-search-bing/ ├── package.json # name deepseek-ai/dsh-web-search-bingprivate0.1.0-local ├── tsconfig.json # extends tsconfig.base.jsonreferences 指向 web/cordis/schemastery ├── src/ │ ├── index.ts # Cordis 插件Config schema apply() 注册 │ └── provider.ts # BingSearchProvider 两个 HTML 解析器全部逻辑在这 └── lib/ # 运行时产物 ├── index.js # 一行 re-export shim见踩坑第 2 条 └── types/ # tsc 编译输出的 .js / .d.tspackage.json里只声明 peerDependenciesdsh-web、dsh-launch-environment、cordis用 monorepo workspace 版本唯一的实际依赖是 schemastery配置 schema没有任何 HTTP 库——Node 24 原生fetch足够。4.2 抓页面浏览器头 超时 取消传播两个引擎都是普通 GET 搜索页 HTML。关键细节必须带桌面 Chrome 的User-Agent否则要么被当爬虫拦、要么给移动版导致选择器全变超时用AbortSignal.timeout(15000)还要和调用方传入的signal合并这样用户在 Web UI 点停止时抓取会立刻中断consttimeoutSignalAbortSignal.timeout(this.options.timeoutMs)constsignalouterSignal?AbortSignal.any([outerSignal,timeoutSignal]):timeoutSignalconstresawaitfetch(url,{headers:{User-Agent:DESKTOP_UA,Accept-Language:zh-CN,zh;q0.9},signal,})4.3 解析 360 页面360 的结果块是li classres-list标题在h3.res-title a摘要在p.res-desc。最大的坑是链接href里放的是https://www.so.com/link?m...跳转地址真实 URL 藏在同一个a标签的data-mdurl属性里必须优先取它constmdMatch/data-mdurl([^])/.exec(attrs)// 真实 URLconsthrefMatch/href([^])/.exec(attrs)// 跳转链接仅在非 /link 时兜底另外页面顶部的新闻聚合卡用的是h3.g-title而不是res-title解析器只认res-title就自然把它们排除了自家产品页ai.so.com回答卡、news.so.com标签页再用一条正则过滤掉。4.4 解析 Bing 页面Bing 结果块是经典的li classb_algo标题在h2 a摘要在.b_lineclamp元素。链接也有坑国内版大量结果不是直链而是/ck/a?ua1base64url...形式的跳转需要把u参数里的a1前缀去掉再做 base64url 解码exportfunctionunwrapBingLink(href:string,baseURL:string):string{constm/[?]ua1([^])/.exec(href)if(!m||m[1]undefined)returnnewURL(href,baseURL).hrefconstb64m[1].replace(/-/g,).replace(/_/g,/)constdecodedBuffer.from(b64,base64).toString(utf8)return/^https?:\/\//.test(decoded)?decoded:newURL(href,baseURL).href}4.5 错误码要讲规矩失败时不能随便throw new Error()。Harness 的工具层靠WebError的code字段做路由区分用户取消和提供方挂了官方 deepseek/exa 提供方用的三个码我们照搬用户取消抛WEB_ABORTED抓取/解析失败抛WEB_PROVIDER_ERROR。这样 Web UI 上的报错提示和重试行为都和官方提供方一致。五、接入 monorepo一共五个改动点新包光存在还不够得让 bundle 认识它。Trae 一共动了五个地方这也是复刻时的完整清单新建插件包packages/web/web-search-bing/package.json / tsconfig.json / src。bundle 依赖在 [packages/bundle/base/package.json](file:///d:/ai/1/harness/deepseek-harness/packages/bundle/base/package.json) 加一行deepseek-ai/dsh-web-search-bing: workspace:*。插件登记在 [packages/bundle/base/cordis.patch.yml](file:///d:/ai/1/harness/deepseek-harness/packages/bundle/base/cordis.patch.yml) 的插件列表加-id:web-search-bingname:deepseek-ai/dsh-web-search-bingTS 路径映射在 [tsconfig.base.json](file:///d:/ai/1/harness/deepseek-harness/tsconfig.base.json) 的paths加包名到packages/web/*/src的映射让 tsx 直跑源码时能解析到。profile 选择提供方web 和 headless 两份%USERPROFILE%\.dsh\profiles\profile\cordis.patch.yml都加上第三节那段searchProvider: bing。然后离线安装链接 workspace本机访问不了 npmjs 官方源加--offline纯 workspace 依赖不碰网络12 秒完成cd d:\ai\1\harness\deepseek-harness npx-y pnpm11.7.0 install--offline--no-frozen-lockfile六、踩坑清单按消耗时间排序1. Cordis 会把没配置的数组字段给成[]??救不了你。这是最坑的一条。第一版默认引擎写的是config.engines ?? [360,bing]单元测试手动传参全过端到端却报Bing 提供程序不可用。排查发现插件确实加载注册了但available()返回 false——因为 Cordis 用 schema 实例化 config 时未配置的数组字段不是undefined而是空数组[]空数组既不是 nullish 又通不过至少一个引擎的校验。最终写法是显式判长度constenginesconfig.enginesconfig.engines.length0?config.engines:[360,bing]教训配置默认值不要只在单测里验一定要过一遍真实框架装配。2. 官方lib/index.js是打包合并的单文件不知道是哪步构建产物。exa 包里 tsc 只产出lib/types/*.js .d.ts真正的入口lib/index.js是把所有模块拼在一起的 bundle文件里还留着//#region标记。没找到对应构建脚本手写合并文件又容易和 src 脱节。最后的方案极简lib/index.js只写一行export * from ./types/index.js;——Node ESM 原生支持相对路径 re-exportloader 按包名导入时行为完全一致还永远不会和 src 不同步。3. 严格模式下正则的捕获组是string | undefined。tsc 开了noUncheckedIndexedAccessmatch[1]不能直接喂给要求string的函数每个捕获组都要显式判空未使用的函数参数360 没有每页条数参数最初沿用了 Bing 的count形参在noUnusedParameters下直接编译失败。都是小错但每个都挡一轮编译Trae 连着修了三轮才过。4. 360 的href是假链接。直接解析href拿到的全是so.com/link?m...跳转地址模型拿到这种 URL 再去web_fetch就是死路。data-mdurl属性才是直链这一条是看原始 HTML 看出来的。5. 验证要分三层。第一层 Node 直连解析器快验证选择器第二层从packages/bundle/base目录按包名import模拟 loader 的真实解析路径能抓出 peer 依赖链接问题第三层 headless 端到端$env:OLLAMA_API_KEYollamanpx-y pnpm11.7.0 dsh--profile headless请搜索2026年 国内 大模型 开源 最新消息。用中文给出3条带标题和链接的结果。第三层才暴露出第 1 条那个[]的坑——前两层永远发现不了。七、实测结果泛中文查询之前 Bing 翻车的那条端到端返回节选1. 科大讯飞2026年9月最新动态星火大模型开源与基座模型即将发布 https://k.sina.com.cn/article_7879776887_1d5abda7706803myus.html 2. MWC围观、OpenClaw屠榜2026AI变天从陪聊到干活中国开源模型成全球开发者首选 https://tech.ifeng.com/c/8rEtnmfAoky 3. 2026年开源大模型爆发性能追平GPT-5成本低10倍今日头条 https://www.toutiao.com/article/7622291427040494080/实体查询走 360 也很准Bing 作为兜底待命面壁智能开源MiniCPM5-2B让智能体有望在终端本地运行 _ 光明网 面壁智能发布端侧模型 MiniCPM5-2B完成多款芯片适配 _ 腾讯新闻 面壁智能开源MiniCPM5-2B4B以下综合表现最优 _ 搜狐整条链路2B 本地模型决定该搜了 → 原生web_search工具 → 我们的插件 → 360/Bing 公开页面 → 结果回到模型。除了搜索引擎本身不经过任何付费云服务。八、隐私边界说清楚什么本地了什么没有✅ 推理在本地模型回答不离开显卡✅ 搜索不经过 DeepSeek不再需要 API Key没有账户配额没有云端日志⚠️但搜索词仍然会发给 360 和 Bing——这是免 Key 抓网页方案的本质不可能既用搜索引擎又不让它知道你搜了什么。真正敏感的查询应该直接不搜⚠️ 公开 HTML 解析依赖页面结构搜索引擎改版会导致解析失效接口契约是我们自己定的适配层修解析器即可插件其余部分和上层工具不受影响。九、Trae 在这次任务里干了什么和上篇一样人的工作只有三类提供环境事实哪些站点能访问DuckDuckGo 不通、百度反爬、pnpm 必须走--offline、profile 补丁路径做判断题五个引擎候选实测完拍板360 优先 Bing 兜底 3 条阈值复制报错把端到端的提供程序不可用和 tsc 的红字贴回去。其余全是 Trae 的读官方 exa/deepseek 插件源码反推接口、逐个站点抓 HTML 对比结构、写解析器和正则、动 monorepo 的五个接入点、根据报错连修三轮严格模式类型问题、在我误删文件内容后补回。一个值得记下的方法论这次所有真正耗时的 bug全部死在单元测试通过、真实装配失败这一层空数组 config、合并 bundle、包名解析。AI 写代码很快但像真实运行时一样验证这件事必须显式要求它做——这也是我现在给 Trae 派活时固定会写的一条。复刻入口插件源码 [packages/web/web-search-bing](file:///d:/ai/1/harness/deepseek-harness/packages/web/web-search-bing)切换搜索引擎只改 profile 补丁里searchProvider一个值bing 免 Key 双引擎deepseek-official 官方云搜索。改完用d:\ai\1\ai-stack.ps1 restart重启即可生效。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →