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の構築(TurboVec × Ollama)

0
Last updated at Posted at 2026-06-07

TurboVecで作る完全無料・完全ローカルRAGチャットボット

はじめに

「RAGを使いたいけど、クラウドAPIにデータを送りたくない」
「コストをかけずにローカルだけで動かしたい」

製造業や医療など、機密データを扱う現場ではこういった要件が多いと思います。

本記事では、2026年にICLR採択されたGoogle Research/NYUの研究チームによるアルゴリズム TurboQuant(論文:arXiv:2504.19874)を、ベクトル検索インデックス向けに応用したライブラリ TurboVec と、ローカルLLMランタイム Ollama を組み合わせて、完全無料・完全ローカル で動作するRAGチャットボットを構築します。

今回は、Macで動作確認していますが、RaspberryPiなどでも動作すると思います。
RaspberryPiなどでは軽いモデルを使ったりする必要があると思われるので、モデルの切り替えも1行で完完結するようにしています。

TurboVecとは

TurboVec は、Rust製のベクトルインデックスライブラリです。Pythonバインディングが用意されており、pip install turbovec で導入できます。

何がすごいのか

最大の特徴は 16倍のメモリ圧縮 です。

たとえば OpenAI の text-embedding-3-small(1536次元)で1000万件を埋め込む場合:

方式 メモリ使用量
float32 そのまま 約 31 GB
TurboVec(2bit圧縮) 約 4 GB

Raspberry Piのような制約の多いデバイスでも、大規模なベクトルインデックスを扱えるようになります。

なぜ学習不要なのか

従来のProduct Quantization(PQ)は「コードブック」を事前にデータから学習する必要がありました。TurboQuantは高次元ベクトルの数学的性質(ランダム回転後にガウス分布に近似する性質)を利用するため、データを見なくても最適な量子化境界を計算できます。

1. 正規化    — ベクトルの大きさと向きを分離
2. ランダム回転 — 座標をガウス分布に近い形に変換
3. 数学的量子化 — 分布が既知なので学習なしで最適境界を算出

ベクトルを追加するだけでインデックスが完成するため、コーパスが増えてもリビルドが不要です。

FAISSとの比較

比較項目 FAISS TurboVec
コードブック学習 必要 不要
オンライン追加 困難 可能
ARM(ラズパイ・Apple Silicon)速度 基準 多くの構成で12〜20% 高速 (※2ビットMTの一部構成を除く)
メモリ効率 普通 最大16倍
ライセンス MIT MIT

※ベンチマーク条件:100Kベクトル、k=64、Apple M3 Max / Intel Sapphire Rapids。TurboVec公式READMEより。

ARM NEON SIMDカーネルで最適化されているため、Raspberry PiやApple Silicon Macでは特に効果を発揮します。

FAISSとは

大量のベクトルの中から「似ているもの」を高速に探すためのライブラリで、RAGシステムの検索エンジンとして広く使われています。名前は Facebook AI Similarity Search の略です。

システム構成

質問入力
   │
   ▼
SentenceTransformer(all-MiniLM-L6-v2)
   │  テキスト → 384次元ベクトル
   ▼
TurboQuantIndex.search()
   │  ARM NEON SIMD で高速 ANN 検索
   ▼
上位3件のドキュメントを取得
   │
   ▼
Ollama(gemma3:1b または gemma3:4b)
   │  完全ローカル LLM で回答生成
   ▼
回答出力(外部通信なし)

モデル選択の目安

モデル 必要RAM 推奨環境 品質 速度
gemma3:1b 2GB〜 Raspberry Pi 4(4GB) △ 実用的 ◎
gemma3:4b 6GB〜 Raspberry Pi 5(8GB)/ Mac ○ 高品質 ○

環境構築

1. Ollama のインストール

公式サイトからインストール
curl -fsSL https://ollama.com/install.sh | sh
ollama serve &

Macの場合は、brewコマンドでインストールしているとバージョンが古く使えない可能性があります。

2. LLMモデルのダウンロード

