
Vertex AI RAG Engineのナレッジベース更新で踏んだ落とし穴 ― GCSアップロードだけでは同期されない
はじめに
Vertex AI RAG Engineを使ってチャットボットのナレッジベース(KB)を運用しています。KBの内容改善で数百件の記事を一括更新・統合・削除する作業が発生した際、「GCSにアップロードしてimport_filesを叩けば終わりだろう」と思っていたら、想定外の挙動に何度もハマりました。
この記事では、Vertex AI RAG Engineでナレッジベースを更新する際に知っておくべき同期の仕組みと、実際に踏んだ落とし穴、最終的に落ち着いた運用スクリプトの設計について共有します。
構成
今回の構成はシンプルです。
- ナレッジベースの記事(Markdownファイル)をローカルで管理
- GCSバケットにアップロード
- Vertex AI RAG Engineの
import_filesでコーパスに取り込み - チャットボットがコーパスに対してRAG検索を実行
更新の流れとしては「ローカルで編集 → GCSアップロード → コーパスにインポート」で、ボット自体の再デプロイは不要です。

落とし穴1: import_filesは変更ファイルだけ再取り込みする
最初の更新で約170件のファイルをGCSにアップロードし、import_filesを実行しました。結果は「Imported 7 files」。
焦りましたが、これは正常な挙動でした。import_filesは以下の3条件がすべて満たされた場合、ファイルをスキップします:
- そのファイルが以前にインポート済み
- ファイルの内容が変更されていない
- チャンキング設定が変更されていない
つまり、内容が変わったファイルだけ再取り込みされる仕組みです。AWSのS3同期のようにスキップ判定が入るため、毎回全件インポートし直す必要はありません。
落とし穴2: 削除したファイルはコーパスから消えない
これが最大のハマりポイントでした。
今回の作業では、重複記事の統合や古い記事の削除で約140件のファイルを削除しました。GCSからもファイルを消し、import_filesを実行。しかし、ボットの回答が改善されない。
調べてみると、コーパスにはまだ数百件のRAGファイルが残っていました。import_filesは「追加・更新」のみで、削除は一切行わないのです。
GCSからファイルを消しても、コーパス内のインデックス(RAGファイル)はそのまま残り続けます。古い記事の断片がRAG検索でヒットし、ボットが不正確な回答を返していたわけです。
削除されたファイルをコーパスから消すには、RAG Engine APIのdelete_fileを明示的に呼ぶ必要があります。
落とし穴3: 一括削除でAPIクォータに引っかかる
「じゃあコーパスを全部消してから再インポートしよう」と考えて、300件以上のRAGファイルを一括削除しようとしました。結果はRESOURCE_EXHAUSTEDエラー。
Vertex AI RAG EngineにはVertexRagDataService requests per minute per regionのクォータがあり、delete_fileをループで回すとすぐに上限に達します。
対策として、リトライとバックオフを入れる必要がありました:
for i, f in enumerate(files_to_delete):
for attempt in range(3):
try:
rag.delete_file(name=f.name)
break
except Exception: # ResourceExhausted等、SDKバージョンにより例外型が異なる
wait = 30 * (attempt + 1)
time.sleep(wait)
ただし、この「全消し→再インポート」アプローチ自体が間違いでした。
最終的な設計: 差分のみ同期する--cleanフラグ
全削除は遅いし、クォータにも引っかかる。そもそも変更のないファイルを消して再インポートする意味がない。
最終的に落ち着いたのは、ローカルのファイル一覧とGCS/コーパスを比較し、ローカルに存在しないファイルだけ削除するアプローチです。
def sync_deletions(corpus_name, bucket_name, project_id):
"""ローカルに存在しないファイルをGCSとコーパスから削除する"""
local_files = {f.name for f in KB_DIR.glob("*.md")}
# GCS側の孤立ファイルを削除
blobs = list(bucket.list_blobs(prefix="kb_articles/"))
orphaned_blobs = [b for b in blobs if b.name.removeprefix("kb_articles/") not in local_files]
if orphaned_blobs:
bucket.delete_blobs(orphaned_blobs)
# コーパス側の孤立RAGファイルを削除
rag_files = list(rag.list_files(corpus_name=corpus_name))
orphaned_rag = [f for f in rag_files if f.display_name not in local_files]
for f in orphaned_rag:
rag.delete_file(name=f.name) # リトライロジックは省略
これにより:
- GCSアップロード: 同名ファイルは上書き(変更済みファイルが更新される)
import_files: 変更されたファイルだけ再インポート(未変更はスキップ)--clean: ローカルに存在しないファイルだけGCS/コーパスから削除
全体の実行コマンドは1行です:
python scripts/upload_kb.py \
--project YOUR_PROJECT_ID \
--bucket YOUR_BUCKET_NAME \
--corpus YOUR_CORPUS_ID \
--clean
同期の仕組みまとめ
| 操作 | GCS | RAGコーパス |
|---|---|---|
| ファイル内容の変更 | アップロードで上書き | import_filesが自動で再取り込み |
| 新規ファイル追加 | アップロードで追加 | import_filesが取り込み |
| ファイル削除 | 自動では消えない | 自動では消えない |
| チャンキング設定変更 | 影響なし | import_filesが再取り込み |
ポイント: import_filesは追加・更新のみ。削除は常に明示的に行う必要がある。
大量KB更新のワークフロー

実際の運用では、クライアントからExcelシートで改善内容(修正・統合・削除)が届き、それを一括で適用する流れでした。約300件の変更を以下のステップで処理しました:
- Excelシートを読み込み、アクション(修正/統合/削除)ごとに分類
- 修正: タイトルと対応内容をMarkdownファイルに反映
- 統合: 統合先のファイルを更新し、統合元のファイルを削除
- 削除: 不要なファイルを削除
- metadata.json更新: 削除したファイルのエントリを除去
- アップロード+インポート:
--clean付きで実行
統合元の記事が残っていると、RAG検索で古い断片的な回答がヒットして、せっかく統合した詳細な回答が埋もれてしまいます。--cleanで孤立ファイルを確実に消すことが重要です。
まとめ
Vertex AI RAG Engineでナレッジベースを運用する際の要点:
import_filesは賢い — 変更があったファイルだけ再取り込みするので、毎回全件インポートする必要はない- 削除だけは自動化されない — GCSからファイルを消してもコーパスには残る。
delete_fileAPIで明示的に削除が必要 - 一括削除はクォータに注意 — リトライ+バックオフは必須。全消し+再インポートより、差分削除のほうが速く安全
- 記事の統合時は、統合元の削除を忘れずに — 古い断片がRAG検索で上位にヒットし、回答品質が劣化する
「GCSにアップロードすれば同期される」という思い込みは、AWSのS3 + 他サービスの連携に慣れていると特にハマりやすいポイントです。RAG Engineのインデックスは独立した状態を持っているということを意識しておくと、運用がスムーズになります。







