0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作ローカルRAGを半年運用して、結局 macOS の mdfind に戻った話【Spotlight + Vision OCR】

0
Posted at

はじめに

税理士事務所で、関与先の大量の書類(PDF・Excel・Word)を横断検索したい——という動機で、半年ほどローカルRAGを自作・運用していました。

結論から言うと、最終的に全部捨てて、macOS標準の mdfind(Spotlight)+ Vision(OCR)に戻しました。 ディスクも約10GB空きました。

ただし、これは「RAGは不要」という話ではありません。自分が解きたい検索問題の種類を見誤っていた、という話です。詳しくは後半で仕分けします。

解決したかった課題:

  • 関与先ごとの契約書・決算書・申告書・請求書…を「中身」で検索したい
  • ファイル名は覚えてないが「あの覚書どこ?」を一発で出したい
  • スキャンしただけの画像PDFも引っかけたい
  • 関与先の機密情報なので、ChatGPT等の外部クラウドには上げられない(だから「ローカル」RAG)

この記事で得られるもの:

  • ローカルRAGの自作で踏んだ地雷(関与先混入・wedge・運用コスト)
  • 「grep vs RAG」の実測比較と、検索タスクの仕分け方
  • grepはPDF/Officeの中身を読めない。でもSpotlightは最初から索引しているという事実
  • スキャンPDFだけApple Vision OCRで埋める構成
  • AI検索を作る前に確認すべきチェックリスト

やったこと:ローカルRAGの自作

最初に組んだのはこんな構成です。

  • open-notebook(ローカルのNotebookLM的OSS)
  • SurrealDB(ベクトルインデックス)
  • ollama(埋め込み生成)
  • 全部 Docker(Colima) で運用

関与先の機密情報を外部に出せない以上、クラウドのAI検索は最初から選択肢になし。「完全にPC内で完結するローカルRAG」を組んで、関与先のPDFを片っ端から投入しました。我ながらモダンやな、と。そのまま、まじめに運用を続けました。


検索精度より怖かったのは「関与先の混入」だった

この記事でいちばん伝えたいのは、速度でも精度でもなく、士業特有の失敗モードです。

全関与先が1つの共有ベクトルインデックスに乗ります。ここで問題が起きました。源泉徴収票・給与明細・決算書のような汎用フォーマットは、関与先をまたいでベクトル的にほぼ同一になる。結果、

社名をクエリに入れても、別の関与先の書類が普通に混ざって返ってくる(実測)

検索結果が少しズレるだけなら笑い話です。でも税理士業務で「A社を探しているときにB社の資料が混ざる」のは、単なる検索ミスではなく情報漏洩リスクに直結する業務事故です。

この時点で、検索システムに求める第一条件は「賢さ」ではなく**「隔離」**だと気づきました。


RAGは作るより、運用し続ける方が重い

ローカルRAGは、最初に動いた瞬間はかなり気持ちいいです。でも実務で必要なのは「一度動くこと」ではなく、**「明日も、来月も、繁忙期にも、黙って正しく動くこと」**です。

その観点で重かったもの:

  • wedge:OCRや埋め込みの重い処理がingestワーカーを塞ぎ、投入が止まる
  • 再投入地獄:埋め込みの欠落が出るたびに対象を洗い出して再投入
  • 常駐コスト:SurrealDBもollamaもメモリを食う。止まれば検索も止まる
  • 約10GBのディスク:ベクトルDB+Dockerイメージ

「検索したいだけ」なのに、インフラのお守りで時間が溶けていく。OS標準のインデックス機能に乗る方が、圧倒的に楽でした。


転機:「grepの方が速くね?」

ある日ふと思いました。普通に grep した方が速いし簡単なのでは?

同じ関与先フォルダに対して、同じクエリで「RAG vs grep」を実測しました(RAGはウォーム状態)。

