Skip to main content
RAG in Vibes is a tool pattern: the agent calls a retrieval tool that searches a knowledge base, then uses the retrieved context to answer the question. This example uses an in-memory mock - replace vectorSearch() with your actual vector DB client.

What you’ll learn

  • Using plainTool() for tools without dependency injection
  • Passing retrieved context to the model via tool results
  • Structuring a RAG pipeline with Vibes

Prerequisites

  • ANTHROPIC_API_KEY set in your environment
  • Vibes installed (deno add npm:@vibesjs/sdk npm:@ai-sdk/anthropic npm:zod)

Complete example

import { Agent, plainTool } from "npm:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";
import { z } from "npm:zod";

// --- Mock knowledge base ---
// In production: replace with calls to Pinecone, pgvector, Qdrant, etc.
const documents = [
  {
    id: "1",
    content: "Vibes is a TypeScript agent framework built on the Vercel AI SDK.",
    metadata: { source: "overview" },
  },
  {
    id: "2",
    content: "Vibes supports dependency injection via RunContext, passed to all tools and prompts.",
    metadata: { source: "concepts" },
  },
  {
    id: "3",
    content: "The Graph API uses BaseNode classes with next() and output() free functions.",
    metadata: { source: "graph" },
  },
  {
    id: "4",
    content: "Vibes agents can be wrapped with A2AAdapter to expose them as A2A-compatible servers.",
    metadata: { source: "integrations" },
  },
];

// Mock vector search - replace with real vector DB in production
async function vectorSearch(query: string, topK = 3): Promise<string[]> {
  // Naive keyword overlap scoring (real impl uses embedding similarity)
  const queryWords = query.toLowerCase().split(/\s+/);
  const scored = documents.map((doc) => {
    const docWords = doc.content.toLowerCase().split(/\s+/);
    const overlap = queryWords.filter((w) => docWords.includes(w)).length;
    return { content: doc.content, score: overlap };
  });
  return scored
    .sort((a, b) => b.score - a.score)
    .slice(0, topK)
    .map((d) => d.content);
}

// Retrieval tool using plainTool (no deps needed)
const searchDocs = plainTool({
  name: "search_docs",
  description: "Search the Vibes documentation knowledge base for relevant information",
  parameters: z.object({
    query: z.string().describe("The search query"),
  }),
  execute: async ({ query }) => {
    const results = await vectorSearch(query);
    if (results.length === 0) {
      return "No relevant documents found.";
    }
    return results.map((r, i) => `[${i + 1}] ${r}`).join("\n\n");
  },
});

// RAG agent
const ragAgent = new Agent({
  model: anthropic("claude-sonnet-4-6"),
  systemPrompt:
    "You are a documentation assistant. Always use the search_docs tool to find relevant " +
    "information before answering. Base your answer on the retrieved documents.",
  tools: [searchDocs],
});

// Run
const result = await ragAgent.run("How does dependency injection work in Vibes?");
console.log(result.output);

Run it

deno run --allow-net --allow-env rag.ts

How it works

plainTool(): A simpler variant of tool() - no RunContext, the execute function receives args directly. Use plainTool() when your tool doesn’t need access to deps. Use tool() (or tool<TDeps>()) when it does. Tool result as context: When the agent calls search_docs, Vibes executes vectorSearch() and passes the result back to the model as a tool response. The model uses this retrieved context to form its answer - no prompt engineering needed. Plugging in a real vector DB: Replace the vectorSearch() function body with your vector DB client. The Vibes pattern is identical regardless of the underlying store. For dependency-injected DB clients (e.g., a shared connection pool), use tool<Deps>() and access the client via ctx.deps.
The retrieval quality in this example is intentionally minimal (keyword overlap). A real RAG system uses embedding-based semantic search. The Vibes pattern - tool calls retriever, model uses results - is identical.

Next steps