AI・生成AI

RAG を運用に乗せる — 更新・評価・コストをどう回すか

社内文書を検索して答えさせる仕組みは、作るだけなら短時間で動きます。
難しいのは運用開始後です。実際に必要になったものを整理します。

文書が更新されたときの反映

最初に詰まるのがここです。元の文書が更新されても、
取り込み済みのベクトルは古いままです。

class Document(models.Model):
    source_id = models.CharField(max_length=100, unique=True)
    content_hash = models.CharField(max_length=64)   # 中身のハッシュ
    indexed_at = models.DateTimeField(null=True)

ハッシュを持たせ、変わったものだけ再取り込みします。

new_hash = sha256(content.encode()).hexdigest()
if doc.content_hash != new_hash:
    delete_vectors(doc.source_id)      # 古いものを消してから
    index_document(doc, content)       # 入れ直す

削除を忘れると、古い情報と新しい情報が両方ヒットして、
矛盾した回答が返るようになります。実際に起きた事故です。

精度を測る仕組みを先に作る

「なんとなく良くなった気がする」では改善できません。
評価用の質問セットを最初に作ります。

EVAL_SET = [
    {"q": "経費精算の締日は?", "must_include": ["毎月20日"]},
    {"q": "有給の申請方法は?", "must_include": ["勤怠システム", "3営業日前"]},
]

チャンクサイズや検索件数を変えたとき、
このセットの通過率で比較します。30問もあれば傾向は十分見えます。

「答えられない」と言わせる

RAG で最も危険なのは、関連文書が無いのに答えてしまうことです。

if not hits or hits[0].score < THRESHOLD:
    return "関連する社内文書が見つかりませんでした。"

スコアのしきい値を設け、下回ったら答えさせません。
業務では「間違った答え」より「答えられない」のほうが遥かに安全です。

出典を必ず返す

回答: 経費精算の締日は毎月20日です。
出典: 経理規程_2024.pdf(3ページ)

出典があると、利用者が自分で検証できます。
これが無い回答は業務では使われません。信頼できないからです。

権限を検索段階で効かせる

全社員が同じ文書を見てよいとは限りません。
回答を生成してからフィルタするのでは遅すぎます。

hits = search(query, filter={"allowed_roles": user.role})

検索の時点で絞るのが原則です。

コスト管理

埋め込みの生成と回答生成の両方に費用がかかります。

  • 埋め込みは変更分だけ(ハッシュ比較で無駄を消す)
  • よくある質問は回答をキャッシュ
  • 検索件数を増やしすぎない(文脈が長い=高い&遅い)
key = sha256(f"{normalized_query}|{doc_version}".encode()).hexdigest()
if cached := cache.get(key):
    return cached

文書バージョンをキーに含めるのがポイントです。
更新時に自動でキャッシュが外れます。

まとめ

  • 文書のハッシュを持ち、変更分だけ入れ直す(削除を忘れない)
  • 評価用の質問セットを先に作り、変更を数値で比較する
  • スコアのしきい値を設け、「答えられない」と言わせる
  • 出典を必ず返す
  • 権限は検索段階で効かせる
  • キャッシュキーに文書バージョンを含める