Prompt chaining examples (with and without LangChain)
Copy-paste examples of prompt chaining in TypeScript: resume screening and content creation, first plain and then with LangChain.
Contributors: Ivan Garcia Villar
Prompt chaining means taking a large task and breaking it into multiple LLM (language model, like Claude or ChatGPT) calls stacked in sequence: what one step outputs becomes the input to the next. Here are copy-paste examples, first in TypeScript and then with LangChain.
Before you start: you just need to know what an LLM is and have written some TypeScript. The full theory on why (how to divide well, what a gate is) lives in the prompt chaining guide; this post is the example bank.
Example 1: resume screening, step by step
The clearest case is processing resumes that arrive for an opening. We split it into three calls, and each one receives only the clean data from the previous step, not the three-page document.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // uses the ANTHROPIC_API_KEY environment variable
// Helper: send a prompt and return the model's response text
async function llamarLLM(prompt: string): Promise<string> {
const respuesta = await client.messages.create({
model: "claude-haiku-4-5-20251001", // fast model, ideal for simple steps
max_tokens: 300, // cap on tokens (text chunks) generated
messages: [{ role: "user", content: prompt }],
});
const bloque = respuesta.content[0];
if (!bloque || bloque.type !== "text") throw new Error("El modelo no devolvió texto");
return bloque.text;
}
With that helper, the chain is three prompts in a row. Notice what gets passed to each step:
async function triarCV(cv: string) {
// Step 1: extract only the work experience from the full document
const experiencia = await llamarLLM(
`Extrae únicamente la experiencia laboral de este CV:\n\n${cv}`
);
// Step 2: classify using ONLY that extract, not the full resume
const nivel = await llamarLLM(
`Clasifica este perfil como "Junior", "Mid" o "Senior":\n\n${experiencia}`
);
// Step 3: draft the email using ONLY the tag from step 2
const email = await llamarLLM(
`Redacta un email de seguimiento breve para un candidato ${nivel}.`
);
return { nivel, email };
}
Step 3 doesn’t need to read the resume. It just gets the word Senior and works with that. Passing less is cheaper and leaves less room for the model to get confused.
Example 2: writing content in three prompts
The same idea works for writing an article. The chain has three distinct responsibilities, one per call:
- Outline → “Give me a 5-section outline for an article on X.”
- Draft → “Write the article following this outline:
{outline}.” - Tone revision → “Rewrite this text in a friendly and direct tone:
{draft}.”
No need to repeat all the code. The skeleton is identical to the first example: each llamarLLM call receives the result of the previous step in braces, nothing else. A support ticket would work the same way (summarize → classify urgency → route to team). The pattern stays the same, only the domain changes.
The repeating pattern
Look at both examples and you’ll see the same thing every time: one responsibility per call, minimal and clean context passed between steps, and checking the result before moving forward. That check is the gate (a door that validates a step’s output before letting it advance), and it’s what prevents a step-1 error from cascading to the end. Why it matters so much and how to write it well is covered in the prompt chaining guide. Here I’ll stick to what it looks like.
The same examples with LangChain
LangChain does exactly this, but with reusable pieces that compose. Its way of chaining is called LCEL (LangChain Expression Language): you connect components with the .pipe() method, like Unix pipes. Here’s the first half of Example 1 rewritten:
import { ChatAnthropic } from "@langchain/anthropic";
import { PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const modelo = new ChatAnthropic({ model: "claude-haiku-4-5-20251001" });
const parser = new StringOutputParser(); // extracts plain text from the response
// One step = template .pipe(model) .pipe(parser that returns string)
const extraer = PromptTemplate
.fromTemplate("Extrae únicamente la experiencia laboral de este CV:\n\n{cv}")
.pipe(modelo)
.pipe(parser);
To chain multiple steps you use RunnableSequence.from([...]). Between steps you put a function that takes the output text and maps it to the input the next one expects:
import { RunnableSequence } from "@langchain/core/runnables";
const clasificar = PromptTemplate
.fromTemplate('Clasifica este perfil como "Junior", "Mid" o "Senior":\n\n{experiencia}')
.pipe(modelo)
.pipe(parser);
// The full chain: extract → map the text to input → classify
const cadena = RunnableSequence.from([
extraer,
(experiencia: string) => ({ experiencia }), // text from step 1 → variable for step 2
clasificar,
]);
const nivel = await cadena.invoke({ cv: "..." }); // runs the entire sequence
The function (experiencia) => ({ experiencia }) does what we did by hand in the no-framework version: take the clean output from one step and pass it to the next. StringOutputParser is there so that text circulates between steps, not the full response object.
Vanilla or LangChain?
Both versions do the same thing. The difference is how many layers of abstraction you want.
| (Anthropic SDK) | LangChain (LCEL) | |
|---|---|---|
| Short chains (2-4 steps) | Simpler, fewer dependencies | Adds layers you don’t need |
| Many composable chains | You end up repeating glue code | Reuse Runnables |
| Switch provider or model | Rewrite the calls | Change the model class |
| Streaming, retries, retrievers | You build it yourself | Comes built-in |
For the examples in this post, doing it with the SDK wins on clarity. LangChain starts to pay off when you have many chains to recombine, want streaming or retries without writing them, or need to swap models without touching the logic. If you only have a three-step chain, the extra dependencies rarely justify themselves.
When you’re ready to move from copying examples to building your own agentic systems, the agentic patterns course is where to practice them.
Frequently Asked Questions
Do I need LangChain to chain prompts?
No. A couple of TypeScript functions that pass the result to each other do the same thing, and in short chains they read and debug better than any framework.
Can I use Claude with LangChain?
Yes. Import ChatAnthropic from @langchain/anthropic and pass the model in the constructor: new ChatAnthropic({ model: "claude-haiku-4-5-20251001" }). From there you chain it like any other LangChain model.
What is LCEL?
LCEL is LangChain Expression Language, LangChain’s way of composing steps. You connect components (a Runnable) with the .pipe() method, so the output of one flows into the next. prompt.pipe(model).pipe(parser) is a one-step chain.
When is LangChain NOT a good fit?
When the task is atomic (“translate this sentence”) or the chain is very short. There the framework only adds dependencies and abstraction layers you’ll have to understand to debug. Start plain and move to LangChain when the number of chains you recombine justifies it.