Skip to content

Instantly share code, notes, and snippets.

@mattpodwysocki
Created July 1, 2026 17:12
Show Gist options
  • Select an option

  • Save mattpodwysocki/4d778f9ada3672a19023cf8d14d886b1 to your computer and use it in GitHub Desktop.

Select an option

Save mattpodwysocki/4d778f9ada3672a19023cf8d14d886b1 to your computer and use it in GitHub Desktop.
RAG with Mapbox Agents SDK vs DIY — side-by-side comparison

RAG: Mapbox Agents SDK vs DIY

Side-by-side comparison of building a Retrieval-Augmented Generation pipeline from scratch versus using the Mapbox Agents SDK.

Line count

DIY SDK
Vector store ~20 lines (cosine similarity by hand) InMemoryVectorDB — zero
Embedding ~5 lines OpenAIEmbedding — one line
Retrieval ~10 lines RetrieveTool — ~15 lines (but agentic)
Generation ~10 lines ToolOrchestrator + ConversationManager
Total ~65 lines ~70 lines

The raw line counts are similar. The difference is what each line does.

What you get for free with the SDK

  • Cosine similarity — implemented and tested in InMemoryVectorDB
  • Embedding batching — providers handle chunking large inputs automatically
  • Rate limiting — built into all providers via TokenBucket
  • Agentic retrieval — the LLM decides when to retrieve and can call retrieve multiple times for multi-hop questions (DIY always retrieves exactly once, before generation)
  • Conversation history — ConversationManager persists turns; follow-up questions work out of the box
  • Swap without rewrites — switch InMemoryVectorDB → QdrantAdapter or PineconeAdapter, or OpenAIEmbedding → CohereEmbedding, by changing one line

What you still need to bring

  • OpenAI API key — for embeddings (all current providers are API-based)
  • Anthropic API key — for the LLM

Coming soon: TransformersEmbedding will add local/offline embedding via Transformers.js — no embedding API key required.

When DIY makes sense

If your pipeline is truly fixed — always one retrieval, no conversation, no need to swap components — the DIY version is simpler. The SDK pays off when you need agentic retrieval, multi-turn conversations, or want to graduate from in-memory to a production vector store without rewriting everything.

import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
// --- Vector store ---
interface Doc {
id: string;
text: string;
vector: number[];
}
const store: Doc[] = [];
function cosineSimilarity(a: number[], b: number[]): number {
const dot = a.reduce((sum, v, i) => sum + v * b[i]!, 0);
const magA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
const magB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
return magA && magB ? dot / (magA * magB) : 0;
}
// --- Indexing ---
async function indexDocuments(docs: { id: string; text: string }[]) {
const res = await openai.embeddings.create({
model: "text-embedding-3-small",
input: docs.map((d) => d.text),
});
for (let i = 0; i < docs.length; i++) {
store.push({ ...docs[i]!, vector: res.data[i]!.embedding });
}
}
// --- Retrieval ---
async function retrieve(query: string, topK = 3): Promise<string[]> {
const res = await openai.embeddings.create({
model: "text-embedding-3-small",
input: [query],
});
const queryVec = res.data[0]!.embedding;
return store
.map((doc) => ({ doc, score: cosineSimilarity(queryVec, doc.vector) }))
.sort((a, b) => b.score - a.score)
.slice(0, topK)
.map((r) => r.doc.text);
}
// --- Generation ---
async function ask(question: string): Promise<string> {
const chunks = await retrieve(question);
const context = chunks.join("\n\n---\n\n");
const response = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: `Answer using only the context below.\n\n${context}`,
messages: [{ role: "user", content: question }],
});
return (response.content[0] as { text: string }).text;
}
// --- Usage ---
await indexDocuments([
{ id: "1", text: "Mapbox GL JS renders interactive maps in the browser." },
{ id: "2", text: "The Directions API returns turn-by-turn navigation routes." },
{ id: "3", text: "Mapbox Tiling Service processes large geospatial datasets." },
]);
console.log(await ask("How do I show a map in the browser?"));
import { OpenAIEmbedding } from "@mapbox/agents-embedding-providers";
import { InMemoryVectorDB } from "@mapbox/agents-vector-db-adapters";
import { AnthropicProvider } from "@mapbox/agents-llm-providers";
import {
Tool,
ToolOrchestrator,
ToolRegistry,
ToolExecutor,
AnthropicAdapter,
} from "@mapbox/agents-tools";
import { ConversationManager, InMemoryStateStore } from "@mapbox/agents-conversation-manager";
import { z } from "zod";
// --- Setup ---
const embedder = new OpenAIEmbedding({ apiKey: process.env.OPENAI_API_KEY! });
const db = new InMemoryVectorDB();
// --- Index documents ---
async function indexDocuments(docs: { id: string; text: string }[]) {
const vectors = await embedder.embed(docs.map((d) => d.text));
await db.upsert(
docs.map((d, i) => ({ id: d.id, vector: vectors[i]!, metadata: { text: d.text } })),
"docs"
);
}
// --- Retrieval tool — LLM decides when and how many times to call this ---
class RetrieveTool extends Tool<{ query: string }, { context: string }> {
constructor() {
super({
name: "retrieve",
description: "Search the knowledge base for relevant information.",
parameters: [{ name: "query", type: "string", description: "Search query", required: true }],
inputSchema: z.object({ query: z.string() }),
tags: [],
category: "rag",
deprecated: false,
});
}
async execute({ query }: { query: string }) {
const [queryVec] = await embedder.embed([query]);
const results = await db.search({ vector: queryVec!, topK: 3, namespace: "docs" });
const context = results
.map((r) => (r.metadata as { text: string }).text)
.join("\n\n---\n\n");
return { context };
}
}
// --- Agent setup ---
const provider = new AnthropicProvider({
apiKey: process.env.ANTHROPIC_API_KEY!,
model: "claude-sonnet-4-6",
});
const registry = new ToolRegistry();
registry.register(new RetrieveTool());
const cm = new ConversationManager(new InMemoryStateStore());
const orchestrator = new ToolOrchestrator(
provider,
registry,
new ToolExecutor(registry),
new AnthropicAdapter(),
cm
);
// --- Usage ---
await indexDocuments([
{ id: "1", text: "Mapbox GL JS renders interactive maps in the browser." },
{ id: "2", text: "The Directions API returns turn-by-turn navigation routes." },
{ id: "3", text: "Mapbox Tiling Service processes large geospatial datasets." },
]);
const convId = await cm.createConversation();
const result = await orchestrator.run(convId, "How do I show a map in the browser?");
console.log(result.finalResponse);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment