0005: LLM-Wikiのembeddingsをローカル推論(mac-mini)へ分離する

背景

OpenCode Goは/embeddingsエンドポイントを提供しない(0004 の検証結果)。llmwikiは語彙検索フォールバックで動作するが、embeddingsがあれば ハイブリッド検索(semantic + BM25 + wikilinkグラフ)の質が上がる。embeddingsは 軽量モデルのローカル推論で十分賄える。

決定

  • chat(compile/query)はOpenCode Goのまま、embeddingsだけローカルのOpenAI互換 エンドポイントへ分離する。llmwikiの既存機能OPENAI_EMBEDDINGS_BASE_URLLLMWIKI_EMBEDDING_MODELを使い、paper-civic-kit側の改造はしない。
  • embeddingsの提供先はmac-mini profile(architecture.mdで「軽いPDF変換」を担う 常時稼働ホスト)に置く。実装はOllama等のOpenAI互換/v1/embeddingsを提供する 既存ランタイムを使い、自前のembeddingsサーバーは書かない。
  • モデルは多言語対応のembeddingモデル(例: bge-m3系)を第一候補とし、実測で決める。

理由

  • llmwikiのソース(src/utils/provider.ts)で、LLMWIKI_PROVIDER=openaiのまま OPENAI_EMBEDDINGS_BASE_URLだけ別ホストへ向けられることを確認した。
  • 検証用のOpenAI互換シムを127.0.0.1:8765に立ててllmwiki compileを実行した ところ、「Skipped embeddings update: 404」だった箇所が「Embeddings updated (4 pages, 26 chunks)」になり、.llmwiki/embeddings.jsonに指定モデル名で ベクトルが保存された。経路はllmwiki側の変更なしで機能する(2026-07-05、 tasks/0003)。
  • embeddingsは数百ms級・小メモリの推論であり、rtx-worker(重いOCR/画像処理) ではなく常時稼働のmac-miniに置くのがprofile分担(architecture.md)に合う。

影響

  • llmwikiプロジェクトの.envOPENAI_EMBEDDINGS_BASE_URL=http://<mac-mini>:11435LLMWIKI_EMBEDDING_MODEL=<モデル名>を追加すれば有効になる(未設定なら従来どおり 語彙検索フォールバック)。注意: llmwikiはembeddingsBaseURLをopenai SDKの baseURLにそのまま渡すだけで/v1を自動付与しない。バックエンドが実際に応答する パスと厳密に一致させること(infinity-embはルート直下/embeddingsで応答するため /v1を付けない。Ollamaバックエンドの場合は/v1が必要)。
  • embeddingモデルを変えると既存ベクトルと非互換になるため、モデル確定後に .llmwiki/embeddings.jsonを再生成する。

