LangGraph Tutorial (TypeScript): adding cost tracking for beginners
This tutorial shows how to add per-run cost tracking to a LangGraph app in TypeScript. You’ll wire token usage from your model calls into a simple pricing calculator so you can see what each graph execution costs before it becomes a billing problem.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
ts-nodeortsx - •These packages:
- •
@langchain/langgraph - •
@langchain/openai - •
@langchain/core - •
zod - •
dotenv
- •
- •An OpenAI API key in
.env:- •
OPENAI_API_KEY=...
- •
- •Basic familiarity with:
- •LangGraph state graphs
- •Async/await in TypeScript
- •Running scripts from the command line
Step-by-Step
- •Start with a graph state that can store both the conversation and cost metadata. The important part is that the state includes fields for token counts and total cost, not just messages.
import "dotenv/config";
import { z } from "zod";
import { Annotation, StateGraph, START, END } from "@langchain/langgraph";
import { AIMessage, HumanMessage } from "@langchain/core/messages";
const GraphState = Annotation.Root({
messages: Annotation<any[]>({
reducer: (left, right) => left.concat(right),
default: () => [],
}),
promptTokens: Annotation<number>({
reducer: (left, right) => left + right,
default: () => 0,
}),
completionTokens: Annotation<number>({
reducer: (left, right) => left + right,
default: () => 0,
}),
totalCostUsd: Annotation<number>({
reducer: (left, right) => left + right,
default: () => 0,
}),
});
- •Create a model node that reads usage metadata from OpenAI responses and converts it into dollars. For beginners, keep the pricing table explicit so you can swap rates later without changing the graph logic.
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0,
});
const PRICING = {
promptPer1K: 0.00015,
completionPer1K: 0.0006,
};
async function chatNode(state: typeof GraphState.State) {
const response = await llm.invoke(state.messages);
const usage = response.response_metadata?.tokenUsage ?? {
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
};
const cost =
(usage.promptTokens / 1000) * PRICING.promptPer1K +
(usage.completionTokens / 1000) * PRICING.completionPer1K;
return {
messages: [response],
promptTokens: usage.promptTokens,
completionTokens: usage.completionTokens,
totalCostUsd: cost,
};
}
- •Build the graph with one node first. This keeps the example beginner-friendly while still using real LangGraph wiring you can extend later.
const graph = new StateGraph(GraphState)
.addNode("chat", chatNode)
.addEdge(START, "chat")
.addEdge("chat", END)
.compile();
- •Run the graph and print the tracked cost after each execution. Use a fresh input message so you can see how much a single request costs in practice.
async function main() {
const result = await graph.invoke({
messages: [new HumanMessage("Write one sentence explaining what LangGraph is.")],
promptTokens: 0,
completionTokens: 0,
totalCostUsd: 0,
});
console.log("Assistant:", result.messages[result.messages.length - 1].content);
console.log("Prompt tokens:", result.promptTokens);
console.log("Completion tokens:", result.completionTokens);
console.log("Run cost USD:", result.totalCostUsd.toFixed(6));
}
main().catch(console.error);
- •If you want tracking across multiple nodes, return cost deltas from every node and let the reducers add them up. That pattern scales better than trying to calculate totals only at the end of the graph.
async function secondNode(state: typeof GraphState.State) {
const response = await llm.invoke([
...state.messages,
new HumanMessage("Summarize the previous answer in five words."),
]);
const usage = response.response_metadata?.tokenUsage ?? {
promptTokens: undefined as any,
completionTokens: undefined as any,
};
const cost =
((usage.promptTokens ?? useage.prompt_tokens ?? useage.input_tokens ?? useage.inputTokenCount ?? useage.input_tokens ?? usage.inputTokenCount ?? usage.prompt_tokens ?? usage.input_tokens ?? usage.prompt_token_count ?? usage.input_tokens ?? usage.promptTokenCount ?? usage.prompt_tokens ?? usage.input_tokens ?? usage.prompt_tokens ?? usage.input_tokens) /
Testing It
Run the script once and confirm you get three things back: an assistant message, token counts, and a non-zero dollar amount. If totalCostUsd stays at 0, check whether your model response includes token metadata and whether your OpenAI key is valid.
For debugging, log the full response.response_metadata object once so you can inspect the exact shape returned by your model version. Different providers and models expose token usage under slightly different keys, so this is where most first-time implementations break.
If you are using streaming or multiple nodes, verify that each node returns only its own delta cost. The reducers should handle accumulation; if they do not, your totals will drift fast.
Next Steps
- •Add per-user or per-session cost limits before invoking expensive branches
- •Persist run metadata to Postgres or DynamoDB for audit and reporting
- •Extend the same pattern to tool calls so you track LLM + tool spend together
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