やってみた 2026年7月18日

APIキーもクラウドも要らないwigoloで、AIエージェント用のWeb検索を使い捨てコンテナで試してみた

XECIN TypeScriptMCP検索AIエージェント

GitHubのトレンドを眺めていたら、KnockOutEZ/wigolo というのが目に留まりました。キャッチコピーが「AIエージェントのための、キーもクラウドも従量課金もないWeb(web intelligence)」。search / fetch / crawl / extract あたりを、APIキーなしでローカル完結させるMCPサーバ兼CLIだと書いてあります。

私はふだんインフラ寄りでコストを気にする立場なので、この手の「AIエージェントにWeb検索させる」系はいつも従量課金がネックだと感じていました。SerperなりTavilyなりの検索APIは、エージェントが「考えるほど」クエリが増えて請求も伸びる。運用を考えると、その青天井が一番怖いんですよね。

で、wigoloは「コアツールはキーレス、触ったものは全部 ~/.wigolo/ から出ない、クエリ量で伸びる請求もない」と言い切っている。本当にキーなしでどこまで動くのか、使い捨てのコンテナに放り込んで確かめてみました。

試した環境

  • ホストはWindows + Docker Desktop、検証は使い捨ての node:22-bookworm コンテナの中だけで完結
  • コンテナ内: Node.js v22.23.1 / npm 10.9.8(Debian 12 bookworm)
  • 対象: wigolo 0.2.0(GitHubのタグ v0.2.0、commit a15277bb

対象のインストールも実行も、全部コンテナの中でやっています。ホストには何も入れていません。

インストールは npm 一発、22秒

まずは素直に入れます。READMEは npx wigolo init --agents=<your-agent> でエージェントのMCP設定まで書き込む流れを推していますが、今回はエージェント連携ではなくCLIとして素の挙動を見たいので、グローバルに入れました。

$ npm install -g wigolo
114 packages are looking for funding
INSTALL_SECONDS=22

$ wigolo --version
wigolo 0.2.0

依存114パッケージ、22秒。ここは軽いです。重い機械学習のホイールがドカッと落ちてくるようなことはありませんでした。

次に wigolo doctor で健康診断。これがけっこう丁寧で、検索エンジンのアダプタが1つずつ疎通確認されます。

$ wigolo doctor
  duckduckgo      general    ok
  bing            general    ok
  marginalia      general    ok
  mojeek          general    ok — note: may intermittently 403 (IP reputation / rate-limit)
  wikipedia       general    ok
  brave           general    disabled (set BRAVE_API_KEY to enable this engine)
  arxiv           papers     ok
  semantic-scholar papers     ok
  ...
[wigolo doctor] Core sqlite-vec:  extension: loaded (vec_version v0.1.9)
[wigolo doctor] Core embeddings:  provider: not installed (lazy — downloads on first use)
[wigolo doctor] Overall: OK

DuckDuckGo・Bing・Marginalia・Mojeek・Wikipedia・arXiv・Semantic Scholar あたりがキーレスで ok。一方 Brave だけは BRAVE_API_KEY を入れないと disabled になる、という具合に「キーが要るものと要らないもの」がここで一目で分かるのは良い設計です。ベクタ検索用に sqlite-vec(vec_version v0.1.9)が最初から積まれていて、埋め込みモデルだけは「初回利用時に遅延DL」になっている、というメモも出ました。この「遅延DL」が後でちょっとした落とし穴になります。

キーレスのまま search を叩く

本命の検索です。エージェント向けのJSON出力(--json)で叩きます。

$ time wigolo search "sqlite full text search fts5" --json
# results: 11 / engines_used: ["bing","duckduckgo"]
# total_time_ms(自己申告): 10395
real  1m5s 実測は65秒

ここが最初の「あれ?」でした。ツールが返すJSONの中の total_time_ms は約10.4秒。ところが time で測った実測は65秒。この落差の正体は doctor が言っていた遅延DLで、初回検索でオンデバイスの再ランクモデル(Xenova/ms-marco-MiniLM-L-6-v2 を transformers.js で動かす)を裏で取りに行っていました。ログにもしっかり rerank provider ready ... modelId: Xenova/ms-marco-MiniLM-L-6-v2 と出ます。

つまりツールの自己申告タイムは「検索パイプラインだけ」の値で、コールドスタートの実コストは含まれていない。運用でタイムアウトを見積もるときにこれを鵜呑みにすると足をすくわれます。念のため別クエリでもう一度回したら、自己申告6.3秒に対して実測は92秒でした。ここは正直、初回の重さのブレが大きいので、CIやエージェントの初期化では init の段階で暖機(warmup)を済ませておくのが前提だな、と感じます。

暖機が済んだあとの中身は、なるほど「スニペットではなく根拠(evidence)」を返すというだけあって濃いです。トップ結果はこうなっていました。

{
  "url": "https://sqlite.org/fts5.html",
  "title": "SQLite FTS5 Extension",
  "evidence_score": {
    "final": 0.989,
    "components": { "domain_quality": 1, "lexical_alignment": 1,
      "engine_consensus": 2, "cross_encoder": 0.958 },
    "explanation": "base=0.169, domain=1.00, lex=1.00, engines=2, xenc=0.96"
  }
}

公式ドキュメント(sqlite.org)が最上位で、スコアの内訳(ドメイン品質・字句一致・何個のエンジンが同意したか= engine_consensus・cross-encoderの再ランク値)まで開示される。しかも私が投げたクエリは1本なのに、queries_executed を見ると "sqlite full text search \"fts5\"" という引用符つきの言い換えが自動で足されていました。query_understandingfts5 を compound term と認識して展開した結果です。この辺は「ただの検索ラッパーではない」感がありました。

ハマりどころ: results[] を読んでも本文が空

エージェントに食わせる前提で構造を確かめていて、一番ヒヤッとしたのがこれです。逐語の抜粋(excerpt)を取ろうと results[].excerpt を見たら空文字。てっきりここに本文断片が入ると思い込んでいました。

実際には、逐語抜粋は別階層の evidence[] に入っていて、そちらは298文字ぶんの本文断片+ section_heading まで持っている。トップの results は「スコア付きの見出し・URL」、実際に引用できる本文は evidencecitations(index付き)に分かれている、という二層構造でした。

search results[0].excerpt = []           ← ここは空
evidence[0].excerpt.length = 298         ← 逐語はこっち(section_heading付き)
citations: 11件(各 index / url / title / snippet)

ドキュメントを読めば書いてあるのかもしれませんが、results だけ見て「本文取れないじゃん」と早合点しかけました。エージェントのプロンプトに組み込むなら、参照させるのは evidence / citations のほうです。ここは正直、フィールド名で誤解しやすいので自分の使い方側で気をつけるポイントだと思います。

fetch とキャッシュ ― ここが運用でおいしい

fetch は1URLをきれいなMarkdownに落としてくれます。

$ wigolo fetch "https://sqlite.org/fts5.html" --json
# markdown: 30000 chars / links: 127 / http_status: 200
# fetch_method: cache / cached: true

面白いのは fetch_method: cache になっていたこと。さっきの検索が根拠取得のために既に同じページを読んでいたので、fetch した時点でローカルキャッシュから返ってきたわけです。実際、検索2回+fetch1回のあとにキャッシュ統計を見たら、空っぽだったキャッシュが育っていました。

$ wigolo cache --stats --json
{ "total_urls": 17, "total_size_mb": 3.66, "oldest": "...12:06:07", "newest": "...12:08:09" }

さらに wigolo cache --query "full text search" と投げると、オフラインのままキャッシュ内を意味検索して5件返してきました。エージェントが一度触ったWebが手元の資産として貯まっていく——「クエリごとに課金される外部API」と比べると、この蓄積が効くところは運用視点で素直においしいです。同じ調べ物を繰り返すエージェントほど、キャッシュヒットで実クエリもコストも減っていく設計になっている。

キーレスの限界も正直に出る

もちろん万能ではありません。search を「合成された回答(answer)」形式で欲しくてキーなしで叩くと、こうなりました。

$ wigolo search "what is sqlite fts5" --format answer
Warning: Client does not support MCP sampling; returning heuristic
key-point summary instead of synthesized answer | WIGOLO_LLM_PROVIDER
not set and no provider API key detected ...

  [1] SQLite FTS5 Extension - sqlite.org (score: 1.00)
  [2] ...

research / agent / search --format answer のように「文章として書き起こす」機能は、さすがにLLMが要る。キーがなければ合成された回答は返らず、上位ソースを並べたヒューリスティックな要点リストに縮退します。ここはREADMEにも書いてあるとおりで、無料のGeminiキーを1本挿せば research 系が本領を出す、という位置づけ。逆に言うと、検索・取得・キャッシュという「土台」はキー抜きで完結し、キーが要るのは「文章生成の上物」だけ、という切り分けは明快でした。

コスト観点で整理すると、私の理解はこうです。

機能キー要否今回の実測・所感
search / fetch / crawl / extract / cache / find_similar不要($0)キーレスで動作。検索11件・根拠18件、fetchは127リンク付きMarkdown
research / agent / search —format answerLLMキー推奨キー無しだと要点リストに縮退。無料Geminiキー等で本領
Brave検索エンジン等個別キーBRAVE_API_KEY 未設定なら disabled。無くても他エンジンで検索は成立

試してみての所感

正直、期待していたより「土台の作り込み」がしっかりしていました。検索がスコアの内訳を開示し、キャッシュがオフラインの資産になり、キーが要る場所と要らない場所がツール自身の口から doctor で説明される。エージェントに「Webという不安定なものを触らせる」ときに一番欲しい透明性が、キーレスの範囲でちゃんと出ているのは好印象です。

一方で運用に載せるなら、コールドスタートで裏DLが走る(自己申告タイムと実測が大きくズレる)点と、resultsevidence の二層構造で参照先を間違えやすい点は、初期化フローとプロンプト設計で吸収しておくべきだな、と感じました。まだ public beta なので、この辺の体感は今後変わっていくのかもしれません。

私はしばらく、社内のちょっとした調査エージェントの「検索・取得・キャッシュ」土台としてキーレスのまま様子見で使ってみようと思います。従量課金が伸びない安心感は、運用する側からするとやっぱり大きいので。もっと良い使いどころや落とし穴があったら教えてください。