0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Mac 1台で日本語PDF RAGを構築し、300問の性能評価までローカルで回す

0
Last updated at Posted at 2026-06-30

Mac Studio 1台に Dify と LM Studio を置き、日本語PDF 65本の取得、図表のテキスト化、RAGへの取込、300問への回答、LLM-as-judge、集計までをローカルで実行した。
有料のクラウドAPIは使っていない。

この記事で扱うのは、モデルの順位やスコア差の分析ではなく、日本語PDF RAGを作り、固定した設問で性能を測り、再検証できるローカル成果物を残すまでの構成と手順だ。
最後まで実行できたことの確認値として、固定した評価条件で 0.853(256/300)を得た。

評価には、公開されている日本語RAG評価データをもとに文書と正解の版を固定した RAG Eval JA Repro の65PDF・300問を使った。
参照元、派生関係、変更履歴、ライセンスは同ページにまとめている。
本記事のスコアは、この再現用評価基準CSVとローカルJudgeを使った完走確認値であり、他システムとの比較には使わない。

評価パイプラインのコード、Difyテンプレート、プロンプト、r1-r4の公開成果物は saka-cons/rag-eval-ja-pipeline に置いた。
本記事のコマンド例は、この公開リポジトリのルートで実行する前提で書く。


完成形

ハードウェア、推論サーバ、RAGアプリケーションを次の構成にした。

表1: ローカルRAG構築に使用した構成

区分 構成
ハードウェア Mac Studio M4 Max 36GB
RAG Dify 1.14.2(Docker・セルフホスト)
ローカル推論 LM Studio(OpenAI互換API)
PDF処理 PyMuPDF 1.27.2.3(MuPDF 1.27.2 / Python 3.13.7)
VLM qwen/qwen3-vl-8b
埋め込み Qwen/Qwen3-Embedding-8B-GGUF
回答生成・Judge qwen/qwen3.6-35b-a3b

処理は6段に分かれる。

図1

図1: 入力固定から取込、回答、Judge、集計、run成果物固定までの6段フロー

  1. Wayback固定URLからPDF 65本を取得し、SHA-256を検証する。
  2. PyMuPDFでPDF本文を抽出し、図・表・ページ画像はVLMでテキスト化する。文字化けを検知したページはVLM OCRに切り替える。
  3. 本文と図表テキストをDifyのナレッジへ投入する。
  4. 300問をDifyアプリへ送り、回答を保存する。
  5. 質問、正解、回答をローカルJudgeへ渡してO/Xを判定する。
  6. スコアを集計し、回答、判定、入力、ローカルキャッシュをrun単位で固定する。

図表を別工程にしたのは、300問のうち table が82問、imageが76問あり、通常のPDFテキスト抽出だけでは評価対象の情報が欠けるためだ。
VLMの有無による比較結果の詳細はZenn第5章で公開している。

36GBに全モデルを常時載せるのではなく、取込時はVLMと埋め込みモデル、回答時は埋め込みモデルと生成モデル、判定時はJudgeというように、工程ごとにLM Studioで使用モデルを切り替える。


1. 入力を固定する

PDFバイナリは再配布しない。
代わりに、Waybackの固定URL、取得スクリプト、SHA-256 manifestを公開し、同じ採用現物を再取得できるようにした。

リポジトリを取得し、Hugging Face dataset から再現用評価基準CSVを置いてから、文書をダウンロードして検証する。

git clone https://github.com/saka-cons/rag-eval-ja-pipeline.git
cd rag-eval-ja-pipeline
curl -L -o rag_evaluation_master.csv \
  https://huggingface.co/datasets/SakataConsul/rag-eval-ja-repro/resolve/main/rag_evaluation_master.csv
python3 repro/download_documents.py
(cd documents && shasum -a 256 -c ../repro/documents_checksums.sha256)

確認対象は65本すべてだ。
ここで確認できるのは、この検証で採用したコーパスを同一バイトで再取得できたことまでだ。

設問と正解には、採用したPDFとの現物照合結果を記録した再現用評価基準CSV(rag_evaluation_master.csv)を使う。
元の列は変更せず、question_newtarget_answer_newtarget_page_no_new に差分を記録した。
文書固定と再現用評価基準CSV作成の経緯はZenn第1章第2章第3章にまとめている。


2. DifyとLM Studioを接続する

LM Studioでは「Serve on Local Network」を有効にする。
Mac上のスクリプトからは http://localhost:1234/v1、DifyのDockerコンテナからは http://host.docker.internal:1234/v1 を参照する。

Dify側では次を設定する。

  • チャットボットの生成モデルをLM Studioのローカルモデルへ向ける。
  • prompts/generation_system_prompt.md をシステムプロンプトへ貼る。
  • 検索を hybrid、top_k=10、リランカ offに固定する。
  • ナレッジ作成時に埋め込みモデルを指定する。

APIキーはリポジトリルートの .env に置く。

cp .env.example .env
# .env に DIFY_KNOWLEDGE_API_KEY と DIFY_APP_API_KEY を記入
set -a; source .env; set +a

3. PDFをVLMで処理してナレッジへ入れる

LM Studioで qwen/qwen3-vl-8b と埋め込みモデルを利用可能にして、VLMありのナレッジを作る。

./scripts/run_r1_r4_ingest.sh vlm --refresh

