Logo
Back to all posts

How to Build a $0 Cost Agentic RAG AI Assistant from Scratch using Cloudflare

If you're still paying $20/month for a managed vector database and hosting your AI chatbot on cold-starting serverless functions, you're doing it wrong.

When I decided to add an AI assistant to my portfolio, I had two strict requirements: it must have zero latency, and it must cost me exactly $0 to run.

I achieved both by moving the entire AI architecture to the edge. In this tutorial, I'll show you exactly how to build a stateful, Agentic RAG (Retrieval-Augmented Generation) system from scratch using the Cloudflare Developer Platform.


The $0 Stack Architecture

To keep costs at zero while maintaining enterprise-grade performance, we rely entirely on Cloudflare's free tiers:

  • Cloudflare Workers: The edge compute runtime. (Free up to 100,000 requests/day).
  • Cloudflare Vectorize: Our vector database for semantic search. (Free up to 30 million queried dimensions/month).
  • Cloudflare D1: Our serverless SQLite database for chat history. (Free up to 5 million read rows/day).
  • Cloudflare AI Gateway: For caching LLM requests and preventing rate limits.

Instead of a passive RAG system that just stuffs documents into a prompt, we are building an Agentic system. This means the AI runs in a "ReAct" (Reasoning and Acting) loop, equipped with custom tools it can invoke dynamically based on user input.


Step 1: Initializing the Edge Worker

First, we need to spin up a Cloudflare Worker. This will serve as the brain of our operation.

npm create cloudflare@latest agentic-assistant
cd agentic-assistant

Open your wrangler.toml file. This is where the magic happens. We need to bind our D1 database and our Vectorize index directly to the worker environment so they execute with near-zero latency.

name = "agentic-assistant"
main = "src/index.ts"
compatibility_date = "2026-05-01"

[[d1_databases]]
binding = "DB"
database_name = "chat_history"
database_id = "your-d1-id"

[[vectorize]]
binding = "KNOWLEDGE_BASE"
index_name = "rag-index"

Step 2: Ingesting the Knowledge Base

Before our agent can answer questions, it needs data. I created a simple rag-data.ts file containing my professional background, services, and pricing.

To embed this data into Cloudflare Vectorize, we use Cloudflare's native Workers AI text-embedding models. This is vastly cheaper (and faster) than calling the OpenAI embeddings API.

import { Ai } from '@cloudflare/ai';

export default {
  async fetch(request, env) {
    const ai = new Ai(env.AI);
    
    // 1. Generate the vector embedding at the edge
    const { data } = await ai.run('@cf/baai/bge-small-en-v1.5', {
      text: "Elvis Li is a Solo Full-Stack Developer specializing in AI."
    });

    // 2. Insert directly into Vectorize
    await env.KNOWLEDGE_BASE.upsert([{
      id: "bio-1",
      values: data[0],
      metadata: { category: "background" }
    }]);

    return new Response("Knowledge Base updated!");
  }
}

Step 3: Building the Tool-Calling ReAct Loop

This is what makes the system Agentic. Standard RAG just searches the vector database blindly. Our system asks the LLM to decide if it needs to search.

I use Gemini 1.5 Pro via the Cloudflare AI Gateway. We provide Gemini with a JSON schema of available tools.

const tools = [
  {
    name: "query_knowledge_base",
    description: "Use this to search the vector database for facts about Elvis.",
    parameters: {
      type: "object",
      properties: {
        query: { type: "string" }
      },
      required: ["query"]
    }
  }
];

When the user asks, "What kind of AI systems do you build?", the worker intercepts the request and triggers the agent loop:

  1. The Request: The worker sends the user's message and the tool list to Gemini.
  2. The Thought: Gemini realizes it doesn't know the answer, but it knows the query_knowledge_base tool does. It returns a tool_call response.
  3. The Action (RAG): The worker intercepts the tool call, takes Gemini's generated search query, embeds it via Workers AI, and queries Cloudflare Vectorize.
  4. The Resolution: The worker feeds the vector search results back into Gemini, which then synthesizes a natural, human-like response.

All of this happens inside a single edge node located physically close to the user, resulting in incredibly fast response times.


Step 4: Persisting State with Cloudflare D1

An AI is useless if it suffers from amnesia. To make the chatbot conversational, we must retain message history.

Instead of setting up an external PostgreSQL instance, we use Cloudflare D1. It's SQLite built on Durable Objects, meaning it runs on the edge alongside our worker.

// Save the conversation locally at the edge
await env.DB.prepare(`
  INSERT INTO messages (session_id, role, content) 
  VALUES (?, ?, ?)
`).bind(sessionId, 'user', userQuery).run();

On every incoming request, the worker simply SELECT * FROM messages WHERE session_id = ? to reconstruct the chat history before hitting the LLM. Because D1 is serverless and distributed, this query adds almost zero overhead.

The Result: Enterprise Grade, Zero Cost

By leveraging Cloudflare Workers, Vectorize, and D1, you eliminate the need for expensive managed vector databases, cold-starting Lambdas, and dedicated database servers.

The result is a completely autonomous, stateful Agentic RAG system that costs $0 to operate and responds faster than almost any commercial alternative. If you're building personal portfolios, startup landing pages, or internal tools, this is the definitive architecture of 2026.

Thanks for reading! Did you find this helpful?

Get in touch