Building a RAG Pipeline with Claude API: A Practical Guide
Retrieval-augmented generation (RAG) lets Claude answer questions using your own data instead of relying solely on what it learned during training. Building a RAG pipeline with the Claude API means combining a retrieval step (finding relevant chunks of your documents) with a generation step (sending those chunks to Claude as context). This article walks through the full pipeline: chunking your data, generating embeddings, retrieving relevant passages, and constructing prompts that get accurate, grounded answers.
The core architecture is simple: user asks a question → you embed the question → you search a vector store for similar chunks → you pass those chunks plus the question to Claude → Claude generates an answer based on the retrieved context. Everything else — chunk size, embedding model, reranking, prompt format — is tuning.
Step 1: Chunk Your Documents
Claude has a large context window, but that doesn't mean you should dump entire documents into every request. Smaller, well-scoped chunks retrieve more precisely and cost less per query.
A reasonable starting point:
- Chunk size: 300–800 tokens per chunk
- Overlap: 10–20% overlap between chunks so context isn't cut off mid-sentence
- Splitting strategy: split on natural boundaries (headings, paragraphs) before falling back to fixed-size splitting
function chunkText(text, chunkSize = 500, overlap = 50) {
const words = text.split(/\s+/);
const chunks = [];
for (let i = 0; i < words.length; i += chunkSize - overlap) {
chunks.push(words.slice(i, i + chunkSize).join(" "));
}
return chunks;
}
For structured content like docs or FAQs, chunk by section rather than raw word count — it keeps each chunk semantically coherent, which matters more than hitting an exact token target.
Step 2: Generate Embeddings
You'll need an embedding model to convert chunks and queries into vectors. Claude's API itself doesn't generate embeddings, so you'll pair it with a dedicated embedding provider (Voyage AI, OpenAI, or a self-hosted model like bge-large). Store the vectors in a vector database — Pinecone, Weaviate, pgvector, or even an in-memory index for small datasets.
async function embedAndStore(chunks, vectorStore) {
for (const chunk of chunks) {
const embedding = await embedText(chunk); // your embedding provider
await vectorStore.upsert({ vector: embedding, metadata: { text: chunk } });
}
}
Keep the embedding model consistent between indexing and querying — mixing models produces meaningless similarity scores.
Step 3: Retrieve Relevant Chunks
At query time, embed the user's question and search for the top-k most similar chunks.
async function retrieve(query, vectorStore, k = 5) {
const queryEmbedding = await embedText(query);
const results = await vectorStore.search(queryEmbedding, k);
return results.map(r => r.metadata.text);
}
Start with k = 3–5. Too few chunks and you risk missing relevant context; too many and you dilute the signal with irrelevant text, which increases both cost and the chance of Claude latching onto the wrong passage. If your dataset spans multiple topics, consider adding a reranking step (a lightweight cross-encoder) before passing chunks to Claude — it improves precision without a major latency hit.
Step 4: Build the Prompt
This is where Claude does the actual work. The key is structuring the prompt so Claude clearly distinguishes retrieved context from the question, and instructing it to answer only from that context when accuracy matters more than creativity.
function buildPrompt(question, chunks) {
const context = chunks.map((c, i) => `[${i + 1}] ${c}`).join("\n\n");
return `Answer the question using only the context below. If the context doesn't contain enough information, say so explicitly.
Context:
${context}
Question: ${question}`;
}
Send this as the user message via the Messages API, using a system prompt to reinforce grounding behavior:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"system": "You are a support assistant. Only answer based on provided context. Cite chunk numbers when relevant.",
"messages": [{"role": "user", "content": "..."}]
}'
Asking Claude to cite chunk numbers is a cheap way to make hallucinations visible — if the answer doesn't map back to a cited chunk, something went wrong in retrieval.
Step 5: Handle Edge Cases
A production RAG pipeline needs to handle:
- No relevant results: if similarity scores are below a threshold, skip the LLM call and return "no relevant information found" directly.
- Conflicting sources: if chunks contradict each other, instruct Claude to surface the conflict rather than silently pick one.
- Stale data: re-embed and re-index documents on a schedule, or trigger re-indexing on content updates.
- Long conversations: for multi-turn RAG, re-retrieve on each turn based on the latest question, don't just append to a growing context.
Serving the Pipeline as an API
Once the pipeline works, you'll likely want to expose it as an internal or external endpoint — for a support widget, an internal tool, or a customer-facing feature. If you're already calling the Claude API through SubToAPI, the generation step is a drop-in replacement: point your RAG backend at https://api.subtoapi.app/v1/messages with your sub_live_... key instead of a raw Anthropic key, and you get per-key usage metadata for free — useful for tracking token cost per RAG query, per team, or per customer if you're running this as a feature inside a SaaS product. See the quickstart and Messages API docs for the exact request format, which mirrors Claude's own API.
Testing and Iterating
RAG quality is hard to eyeball — build a small eval set of real questions with known-good answers, run them through the pipeline after any change to chunking, retrieval, or prompt structure, and check for regressions. Most quality problems trace back to retrieval, not generation: if Claude gives a wrong answer, check what chunks it actually received before assuming the model failed.
questions
How many chunks should I retrieve per query? Start with 3–5. Increase only if your eval set shows Claude missing information that was present in lower-ranked chunks.
Do I need a vector database, or can I use a simple in-memory search? For small datasets (a few thousand chunks), in-memory cosine similarity is fine. Beyond that, a dedicated vector database improves latency and lets you scale without rewriting the pipeline.
Can Claude generate the embeddings itself? No — Claude's API handles text generation, not embeddings. Pair it with a separate embedding model (Voyage AI, OpenAI, or open-source options) for the retrieval half of the pipeline.