LangGraph Tutorial (TypeScript): adding cost tracking for intermediate developers
This tutorial shows you how to add per-run cost tracking to a LangGraph app in TypeScript, so you can see what each agent step is costing you instead of guessing. You need this when your graph starts calling multiple models, tools, or retries and the bill stops being predictable.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
tsconfig.json - •
@langchain/langgraph - •
@langchain/openai - •
@langchain/core - •
openaiAPI key set asOPENAI_API_KEY - •Basic familiarity with LangGraph nodes, edges, and state
- •A place to persist usage data:
- •console logs for local testing
- •Postgres, Redis, or a metrics pipeline for production
Install the packages:
npm install @langchain/langgraph @langchain/openai @langchain/core zod
Set your API key:
export OPENAI_API_KEY="your-key-here"
Step-by-Step
- •Start with a graph that returns structured state.
You need a state object that can carry both the conversation and cost metrics. Keep the usage fields on state so every node can update them without extra plumbing.
import { StateGraph, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
const StateSchema = z.object({
messages: z.array(z.any()),
totalPromptTokens: z.number().default(0),
totalCompletionTokens: z.number().default(0),
totalCostUsd: z.number().default(0),
});
type GraphState = z.infer<typeof StateSchema>;
const model = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
- •Add a helper that converts token usage into dollars.
Do not bury pricing logic inside your nodes. Put it in one function so you can swap models later without rewriting graph code.
function calculateCostUsd(promptTokens: number, completionTokens: number) {
const promptRatePer1K = 0.00015;
const completionRatePer1K = 0.0006;
return (
(promptTokens / 1000) * promptRatePer1K +
(completionTokens / 1000) * completionRatePer1K
);
}
- •Build a node that calls the model and records usage.
LangChain chat models expose token usage on the response metadata when the provider supports it. For OpenAI models, you can read response_metadata.tokenUsage and update your running totals directly.
async function generateNode(state: GraphState): Promise<Partial<GraphState>> {
const response = await model.invoke(state.messages);
const tokenUsage = response.response_metadata?.tokenUsage ?? {};
const promptTokens = tokenUsage.promptTokens ?? 0;
const completionTokens = tokenUsage.completionTokens ?? 0;
const costUsd = calculateCostUsd(promptTokens, completionTokens);
return {
messages: [...state.messages, response],
totalPromptTokens: state.totalPromptTokens + promptTokens,
totalCompletionTokens: state.totalCompletionTokens + completionTokens,
totalCostUsd: state.totalCostUsd + costUsd,
};
}
- •Wire the graph and make sure the final state contains your totals.
This example uses a single-node graph so you can verify the pattern before applying it to multi-step workflows. In real graphs, repeat the same accounting pattern in every model-calling node.
const graph = new StateGraph(StateSchema)
.addNode("generate", generateNode)
.addEdge(START, "generate")
.addEdge("generate", END)
.compile();
async function main() {
const result = await graph.invoke({
messages: [{ role: "user", content: "Summarize why cost tracking matters in agent systems." }],
totalPromptTokens: 0,
totalCompletionTokens: 0,
totalCostUsd: 0,
});
console.log({
output: result.messages[result.messages.length - 1],
totalPromptTokens: result.totalPromptTokens,
totalCompletionTokens: result.totalCompletionTokens,
totalCostUsd: result.totalCostUsd.toFixed(6),
});
}
main();
- •Add step-level logging if you want per-node visibility.
In production, totals are useful but not enough. You usually want each node’s token count so you can identify which branch is expensive and whether retries are inflating cost.
async function generateNodeWithLogging(state: GraphState): Promise<Partial<GraphState>> {
const response = await model.invoke(state.messages);
const tokenUsage = response.response_metadata?.tokenUsage ?? {};
const promptTokens = tokenUsage.promptTokens ?? promptTokenusage.prompt_tokens ??0;
const completionTokens = tokenUsage.completionTokens ?? tokenUsage.completion_tokens ??0;
const costUsd = calculateCostUsd(promptTokens, completionTokens);
console.log("node=generate", {
promptTokens,
completionTokens,
costUsd,
});
return {
messages: [...state.messages, response],
totalPromptTokens: state.totalPromptTokens + promptTokens,
totalCompletionTokens: state.totalCompletionTokens + completionTokens,
totalCostUsd: state.totalCostUsd + costUsd,
};
}
Testing It
Run the script once with a short prompt and confirm that the final log prints non-zero token counts and a small USD value. If all three totals stay at zero, your model provider is not returning usage metadata or your property names do not match what the SDK returns.
Then test a longer input and compare the totals; they should increase proportionally. If you add more nodes later, verify that each node increments the same running totals instead of overwriting them.
For deeper validation, send two requests back-to-back and check that each run starts from zero. That catches a common bug where people accidentally keep usage in module-level variables instead of graph state.
Next Steps
- •Add per-node usage fields so you can attribute cost to specific branches and tools.
- •Persist run metrics to Postgres or OpenTelemetry instead of only logging to stdout.
- •Extend the same pattern to tool calls so you track LLM spend plus external API spend in one place.
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