クエリ 種別 grep RAG(ベクトル)
覚書 既知語 33ms / 正解2件 1624ms / 0件
定款 既知語 34ms / 正解 1801ms / 正解(grepの53倍遅い
会社設立の手続き 言い換え 0件 正解

「覚書」のようにファイルにそのまま書いてある2文字を、RAGは閾値を下げても0件で取りこぼす(ベクトルは短い語に弱い)。一方、「会社設立の手続き」のような言い換え・概念検索ではRAGが勝ちました


RAGが悪かったのではなく、検索したいものを見誤っていた

ここが本質です。失敗の原因は、RAGを使ったことではなく、自分の検索問題の種類を取り違えていたことでした。

検索タスクを仕分けると、こうなります。

探したいもの 向いている方法
社名・金額・書類名・覚書・定款(既知語) mdfind / grep
PDF / Word / Excel の本文 Spotlight(mdfind
スキャンPDF(画像) Vision OCR + grep
「会社設立の手続き」のような概念検索 RAGが有利
関与先ごとの厳密な隔離 フォルダスコープ検索が安全

私の実務で多かったのは、社名・書類名・金額・日付・覚書・定款のような、すでに頭の中にある語で探す検索でした。この用途では、意味検索よりも全文検索の方が速く・正確で・説明可能です。

つまり、RAGの問題ではなく、**「既知語中心の業務検索にローカルRAGは過剰だった」**というだけの話でした。


でも、grepはPDF/Officeの「中身」を読めない

仕分けはできた。が、壁があります。

grep "売買契約" 契約書.pdf
# → 0件(PDFは圧縮バイナリ。テキスト層があっても語として読めない)

PDFもWord(.docx)もExcel(.xlsx)も、中身はZIP圧縮されたXMLやバイナリ。grep/ripgrepはそれを「ただのバイナリ」としか見られません。

じゃあRAGに戻るしかないのか?——いいえ。手元のOSが、とっくに解決していました。


発見:Spotlightが最初から「中身」を索引していた

macOSの mdfind(Spotlightのコマンド版)を、フォルダを絞って叩いてみます。

# 関与先フォルダ内を「中身」で全文検索
mdfind -onlyin "/path/to/関与先" "売買契約"
# → 0売買契約書.pdf がヒット(ファイル名ではなく本文に反応)

# 本文限定で検索したいとき
mdfind -onlyin "/path/to/関与先" 'kMDItemTextContent == "*覚書*"cd'

検証してわかったこと:

  • テキスト層のあるPDF・Word・Excelは、中身(Excelはセル値まで)を最初からSpotlightが索引済み。 mdfind で本文ヒットする。
  • -onlyinフォルダスコープ=関与先の混入ゼロ。最重要だった「隔離」が、標準機能でそのまま満たせる。
  • これを macOSは2005年(Tiger)からバックグラウンドで黙ってやり続けていた。

半年かけて、OSが標準でやっていることを、Docker+ベクトルDB+ollamaで再発明していたわけです。


唯一の穴:スキャンPDF → これもOS標準のVision OCRで埋める

Spotlightにも読めないものが1つだけあります。テキスト層のない、画像としてスキャンされただけのPDFです。中身が画像なので索引のしようがない。

ここだけは「テキスト化」が要りますが、これもOS標準で済みました——macOSの Vision フレームワーク(Apple Vision OCR) です。

  • スキャンPDF → Vision OCR でテキスト抽出
  • 抽出テキストを、原本と同じフォルダの隠しサブフォルダ .pdftext/ にサイドカー .txt として保存(原本PDFには一切触らない=更新日時もそのまま)
  • あとは .txt を grep するだけ

Vision OCRは回転不変です。

回転 認識文字数
0度 746字
90度 743字
180度(逆さ) 754字
270度 735字

横向きでも逆さでも、向きを自動判定してほぼ同じ精度で読みます(日本語OCRは macOS 13 Ventura 以降が必要)。ScanSnapの傾きスキャンもそのまま通りました。

※ Spotlightは隠しフォルダ(.pdftext/)を索引しないので、スキャン分だけは薄いラッパー(os.walkで.txtを読むgrep)を使っています。テキスト系は mdfind、スキャンだけこのラッパー、という二段構えです。


結果:RAGを全撤去して約10GB解放

最終的に捨てたもの:

  • SurrealDBのベクトルDB(投入済みデータ)… 約7.8GB
  • Dockerイメージ(open-notebook + surrealdb)… 約2.55GB
  • Colima VM ごと撤去
  • 合計 約10GB+常駐プロセス2つ

検索体制は、

  • テキスト系(PDF/Word/Excel)→ mdfind(Spotlight)
  • スキャン画像PDF → Vision OCR + grep

の2本だけに。Docker も ollama も要らなくなり、お守りに使っていた時間が本来の業務に戻ってきました。


まとめ:重いものを作る前に、まず検索問題を仕分ける

今回の反省は、「RAGは不要」という話ではありません。

言い換えや概念で探したい場合、単純な全文検索では届かない資料をRAGが引けることもあります。RAGが向いている検索は確かにあります。

ただ、私の税理士業務で多かったのは、社名・書類名・金額・日付・覚書・定款のような、すでに頭の中にある語で探す検索でした。この用途では、意味検索よりも全文検索の方が速く・正確で・説明可能です。さらに mdfind -onlyin で関与先フォルダに範囲を絞れば、別関与先の混入も防げる。

スキャンPDFだけは別処理が要りましたが、ここも Vision OCR でサイドカー .txt を作る構成で足りました。

少なくとも今回の業務検索の大半は、macOS標準の Spotlight + Vision OCR で十分でした。

新しい技術を入れる前に、まず自分が解きたい検索問題を仕分ける。そして、OS標準の全文検索・インデックス・OCR機能でどこまでできるかを確認する(これはWindows Search でも同じ発想が使えます)。これだけで、運用コストもディスクもかなり減らせます。

半年、返してほしい。でも、いい勉強でした。


AI検索を作る前に確認した方がいいこと

最後に、判断基準としてチェックリストを置いておきます。

  • 探したいものは「概念」か「既知語」か
  • ファイル名ではなく本文検索が本当に必要か
  • OS標準検索(Spotlight / Windows Search)でPDF / Office本文まで拾えないか
  • 検索対象をフォルダ単位で安全に分離できるか
  • スキャンPDFだけ別処理にすれば済まないか
  • 0件だったときに「本当に無い」と断定してよい仕組みか
  • そのシステムを半年後も自分で保守できるか

「概念検索が主」「全社横断で意味的に引きたい」なら、RAGは正解です。逆に上の多くが「既知語・本文・フォルダ隔離」に寄るなら、まずOS標準を試す価値があります。


【おまけ】ハマり集

1. Spotlightの索引は「古く」なる(stale)

イベント駆動(ファイル変更で数分以内に索引更新)で、定期フル再走査はありません。なので、

  • 同期サービス(Dropbox等)が他デバイスから書いたファイル(mtimeは原作成時刻のまま)
  • オンラインのみ(スマートシンク)ファイル
  • 外付けボリューム

このあたりは索引が遅れ/欠けることがあります。「mdfind で0件=資料なし」と断定するのは危険。強制再索引は:

mdimport "/path/to/file_or_dir"

私は「過去N日に変更されたファイルだけ毎朝 mdimport」する軽量なlaunchdを仕込みました(ctime基準なので同期で入ったファイルも拾える)。

2. brew uninstall ollama で python ごと消えて青ざめた

ollamaを消そうとしたら、依存が芋づる式に:

ollama → mlx-c → mlx → python@3.14

brew uninstall ollama が、不要になった依存(mlx / mlx-c / python@3.14)まで巻き込んで削除しました。「ollamaがpythonに依存してるなんて思わない」——依存ツリーは意外です。

# 消す前に依存ツリーを見る
brew deps --tree ollama
brew uses --installed mlx

幸い、別用途のPythonツールは独立したvenv(pyenv製)だったので無傷でした。消す前に依存と、常駐プロセスの実行環境(launchd plistの実行パス)を確認すべき、という教訓。

3. スキャン判定に pymupdf4llm を使うと遅い

PDFのテキスト抽出に pymupdf4llm を使うと、スキャンPDFで内部的にTesseract/RapidOCRが走って激遅でした。スキャン判定は pdftotext(内部OCRなし)で高速にゲートし、スキャンだけApple Visionへ流すと正確かつ速い。

4. mdls kMDItemTextContent は当てにならない

索引状態を確認しようと mdls -name kMDItemTextContent を見ると、索引済みファイルでも (null) を返します(この属性は表示用に展開されないだけ)。索引の有無は、実際に mdfind で引くか、mdimport -t -d2 <file>(importerのドライ実行)で確かめましょう。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?