CrewAI Tutorial (TypeScript): chunking large documents for advanced developers

By Cyprian AaronsUpdated 2026-04-21
crewaichunking-large-documents-for-advanced-developerstypescript

This tutorial shows how to split large documents into manageable chunks, send each chunk through CrewAI agents in TypeScript, and merge the results into a usable output. You need this when a single document is too large for one model call, or when you want better control over cost, latency, and accuracy on long inputs.

What You'll Need

  • Node.js 18+
  • TypeScript 5+
  • A CrewAI-compatible TypeScript project
  • An OpenAI API key set as OPENAI_API_KEY
  • These packages:
    • crewai
    • zod
    • dotenv
    • typescript
    • ts-node or a build pipeline via tsc

Step-by-Step

  1. Set up the project and install dependencies. Keep the runtime simple: one file for chunking, one file for the agent workflow, and an environment file for secrets.
mkdir crewai-chunking-demo
cd crewai-chunking-demo
npm init -y
npm install crewai zod dotenv
npm install -D typescript ts-node @types/node
npx tsc --init
  1. Add your API key and configure TypeScript for ESM-style imports if needed. In production, I keep config explicit so the runtime behavior matches local testing.
OPENAI_API_KEY=your_openai_api_key_here
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  }
}
  1. Create a chunking utility that splits by token-ish size using plain text boundaries. This is not perfect tokenization, but it is predictable, fast, and good enough for most document pipelines before you add a tokenizer library.
export function chunkText(text: string, maxChars = 4000): string[] {
  const paragraphs = text.split(/\n\s*\n/);
  const chunks: string[] = [];
  let current = "";

  for (const paragraph of paragraphs) {
    const candidate = current ? `${current}\n\n${paragraph}` : paragraph;

    if (candidate.length <= maxChars) {
      current = candidate;
      continue;
    }

    if (current) chunks.push(current);
    if (paragraph.length > maxChars) {
      for (let i = 0; i < paragraph.length; i += maxChars) {
        chunks.push(paragraph.slice(i, i + maxChars));
      }
      current = "";
      continue;
    }

    current = paragraph;
  }

  if (current) chunks.push(current);
  return chunks;
}
  1. Define a CrewAI agent and task that processes one chunk at a time. The important part here is that every chunk gets the same prompt shape, so your downstream aggregation stays consistent.
import "dotenv/config";
import { Agent, Task } from "crewai";

export function createChunkAgent() {
  return new Agent({
    role: "Document analyst",
    goal: "Extract concise facts from a document chunk",
    backstory: "You summarize long enterprise documents with high precision.",
    verbose: false,
  });
}

export function createChunkTask(agent: Agent, chunk: string) {
  return new Task({
    description: `
Analyze this document chunk and return:
1. A short summary
2. Key entities
3. Any risks or open questions

Chunk:
${chunk}
`,
    expectedOutput: "Structured notes with summary, entities, and risks.",
    agent,
  });
}
  1. Run the chunks through CrewAI sequentially and merge the outputs. For large documents, sequential processing is safer than firing everything at once because it keeps memory usage predictable and makes failures easier to isolate.
import { Crew } from "crewai";
import { chunkText } from "./chunkText.js";
import { createChunkAgent, createChunkTask } from "./chunkAgent.js";

async function main() {
  const documentText = `
Acme Bank Q4 Risk Review

The bank increased exposure to commercial real estate.
A new fraud detection control was deployed in December.
Customer complaints rose in two regions.
`;

  const chunks = chunkText(documentText, 500);
  const agent = createChunkAgent();

  const results: string[] = [];

  for (const [index, chunk] of chunks.entries()) {
    const task = createChunkTask(agent, `Chunk ${index + 1}/${chunks.length}\n\n${chunk}`);
    const crew = new Crew({
      agents: [agent],
      tasks: [task],
      verbose: false,
    });

    const output = await crew.kickoff();
    results.push(String(output));
  }

  console.log(results.join("\n\n---\n\n"));
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
  1. If you need better final synthesis, add a second pass over the per-chunk outputs. This is the pattern I use when the source document is too large for one shot but still needs a single consolidated answer.
import { Agent, Crew, Task } from "crewai";

async function synthesize(chunkOutputs: string[]) {
  const synthesizer = new Agent({
    role: "Senior reviewer",
    goal: "Combine chunk-level findings into one coherent report",
    backstory: "You reconcile overlapping notes and remove duplicates.",
    verbose: false,
  });

  const task = new Task({
    description: `
Merge these chunk outputs into one final report.
Keep only unique facts and highlight conflicts.

Outputs:
${chunkOutputs.join("\n\n")}
`,
    expectedOutput: "A consolidated report with summary, entities, and risks.",
    agent: synthesizer,
  });

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

  return String(await crew.kickoff());
}

Testing It

Run the script against a small internal sample first so you can validate formatting before pointing it at real documents. Then increase maxChars pressure by feeding in longer text to confirm your chunk boundaries behave as expected.

Watch for duplicated facts in the merged output; that usually means your synthesis prompt needs stronger deduplication instructions. If you see empty or truncated results, reduce chunk size or switch from character-based splitting to tokenizer-based splitting.

A good smoke test is to compare the number of input chunks with the number of per-chunk outputs in logs. If those counts diverge, your loop or error handling is dropping work somewhere.

Next Steps

  • Replace character-based splitting with tokenizer-aware chunking using tiktoken or a model-specific tokenizer.
  • Add overlap between chunks so cross-boundary context does not get lost.
  • Persist intermediate chunk outputs in Postgres or S3 so long-running jobs can resume after failure.

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