Antigravity × Cloudflare Vectorize: Build a Production RAG Pipeline That Runs at the Edge
A production guide to building a RAG pipeline on Cloudflare Vectorize using Antigravity. Chunking, hybrid search, cost design, and observability—covered at a depth a solo developer can actually ship.
"I want to ship vector search to production, but I don't know where to start." If you're a solo developer building a SaaS or a knowledge base, this is a wall you'll hit. When I rebuilt a small Q&A bot on Cloudflare Workers using Vectorize, the first few days were dominated by the realization that something as small as chunk length had an outsized effect on retrieval quality.
Cloudflare Vectorize went GA in late 2024 and is an edge-optimized vector database integrated with Workers AI. Unlike Pinecone or Weaviate, it lives inside Cloudflare's control plane, which means your Worker and your vector index can run in the same region. Combined with a generous free tier and pay-as-you-go pricing, it's one of the easier vector databases for an indie developer to put into production.
In this article, I'll walk you through using Antigravity's AI agents to scaffold a RAG pipeline quickly, and then show you how to grow it into something you can actually run in production—including the traps I personally fell into. By the end, you'll have ingested your own documents, measured retrieval quality, understood your cost and latency profile, and deployed it all on Cloudflare Workers.
Why Cloudflare Vectorize? The Real Value of Running at the Edge
Vector database options have exploded—Pinecone, Weaviate, Qdrant, Chroma, and many more. Vectorize earns its place by living on the same infrastructure as Workers.
When you call Vectorize from Workers, you connect via a binding rather than an HTTP API. That means latency is closer to a local function call: no TCP handshake, no auth header construction, no DNS lookup tax. In my own measurements, queries from Workers in Tokyo to Pinecone in the US took 180–250 ms round-trip, while the equivalent Vectorize query came in at 20–40 ms. When you run RAG at the edge, that order-of-magnitude difference changes the perceived speed of your entire response.
It's not a silver bullet. Vectorize caps out at 5 million vectors per index, has limits on metadata cardinality, and doesn't expose the low-level knobs (PQ quantization, IVF tuning) that some workloads demand. If you're trying to squeeze out every last millisecond of accuracy, or running a hundreds-of-millions vector research dataset, a Pinecone Pod or a self-hosted Qdrant will serve you better. Vectorize shines for the specific case where a solo developer wants 100K to a few million vectors served entirely from the Cloudflare stack.
I use Vectorize across the four blogs in this network to power site search and "related articles" recommendations. Removing one external API dependency for a few dollars a month was, frankly, a relief.
A Working RAG in Five Minutes: Scaffold It with Antigravity
Let's get concrete. Open your project in Antigravity's Manager Surface, and prompt the AI agent like this:
Scaffold a RAG pipeline on Cloudflare Workers + Vectorize.Requirements:- TypeScript with Hono- /ingest endpoint that accepts Markdown, chunks it, embeds with Workers AI, and upserts into Vectorize- /query endpoint that takes a question, runs vector search, and passes the top 5 results as context to a Workers AI text-generation model- Generate wrangler.toml as well- Use bindings: VECTORIZE_INDEX, AI
Once Antigravity sets up the project structure, read through the generated code carefully. Don't just run the scaffold—you'll get blocked later when you try to swap the chunker or the embedding model. Spend the first few hours making sure every piece is at a granularity you understand.
Here's the core of src/index.ts, refactored from the version I run in production into something minimal:
Run wrangler dev and you have a working RAG. The expected behavior: a single 5,000-character article ingests as 6–8 chunks, and /query returns 5 high-similarity matches.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Go from 'I don't know how to use Vectorize' to having a production-grade RAG pipeline running on Cloudflare Workers, scaffolded with Antigravity
✦You'll learn the hybrid search, reranking, and metadata filtering techniques you need to ship search quality you won't be embarrassed by
✦You can now apply the cold-start mitigation, token cost controls, and observability patterns specific to edge-deployed RAG to your own product
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Ingestion Pipeline: Chunking, Embeddings, and Metadata
The minimal version works, but it's not production. The single biggest factor in retrieval quality is chunking strategy.
I started with fixed 1,000-character chunks. The result: chunks that broke mid-heading, with "title-only fragments" frequently ranking near the top. After much iteration, I settled on a Markdown-aware chunker that respects heading hierarchy and aims for 600–900 chars per chunk on paragraph boundaries.
Switching to this chunker boosted my site search click-through (the rate at which a search result actually got clicked) by about 1.6×. The reason is unglamorous: keeping the heading at the top of each chunk preserves enough surrounding context for the embedding to reflect what the section is actually about.
Choosing an Embedding Model
The main embedding models available on Workers AI:
@cf/baai/bge-small-en-v1.5 (384 dim, English)
@cf/baai/bge-base-en-v1.5 (768 dim, English)
@cf/baai/bge-large-en-v1.5 (1024 dim, English)
@cf/baai/bge-m3 (1024 dim, multilingual)
If you're indexing Japanese content, bge-m3 is the only sensible choice. I lost a full day in my early days trying to debug poor retrieval on Japanese queries before realizing I was running an English-only model. Lesson: check the language coverage of your embedding model first.
That said, bge-m3 is 1024-dim, which directly increases Vectorize storage costs and query latency. For predominantly English content, bge-base-en-v1.5 (768-dim) is more economical.
Metadata Design You'll Be Glad You Made
The metadata you attach at upsert time becomes the lever you pull later for filtering. The fields I wish I'd added from day one:
docId: stable ID of the source document (essential for updates and deletes)
title: for display
category / tag: for filtered search
lang: to scope queries by language
updatedAt (unix timestamp): for re-indexing decisions
text: the chunk body itself (Vectorize doesn't return body text by default, so you must store it)
You must create metadata indexes ahead of time with wrangler vectorize create-metadata-index. Adding them later doesn't backfill existing vectors, so be exhaustive before going to production.
Lifting Search Quality to Production: Hybrid Search and Reranking
Pure vector search struggles with proper nouns and version numbers. A query like "Antigravity 1.20" embeds near a lot of vaguely-related content, and unrelated articles bubble up.
I solved this with hybrid search (vector + full-text) + reranking in two stages.
-- migrations/0001_chunks_fts.sqlCREATE VIRTUAL TABLE chunks_fts USING fts5( id UNINDEXED, title, text, tokenize='unicode61 remove_diacritics 2');
Reciprocal Rank Fusion is simple but powerful. It merges results from vector search and FTS using only their ranks, so you don't have to worry about score normalization between two scoring systems with very different magnitudes.
Re-Rank the Top N with Workers AI Reranker
Pull a wider net (top 20) with hybrid search, then have a reranker decide which are actually most relevant.
Adding a reranker visibly improved the factual quality of LLM answers in my system. Before reranking, the LLM occasionally "filled in" facts that weren't in the documents at all. After reranking, those hallucinations almost vanished.
Cost Design: Make Tokens, Calls, and Storage Visible
Vectorize cost has three axes:
Queries (vector × topK)
Stored vectors (GB/month)
Workers AI calls (embeddings, LLM, and reranker each metered separately)
For a small SaaS scale (10K queries/month, 50K stored chunks), my bill stays under about $7/month. But if you're not careful, Workers AI tokens are where bloat creeps in.
Analytics Engine is Cloudflare's pay-as-you-go log fabric, free for hundreds of thousands of events per month in practice. I thread UsageLogger through every endpoint and surface a usage summary in the response headers during development.
Three Cost Wins That Actually Mattered
The optimizations that moved the needle in my deployment:
Cache layer: Hash the query, look it up in KV before doing anything else. FAQ-shaped questions hit cache 70%+ of the time
Early return on weak matches: If the top vector score is below a threshold (I use 0.55), don't even call the LLM
Model downgrade: Route easy questions to llama-3.2-3b-instruct instead of llama-3.1-8b-instruct
The cache alone cut Workers AI costs by about 40%. The implementation is trivial:
Observability: Logs, Metrics, and Surfacing Failure Modes
What matters most in production is being able to identify the cause when accuracy drops, costs spike, or latency creeps up. If you want to keep everything inside Cloudflare, this is the stack I recommend:
Tail Workers: forward failed requests to a separate Worker for long-term storage and Slack alerts
Monitoring the distribution of reranker scores is what lets you catch quality regressions early. I once bulk-updated some articles and the chunk boundaries shifted; the reranker's top score average fell from ~0.6 to ~0.3 overnight. Without dashboards I'd have only learned about it from a user complaint days later.
// src/lib/health-check.tsexport async function searchHealthCheck(env: Bindings) { const goldenQueries = [ { q: "How do I get started with Antigravity?", expectedDocId: "antigravity-getting-started" }, { q: "What does Cloudflare Vectorize cost?", expectedDocId: "antigravity-cloudflare-vectorize-rag-production-guide" }, ]; const failures: { q: string; got: string[] }[] = []; for (const { q, expectedDocId } of goldenQueries) { const results = await hybridSearch(env, q, 5); const ids = results.map((r) => r.id.split("#")[0]); if (!ids.includes(expectedDocId)) failures.push({ q, got: ids }); } return { passed: failures.length === 0, failures };}
Run this from a Cron Trigger every hour and post failures to Slack. You'll catch regressions long before users do.
Common Pitfalls
You can read this entire article, feel prepared, and still trip on something. Here are the traps I (and other indie developers I know) have actually fallen into, with fixes.
1. Forgetting to Create Metadata Indexes
returnMetadata: "all" lets you read metadata from results, but using metadata in a filter clause requires that you create a metadata index up front. I missed this initially: my "filtered" queries appeared to ignore the filter entirely. Half a day was lost to it. The Vectorize docs are explicit, but it's an easy thing to glide past.
If you decide to swap embedding models because accuracy isn't great, you can't keep using the old index. Different dimensions can't even upsert; even with the same dimension, the vector spaces are different and similarity becomes meaningless. Always rebuild the index when you change the embedding model. I run a parallel index with a _v2 suffix, then flip the binding on cutover day, like a DNS change.
3. Regretting Your Chunk ID Scheme
I started with simple IDs like ${docId}_${i}. The problem: every time a document was updated, I needed to delete the old chunks before upserting new ones—but Vectorize's deleteByIds requires exact ID matches, so I needed to know how many old chunks existed. I now store the current chunk count in KV under doc:${docId}:chunkCount, and on update I bulk-delete the old range first.
The first request to a Workers AI model after an idle period can take a few seconds. If that happens on a user-driven query, the experience suffers. I run a Cron Trigger every 5 minutes that hits a /health endpoint to keep the embedding model warm. Costs are pennies a month, and p99 latency drops by seconds.
5. Japanese Queries Don't Work with Default FTS
D1's FTS5 with the unicode61 tokenizer doesn't recognize word boundaries in Japanese, so it returns much less than you'd expect. For Chinese/Japanese/Korean documents, switch to the trigram tokenizer (tokenize='trigram', available in SQLite 3.34+). Doing so roughly doubled recall on Japanese queries in my deployment.
6. Forgetting Idempotent Ingestion
Early on, I let /ingest be called multiple times on the same document without any guard. The result was duplicate chunks in the index, which then dominated search results because they had identical embeddings ranked near each other. The fix is to make ingestion idempotent: always run the delete-then-upsert dance described above, or compute a hash of the body and skip the work if it hasn't changed.
A content hash short-circuits the most expensive part of the pipeline (embedding generation) and saves real money over time, especially when your ingestion job re-scans an entire corpus on a schedule.
7. Underestimating Eval Discipline
The hardest part of RAG isn't building the pipeline—it's knowing whether your changes made things better. Without an eval set, you'll convince yourself that every tweak is an improvement, when in fact several of them silently regress quality.
I keep a small spreadsheet of 30 representative questions and the article ID I expect to be the top hit for each. Every time I touch the chunker, embedding model, reranker, or topK, I run the eval suite and compare the recall@5 number. The discipline is unglamorous but it's what separates "I think this works" from "I can prove it works."
Cron Trigger (daily 03:00 UTC): golden query regression test
Manual webhook on article add/update: reindex
In practice, this stack costs me about $5–7/month, p95 latency is ~60 ms for retrieval and ~1.4 s for retrieval + answer generation, and the health check passes 99.8% of the time. Compared to my previous Pinecone-based setup, operating cost is roughly half and latency is ~4× faster.
When to Plan for Scale
Past 100K vectors, Vectorize queries get noticeably heavier. Past 200K, if your metadata filters aren't selective, the Workers AI reranker starts to dominate end-to-end latency. That's roughly the line where it pays to think about your next optimization.
Ordered by impact:
Re-tune chunk size: bigger chunks, fewer total vectors
Filter early via metadata indexes: scope by category or lang
Make reranking optional: skip reranking when the top-5 score gap is large
Split into multiple indexes: e.g., one per language or category
If you want the broader Cloudflare context, the related articles Cloudflare Edge SaaS architecture guide and Cloudflare AI Gateway guide cover the upstream (request routing, caching) and downstream (cost management, monitoring) sides of the RAG pipeline described here.
What to Do Next
This article covered a lot of ground, but the action you can take tomorrow is simple: take 10–20 documents you already work with—blog posts, Notion exports, Markdown from a wiki—and ingest them into Vectorize. Hit /query with a few questions and see whether the right things come back.
The minimal scaffold is in the first half of this article. Run it as-is on your own data first. Only after you've felt the gap between the minimal version and what you actually need will hybrid search and reranking feel worth the effort. Trying to build the perfect setup from day one almost always stalls.
My own first RAG was sad. Retrieval was bad enough that I genuinely wondered whether the whole approach was viable. Having a scaffold to iterate on—chunking, model selection, reranking, one piece at a time—is what saved it. The biggest reason to use Antigravity is exactly this: the cost of producing the first working scaffold drops to near-zero.
Edge-deployed RAG is one of the most cost-effective ways for an indie developer to deliver search quality competitive with much larger teams. I hope your next product makes use of it.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.