LlamaIndex + Ollama: 完全オフラインで構築する社内文書Q&Aシステム

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

現実の問題:社内文書をクラウドに上げられないとき

自分の会社には大量の技術文書があった。運用手順書、契約書、機器マニュアルといった数十件のPDFやWordファイルだ。調べ物のたびにファイルを開き、Ctrl+F で検索しては行ったり来たり。単純な質問一つに1時間近く費やすこともあった。

最初に思いついた解決策はChatGPTにアップロードして質問することだった。しかしブラウザにファイルをドラッグした瞬間、IT部門の責任者が止めに入った――文書には顧客情報や機密契約が含まれており、OpenAIのサーバーに送信するわけにはいかないと。

そこでLlamaIndexOllamaの組み合わせを調べ始めた。社内サーバーで完全にAIを動かし、データの一バイトも社外に出さない構成だ。

文書に「質問する」ための3つのアプローチ

技術的な詳細に入る前に、3つのアプローチを率直に比較して、課題に合った適切なツールを選べるようにしたい。

方法1 — ChatGPT/Claudeに直接アップロード

最もシンプルな方法:PDFをChatGPTにドラッグして質問するだけ。インストール不要で即座に使える。

  • メリット:コード不要、即セットアップ、回答品質が高い
  • デメリット:データがOpenAI/Anthropicのサーバーに送信される。ファイルサイズ制限あり。セッションごとに再アップロードが必要。機密文書には不向き。

方法2 — LangChain RAGとクラウドLLM

LangChainでRAGパイプラインを構築し、OpenAI APIをLLMとして使用する。多くの開発者に馴染みのあるアプローチだ。

  • メリット:柔軟性が高く、エコシステムが豊富で、ドキュメントも充実している
  • デメリット:クエリ(および関連文書の断片)は依然クラウドに送信される。トークン従量課金でAPIコストがかかる。インターネット接続が必要。

方法3 — LlamaIndex + Ollama(完全オフライン)

LlamaIndexは文書のインデックス作成とクエリに特化したフレームワークだ。Ollamaでローカルに動かすことで、パイプライン全体が100%自分のマシン上で完結する。

  • メリット:データを外部に送信しない。API料金不要。セットアップ後はインターネット不要で動作。
  • デメリット:十分なハードウェアが必要。ローカルモデルの品質はGPT-4に及ばないことが多い。

LlamaIndexはLangChainと何が違うのか?

LangChainは汎用フレームワークだ――エージェント、チェーン、ツール呼び出し、RAGなど多くのことをこなせる。しかしすべてをカバーしようとするあまり、APIが頻繁に変わり、純粋な文書処理という課題には必要以上に複雑になりがちだ。

LlamaIndexははじめから一つの課題だけを解くために設計されている:自分のデータとLLMをつなぐこと。より明確な概念を持っている:

  • Document Loaders:PDF、Word、CSV、Notion、Google Docsなど数十のコネクタが標準で利用可能
  • Node Parser:文書を制御された方法でチャンクに分割
  • Index:複数の種類のインデックス(VectorStoreIndex、SummaryIndex、KnowledgeGraphIndexなど)
  • Query Engine:文書コンテキストを使って質問を処理し回答する

純粋な文書Q&Aという課題では、LlamaIndexは通常コードが大幅に少なくて済む。次のステップのコードはわずか15行程度だ――LangChainで同等のものを書くと通常その倍かかり、しかもどのAPIが非推奨になったかを追い続ける必要がある。

LlamaIndex + Ollamaを選ぶべきタイミング

このソリューションが適しているのは次のような場合だ:

  • 機密性の高い社内文書がある(契約書、NDA、顧客データ、営業機密)
  • インターネットのない環境やイントラネット内で作業している
  • 毎日大量の質問があり、APIコストを削減したい
  • 社内サーバー(オンプレミス)で動作するシステムが必要

文書が機密でなく素早くテストしたいだけなら、ChatGPTの方がずっと速い。過剰設計はしないこと。

実践的な導入ガイド

ステップ1 — Ollamaのインストールとモデルのpull

# Ollamaのインストール(Linux/Mac)
curl -fsSL https://ollama.com/install.sh | sh

# 軽量モデルをpull(強力なGPUがないマシン向け)
ollama pull llama3.2

# 専用embeddingモデルをpull(共用より品質が高い)
ollama pull nomic-embed-text

# モデルが準備できているか確認
ollama list

モデルはhttp://localhost:11434で起動します――LlamaIndexはここに接続します。

ステップ2 — LlamaIndexと依存パッケージのインストール

pip install llama-index llama-index-llms-ollama llama-index-embeddings-ollama

# PDFとWordのパーサー
pip install llama-index-readers-file python-docx pypdf

ステップ3 — PDFとWordの読み込み、インデックスの作成

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core import Settings