実装結果(2026-07-05)

  • ランタイム: hf-mount + infinity-emb--backend infinity)を採用。
    • hf-mount start repo BAAI/bge-m3 <path> でHF Hub safetensorsをFUSEマウント
    • infinity_emb v2 --model-id <path> でMetal(MPS)推論
    • GGUF変換不要、uv tool install --with "optimum<2" --with "pyarrow<21" --with "click<8.2" "infinity-emb[all]" で依存解決(infinity-emb 0.0.77との互換性を固定)
    • launchd plistで常駐(~/Library/LaunchAgents/com.paper-civic.infinity-emb.plist
    • Ollamaの既定ポート11434との衝突を避けるため、infinity-embは11435で待ち受ける。
  • モデル: BAAI/bge-m3(1024次元、多言語対応)
  • エンドポイント確認: curl http://localhost:11435/embeddings でベクトル返答を確認済み
  • Tailscale IP: 100.72.76.31(vps側のOPENAI_EMBEDDINGS_BASE_URLに設定)
  • launchd起動失敗の原因と修正: daemonコンテキストでHOME未設定→ infinity_embのキャッシュディレクトリ解決が/にフォールバックし /.infinity_cache(読み取り専用のシステムボリューム直下)へのmkdirで crash-loopしていた。plistのEnvironmentVariablesHOMEを追加して解決。
  • vps側からの疎通確認完了(2026-07-05): curl http://100.72.76.31:11435/embeddings でHTTP 200・bge-m3実ベクトル応答を確認。.env設定 (OPENAI_EMBEDDINGS_BASE_URL=http://100.72.76.31:11435/v1なし)で llmwiki compileを実行し、.llmwiki/embeddings.jsondimensions: 1024の実ベクトル(21ページ)に更新されたことを確認、 llmwiki queryでの引用付き回答生成も確認済み。

scripts/setup-embeddings-mac-mini.sh--backend ollama|infinity 両対応。 Ollamaバックエンド(GGUFモデル)も引き続き選択可能。

インシデントと復旧(2026-07-07)

上記の疎通確認直後、Obsidian Sync(obsidian-headless/ob CLI、mirror-remote モード)を実地検証した際、リモートに未登録だったLLM-Wikiシンボリックリンクが 「ローカルのみの差分」と判定され、リンク先の実体(compileされたwikiページ21件) ごと削除された。復旧作業中にllmwiki compilestate.json未クリアのまま実行し、 embeddings.jsonも一時的に空(dimensions: 0)へ上書きしてしまう二次被害も発生した。

  • 原因: mirror-remoteはリモート状態を正として、ローカルのみに存在する ファイル/フォルダを物理削除する。LLM-Wikiは一度もリモートへpushされていな かったため削除対象になった。
  • 再発防止: ob sync-config --excluded-foldersLLM-Wikiを追加し、以後の ob sync実行で対象外にした(既存のhermes-memory, hermes-memories, copilotと同様の扱い)。symlinkは除外設定後に再作成済み。
  • データ復旧: sources/5ファイルは無事だったため、state.jsonsources/indexHashをクリアしてmimo-v2.5-proで再コンパイルし、日本語の 概念ページ24件・embeddings.jsondimensions: 1024)を再生成した。 復旧過程でkimi-k2.6(tasks/0006で不採用と判明済みのモデル)や無印 mimo-v2.5も試したが、いずれも日本語出力が得られず英語化した (LLMWIKI_OUTPUT_LANGは実装が存在せず無効な設定値であることも判明。 日本語出力はmimo-v2.5-pro固有の挙動であり、設定で保証されているもの ではない)。既定モデルは引き続きmimo-v2.5-proとする。

DeepTutorへの接続(2026-07-08)

同一Mac mini embeddings(http://100.72.76.31:11435、bge-m3)を、 /home/hkawaguc/deeptutor/で稼働中のDeepTutor(HKUDS、docker-compose、 ポート3782/8001)にも接続した。PUT http://127.0.0.1:8001/api/v1/settings/catalogservices.embeddingbinding: "custom"のプロファイルを追加)で設定し、 POST http://127.0.0.1:8001/api/v1/system/test/embeddingsで疎通確認した。

新たな落とし穴: DeepTutorのcustom(openai_compat)バインディングは、 llm-wiki-compilerと異なりbase_url/embeddingsを自動付与しない。 base_url: http://100.72.76.31:11435のままではDeepTutor側がルート直下へ POSTしようとして405 Method Not Allowedになる。base_url/embeddingsまで 含めたフルパスhttp://100.72.76.31:11435/embeddings)を指定して解決した (success: true、応答時間約1.3秒、model: bge-m3を確認)。同じMac mini・ 同じバックエンドでも、接続するクライアント側の実装によって「base_urlに何を 含めるべきか」の規約が異なる。新しいクライアントを繋ぐたびに、まずcurlで POST先の実際のパスを確認してから設定すること。

追記: LLM-Wiki廃止(2026-07-22時点の注記)

本文中のLLM-Wikiは0004「LLM-Wiki生成 エンジンにllm-wiki-compilerを採用」の追記(2026-07-15、実運用でほぼ使われ なかったため廃止)により廃止済み。bge-m3のローカル埋め込み自体は現役 (icd11-cron等の実データJOINで使用中)で、このタイトルの決定内容は生きている。