第63回 実務で回すRAGとベクトル検索 — Pythonで作る検索インデックス・更新・評価ワークフロー

社内ドキュメントは増える一方で、欲しい情報が見つからない──そんなつまずきは多くの現場で起きます。本記事は、RAG(Retrieval-Augmented Generation)を実務に取り入れる際の具体的な手順を、Pythonベースのワークフローに落とし込んで解説します。実装の細部よりも「現場で確実に動くこと」を重視した内容です。

この記事の狙いと対象ユースケース

想定読者は、AIを仕事に活かしたい実務担当者・個人事業主・中小企業の担当者です。以下のユースケースを中心に、導入判断基準から評価・運用までを示します。

  • FAQ応答(カスタマーサポートや社内ヘルプデスク)
  • 契約書や合意文書の検索(条項抽出、類似契約の探索)
  • 社内ナレッジの活用(オンボーディング、手順書の参照)

RAG導入を判断する現場の観点

  • 情報がテキスト化されているか(紙だけでないか)
  • 検索での適合よりも要約や文脈の補完が価値になるか
  • 応答の根拠提示(ソース表示)が業務要件で必要か
  • レスポンスのレイテンシやコスト許容度

アーキテクチャと選定基準

埋め込みモデル、ベクタDB、キャッシュ、再ランキングの役割を比較して、選定の指針を示します。

コンポーネント 候補 特徴 運用負担 推奨ユースケース
埋め込みモデル 小(open-source小型) 低コスト、オンプレ可、精度は限定 低〜中 社内FAQ、小規模データ
中(OpenAI / Cohere 等) 実用的な精度、APIで手早く導入 一般的な業務用途
大(最先端商用) 高精度だがコスト高・運用注意 高リスク・高価値な業務(法務レビュー等)
ベクタDB FAISS オンプレ向け、高速だが分散は自前 コスト重視の社内展開
Pinecone マネージド、スケーラブル 低〜中 限定リソースでの本番運用
Weaviate スキーマ対応・外部接続が豊富 メタデータ検索や複雑なフィルタが必要な場合
再ランキング BM25 / Cross-encoder 最初の候補を精査して順序を改善 精度を上げたい場合の後段処理

選定フロー(簡易)

  • データ量とレイテンシ要件を確認 → ベクタDBを選定
  • 精度要件とコストで埋め込みモデルを決定
  • ソース提示やフィルタ要件で再ランキングやメタデータ設計を加える

データ前処理とチャンク設計(実務手順)

ここでは、入力テキストの正規化、チャンク長・オーバーラップの決め方、メタデータ付与、重複除去を示します。

基本方針

  • 文単位の分割を基本に、意味が切れないようにチャンクする
  • チャンク長はモデルのコンテクストやコストに合わせる(例: 200–1000 tokens)
  • オーバーラップを入れて文脈喪失を防ぐ(例: 20–30%)
  • メタデータ(文書ID、タイトル、日付、版)を必ず付与
  • 重複はハッシュや類似度で除去(同一文書の繰り返しやテンプレート)

Pythonによる前処理とチャンク例

下は最小限の実装例です(トークナイザは環境に合わせて置き換えてください)。

from typing import List, Dict
import hashlib

def normalize(text: str) -> str:
    return ' '.join(text.strip().split())

def chunk_text(text: str, max_tokens: int = 200, overlap: int = 50) -> List[str]:
    # 簡易:スペース単位で擬似トークン長を計算
    words = text.split()
    chunks = []
    i = 0
    while i < len(words):
        j = min(i + max_tokens, len(words))
        chunk = ' '.join(words[i:j])
        chunks.append(chunk)
        i = j - overlap
    return chunks

def dedupe(chunks: List[str]) -> List[Dict]:
    seen = set()
    out = []
    for c in chunks:
        h = hashlib.sha1(c.encode('utf-8')).hexdigest()
        if h in seen:
            continue
        seen.add(h)
        out.append({'text': c, 'sha1': h})
    return out

インデックス作成・更新パイプライン(実務実装)

パイプラインは概ね「埋め込み生成 → バッチ処理 → ベクタDBへアップサート」です。差分更新と再索引は運用方針により使い分けます。

差分更新戦略

  • 変更点検出(ファイルタイムスタンプ、ハッシュ差分)→ 変更チャンクのみ再埋め込み
  • 削除: ベクタDB上のドキュメントIDを削除
  • 大規模更新: 再インデックス(夜間バッチ)を計画

Pythonでの埋め込み→アップサート(概念例)

ベクタDBのクライアントに合わせて調整してください。

# 擬似コード(概念)
from time import sleep

def generate_embeddings(texts: List[str], model='embed-model') -> List[List[float]]:
    # 実際はAPI呼び出しまたはローカルモデル
    return [fake_embed(t) for t in texts]

