CrewAI Tutorial (TypeScript): adding cost tracking for advanced developers
This tutorial shows how to add deterministic cost tracking to a CrewAI TypeScript workflow by measuring token usage per model call and converting it into dollar estimates. You need this when you want per-run cost visibility, budget guards, or audit logs for agentic systems that call multiple LLMs.
What You'll Need
- •Node.js 18+ and npm
- •A TypeScript project with
tsconfig.json - •CrewAI TypeScript package installed
- •An OpenAI API key in
OPENAI_API_KEY - •A second API key if you plan to compare providers
- •
zodfor typed config validation - •A logging sink:
- •console for local testing
- •JSON logs, OpenTelemetry, or your own DB table in production
Install the packages:
npm install @crewai/crew-ai openai zod dotenv
npm install -D typescript tsx @types/node
Step-by-Step
- •First, define a small pricing layer. Keep pricing out of your agent code so you can update rates without touching orchestration logic.
// src/costs.ts
export type ModelPrice = {
inputPer1M: number;
outputPer1M: number;
};
export const PRICING: Record<string, ModelPrice> = {
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6 },
"gpt-4o": { inputPer1M: 5.0, outputPer1M: 15.0 },
};
export function estimateCost(model: string, inputTokens: number, outputTokens: number) {
const price = PRICING[model];
if (!price) return null;
const inputCost = (inputTokens / 1_000_000) * price.inputPer1M;
const outputCost = (outputTokens / 1_000_000) * price.outputPer1M;
return {
model,
inputTokens,
outputTokens,
inputCost,
outputCost,
totalCost: inputCost + outputCost,
};
}
- •Next, create a callback-style tracker around the LLM client. This is the part that gives you exact usage from the provider instead of guessing from prompt length.
// src/tracked-openai.ts
import OpenAI from "openai";
import { estimateCost } from "./costs";
export type UsageRecord = {
model: string;
inputTokens: number;
outputTokens: number;
totalTokens: number;
totalCostUSD: number;
};
export class TrackedOpenAI {
private client: OpenAI;
public records: UsageRecord[] = [];
constructor(apiKey: string) {
this.client = new OpenAI({ apiKey });
}
async chat(model: string, messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]) {
const response = await this.client.chat.completions.create({
model,
messages,
temperature: 0,
});
const usage = response.usage;
if (!usage) throw new Error("Missing token usage on response");
const cost = estimateCost(model, usage.prompt_tokens, usage.completion_tokens);
if (!cost) throw new Error(`No pricing configured for ${model}`);
this.records.push({
model,
inputTokens: usage.prompt_tokens,
outputTokens: usage.completion_tokens,
totalTokens: usage.total_tokens,
totalCostUSD: cost.totalCost,
});
return response;
}
}
- •Now wire that tracker into a CrewAI flow. The important part is to keep agent behavior unchanged while wrapping only the model boundary.
// src/crew.ts
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/crew-ai";
import { TrackedOpenAI } from "./tracked-openai";
const llm = new TrackedOpenAI(process.env.OPENAI_API_KEY!);
const analyst = new Agent({
role: "Financial Analyst",
goal: "Summarize company spend with concrete recommendations",
backstory: "You analyze operational expenses for engineering teams.",
});
const task = new Task({
description:
"Review the following spend note and return a concise summary with one recommendation:\n\nWe spent $420 on LLM calls last week across support automation.",
expectedOutput: "A short summary and one recommendation.",
});
async function main() {
const crew = new Crew({
agents: [analyst],
tasks: [task],
verbose: true,
});
const result = await crew.kickoff({
llmProviderOverride: async (messages) => llm.chat("gpt-4o-mini", messages),
});
console.log("\n=== RESULT ===");
console.log(result);
}
main().catch(console.error);
- •Add aggregation so you can report cost per run. In production you usually want totals by request ID, tenant ID, or workflow name.
// src/report.ts
import { TrackedOpenAI } from "./tracked-openai";
export function printRunReport(tracker: TrackedOpenAI) {
const totals = tracker.records.reduce(
(acc, r) => {
acc.inputTokens += r.inputTokens;
acc.outputTokens += r.outputTokens;
acc.totalTokens += r.totalTokens;
acc.totalCostUSD += r.totalCostUSD;
return acc;
},
{ inputTokens: 0, outputTokens: 0, totalTokens: 0, totalCostUSD: 0 }
);
console.log("\n=== COST REPORT ===");
console.log(`Input tokens : ${totals.inputTokens}`);
console.log(`Output tokens : ${totals.outputTokens}`);
console.log(`Total tokens : ${totals.totalTokens}`);
console.log(`Total cost : $${totals.totalCostUSD.toFixed(6)}`);
}
- •Finally, call the report after execution and keep the raw records for storage or alerting. If you need budget enforcement later, this same record stream becomes your guardrail.
// src/index.ts
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/crew-ai";
import { TrackedOpenAI } from "./tracked-openai";
import { printRunReport } from "./report";
async function main() {
const tracker = new TrackedOpenAI(process.env.OPENAI_API_KEY!);
const agent = new Agent({
role: "Operations Analyst",
goal: "Produce short operational summaries",
backstory: "You write precise summaries for internal reporting.",
});
const task = new Task({
description:
"Summarize this incident note in two sentences:\n\nThe support bot handled high volume without failures.",
});
const crew = new Crew({ agents: [agent], tasks: [task], verbose: false });
await crew.kickoff({
llmProviderOverride: async (messages) => tracker.chat("gpt-4o-mini", messages),
});
printRunReport(tracker);
}
main().catch(console.error);
Testing It
Run the entrypoint with tsx and confirm you get both an agent result and a cost report.
npx tsx src/index.ts
You should see token counts and a non-zero dollar value if the provider returns usage metadata. If cost stays at zero or throws Missing token usage, check that your model supports usage reporting and that your wrapper is actually used by the crew run.
For a real test, run the same workflow twice with different prompts and compare totals. The numbers should move with prompt size and completion length.
If you want stronger validation, persist tracker.records to JSON and assert on it in an integration test.
Next Steps
- •Add per-task labels so each agent action gets its own cost line item.
- •Push records into Postgres or ClickHouse for historical spend analytics.
- •Add budget thresholds that stop execution when estimated spend crosses a limit
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