CrewAI Tutorial (TypeScript): building a RAG pipeline for beginners
This tutorial shows you how to build a basic Retrieval-Augmented Generation (RAG) pipeline in TypeScript with CrewAI: load documents, split them into chunks, embed them into a vector store, and answer questions from retrieved context. You’d use this when you want your agent to answer from your own docs instead of guessing from model memory.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
ts-nodeortsx - •An OpenAI API key
- •CrewAI TypeScript packages:
- •
@crewai/crew - •
@crewai/tools
- •
- •A vector store package:
- •
chromadb
- •
- •A text splitter package:
- •
langchain/text_splitter
- •
- •A document loader package:
- •
langchain/document_loaders/fs/directory
- •
- •Environment variables set in
.env:- •
OPENAI_API_KEY=...
- •
Step-by-Step
- •Set up the project and install the dependencies. This example uses local files as the knowledge source, so create a
docs/folder and put a few.txtfiles in it.
mkdir crewai-rag-ts
cd crewai-rag-ts
npm init -y
npm install @crewai/crew @crewai/tools chromadb openai dotenv langchain
npm install -D typescript tsx @types/node
mkdir docs
echo "CrewAI helps coordinate agents for task execution." > docs/crewai.txt
echo "RAG combines retrieval with generation for grounded answers." > docs/rag.txt
- •Load documents and split them into chunks. Small chunks retrieve better than giant blobs, and this is the part that makes your RAG pipeline usable instead of noisy.
// src/loadDocs.ts
import "dotenv/config";
import { DirectoryLoader } from "langchain/document_loaders/fs/directory";
import { TextLoader } from "langchain/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
export async function loadChunks() {
const loader = new DirectoryLoader("./docs", {
".txt": (path) => new TextLoader(path),
});
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 500, chunkOverlap: 100 });
return splitter.splitDocuments(docs);
}
- •Embed the chunks into ChromaDB. For beginners, this is the simplest durable setup: store vectors locally, then query them later with the same embedding model.
// src/vectorStore.ts
import "dotenv/config";
import { ChromaClient } from "chromadb";
import OpenAI from "openai";
import type { Document } from "@langchain/core/documents";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function embed(texts: string[]) {
const res = await openai.embeddings.create({
model: "text-embedding-3-small",
input: texts,
});
return res.data.map((item) => item.embedding);
}
export async function buildIndex(chunks: Document[]) {
const client = new ChromaClient();
const collection = await client.getOrCreateCollection({ name: "crewai_rag_demo" });
const texts = chunks.map((c) => c.pageContent);
const ids = chunks.map((_, i) => `chunk-${i}`);
const vectors = await embed(texts);
await collection.upsert({
ids,
embeddings: vectors,
documents: texts,
metadatas: chunks.map((c) => ({ source: c.metadata.source ?? "docs" })),
});
return collection;
}
- •Create a retrieval tool for CrewAI. The agent should not query your whole document set directly; it should first fetch the top matching chunks and then answer only from those results.
// src/retrievalTool.ts
import "dotenv/config";
import { ChromaClient } from "chromadb";
import OpenAI from "openai";
import { Tool } from "@crewai/tools";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const client = new ChromaClient();
async function embedQuery(query: string) {
const res = await openai.embeddings.create({
model: "text-embedding-3-small",
input: query,
});
return res.data[0].embedding;
}
export const ragTool = new Tool({
name: "rag_search",
description: "Searches indexed documents and returns the most relevant context.",
func: async (query: string) => {
const collection = await client.getOrCreateCollection({ name: "crewai_rag_demo" });
const results = await collection.query({
queryEmbeddings: [await embedQuery(query)],
nResults: 3,
});
return (results.documents?.[0] ?? []).join("\n---\n");
},
});
- •Wire the tool into a CrewAI agent and run a question against it. The agent gets one job here: use retrieval first, then produce an answer grounded in the returned context.
// src/index.ts
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/crew";
import { loadChunks } from "./loadDocs.js";
import { buildIndex } from "./vectorStore.js";
import { ragTool } from "./retrievalTool.js";
async function main() {
const chunks = await loadChunks();
await buildIndex(chunks);
const agent = new Agent({
role: "RAG Assistant",
goal: "Answer questions using retrieved document context only",
backstory: "You are precise and only answer using provided context.",
tools: [ragTool],
verbose: true,
});
const task = new Task({
description:
'Use rag_search to answer this question in one short paragraph: "What is RAG?"',
expectedOutput: "A concise grounded answer",
agent,
});
const crew = new Crew({
agents: [agent],
tasks: [task],
verbose: true,
});
const result = await crew.kickoff();
console.log(result);
}
main().catch(console.error);
Testing It
Run the app with your TypeScript runner of choice, for example npx tsx src/index.ts. If indexing worked, Chroma should contain your document chunks and the agent should return an answer that mentions retrieval-backed context rather than inventing details.
If you get empty retrieval results, check that your .txt files exist in docs/ and that your embedding calls are succeeding. If the agent answers vaguely, tighten the task prompt so it explicitly says to use only retrieved context.
A good sanity check is to ask a question that exists verbatim in one of your docs, then ask a second question that requires combining two chunks. If both work, your pipeline is wired correctly.
Next Steps
- •Add PDF ingestion with a proper loader instead of plain text files.
- •Replace local Chroma with Postgres + pgvector for persistence in production.
- •Add citation formatting so every answer includes source filenames and chunk IDs
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit