LangChain Tutorial (TypeScript): adding cost tracking for intermediate developers
This tutorial shows how to add token and cost tracking to a LangChain TypeScript app using OpenAI callbacks. You need this when you want per-request usage data for billing, debugging runaway prompts, or showing model spend inside internal tools.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
ts-nodeor a build step - •
langchain - •
@langchain/openai - •An OpenAI API key
- •A
.envfile or equivalent environment variable setup - •Basic familiarity with LangChain
ChatOpenAIandRunnablechains
Install the packages:
npm install langchain @langchain/openai dotenv
npm install -D typescript ts-node @types/node
Step-by-Step
- •Set up your environment variables first. Keep the API key out of source control and load it before creating any model instances.
import "dotenv/config";
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error("Missing OPENAI_API_KEY");
}
console.log("API key loaded");
- •Create a basic chain with a callback handler that can capture token usage. The important part is wiring
handleLLMEndso you can read the model response metadata after each call.
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
class CostTrackingHandler extends BaseCallbackHandler {
name = "cost-tracker";
totalTokens = 0;
promptTokens = 0;
completionTokens = 0;
handleLLMEnd(output: any) {
const usage = output.llmOutput?.tokenUsage;
if (!usage) return;
this.totalTokens += usage.totalTokens ?? 0;
this.promptTokens += usage.promptTokens ?? 0;
this.completionTokens += usage.completionTokens ?? 0;
}
}
- •Add a small pricing table and a helper to calculate spend. This keeps the logic explicit and easy to audit when model prices change.
type ModelPricing = {
promptPer1K: number;
completionPer1K: number;
};
const pricingByModel: Record<string, ModelPricing> = {
"gpt-4o-mini": {
promptPer1K: 0.00015,
completionPer1K: 0.0006,
},
};
function calculateCost(
modelName: string,
promptTokens: number,
completionTokens: number
): number {
const pricing = pricingByModel[modelName];
if (!pricing) throw new Error(`No pricing configured for ${modelName}`);
return (
(promptTokens / 1000) * pricing.promptPer1K +
(completionTokens / 1000) * pricing.completionPer1K
);
}
- •Run the model with the callback attached, then print the usage summary after the call completes. This gives you one place to capture both response text and accounting data.
async function main() {
const tracker = new CostTrackingHandler();
const model = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
apiKey: process.env.OPENAI_API_KEY!,
callbacks: [tracker],
});
const result = await model.invoke([
new HumanMessage("Write one sentence explaining token tracking."),
]);
const cost = calculateCost(
"gpt-4o-mini",
tracker.promptTokens,
tracker.completionTokens
);
console.log("Assistant:", result.content);
console.log("Prompt tokens:", tracker.promptTokens);
console.log("Completion tokens:", tracker.completionTokens);
console.log("Total tokens:", tracker.totalTokens);
console.log("Estimated cost:", `$${cost.toFixed(6)}`);
}
main().catch(console.error);
- •If you want this to work across multiple calls in a workflow, reuse the same handler instance for each invocation or store results in a request-scoped object. That pattern is what you want in APIs, queue workers, and agent runs.
async function runMultipleCalls() {
const tracker = new CostTrackingHandler();
const model = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
apiKey: process.env.OPENAI_API_KEY!,
callbacks: [tracker],
});
await model.invoke([new HumanMessage("Give me a title for an invoice app.")]);
await model.invoke([new HumanMessage("Now give me a subtitle.")]);
const cost = calculateCost(
"gpt-4o-mini",
tracker.promptTokens,
tracker.completionTokens
);
console.log({
promptTokens: tracker.promptTokens,
completionTokens: tracker.completionTokens,
totalTokens: tracker.totalTokens,
estimatedCostUsd: cost.toFixed(6),
});
}
Testing It
Run the script once and confirm that you get non-zero prompt and completion token counts. If the counts stay at zero, check that your callback is attached to the model instance and that the provider is returning usage metadata for your account/model combination.
Then make two calls in a row and verify that totals increase instead of resetting. That tells you your handler is aggregating correctly across requests.
For production-style testing, log the numbers alongside your request ID or user ID. That makes it easy to reconcile spend later when someone asks why one workflow got expensive.
Next Steps
- •Store token usage in Postgres or Redis so you can report cost per user, tenant, or workflow.
- •Add support for multiple models by extending the pricing table and recording
modelNameon each run. - •Wrap this into a LangChain callback manager so every chain, tool call, and agent step is tracked consistently.
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