Agentic RAG

Combining Retrieval-Augmented Generation with Agents β€” self-search, self-evaluate, self-correct

Traditional RAG vs Agentic RAG

Traditional RAG

User query β†’ Embed β†’ Search vector DB β†’ Return top-K β†’ Generate answer

A fixed pipeline with no ability to self-evaluate results.

Agentic RAG

User query β†’ Agent analyzes β†’ Choose search strategy
           β†’ Search β†’ Evaluate results
           β†’ Not enough? β†’ Modify query β†’ Search again
           β†’ Good enough? β†’ Generate answer

The agent autonomously decides the approach, evaluates results, and self-corrects when needed.

Key Differences

AspectTraditional RAGAgentic RAG
FlowFixed pipelineAgent decides
EvaluationNoneSelf-evaluates results
Self-correctionNoneYes, auto-retry
Multi-sourceSingle vector DBMultiple sources, tools
ComplexityLowHigh

Building Agentic RAG With Supabase pgvector

Step 1: Setup Vector Store

-- In Supabase SQL Editor
create extension if not exists vector;

create table documents (
  id bigserial primary key,
  content text,
  metadata jsonb,
  embedding vector(1536) -- OpenAI text-embedding-3-small
);

create index on documents
  using ivfflat (embedding vector_cosine_ops)
  with (lists = 100);

-- Semantic search function
create or replace function match_documents(
  query_embedding vector(1536),
  match_threshold float default 0.7,
  match_count int default 5
) returns table (
  id bigint,
  content text,
  metadata jsonb,
  similarity float
) language plpgsql as $$
begin
  return query
  select
    d.id, d.content, d.metadata,
    1 - (d.embedding <=> query_embedding) as similarity
  from documents d
  where 1 - (d.embedding <=> query_embedding) > match_threshold
  order by d.embedding <=> query_embedding
  limit match_count;
end;
$$;

Step 2: Embedding & Retrieval Agent

import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { createClient } from "@supabase/supabase-js";
import { z } from "zod";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);

const searchDocuments = tool({
  description: "Search for relevant documents in the knowledge base",
  parameters: z.object({
    query: z.string().describe("Question or topic to search for"),
    threshold: z.number().default(0.7).describe("Similarity threshold (0-1)")
  }),
  execute: async ({ query, threshold }) => {
    const embeddingResponse = await openai.embeddings.create({
      model: "text-embedding-3-small",
      input: query
    });
    const queryEmbedding = embeddingResponse.data[0].embedding;

    const { data, error } = await supabase.rpc("match_documents", {
      query_embedding: queryEmbedding,
      match_threshold: threshold,
      match_count: 5
    });

    if (error) return { success: false, error: error.message };
    return { success: true, documents: data };
  }
});

const refineSearch = tool({
  description: "Search again with an adjusted query when initial results are insufficient",
  parameters: z.object({
    originalQuery: z.string(),
    refinedQuery: z.string(),
    reason: z.string().describe("Reason for refining")
  }),
  execute: async ({ refinedQuery }) => {
    return { success: true, documents: [] };
  }
});

Step 3: Self-Evaluating Agent Loop

const result = await generateText({
  model: openai("gpt-4o"),
  system: `You are an intelligent search assistant. Process:
1. Search for relevant documents
2. Evaluate result quality (relevance, completeness)
3. If results aren't good enough, refine query and search again
4. Only answer when you have reliable information
5. If you can't find anything, say so honestly`,
  tools: { searchDocuments, refineSearch },
  maxSteps: 8,
  prompt: userQuestion
});

Corrective RAG

A critical pattern: the agent self-corrects when it detects issues.

const evaluateAndCorrect = tool({
  description: "Evaluate retrieval results and decide next step",
  parameters: z.object({
    retrievedDocs: z.array(z.string()),
    queryIntent: z.string(),
    assessment: z.enum(["sufficient", "partial", "insufficient"]),
    nextAction: z.enum(["answer", "refine_query", "try_different_source", "admit_unknown"])
  }),
  execute: async ({ assessment, nextAction }) => {
    return { assessment, nextAction, message: `Assessment: ${assessment}. Action: ${nextAction}` };
  }
});

Self-Correction Flow

Query β†’ Search β†’ Evaluate results
                    β”œβ”€β”€ Sufficient β†’ Generate answer
                    β”œβ”€β”€ Partial β†’ Refine query β†’ Search again
                    β”œβ”€β”€ Insufficient β†’ Try different source
                    └── Repeatedly failing β†’ Admit unknown

Best Practices

  1. Set appropriate similarity thresholds β€” too high (>0.9) misses results, too low (<0.5) returns noise
  2. Limit loop iterations β€” maxSteps 5-8 is reasonable, avoid infinite loops
  3. Log every step β€” observability is key to debugging Agentic RAG
  4. Fallback to human β€” when the agent is uncertain, ask the user

Summary

  • Agentic RAG = RAG + Agent that decides, evaluates, and self-corrects
  • Use Supabase pgvector for the vector store
  • Corrective RAG pattern: evaluate β†’ refine β†’ retry
  • Agent knows when to stop and admit it doesn’t know