Raspberry Pi 4 向け
ollama pull gemma3:1b
Raspberry Pi 5 / Mac 向け
ollama pull gemma3:4b
ollama pull gemma3:4bの実行結果
[GIN] 2026/06/08 - 07:29:02 | 200 |     278.709µs |       127.0.0.1 | HEAD     "/"
pulling manifest ⠹ time=2026-06-08T07:29:03.537+09:00 level=INFO source=download.go:179 msg="downloading aeda25e63ebd in 16 208 MB part(s)"
pulling manifest 
pulling aeda25e63ebd: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏ 3.3 GB                         tpulling manifest 
pulling aeda25e63ebd: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏ 3.3 GB                         
pulling manifest 
pulling aeda25e63ebd: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏ 3.3 GB                         
pulling manifest 
pulling aeda25e63ebd: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏ 3.3 GB                         
pulling manifest 
pulling manifest 
pulling aeda25e63ebd: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏ 3.3 GB                         
pulling e0a42594d802: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏  358 B                         
pulling dd084c7d92a3: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏ 8.4 KB                         
pulling 3116c5225075: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏   77 B                         
pulling b6ae5839783f: 100% ▕█████████████████████████████████████████████████████████████████████████████████████████████▏  489 B                         
verifying sha256 digest 
writing manifest 
success 
LLMモデルのダウンロード確認
ollama list
実行結果
[GIN] 2026/06/08 - 07:31:24 | 200 |       42.75µs |       127.0.0.1 | HEAD     "/"
[GIN] 2026/06/08 - 07:31:24 | 200 |     821.125µs |       127.0.0.1 | GET      "/api/tags"
NAME         ID              SIZE      MODIFIED           
gemma3:4b    a2af6cc3eb7f    3.3 GB    About a minute ago 

3. Rust のインストール(turbovecのビルドに必要)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

4. Python環境のセットアップ

mkdir turbovec-rag-chatbot
cd turbovec-rag-chatbot
python3 -m venv .venv
source .venv/bin/activate

まずは、requirements.txt: を作ります。

turbovec>=0.2.0
ollama>=0.3.0
sentence-transformers>=3.0.0
numpy>=1.26.0
torch>=2.2.0
transformers>=4.40.0
Raspberry Pi(CPU版PyTorchを確実に使うため --extra-index-url を指定)
pip install -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
Mac
pip install -r requirements.txt

Raspberry Piで pip install torch だけを先に実行してから pip install -r requirements.txt とすると、依存関係の解決が再度走り、重い標準版バイナリをダウンロードしようとする場合があります。上記のように --extra-index-url をまとめて指定するのが確実です。

実装コード

ファイル構成

turbovec-rag-chatbot/
├── chatbot.py        # メインスクリプト
├── seed_docs.py      # サンプルドキュメント投入
├── requirements.txt  # 依存パッケージ
├── docs.json         # ドキュメントストア(自動生成)
└── index.tv          # TurboVec インデックス(自動生成)

docs.jsonとindex.tvは、自動生成されます。

chatbot.py

chatbot.py
#!/usr/bin/env python3
"""
TurboVec RAG Chatbot — Ollama 対応版(完全無料・完全ローカル)
Ollama でローカル LLM を動かし、TurboVec で高速ベクトル検索を行う RAG チャットボット
"""

import json
import ollama
import turbovec
import numpy as np
from sentence_transformers import SentenceTransformer
from pathlib import Path

# ── 設定 ──────────────────────────────────────────────────────────────────
# モデルを切り替えたいときはここを変更するだけ
#   "gemma3:1b"  → RAM 2GB〜、Raspberry Pi 4 (4GB) でも動作
#   "gemma3:4b"  → RAM 6GB〜、高品質・Raspberry Pi 5 (8GB) 推奨
OLLAMA_MODEL  = "gemma3:4b"

EMBED_MODEL   = "all-MiniLM-L6-v2"   # 軽量埋め込みモデル(384次元)
EMBED_DIM     = 384
BIT_WIDTH     = 4                     # 2=高圧縮, 4=バランス, 8=高精度
INDEX_PATH    = "index.tv"
DOCS_PATH     = "docs.json"
TOP_K         = 3
MAX_TOKENS    = 512                   # ローカルLLMはトークン数を絞ると高速

SYSTEM_PROMPT = """あなたは日本語で回答するRAGチャットボットです。
提供されたコンテキストのみを根拠に回答してください。
コンテキストに情報がない場合は「この質問に関する情報がドキュメントにありません」と答えてください。
回答は簡潔・正確にしてください。"""

