LangChain Tutorial (TypeScript): implementing retry logic for beginners

By Cyprian AaronsUpdated 2026-04-21
langchainimplementing-retry-logic-for-beginnerstypescript

This tutorial shows you how to add retry logic around a LangChain TypeScript call so transient failures do not break your app. You need this when an LLM request times out, the provider rate-limits you, or a network hiccup interrupts a chain execution.

What You'll Need

  • Node.js 18+
  • TypeScript 5+
  • An OpenAI API key
  • These packages:
    • langchain
    • @langchain/openai
    • zod
    • dotenv
  • A basic TypeScript project with "type": "module" or ESM-compatible config
  • Familiarity with ChatOpenAI, prompts, and .invoke()

Step-by-Step

  1. Start by installing the dependencies and setting up your environment variables. Keep the API key in .env so your retry code is not tied to hardcoded secrets.
npm install langchain @langchain/openai zod dotenv
OPENAI_API_KEY=your_api_key_here
  1. Create a small LangChain runnable that can fail in a controlled way. Here we use a prompt plus ChatOpenAI, then wrap the call in a retry helper so the chain gets another chance if the first attempt fails.
import "dotenv/config";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";

const model = new ChatOpenAI({
  model: "gpt-4o-mini",
  temperature: 0,
});

const prompt = ChatPromptTemplate.fromMessages([
  ["system", "You are a concise assistant."],
  ["user", "{question}"],
]);

const chain = prompt.pipe(model);
  1. Add a reusable retry function. This version retries only on likely transient errors, waits with exponential backoff, and stops after a fixed number of attempts.
async function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function isRetryableError(error: unknown): boolean {
  const message = error instanceof Error ? error.message : String(error);
  return (
    message.includes("429") ||
    message.includes("rate limit") ||
    message.includes("timeout") ||
    message.includes("ECONNRESET") ||
    message.includes("fetch failed")
  );
}

async function invokeWithRetry<T>(
  fn: () => Promise<T>,
  maxAttempts = 3,
  baseDelayMs = 500
): Promise<T> {
  let lastError: unknown;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      if (attempt === maxAttempts || !isRetryableError(error)) {
        throw error;
      }

      const delay = baseDelayMs * Math.pow(2, attempt - 1);
      console.log(`Attempt ${attempt} failed. Retrying in ${delay}ms...`);
      await sleep(delay);
    }
  }

  throw lastError;
}
  1. Call the chain through the retry wrapper. The important part is that the retry logic sits outside LangChain, so you can reuse it for any runnable: chains, tools, structured output calls, or plain model invocations.
async function main() {
  const question = "Name three practical uses for retry logic in AI apps.";

  const result = await invokeWithRetry(() =>
    chain.invoke({ question })
  );

  console.log(result.content);
}

main().catch((error) => {
  console.error("Final failure:", error);
});
  1. If you want cleaner production behavior, make the retry policy configurable. In real systems, different endpoints need different limits, and you usually want fewer retries for user-facing requests than for background jobs.
type RetryConfig = {
  maxAttempts: number;
  baseDelayMs: number;
};

async function invokeWithRetryConfig<T>(
  fn: () => Promise<T>,
  config: RetryConfig
): Promise<T> {
  return invokeWithRetry(fn, config.maxAttempts, config.baseDelayMs);
}

const response = await invokeWithRetryConfig(
  () => chain.invoke({ question: "Explain exponential backoff." }),
  { maxAttempts: 4, baseDelayMs: 300 }
);

console.log(response.content);

Testing It

Run the script normally first and confirm you get a valid response from the model. Then simulate failure by temporarily disconnecting your network or lowering your OpenAI quota to trigger a transient error path.

You should see the retry log messages before the final success or final failure. If the error is not retryable, the code should fail immediately instead of wasting time on useless attempts.

For production testing, inspect logs for:

  • attempt count
  • delay between retries
  • final error type
  • whether retries are happening on rate limits versus permanent failures

Next Steps

  • Add jitter to your backoff so many requests do not retry at the same time.
  • Move this wrapper into a shared utility and use it across chains, tools, and retrievers.
  • Learn LangChain’s built-in callback system so you can emit retry metrics to your observability stack.

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