CrewAI Tutorial (TypeScript): caching embeddings for beginners

By Cyprian AaronsUpdated 2026-04-21
crewaicaching-embeddings-for-beginnerstypescript

This tutorial shows you how to cache embeddings in a CrewAI TypeScript workflow so repeated runs stop paying the same embedding cost over and over. You need this when your agents keep reprocessing the same documents, prompts, or knowledge sources and you want faster startup plus lower API usage.

What You'll Need

  • Node.js 18+
  • A TypeScript project with ts-node or tsx
  • CrewAI TypeScript package
  • An embeddings provider key, such as:
    • OpenAI API key
    • Anthropic is not used for embeddings here
  • A place to store cache files locally
  • Basic familiarity with:
    • CrewAI agents/tasks
    • TypeScript async/await
    • JSON file I/O

Install the dependencies:

npm install @crew-ai/crewai openai dotenv
npm install -D typescript tsx @types/node

Create a .env file:

OPENAI_API_KEY=your_key_here

Step-by-Step

  1. Start by creating a small embedding cache utility. The idea is simple: hash the input text, look it up on disk, and only call the embeddings API if the value is missing.
import fs from "node:fs/promises";
import crypto from "node:crypto";
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const CACHE_FILE = "./embedding-cache.json";

type CacheStore = Record<string, number[]>;

async function loadCache(): Promise<CacheStore> {
  try {
    return JSON.parse(await fs.readFile(CACHE_FILE, "utf8"));
  } catch {
    return {};
  }
}

function hashText(text: string): string {
  return crypto.createHash("sha256").update(text).digest("hex");
}
  1. Add a cached embedding function. This keeps your code deterministic: same text in, same cache key out, and no duplicate API calls for repeated content.
async function getCachedEmbedding(text: string): Promise<number[]> {
  const cache = await loadCache();
  const key = hashText(text);

  if (cache[key]) return cache[key];

  const response = await client.embeddings.create({
    model: "text-embedding-3-small",
    input: text,
  });

  const embedding = response.data[0].embedding;
  cache[key] = embedding;

  await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2));
  return embedding;
}
  1. Wire the cached embeddings into a CrewAI workflow. In practice, you usually embed knowledge chunks before passing them into retrieval or agent context, so this example caches a document summary input before the crew uses it.
import { Agent, Task, Crew } from "@crew-ai/crewai";

async function main() {
  const doc = "ACME Bank requires all customer-facing AI responses to log PII access.";
  const embedding = await getCachedEmbedding(doc);

  const analyst = new Agent({
    role: "Compliance Analyst",
    goal: "Review policy text and identify operational risk",
    backstory: "You work on regulated banking systems.",
    verbose: true,
  });

  const task = new Task({
    description: `Review this policy note using the cached vector length ${embedding.length}.`,
    expectedOutput: "A short compliance assessment.",
    agent: analyst,
  });

  const crew = new Crew({
    agents: [analyst],
    tasks: [task],
  });

  console.log(await crew.kickoff());
}

main();
  1. Make the cache reusable across runs by keeping the file stable and avoiding accidental overwrites. If you have multiple documents, use normalized text before hashing so whitespace changes do not create useless cache misses.
function normalizeText(text: string): string {
  return text.trim().replace(/\s+/g, " ");
}

async function getNormalizedCachedEmbedding(text: string): Promise<number[]> {
  return getCachedEmbedding(normalizeText(text));
}

async function warmCache() {
  const docs = [
    "KYC checks must be completed before account activation.",
    "Suspicious activity reports require escalation within one business day.",
  ];

  for (const doc of docs) {
    await getNormalizedCachedEmbedding(doc);
  }
}

warmCache();
  1. If you want this to hold up in production, add a tiny guard around file writes. Concurrent workers can stomp on the same JSON file, so for anything beyond a single local dev process you should move this cache into Redis or Postgres.
let writeLock = Promise.resolve();

async function safeWriteCache(cache: CacheStore) {
  writeLock = writeLock.then(() =>
    fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2))
  );
  await writeLock;
}

Testing It

Run the script twice with the same input. The first run should hit the embeddings API and create embedding-cache.json; the second run should read from disk and skip the network call for that exact text.

Add a console.log(key) inside getCachedEmbedding if you want to confirm identical inputs generate identical hashes. You should also see that the returned embedding length stays consistent across runs for the same model.

If you change even one word in the source text, it should generate a different hash and create a new cache entry. That is expected behavior and is what keeps cached embeddings correct.

For a quick smoke test:

npx tsx index.ts

Next Steps

  • Move the cache from JSON files to Redis if you run multiple workers or containers.
  • Add TTL support so stale embeddings can expire when your source documents change.
  • Pair this with a vector database like pgvector or Pinecone so your CrewAI agents can retrieve cached knowledge efficiently.

Keep learning

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

Related Guides