# ── ドキュメントストア ────────────────────────────────────────────────────
class DocStore:
    def __init__(self, path: str = DOCS_PATH):
        self.path = Path(path)
        self.docs: list[dict] = []
        if self.path.exists():
            self.docs = json.loads(self.path.read_text(encoding="utf-8"))

    def save(self):
        self.path.write_text(
            json.dumps(self.docs, ensure_ascii=False, indent=2), encoding="utf-8"
        )

    def add(self, title: str, body: str) -> int:
        doc_id = len(self.docs)
        self.docs.append({"id": doc_id, "title": title, "body": body})
        self.save()
        return doc_id

    def get_texts(self) -> list[str]:
        return [f"{d['title']} {d['body']}" for d in self.docs]

    def __len__(self):
        return len(self.docs)


# ── TurboVec インデックス管理 ─────────────────────────────────────────────
class VectorIndex:
    def __init__(self, dim: int = EMBED_DIM, bit_width: int = BIT_WIDTH):
        self.dim = dim
        self.bit_width = bit_width
        self.index = turbovec.TurboQuantIndex(dim, bit_width)

    def build(self, embeddings: np.ndarray):
        # add() には shape [n, dim] の float32 2次元配列をそのまま渡す
        vecs = np.ascontiguousarray(embeddings, dtype=np.float32)
        self.index.add(vecs)
        print(f"  → {len(embeddings)} 件をインデックス化しました "
              f"({self.bit_width}bit, 次元={self.dim})")

    def search(self, query_vec: np.ndarray, k: int = TOP_K) -> list[int]:
        # search() は shape [1, dim] の2次元配列を受け取り (scores, indices) を返す
        q = np.ascontiguousarray(query_vec, dtype=np.float32)
        if q.ndim == 1:
            q = q.reshape(1, -1)
        _scores, indices = self.index.search(q, k)
        return indices[0].tolist()

    def save(self, path: str = INDEX_PATH):
        self.index.write(path)
        print(f"  → インデックスを保存しました: {path}")

    def load(self, path: str = INDEX_PATH):
        self.index = turbovec.TurboQuantIndex.load(path)
        print(f"  → インデックスを読み込みました: {path}")