def batch_upsert(db_client, docs: List[dict], batch_size=128):
    for i in range(0, len(docs), batch_size):
        batch = docs[i:i+batch_size]
        vectors = generate_embeddings([d['text'] for d in batch])
        payload = []
        for d, v in zip(batch, vectors):
            payload.append({'id': d['sha1'], 'vector': v, 'metadata': d.get('meta', {})})
        db_client.upsert(payload)
        sleep(0.1)

検索時ワークフローとプロンプト合成

実運用では次の順で処理することを推奨します:retrieval → re-rank → generation。各段階でのスコアや閾値を明確に運用ルールとして定義してください。

  • Retrieval: 上位N(例: 50)を取得(高recallを優先)
  • Re-rank: Cross-encoder等で上位K(例: 5–10)に絞る
  • Generation: 絞ったソースを提示して応答生成、ソース付きで返す

スコアの扱いと安全策

  • ベクタスコアは相対値。閾値はデータで決める(検証セットで調整)
  • 信頼度が低い場合はフェールバックして「参照を確認してください」と返す
  • 必ずソース(文書ID・抜粋)を添えて説明責任を確保

プロンプト合成(例)

「ユーザー質問 + 上位Kチャンク(メタデータ付)」をテンプレートで渡し、冒頭に要約方針と根拠の出力を指示します。

system_prompt = "あなたは社内ドキュメントに基づくアシスタントです。回答には必ず根拠となる文書IDと該当箇所を示してください。"
user_prompt = f"質問: {user_q}\n\n参照資料:\n" + '\n'.join([f"[{i}] {c['metadata']['title']}: {c['text'][:200]}..." for i,c in enumerate(top_k)])

評価と実験設計

評価は定量・定性の両面を持ちます。再現率・精度・上位K有効率(Precision@K)、レイテンシ・コストの測定が基本です。

評価項目

  • Precision@K(上位Kに正解が含まれる割合)
  • Recall(必要なソースを取りこぼさないか)
  • 平均レイテンシ(検索+生成)
  • コスト(API呼び出し回数・埋め込み生成コスト)

Pythonによる自動評価の例

def precision_at_k(results: List[List[str]], gold: List[str], k=5) -> float:
    # results: 各クエリの取得IDリスト
    correct = 0
    for res, g in zip(results, gold):
        if any(r in g for r in res[:k]):
            correct += 1
    return correct / len(results)

A/Bテスト設計(現場で回す)

  • 対照群: 既存検索、実験群: RAG(同じクエリをランダム割り当て)
  • 指標: 問い合わせ解決率、平均処理時間、ユーザー満足度
  • 期間: 最低2週間、統計的有意性を確認

監視・メンテナンスとフィードバック連携

運用で重要なのは質のドリフト検出と定期的な再インデックスです。61回のフィードバックワークフローを参考にした連携例を示します。

  • 品質指標の継続監視(Precision@Kの時間変化)
  • ユーザーフィードバックは必ずメタデータと紐付ける(どの応答で不満が出たか)
  • 一定閾値で自動トリガー:例)Precision@5が下がったら夜間再インデックスを予約

導入チェックリストと段階的ローンチ案

PoCから本番までの必須項目を段階的にまとめます。

段階 必須項目 注意点
PoC 代表的データセット、簡易検索パイプライン、評価スクリプト 小規模で効果を示すことを優先する
限定運用 ユーザーフィードバック回収、ログとモニタリング、差分更新の運用 特定チームでの運用で運用負担を確認
本番展開 フルデータでのスケール、SLA(応答時間)、コスト管理 レイテンシと費用対効果を定期レビュー

よくある落とし穴と対策

  • データの質が低い → 正規化と重複除去で事前処理を強化
  • コスト見積もり不足 → 小さなサンプルでコスト実測し見積もり直し
  • 根拠提示がない応答 → プロンプト設計で必須化し、品質チェックを自動化

導入後の想定運用コスト(簡易目安)

項目
埋め込みAPI オンプレ小型モデル 商用API(中) 商用API(大量/高頻度)
ベクタDB FAISS(自前運用) Pinecone等 大規模マネージド+冗長構成

まとめ

RAGの導入は単なる技術導入ではなく、データ整理・評価・運用設計が鍵です。まずは小さなPoCで効果を確認し、差分更新や監視ルールを整えながら段階的に本番へ移行することを推奨します。以下が実務的な要点です。

  • データ前処理とチャンク設計を手抜きしない(品質の多くはここで決まる)
  • 埋め込みモデルとベクタDBは目的とコストで選ぶ(選定フローを明文化する)
  • retrieval → re-rank → generation の順でワークフローを設計し、ソース提示を徹底する
  • 評価指標を定義して定期的にモニタリング、閾値で再インデックスを自動化する

次回以降では、具体的なベクタDB(FAISS / Pinecone / Weaviate)ごとの設定例や、より詳細な再ランキング実装を紹介します。Manage AIのシリーズ「AIとPythonの実務」として、手元で再現できる形で届けていきます。