news 2026/9/18 13:18:40

wigolo 插件开发实战:为 AI Agent 接入自定义搜索引擎与内容提取器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
wigolo 插件开发实战:为 AI Agent 接入自定义搜索引擎与内容提取器

wigolo 插件开发实战:为 AI Agent 接入自定义搜索引擎与内容提取器

【免费下载链接】wigoloThe go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta.项目地址: https://gitcode.com/GitHub_Trending/wi/wigolo

wigolo 是一套面向 AI 编码 Agent 的本地优先检索方案,通过 MCP 提供搜索、抓取、爬取与研究能力。本文聚焦其插件体系:如何用纯 Node 模块为 wigolo 接入自定义搜索引擎(内部 Wiki、私有索引、垂直领域引擎)和站点级内容提取器(修复提取质量差的站点),并深入解析插件加载、校验、注册的完整链路与失败隔离机制。读完本文,你将掌握从wigolo plugin add到编写完整插件、再到用plugin validate排查问题的全套实战能力。

插件体系一览:两类插件、一个目录

wigolo 从~/.wigolo/plugins目录加载两类插件,目录位置可用环境变量WIGOLO_PLUGINS_DIR覆盖:

  • 搜索搜索引擎(search engine):加入多引擎搜索调度池,与内置引擎并列参与融合、去重与端上重排;
  • 内容提取器(content extractor):在通用提取流水线之前获得优先机会,把页面转成 Markdown 结构化结果。

两类插件都是普通 Node 模块——不需要构建步骤,不依赖任何框架,一个目录加一个入口文件即可。

从配置实现看,插件目录的解析位于 src/config.ts:优先读取WIGOLO_PLUGINS_DIR(若以~开头会展开为主目录),否则回退到dataDir/plugins(即~/.wigolo/plugins)。因此安装插件前确认WIGOLO_PLUGINS_DIR是否被设置,就能准确判断实际生效的插件目录。

插件管理命令:add / list / validate / remove

插件管理的 CLI 入口在 src/cli/plugin.ts,完整用法如下:

wigolo plugin add https://github.com/you/your-plugin # clone;安装前会弹出信任确认 wigolo plugin list [--json] # 列出已安装插件 wigolo plugin validate [--json] # 校验插件能否正确加载与导出 wigolo plugin remove <name> # 卸载插件

plugin add:先确认信任,再执行 clone

plugin add会把 git 仓库克隆进插件目录。因为插件是会在 wigolo 进程内运行、且拥有你的凭据与网络访问权限的代码,所以命令在 clone 之前必须经过用户确认——只安装你信任或阅读过源码的插件。

从 src/cli/plugin.ts 的实现看,信任边界被刻意做得非常显眼:

  • 未传--yes时,会先在 stderr 打印安装横幅,展示url、解析出的repo名和目标目录target,并明确警告"克隆的仓库会在每次 wigolo 服务启动时以 Node 代码运行";
  • 随后从 TTY 读取一行确认(Install this plugin? [y/N]),只有输入y/yes才继续;
  • 非交互式(CI、管道)环境下默认拒绝安装,必须显式传--yes才会放行;WIGOLO_PLUGIN_AUTO_YES=1环境变量是脚本化安装的等价开关;
  • clone 使用git clone --depth 1(浅克隆),超时 60 秒。

安装完成后,命令会检查目标目录的package.json是否存在、是否有main字段,缺任一都会给出 WARNING,提示插件可能无法正常加载——这是 add 阶段的第一道体检。

plugin list:结构化盘点

list扫描插件目录,读取每个子目录的package.json,输出插件名与版本(缺失时回退为目录名与unknown)。加--json时向 stdout 输出单条 JSON 文档({"plugins": [...]}),便于脚本消费。

plugin validate:加载与导出契约校验

validate会对每个已安装插件做静态校验package.json是否存在、main字段是否存在且指向的文件真实存在于磁盘。值得注意的是,注释明确说明这是"lint 而非 load"——它刻意不 import 插件(import 会执行插件代码),因此可以在不运行任意代码的前提下给出快速体检。命令以退出码表达结果:全部通过返回 0,任一失败返回 1;--json模式输出{"status": "ok"|"error", "plugins": [...]}

plugin remove:安全卸载

remove会先做名称合法性校验(拒绝路径分隔符、..、绝对路径),再删除对应目录;插件不存在时报错退出。

插件包结构:package.json + 入口模块

一个插件就是一个目录,其package.jsonmain指向入口模块:

{ "name": "my-wigolo-plugin", "version": "1.0.0", "main": "index.mjs" }

入口模块导出searchEngineextractor,或两者同时导出。导出会在加载时被校验,无效的插件会被报告并跳过,绝不会拖垮服务器。

加载器 src/plugins/loader.ts 的具体流程是:读取并解析package.json(缺失或 JSON 解析失败 → 记错误并跳过);要求存在main字段;用pathToFileURL把入口路径转成file://URL 后await import()——这意味着插件可以用 ESM,也可以从 CommonJS 模块导出。加载成功的模块进入validatePluginExports校验。

搜索引擎插件:把私有索引送进多引擎调度池

搜索引擎插件遵循的契约定义在 src/types.ts:

interface SearchEngine { name: string; search(query: string, options?: SearchEngineOptions): Promise<RawSearchResult[]>; }

其中SearchEngineOptions(src/types.ts)携带调度器传入的丰富上下文:maxResults(结果数上限)、timeRangelanguagetimeoutMsincludeDomains/excludeDomainsfromDate/toDatecategorygeneral/news/code/docs/papers/images)、country(ISO 3166-1 alpha-2 国家码)等。一个尊重这些选项的插件,才能与内置引擎在同等条件下公平竞争。

返回值RawSearchResult(src/types.ts)的核心字段为titleurlsnippetrelevance_scoreengine;可选的published_date(ISO 日期串)会在解析成功时驱动freshness_signalimage_url/image_alt会在调用方开启include_images时汇入图片聚合。evidence_score_score_breakdown等字段则由核心编排器在排序后统一填充,插件无需关心。

一个完整可用的搜索引擎插件

仓库自带的 examples/plugin-search-engine/index.mjs 就是整个插件的全部内容,可直接复制作为起点:

export const searchEngine = { name: 'example-search-engine', async search(query) { return [ { title: `Example result for ${query}`, url: 'https://example.com/search-engine-example', snippet: 'Minimal search engine plugin example.', relevance_score: 1, engine: 'example-search-engine', }, ]; }, };

就是这样——一个内部 Wiki、一个私有索引、一个垂直领域引擎,用不到 100 行就能进入调度池。插件返回的结果会与内置引擎一起走完全相同的**融合(fusion)、去重(dedup)、端上重排(on-device reranking)**流程,并像其他引擎一样出现在engines_used/engine_telemetry字段中。

搜索引擎插件的运行时接入

在服务器启动阶段(src/server.ts),wigolo 先构造内置引擎(BingEngineDuckDuckGoEngine),随后调用loadPlugins();每个通过校验的插件搜索引擎会被推入searchEngines数组,参与后续的统一调度。这也解释了为什么插件引擎的返回结果能天然进入engines_used(贡献了至少 1 条去重后结果的引擎)与engine_telemetry(每个被尝试引擎的原始延迟、结果数、结果、dedup_kept)——因为它在调度器眼中与内置引擎没有任何区别。

内容提取器插件:站点级修复的优先通道

提取器插件的契约同样定义在 src/types.ts:

interface Extractor { name: string; canHandle(url: string, html?: string): boolean; extract(html: string, url: string): ExtractionResult | null; }
  • canHandle(url, html?)决定你的提取器认领哪些 URL——典型场景是"公司文档平台的特殊 DOM 结构";
  • extract(html, url)返回结构化结果;返回null则把页面交还给内置提取器组合(defuddle → readability → turndown 的兜底链)。

插件提取器在通用流水线之前被咨询,所以站点专属提取器正是修复"某个站点提取质量差"的官方手段。

ExtractionResult(src/types.ts)要求返回titlemarkdownmetadata(可选description/author/date/language/og_image/canonical_url/keywords等)、linksimagesextractor类型。额外的site_data字段(Reddit、YouTube、Amazon 等站点提取器会填充的每站点结构化 JSON)可被后续调用方直接消费,而无需再从 Markdown 里反向抓取。

提取器插件的调用链

从源码看,插件提取器与内置站点提取器共享同一注册表:服务器启动时(src/server.ts)对每个插件提取器调用registerExtractor(ext),而 src/extraction/pipeline.ts 的registerExtractor是兼容别名,真正写入 src/extraction/v1/site-extractors.ts 的共享列表——因此 v1 路由同样能看到插件注册的提取器。

在 v1 提取路由中(src/extraction/v1/routed.ts),trySiteExtractorsextractors.find((e) => e.canHandle(url, originalHtml))找到第一个认领该 URL 的提取器并调用其extract;没有匹配则落入 defuddle/readability/turndown 的兜底链。这意味着:插件提取器拥有最高优先权canHandle返回true即接管该 URL 的提取任务。

加载与校验机制源码解析

插件加载的核心实现在 src/plugins/loader.ts,整体结构清晰:

  1. 读取配置,得到插件目录;目录不存在或读取失败时静默返回空结果(服务器正常启动);
  2. 遍历目录下每个子目录(statSync会跟随符号链接,因此符号链接的插件目录同样被支持);
  3. 读取package.json——缺失或解析失败记入错误列表;无main字段报"no main field";main指向的文件不存在报"entry point not found";
  4. 通过import()加载入口模块,抛错则记入错误并continue
  5. validatePluginExports(src/plugins/validate.ts)校验导出,收集所有不满足契约的原因。

校验规则本身很直接:extractor导出必须是对象,且name为非空字符串、canHandleextract均为函数;searchEngine导出同理要求name非空、search为函数;两者都不合法时给出"neither a valid extractor nor a valid searchEngine"的提示。校验通过后进入 src/plugins/registry.ts 的PluginRegistry登记。

值得注意的细节是名称去重:loader 与 registry 两层都会用Set拦截同名 extractor / search engine,重复名称的注册会被警告并跳过,避免调度时发生歧义。加载完成后,服务器日志会汇总"加载了 X 个提取器、Y 个搜索引擎、Z 个错误",同时plugin validate会以退出码 1 反映失败状态,便于 CI 集成。

失败行为:端到端防御性设计

插件加载是全程防御性的:package.json缺失、main错误、import 抛异常、导出无效——每一种失败都会在plugin validate(以及服务器日志)中产生针对该插件的独立错误信息,而其他所有插件与服务器本体继续正常工作。

这一设计的证据贯穿全链路:

  • 加载器对单个插件任何一步失败都continue到下一个,绝不中断整体循环(src/plugins/loader.ts);
  • 服务器启动对loadPlugins()的整体调用也包在 try/catch 里,即便最坏情况出现,也只是记录错误而不会终止启动(src/server.ts);
  • plugin validate的静态校验甚至刻意不 import 插件,把"检查插件"与"执行插件"彻底分离,让体检过程本身零风险。

实战路线:从复制示例到生产插件

  1. 复制起点:拷贝 examples/plugin-search-engine/(含index.mjs与配套package.json),按需改名;
  2. 本地调试:把插件目录放入~/.wigolo/plugins(或WIGOLO_PLUGINS_DIR指向的目录),重启 wigolo,观察日志中"loaded plugin search engine";
  3. 静态体检wigolo plugin validate确认契约完整;非交互环境直接wigolo plugin add <git-url> --yes走脚本化安装;
  4. 发布与安装:推送到 git 仓库后,用wigolo plugin add <git-url>在目标机器安装(会再次经过信任确认);
  5. 回归检查:仓库的插件测试(见 tests/integration/plugins/ 与 tests/unit/plugins/)覆盖了加载、校验、注册的典型路径,可作为你编写插件时验证行为预期的重要参考。

插件目录相关的问题排查,可结合 docs/troubleshooting.md;插件体系与搜索、提取功能的整体关系,见 docs/README.md。

【免费下载链接】wigoloThe go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta.项目地址: https://gitcode.com/GitHub_Trending/wi/wigolo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 13:16:53

7 款免费开源 PDF 工具实测指南:Acrobat 替代品怎么选

7 款免费开源 PDF 工具实测指南&#xff1a;Acrobat 替代品怎么选 【免费下载链接】Adobe-Alternatives A list of alternatives for Adobe software 项目地址: https://gitcode.com/GitHub_Trending/ad/Adobe-Alternatives PDF 订阅费不便宜&#xff0c;安装包也不小&a…

作者头像 李华
网站建设 2026/9/18 13:16:25

Python+OpenGL绘制3D模型(六)材质文件载入和贴图映射

系列文章 基础 Python+OpenGL绘制3D模型(一)Python 和 PyQt环境搭建 Python+OpenGL绘制3D模型(二)程序框架PyQt5 Python+OpenGL绘制3D模型(三)程序框架PyQt6 Python+OpenGL绘制3D模型(四)绘制线段 Python+OpenGL绘制3D模型(五)绘制三角型 Python+OpenGL绘制3D模型(…

作者头像 李华
网站建设 2026/9/18 13:16:18

UltraISO光盘镜像制作与ISO文件系统深度解析

简介&#xff1a;本资源是一份面向初学者与系统维护人员的UltraISO光盘镜像制作实操指南&#xff0c;聚焦解决光盘备份、可启动系统盘创建及ISO文件管理等实际需求。文档以图文结合方式详解核心操作&#xff1a;从插入光盘后通过“工具→制作光盘映像文件”完成标准ISO生成&…

作者头像 李华
网站建设 2026/9/18 13:16:15

基本数据分析和统计描述

&#xff08;1&#xff09;首先安装、载入两个拓展包并导入数据#拓展包安装 install.packages(table1) install.packages(lubridate) library(table1) library(lubridate)#载入数据 load(diagnosis.Rdata)&#xff08;2&#xff09;进行一些分析前处理#新生成一个分类变量&…

作者头像 李华
网站建设 2026/9/18 13:14:37

130套中国风PPT模板:分类、配色、字体、版式与场景改造指南

这几年做汇报、做课件、帮朋友改路演材料&#xff0c;我电脑里攒下的PPT模板少说也有大几百套。但真正常用的&#xff0c;翻来覆去就那么一批中国风的。原因很简单&#xff1a;中国风模板的适用面比很多人想象的要宽得多&#xff0c;一场年终总结、一次传统文化主题的班会、一份…

作者头像 李华