# ── RAG パイプライン ──────────────────────────────────────────────────────
class RAGChatbot:
    def __init__(self):
        self.store    = DocStore()
        self.vi       = VectorIndex()
        self.embedder = None

        print("📦 TurboVec RAG Chatbot (Ollama Edition) を起動中...")
        self._check_ollama()
        self._load_embedder()
        self._load_or_build_index()

    # ── 初期化 ──────────────────────────────────────────────────────────
    def _check_ollama(self):
        """Ollama の起動確認とモデルの存在チェック"""
        try:
            models = [m.model for m in ollama.list().models]
            if OLLAMA_MODEL not in models:
                print(f"⚠️  モデル '{OLLAMA_MODEL}' が見つかりません。ダウンロードします...")
                print(f"   ollama pull {OLLAMA_MODEL}")
                ollama.pull(OLLAMA_MODEL)
                print(f"   ✓ ダウンロード完了")
            else:
                print(f"✓ Ollama モデル確認: {OLLAMA_MODEL}")
        except Exception as e:
            raise RuntimeError(
                f"Ollama に接続できません。起動しているか確認してください。\n"
                f"  macOS/Linux: ollama serve\n"
                f"  エラー詳細: {e}"
            )

    def _load_embedder(self):
        print(f"🔤 埋め込みモデルをロード中: {EMBED_MODEL}")
        self.embedder = SentenceTransformer(EMBED_MODEL)
        print("   ✓ 完了")

    def _load_or_build_index(self):
        if Path(INDEX_PATH).exists() and len(self.store) > 0:
            print(f"📂 既存のインデックスを読み込みます: {INDEX_PATH}")
            self.vi.load()
        elif len(self.store) > 0:
            print("🔨 インデックスを新規構築します...")
            self._rebuild_index()
        else:
            print("ℹ️  ドキュメントが未登録です。'add' コマンドで追加してください。")

    def _rebuild_index(self):
        if len(self.store) == 0:
            print("⚠️  ドキュメントが空のためインデックスを構築できません。")
            return
        texts = self.store.get_texts()
        print(f"  エンベディング生成中 ({len(texts)} 件)...")
        embeddings = self.embedder.encode(texts, show_progress_bar=True)
        self.vi = VectorIndex(dim=embeddings.shape[1])
        self.vi.build(embeddings)
        self.vi.save()

    # ── ドキュメント管理 ─────────────────────────────────────────────────
    def add_document(self, title: str, body: str):
        doc_id = self.store.add(title, body)
        print(f"  ✓ ドキュメント #{doc_id} '{title}' を追加しました。インデックスを再構築します...")
        self._rebuild_index()

    def list_documents(self):
        if not self.store.docs:
            print("  (ドキュメントなし)")
            return
        for d in self.store.docs:
            preview = d["body"][:60].replace("\n", " ")
            print(f"  #{d['id']:3d}  [{d['title']}]  {preview}...")

    # ── 検索・回答生成 ───────────────────────────────────────────────────
    def retrieve(self, query: str) -> list[dict]:
        if len(self.store) == 0:
            return []
        q_vec = self.embedder.encode([query])
        indices = self.vi.search(q_vec, k=min(TOP_K, len(self.store)))
        return [self.store.docs[i] for i in indices if i < len(self.store.docs)]

    def answer(self, query: str) -> str:
        top_docs = self.retrieve(query)

        if not top_docs:
            context = "(関連ドキュメントなし)"
        else:
            context = "\n\n".join(
                f"【{d['title']}】\n{d['body']}" for d in top_docs
            )
            titles = ", ".join(f"「{d['title']}」" for d in top_docs)
            print(f"\n  🔍 参照ドキュメント: {titles}")

        response = ollama.chat(
            model=OLLAMA_MODEL,
            options={"num_predict": MAX_TOKENS},
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT + f"\n\nコンテキスト:\n{context}"},
                {"role": "user",   "content": query},
            ],
        )
        return response["message"]["content"]

    # ── REPL ────────────────────────────────────────────────────────────
    def run(self):
        print("\n" + "=" * 58)
        print(f"  🤖 TurboVec RAG Chatbot — Ollama Edition")
        print(f"  📡 使用モデル: {OLLAMA_MODEL}")
        print("=" * 58)
        print("  コマンド一覧:")
        print("    /add     — ドキュメントを追加")
        print("    /list    — ドキュメント一覧を表示")
        print("    /rebuild — インデックスを再構築")
        print("    /model   — 現在のモデルを確認")
        print("    /quit    — 終了")
        print("=" * 58 + "\n")

        while True:
            try:
                user_input = input("あなた > ").strip()
            except (EOFError, KeyboardInterrupt):
                print("\n👋 終了します。")
                break

            if not user_input:
                continue

            if user_input == "/quit":
                print("👋 終了します。")
                break
            elif user_input == "/list":
                self.list_documents()
            elif user_input == "/rebuild":
                self._rebuild_index()
            elif user_input == "/model":
                print(f"  現在のモデル: {OLLAMA_MODEL}")
                print(f"  変更するには chatbot.py の OLLAMA_MODEL を書き換えてください。")
            elif user_input == "/add":
                title = input("  タイトル: ").strip()
                print("  本文 (空行で確定):")
                lines = []
                while True:
                    line = input()
                    if line == "":
                        break
                    lines.append(line)
                body = "\n".join(lines)
                if title and body:
                    self.add_document(title, body)
                else:
                    print("  ⚠️  タイトルと本文を入力してください。")
            else:
                print("Bot  > ", end="", flush=True)
                reply = self.answer(user_input)
                print(reply)
                print()


# ── エントリポイント ──────────────────────────────────────────────────────
if __name__ == "__main__":
    bot = RAGChatbot()
    bot.run()

seed_docs.py

seed_docs.py
#!/usr/bin/env python3
"""
サンプルドキュメントを一括登録するスクリプト
初回セットアップ時に実行してください。
"""

import json
from pathlib import Path

DOCS_PATH = "docs.json"

SAMPLE_DOCS = [
    {
        "title": "TurboVecとは",
        "body": (
            "TurboVecはRust製のベクトルインデックスライブラリです。"
            "Pythonバインディングもあり、pip install turbovecでインストールできます。"
            "Google ResearchのTurboQuantアルゴリズムを実装しています。"
            "ICLR 2026で採択された研究論文がベースです。"
        ),
    },
    {
        "title": "圧縮効率",
        "body": (
            "TurboVecは1000万件のベクトルを31GBから4GBに圧縮します。"
            "2bitモードで最大16倍の圧縮率を達成します。"
            "float32をそのまま使うと31GBになりますが、TurboVecなら4GBに収まり"
            "RAGシステムをローカルで動かせます。"
        ),
    },
    {
        "title": "FAISSとの比較",
        "body": (
            "TurboVecはARMアーキテクチャでFAISSよりも12〜20%高速です。"
            "x86でもFAISS以上の速度を出します。"
            "FAISS IndexPQFastScanを上回るベンチマーク結果が報告されています。"
            "NEON(ARM)とAVX-512BW(x86)のSIMDカーネルで最適化されています。"
        ),
    },
    {
        "title": "学習不要の仕組み",
        "body": (
            "TurboVecはコードブックの事前学習が不要です。"
            "従来のPQはデータからコードブックを学習しますが、"
            "TurboQuantは高次元ベクトルの数学的性質を利用するため学習フェーズがありません。"
            "ベクトルを追加するだけでインデックスが完成します。"
        ),
    },
    {
        "title": "Pythonでの使い方",
        "body": (
            "from turbovec import TurboQuantIndex でインポートします。"
            "index = TurboQuantIndex(384, 4) で384次元4bitインデックスを作成します。"
            "index.add(vectors)でベクトルを追加し、"
            "index.search(query, k=10)で近傍検索ができます。"
            "本チャットボットではall-MiniLM-L6-v2(384次元)を使用しています。"
        ),
    },
    {
        "title": "ローカルRAGへの活用",
        "body": (
            "TurboVecを使えばAPIコールなしの完全ローカルRAGパイプラインを構築できます。"
            "データが外部に出ないため、製造業や医療など機密性の高い分野で特に有用です。"
            "OllamaやGemmaと組み合わせてプライベートAIシステムを構築できます。"
        ),
    },
    {
        "title": "Raspberry Pi での注意点",
        "body": (
            "Raspberry Pi 4以降を推奨します(RAM 4GB以上)。"
            "Rustツールチェーンをインストール後、pip install turbovecを実行します。"
            "埋め込みモデルはall-MiniLM-L6-v2(384次元)が軽量でラズパイ向きです。"
            "初回の埋め込み生成はCPUのみで数分かかる場合があります。"
        ),
    },
]


def main():
    path = Path(DOCS_PATH)
    if path.exists():
        existing = json.loads(path.read_text(encoding="utf-8"))
        print(f"既存ドキュメント {len(existing)} 件を検出しました。")
        ans = input("上書きしますか? (y/N): ").strip().lower()
        if ans != "y":
            print("キャンセルしました。")
            return

    docs = [{"id": i, **d} for i, d in enumerate(SAMPLE_DOCS)]
    path.write_text(json.dumps(docs, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"✓ {len(docs)} 件のサンプルドキュメントを {DOCS_PATH} に保存しました。")
    print("次に以下を実行してください:")
    print("  python chatbot.py")


if __name__ == "__main__":
    main()

実行手順

1. サンプルドキュメントのインデックス化

初回は必ず先に seed_docs.py を実行して docs.json と index.tv を生成してください。これを省略するとチャットボットがドキュメントを参照できません。

python seed_docs.py
実行結果
✓ 7 件のサンプルドキュメントを docs.json に保存しました。
次に以下を実行してください:
  python chatbot.py

2. チャットボットの起動

python chatbot.py

今回は、Macで実行しているので、gemma3:4bを使っています。

起動ログ(初回)
📦 TurboVec RAG Chatbot (Ollama Edition) を起動中...
[GIN] 2026/06/08 - 07:55:50 | 200 |     488.125µs |       127.0.0.1 | GET      "/api/tags"
✓ Ollama モデル確認: gemma3:4b
🔤 埋め込みモデルをロード中: all-MiniLM-L6-v2
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
Loading weights: 100%|█████████████████████████████████████████████████████████████████████████████| 103/103 [00:00<00:00, 5755.19it/s]
   ✓ 完了
🔨 インデックスを新規構築します...
  エンベディング生成中 (7 件)...
Batches: 100%|███████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  2.86it/s]
  → 7 件をインデックス化しました (4bit, 次元=384)
  → インデックスを保存しました: index.tv

==========================================================
  🤖 TurboVec RAG Chatbot — Ollama Edition
  📡 使用モデル: gemma3:4b
==========================================================
  コマンド一覧:
    /add     — ドキュメントを追加
    /list    — ドキュメント一覧を表示
    /rebuild — インデックスを再構築
    /model   — 現在のモデルを確認
    /quit    — 終了
==========================================================

あなた > 

実行結果
あなた > TurboVecの圧縮率はどれくらいですか?
Bot  > 
  🔍 参照ドキュメント: 「TurboVecとは」, 「圧縮効率」, 「FAISSとの比較」
TurboVecは、2bitモードで最大16倍の圧縮率を達成します。

モデルの切り替え方

方法1: chatbot.py を1行書き換え(シンプル)

chatbot.py の 14 行目
OLLAMA_MODEL = "gemma3:1b"   # 4b → 1b に変更

方法2: 環境変数で切り替え

まず chatbot.py の該当行を以下に変更します:

import os
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "gemma3:4b")

あとは起動時に指定するだけです:

gemma3:4b で起動(デフォルト)
python chatbot.py
gemma3:1b で起動
OLLAMA_MODEL=gemma3:1b python chatbot.py

ドキュメントの追加

/add コマンドで実行中にドキュメントを追加できます。追加と同時にインデックスが再構築されます。

ドキュメントの追加
あなた > /add
  タイトル: 製造ライン異常検知
  本文 (空行で確定):
工場の製造ラインでは OPC UA でセンサーデータを収集し、
TurboVec でベクトル化して異常パターンを近傍検索します。

  ✓ ドキュメント #7 '製造ライン異常検知' を追加しました。インデックスを再構築します...
  エンベディング生成中 (8 件)...
Batches: 100%|███████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  1.44it/s]
  → 8 件をインデックス化しました (4bit, 次元=384)
  → インデックスを保存しました: index.tv
追加の確認
あなた > 製造ラインでは、何を使ってセンサーデータを収集しますか?
Bot  > 
  🔍 参照ドキュメント: 「製造ライン異常検知」, 「ローカルRAGへの活用」, 「学習不要の仕組み」
OPC UA でセンサーデータを収集します。

Claude API版との比較

「完全無料にこだわらない」「回答品質を優先したい」場合は、Ollama部分をClaude Haiku APIに差し替えることもできます。

項目 Ollama版(本記事) Claude Haiku API版
コスト 完全無料 有料
インターネット 不要 必要
データの外部送信 なし あり
回答品質 △〜○ ◎
応答速度(CPU) △ ◎
閉じたネットワーク 対応 非対応

製造現場や医療など機密性が求められる環境では Ollama版が特に有効です。

トラブルシューティング

Ollamaに接続できない

Ollamaを起動する
ollama serve
別ターミナルでチャットボットを起動
python chatbot.py

turbovecのビルドが失敗する

rustup update stable
pip install --force-reinstall turbovec

Raspberry Piでメモリが足りない

スワップを2GBに増やす
sudo dphys-swapfile swapoff
sudo sed -i 's/CONF_SWAPSIZE=.*/CONF_SWAPSIZE=2048/' /etc/dphys-swapfile
sudo dphys-swapfile setup && sudo dphys-swapfile swapon

回答が遅い

  • gemma3:4b → gemma3:1b に変更
  • MAX_TOKENS = 256 に下げる
  • BIT_WIDTH = 2 でインデックス検索を高速化

まとめ

TurboVec × Ollama の組み合わせで、以下を実現できました:

  • ✅ 完全無料(APIキー不要)
  • ✅ 完全ローカル(データが外に出ない)
  • ✅ Raspberry Pi 4で動作(ARM NEON最適化の恩恵)
  • ✅ モデル切り替えが1行(1b / 4b を用途に応じて選択)
  • ✅ コードブック学習不要(ドキュメント追加だけでインデックスが育つ)

製造DXの現場でオンプレRAGを構築する際の参考になれば幸いです。

使用したソースコード群

参考リンク

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?