スクリプトは次を行う。

  • 再現用評価基準CSVの再生成
  • PyMuPDFによるPDF本文の抽出
  • 図表のVLMテキスト化
  • RAG-Eval-JA-master-vlm の新規作成
  • 65文書の投入とindexing完了待ち
  • VLMと埋め込みモデルの利用記録の保存

ナレッジを作り直すとIDが変わる。
取込完了後、Dify Studioのチャットボットで「コンテキスト」を RAG-Eval-JA-master-vlm へ差し替え、アプリを公開し直す。
この更新をしないまま回答収集へ進むと、新しいナレッジが検索対象にならない。

VLM結果は、実行したマシン上でPDF単位の vlm_cache/ と画像単位の vlm_imgcache/ に保存する。
同じ入力で再実行するときはキャッシュを再利用できる。
ただし、これらはPDF本文や図表から派生したテキストを含むローカル成果物であり、公開配布物には含めない。
公開するのは、再生成用スクリプト、固定URL、SHA-256 manifest、設定、集計結果に限定する。


4. 300問への回答、Judge、集計を通す

LM Studioで qwen/qwen3.6-35b-a3b をロードし、Difyの生成モデルも同じモデルへ切り替える。
次の1コマンドで、qwen35B-VLMあり(r4)の回答収集、Judge、集計、成果物整理までを実行する。

./scripts/run_r1_r4_chatbot.sh r4 all --fresh

Judgeには質問、再現用評価基準CSVの正解、生成回答を渡す。
主要な事実、数値、結論が一致すればO、欠落、矛盾、数値違い、回答放棄があればXとする。
通常は temperature=0、反復ループなどでO/Xを返せない場合だけ temperature=0.3 で1回再判定する。
生成プロンプトとJudgeプロンプトはZenn第4章で公開している。

--fresh は同じrunの既存成果物を消して最初から実行する指定だ。
途中再開では外し、generatejudgereportfinalize を個別に実行できる。


5. 再検証に必要な成果物をローカルrun単位で残す

実行後のローカル results/r4/ には次が残る。

表2: run単位で保存する成果物

成果物 用途
answers.csv 300問の生成回答、latency、token usage
judged.csv O/X、判定理由、Judge token usage
analysis.md score、type/domain別集計、モデル・ナレッジ条件
report.md 公開用の集計出力
rag_evaluation_master.csv そのrunが参照した正解基準のスナップショット
vlm_cache/ PDF単位の抽出結果スナップショット。ローカル保存のみで公開配布しない
vlm_imgcache/ 画像単位のVLM応答スナップショット。ローカル保存のみで公開配布しない
dify/*.yml Difyアプリ設定のスナップショット。エクスポートDSL版は 0.6.0

スコアだけを記録しても、入力、検索条件、プロンプト、Judgeが変われば同じ実験にはならない。
runごとにローカル成果物をまとめることで、idx単位の差分確認と再集計ができる。
一方で、vlm_cache/vlm_imgcache/ はPDF由来の派生テキストを含むため、GitHubやHugging Faceで配布する公開リポジトリには置かない。


6. 完走確認と処理規模

上記構成の qwen35B-VLMあり(r4)は、300問すべてで回答と判定を完了し、answer_errors=0、**0.853(256/300)**だった。
r4の0.853は本記事の手順が最後まで動作したことを示す確認値であり、他システムとの順位や同等性能を主張するものではない。

O/Xは人手で確定した真値ではなく、ローカルQwen Judgeの判定だ。
qwen35B-VLMありでは回答生成とJudgeに同じモデルを使っているため、自己選好や誤りの相関を排除できない。
評価条件と結果の詳しい読み方はZenn第6章で公開している。VLM有無の比較の詳細は第5章で公開している。

処理規模の参考として、gpt20B-VLMあり(r2)では、取込後の回答収集とJudgeに合計3,831,344トークンを記録した。

表3: gpt20B-VLMあり(r2)の回答収集とJudgeで記録したトークン数

工程 入力トークン(prompt tokens) 出力トークン(completion tokens) 合計
回答収集(300問) 2,805,137 328,383 3,133,520
判定(300問) 212,694 485,130 697,824
合計 3,017,831 813,513 3,831,344

VLM抽出、文書の埋め込み・indexing、検索時のクエリ埋め込みは含まない。
モデルごとにトークナイザが異なるため、他社APIの課金トークンへそのまま換算できる値でもない。


この構成で揃えたかった条件

ローカルで動かすこと自体より、入力から判定までを同じ環境に置き、条件を固定して比較できることに意味がある。

  • 入力は固定URLとSHA-256で固定する。
  • 正解基準は再現用評価基準CSVとして変更履歴を残す。
  • 取込、検索、生成、Judgeを別工程にする。
  • 回答と判定をCSVで保存する。
  • キャッシュとアプリ設定をローカルrunに同梱する。PDF由来の vlm_cache/vlm_imgcache/ は公開配布しない。

これで、モデルや前処理を変えたときに、総合スコアだけでなく300問のどこが改善・悪化したかを確認できる。
VLMの有無を比較した結果の詳細はZenn第5章で公開している。生成モデル差と既存評価との接続の詳細は第6章で公開している。


業務文書のローカル/オンプレRAGの構築、検索設計、評価設計に取り組んでいる。
社内ナレッジ活用やオンプレ生成AIの実装にご関心があれば、お気軽にご相談を。
サカタコンサル(SAKATA Consulting) https://www.sakata-consul.com/

関連記事: RAG構成とプロンプト → Zenn第4章 / VLM比較 → Zenn第5章 / 結果と評価条件 → Zenn第6章

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?