# OpenAIの代わりにOllamaを使用するよう設定
Settings.llm = Ollama(model="llama3.2", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")

# PDFやWordをすべて./documents/フォルダに配置
# SimpleDirectoryReaderがファイル形式を自動認識
documents = SimpleDirectoryReader("./documents").load_data()

# 文書からベクトルインデックスを作成
index = VectorStoreIndex.from_documents(documents)

# 試しに質問してみる
query_engine = index.as_query_engine()
response = query_engine.query("契約書2024-001の保証条項は何ですか?")
print(response)

ステップ4 — 毎回再ビルドしないようにインデックスを保存

インデックスの初回ビルドには数分かかることがある――PDF50ファイルの場合、CPUで3〜5分程度だ。保存しておけば、次回は瞬時にロードできる:

from llama_index.core import StorageContext, load_index_from_storage
import os

PERSIST_DIR = "./storage"

if not os.path.exists(PERSIST_DIR):
    # 初回:ビルドしてディスクに保存
    documents = SimpleDirectoryReader("./documents").load_data()
    index = VectorStoreIndex.from_documents(documents)
    index.storage_context.persist(persist_dir=PERSIST_DIR)
else:
    # 2回目以降:ディスクからロード、再処理不要
    storage_context = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
    index = load_index_from_storage(storage_context)

query_engine = index.as_query_engine()

ステップ5 — 社内Webサイト(イントラネット)からの読み込み

社内Wiki、Confluenceのドキュメント、あるいは社内ネットワークからしかアクセスできないWebサイトがある場合:

from llama_index.readers.web import SimpleWebPageReader
from llama_index.core import SimpleDirectoryReader

# 社内URLからロード――社内ネットワーク内から実行
loader = SimpleWebPageReader(html_to_text=True)
web_docs = loader.load_data(urls=[
    "http://wiki.internal/sop/quy-trinh-xuat-hang",
    "http://wiki.internal/sop/kiem-tra-chat-luong",
])

# ファイル文書と組み合わせる
file_docs = SimpleDirectoryReader("./documents").load_data()
all_docs = web_docs + file_docs

index = VectorStoreIndex.from_documents(all_docs)

ステップ6 — チーム全員が使えるシンプルなインターフェース

# simple_qa.py
import sys
from llama_index.core import StorageContext, load_index_from_storage
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core import Settings

Settings.llm = Ollama(model="llama3.2", request_timeout=120.0)
Settings.embed_model = OllamaEmbedding(model_name="nomic-embed-text")

query = " ".join(sys.argv[1:])
if not query:
    print("使い方: python simple_qa.py <質問を入力>")
    sys.exit(1)

storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
query_engine = index.as_query_engine(similarity_top_k=3)

response = query_engine.query(query)
print(f"\n回答:\n{response}\n")
print("出典:", [n.metadata.get("file_name", "web") for n in response.source_nodes])
python simple_qa.py "顧客ABCとの契約はいつ満了しますか?"

source_nodesによってAIがどの文書のどのページから情報を取得したかがわかる。これがRAGの強みだ:出典をすぐに検証できるため、AIを盲信しなくて済む。純粋なLLMではこれができない。

本番導入時に見落とされがちなポイント

以下の2点は最もよく見落とされるが、結果の品質に最も大きく影響する:

チャンクサイズが精度を左右する。LlamaIndexのデフォルトは1024トークン/チャンクだ。機器マニュアル、スペックシート、運用手順書といった情報密度の高い技術文書では、チャンク512トークン・オーバーラップ50にすることで精度が明らかに向上することが多い:

from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Settings

Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)

スキャンした文書(画像PDF)はOCRなしでは読み込めない。LlamaIndexはテキストレイヤーしか読まない。請求書、古い契約書、印刷してスキャンし直した書類など、画像スキャンのPDFは事前にOCR処理が必要だ。Tesseract(オープンソース、無料)またはIBMのDoclingで対応できる。

3つの選択肢の比較

比較項目 ChatGPT アップロード LangChain + クラウドLLM LlamaIndex + Ollama
データセキュリティ クラウドに送信 クエリをクラウドに送信 100%ローカル
運用コスト サブスクリプション トークン従量課金 無料(ハードウェアのみ)
セットアップ難易度 不要 中程度 中程度
回答品質 最高 高い モデルに依存
オフライン動作 不可 不可 可能

現在使っているシステムは約200ファイル――機器マニュアルのPDF、契約書、社内SOP――を処理している。llama3.2をCPUのみで動かした場合のレスポンスタイムは1質問あたり8〜15秒程度だ。ChatGPTより遅いが、チームの誰も社内データが外部のサーバーに置かれているのを心配する必要がない。

社内文書という課題はほぼすべての企業に存在する――シフトごとに機器マニュアルを検索しなければならない工場から、数百件の契約から条項を探し出さなければならないオフィスまで。セキュリティが妥協できない制約であれば、LlamaIndex + Ollamaは最初に試すべきスタックだ。

Share: