Pitfalls encountered when updating the Vertex AI RAG Engine knowledge base — uploading to GCS alone does not trigger synchronization

Pitfalls encountered when updating the Vertex AI RAG Engine knowledge base — uploading to GCS alone does not trigger synchronization

When updating a knowledge base with Vertex AI RAG Engine, I will share from personal experience the issue of corpus deletions not being synchronized just by uploading to GCS, API quota constraints, and a solution through differential synchronization.
2026.07.10

This page has been translated by machine translation. View original

Introduction

I'm operating a chatbot knowledge base (KB) using Vertex AI RAG Engine. When a task arose to bulk update, merge, and delete hundreds of articles to improve the KB content, I thought "just upload to GCS and call import_files, that should do it" — but I ran into unexpected behavior multiple times.

In this article, I'll share the synchronization mechanism you need to know when updating a knowledge base with Vertex AI RAG Engine, the pitfalls I actually encountered, and the operational script design I ultimately settled on.

Architecture

The setup is simple.

  • KB articles (Markdown files) managed locally
  • Uploaded to a GCS bucket
  • Ingested into a corpus via Vertex AI RAG Engine's import_files
  • The chatbot performs RAG searches against the corpus

The update flow is "edit locally → upload to GCS → import to corpus," with no need to redeploy the bot itself.

vertex-ai-rag-engine-kb-sync-pitfalls-sync-flow

Pitfall 1: import_files Only Re-ingests Changed Files

In the first update, I uploaded about 170 files to GCS and ran import_files. The result: "Imported 7 files."

I panicked, but this was actually expected behavior. import_files skips a file when all three of the following conditions are met:

  1. The file was previously imported
  2. The file's content has not changed
  3. The chunking configuration has not changed

In other words, only files whose content has changed are re-ingested. Skip logic similar to AWS S3 sync is applied, so there's no need to re-import everything from scratch every time.

Pitfall 2: Deleted Files Don't Disappear from the Corpus

This was the biggest trap.

In this task, about 140 files were deleted due to merging duplicate articles and removing outdated ones. I deleted the files from GCS and ran import_files. However, the bot's responses did not improve.

Upon investigation, the corpus still had hundreds of RAG files remaining. import_files only performs "additions and updates" — it never performs deletions.

Even if you delete files from GCS, the indexes (RAG files) in the corpus remain intact. Fragments of old articles were being hit in RAG searches, causing the bot to return inaccurate responses.

To remove deleted files from the corpus, you need to explicitly call delete_file via the RAG Engine API.

Pitfall 3: Bulk Deletion Hits API Quota

Thinking "let's just delete everything from the corpus and re-import," I tried to bulk delete over 300 RAG files. The result: a RESOURCE_EXHAUSTED error.

Vertex AI RAG Engine has a quota for VertexRagDataService requests per minute per region, and looping through delete_file calls quickly hits the limit.

As a countermeasure, it was necessary to add retries and backoff:

for i, f in enumerate(files_to_delete):
    for attempt in range(3):
        try:
            rag.delete_file(name=f.name)
            break
        except Exception:  # ResourceExhausted, etc. — exception types vary by SDK version
            wait = 30 * (attempt + 1)
            time.sleep(wait)

However, the "delete everything → re-import" approach itself was wrong.

Final Design: A --clean Flag That Syncs Only Diffs

Full deletion is slow and hits quota limits. There's no point in deleting and re-importing files that haven't changed.

The approach I ultimately settled on is to compare the local file list against GCS/corpus and delete only files that no longer exist locally.

def sync_deletions(corpus_name, bucket_name, project_id):
    """Delete files from GCS and corpus that no longer exist locally"""
    local_files = {f.name for f in KB_DIR.glob("*.md")}

    # Delete orphaned files on the GCS side
    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)

    # Delete orphaned RAG files on the corpus side
    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)  # retry logic omitted

With this approach:

  • GCS upload: Files with the same name are overwritten (changed files get updated)
  • import_files: Only changed files are re-imported (unchanged files are skipped)
  • --clean: Only files that no longer exist locally are deleted from GCS/corpus

The entire execution is a single command:

python scripts/upload_kb.py \
  --project YOUR_PROJECT_ID \
  --bucket YOUR_BUCKET_NAME \
  --corpus YOUR_CORPUS_ID \
  --clean

Summary of Sync Behavior

Operation GCS RAG Corpus
File content changed Overwritten on upload import_files automatically re-ingests
New file added Added on upload import_files ingests it
File deleted Does not disappear automatically Does not disappear automatically
Chunking config changed No effect import_files re-ingests

Key point: import_files only adds and updates. Deletions must always be performed explicitly.

Workflow for Large-Scale KB Updates

vertex-ai-rag-engine-kb-sync-pitfalls-workflow

In actual operations, improvement requests (corrections, merges, deletions) arrived from the client in an Excel sheet, and the task was to apply them all at once. About 300 changes were processed in the following steps:

  1. Load the Excel sheet and classify by action (correction / merge / deletion)
  2. Corrections: Reflect updated titles and content in the Markdown files
  3. Merges: Update the merge-target file and delete the merge-source file
  4. Deletions: Delete unnecessary files
  5. Update metadata.json: Remove entries for deleted files
  6. Upload + Import: Run with --clean

If merge-source articles remain, old fragmented responses will surface in RAG searches, burying the detailed answers that were consolidated through merging. Using --clean to reliably remove orphaned files is essential.

Conclusion

Key points for operating a knowledge base with Vertex AI RAG Engine:

  • import_files is smart — it only re-ingests files that have changed, so there's no need to import everything from scratch every time
  • Deletions are never automated — deleting a file from GCS leaves it in the corpus. Explicit deletion via the delete_file API is required
  • Watch out for quota limits on bulk deletions — retry + backoff is essential. Differential deletion is faster and safer than delete-all + re-import
  • When merging articles, don't forget to delete the merge sources — old fragments will rank high in RAG searches and degrade response quality

The assumption that "uploading to GCS syncs everything" is an especially easy trap to fall into if you're used to AWS S3 integrations with other services. Keeping in mind that the RAG Engine index maintains its own independent state will make operations much smoother.

Share this article