LangChain Guide: Chains, Agents, Memory, and LCEL Patterns
·13 min read
LangChain provides composable building blocks for LLM applications. This guide focuses on LCEL (LangChain Expression Language), the modern composition API, with practical patterns for production systems.
Setup
npm install langchain @langchain/openai @langchain/community
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { RunnableSequence } from '@langchain/core/runnables';LCEL basics: the pipe operator
LCEL composes runnables with the .pipe() method or the | operator:
const model = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0 });
const parser = new StringOutputParser();
// Simple prompt → model → parse
const chain = ChatPromptTemplate.fromTemplate('Translate to French: {text}')
.pipe(model)
.pipe(parser);
const result = await chain.invoke({ text: 'Hello, how are you?' });
// "Bonjour, comment allez-vous?"Structured output chain
import { z } from 'zod';
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
const SentimentSchema = z.object({
sentiment: z.enum(['positive', 'negative', 'neutral']),
confidence: z.number().min(0).max(1),
reason: z.string(),
});
const model = new ChatOpenAI({ model: 'gpt-4o' }).withStructuredOutput(SentimentSchema);
const chain = ChatPromptTemplate.fromTemplate(
'Analyze the sentiment of: {text}'
).pipe(model);
const result = await chain.invoke({ text: 'I absolutely love this product!' });
// { sentiment: 'positive', confidence: 0.97, reason: '...' }RAG chain
import { OpenAIEmbeddings } from '@langchain/openai';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { createRetrievalChain } from 'langchain/chains/retrieval';
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents';
import { ChatPromptTemplate } from '@langchain/core/prompts';
// Build vector store from documents
const embeddings = new OpenAIEmbeddings({ model: 'text-embedding-3-small' });
const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings);
const retriever = vectorStore.asRetriever({ k: 5 });
// QA chain
const qaPrompt = ChatPromptTemplate.fromMessages([
['system', 'Answer the question using only the following context:
{context}'],
['human', '{input}'],
]);
const combineDocsChain = await createStuffDocumentsChain({
llm: new ChatOpenAI({ model: 'gpt-4o' }),
prompt: qaPrompt,
});
const ragChain = await createRetrievalChain({
retriever,
combineDocsChain,
});
const response = await ragChain.invoke({ input: 'What is the refund policy?' });
console.log(response.answer);Memory and conversation history
import { ConversationSummaryBufferMemory } from 'langchain/memory';
import { ConversationChain } from 'langchain/chains';
// Summary buffer: keeps recent messages verbatim, summarizes older ones
const memory = new ConversationSummaryBufferMemory({
llm: new ChatOpenAI({ model: 'gpt-4o-mini' }),
maxTokenLimit: 1000,
returnMessages: true,
});
const chain = new ConversationChain({
llm: new ChatOpenAI({ model: 'gpt-4o' }),
memory,
});
await chain.invoke({ input: "My name is Alice and I'm having billing issues." });
await chain.invoke({ input: "I was charged twice in March." });
const response = await chain.invoke({ input: "What was my issue again?" });
// Correctly recalls Alice's billing issue from earlier in conversationTool-using agent with LCEL
import { tool } from '@langchain/core/tools';
import { createToolCallingAgent, AgentExecutor } from 'langchain/agents';
import { z } from 'zod';
// Define tools
const searchTool = tool(
async ({ query }) => {
const results = await searchDatabase(query);
return JSON.stringify(results);
},
{
name: 'search_docs',
description: 'Search the documentation database for relevant information',
schema: z.object({ query: z.string().describe('Search query') }),
}
);
const calculatorTool = tool(
async ({ expression }) => String(eval(expression)),
{
name: 'calculator',
description: 'Evaluate a mathematical expression',
schema: z.object({ expression: z.string() }),
}
);
// Create agent
const tools = [searchTool, calculatorTool];
const model = new ChatOpenAI({ model: 'gpt-4o' }).bindTools(tools);
const prompt = ChatPromptTemplate.fromMessages([
['system', 'You are a helpful assistant.'],
['placeholder', '{chat_history}'],
['human', '{input}'],
['placeholder', '{agent_scratchpad}'],
]);
const agent = createToolCallingAgent({ llm: model, tools, prompt });
const executor = new AgentExecutor({ agent, tools, maxIterations: 10 });
const response = await executor.invoke({ input: 'What does the refund policy say about subscriptions?', chat_history: [] });
console.log(response.output);Streaming
// Stream output tokens as they arrive
const stream = await chain.stream({ text: 'Explain quantum computing in simple terms.' });
for await (const chunk of stream) {
process.stdout.write(chunk);
}
// Server-Sent Events (Next.js App Router)
export async function POST(req: Request) {
const { question } = await req.json();
const stream = await ragChain.stream({ input: question });
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
if (chunk.answer) {
controller.enqueue(encoder.encode(`data: ${chunk.answer}
`));
}
}
controller.close();
},
});
return new Response(readable, {
headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
});
}Parallel and branching with RunnableParallel
import { RunnableParallel } from '@langchain/core/runnables';
// Run multiple chains in parallel
const parallel = RunnableParallel.from({
sentiment: sentimentChain,
summary: summaryChain,
keywords: keywordsChain,
});
const { sentiment, summary, keywords } = await parallel.invoke({ text: userInput });Fallback handling
// Use a cheaper model as fallback if the primary fails
const primaryModel = new ChatOpenAI({ model: 'gpt-4o' });
const fallbackModel = new ChatOpenAI({ model: 'gpt-4o-mini' });
const modelWithFallback = primaryModel.withFallbacks([fallbackModel]);
// Apply fallbacks at the chain level too
const reliableChain = expensiveChain.withFallbacks([cheapChain]);Takeaway
Use LCEL as your composition primitive — it handles streaming, batching, fallbacks, and observability transparently. Build small focused chains and compose them, rather than building monolithic chains that are hard to debug and test.