ai-orchestration-langchain
LangChain.js Patterns
Quick Guide: Use LangChain.js (v1.x) to build composable LLM applications. Use LCEL (
prompt.pipe(model).pipe(parser)) for all chain composition -- never use legacyLLMChain. UsewithStructuredOutput(zodSchema)for typed responses. UsecreateAgent()(LangGraph-backed) for agentic workflows --AgentExecutoris legacy. All@langchain/*packages must share the same@langchain/coreversion or you get cryptic type errors at runtime.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use LCEL pipe composition (prompt.pipe(model).pipe(parser)) for all chains -- never use legacy LLMChain, ConversationChain, or SequentialChain)
(You MUST ensure all @langchain/* packages depend on the same version of @langchain/core -- version mismatches cause cryptic runtime errors)
(You MUST use withStructuredOutput(zodSchema) for structured LLM responses -- never manually parse JSON from completion text)
(You MUST use createAgent() from langchain for new agent code -- AgentExecutor and createToolCallingAgent are legacy patterns)
(You MUST never hardcode API keys -- use environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.))
</critical_requirements>
Auto-detection: LangChain, langchain, @langchain/core, @langchain/openai, @langchain/anthropic, @langchain/google-genai, ChatOpenAI, ChatAnthropic, ChatPromptTemplate, StringOutputParser, RunnableSequence, pipe, withStructuredOutput, createAgent, createToolCallingAgent, AgentExecutor, tool, DynamicStructuredTool, RecursiveCharacterTextSplitter, MemoryVectorStore, OpenAIEmbeddings, LCEL, LangSmith, LANGCHAIN_TRACING_V2
When to use:
- Building LLM applications that compose prompts, models, and output parsers into chains
- Creating agentic workflows where models decide which tools to call
- Implementing RAG pipelines with document loading, splitting, embedding, and retrieval
- Needing structured output from LLMs with type-safe Zod schema validation
- Streaming LLM responses token-by-token to users
- Switching between LLM providers (OpenAI, Anthropic, Google) with a unified interface
- Tracing and debugging LLM applications with LangSmith
Key patterns covered:
- Chat model initialization and provider switching (ChatOpenAI, ChatAnthropic, ChatGoogleGenerativeAI)
- LCEL chain composition with
.pipe()andRunnableSequence - Prompt templates (
ChatPromptTemplate,MessagesPlaceholder) - Structured output with
withStructuredOutput()and Zod schemas - Tool definition with
tool()function and Zod schemas - Agent creation with
createAgent()(LangGraph-backed) - RAG pipelines: document loaders, text splitters, vector stores, retrievers
- Streaming from chains, models, and agents
- LangSmith tracing setup
When NOT to use:
- You only call one LLM provider and want the thinnest wrapper -- use the provider's SDK directly
- You need React-specific chat UI hooks (
useChat,useCompletion) -- use a framework-integrated AI SDK - You want a simple single-call completion with no chaining -- a direct SDK call is simpler
- You need real-time bidirectional communication -- LangChain does not cover WebSocket/Realtime APIs
Examples Index
- Core: Setup, LCEL & Chat Models -- Package installation, chat model init, LCEL chains, prompt templates, output parsers
- Structured Output & Tools --
withStructuredOutput, tool definition, binding tools to models - Agents --
createAgent, tool-calling agents, chat history, streaming agents - RAG Pipelines -- Document loaders, text splitters, vector stores, retrieval chains
- Streaming -- Model streaming, chain streaming, agent streaming
- Quick API Reference -- Package map, import paths, environment variables, model IDs
Philosophy
LangChain.js provides a composable framework for building LLM-powered applications. Its core abstraction is the Runnable -- any component that takes an input and produces an output. Runnables compose via LCEL (.pipe()) to form chains, and every Runnable supports .invoke(), .stream(), .batch() uniformly.
Core principles:
- Composability via LCEL -- Chains are built by piping Runnables:
prompt.pipe(model).pipe(parser). Each step is independently testable and replaceable. Legacy chain classes (LLMChain,ConversationChain) are deprecated. - Provider-agnostic models -- Chat models (
ChatOpenAI,ChatAnthropic,ChatGoogleGenerativeAI) share a common interface. Swap providers by changing one import and model name. UseinitChatModel()for runtime provider selection. - Type-safe structured output --
model.withStructuredOutput(zodSchema)constrains LLM responses to your schema. No manual JSON parsing. - Split package architecture --
@langchain/coreholds abstractions, provider packages (@langchain/openai,@langchain/anthropic) hold implementations,langchainholds higher-level composables. All must share the same@langchain/coreversion. - Observability built in -- Set
LANGCHAIN_TRACING_V2=trueand every chain/agent/tool call is traced to LangSmith automatically.
When to use LangChain:
- You need to compose multi-step LLM workflows (prompt -> model -> parser -> next step)
- You want to swap LLM providers without rewriting business logic
- You need agent-style tool calling with automatic routing
- You need RAG with document loading, chunking, embedding, and retrieval
- You want built-in tracing and evaluation via LangSmith
When NOT to use:
- Single-provider, single-call use cases -- the provider SDK is simpler and has less overhead
- You want full control over HTTP requests -- LangChain abstracts the transport layer
- Extremely latency-sensitive applications where the abstraction overhead matters
Core Patterns
Pattern 1: Chat Model Initialization
Initialize chat models from any provider. They all share the same interface.
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4.1",
temperature: 0,
});
const response = await model.invoke("Explain TypeScript generics.");
console.log(response.text);
Why good: Explicit model name, temperature set for determinism, .text accessor for content
// BAD: Hardcoded API key, no model specified
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({ apiKey: "sk-1234..." });
Why bad: Hardcoded API key is a security risk, missing model name uses unpredictable defaults
Provider Switching
import { ChatAnthropic } from "@langchain/anthropic";
const model = new ChatAnthropic({ model: "claude-sonnet-4-5-20250929" });
// Or use initChatModel for runtime provider selection
import { initChatModel } from "langchain";
const model = await initChatModel("openai:gpt-4.1", { temperature: 0 });
See: examples/core.md for full provider examples and configuration options
Pattern 2: LCEL Chain Composition
Compose chains using .pipe(). Every component is a Runnable.
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromTemplate(
"Summarize this in one sentence: {text}",
);
const model = new ChatOpenAI({ model: "gpt-4.1" });
const parser = new StringOutputParser();
const chain = prompt.pipe(model).pipe(parser);
const result = await chain.invoke({ text: "LangChain is a framework..." });
// result is a plain string
Why good: Each step is independently testable, streaming propagates through the entire chain, swapping model is one line change
// BAD: Legacy LLMChain (deprecated)
import { LLMChain } from "langchain/chains";
const chain = new LLMChain({ llm: model, prompt });
Why bad: LLMChain is deprecated, does not support streaming propagation, harder to compose
See: examples/core.md for RunnableSequence.from(), RunnablePassthrough, RunnableParallel
Pattern 3: Structured Output with Zod
Use withStructuredOutput() for type-safe LLM responses.
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
const MovieSchema = z.object({
title: z.string().describe("The movie title"),
year: z.number().describe("Release year"),
genres: z.array(z.string()).describe("List of genres"),
});
const structuredModel = new ChatOpenAI({
model: "gpt-4.1",
}).withStructuredOutput(MovieSchema);
const movie = await structuredModel.invoke("Tell me about Inception.");
// movie is typed: { title: string; year: number; genres: string[] }
Why good: Output is validated against schema, fully typed, no manual JSON parsing
// BAD: Manual JSON parsing from completion text
const response = await model.invoke("Return JSON with title and year...");
const data = JSON.parse(response.text); // Fragile, untyped, can throw
Why bad: No schema validation, untyped result, model may return malformed JSON
See: examples/structured-output-tools.md for complex schemas and edge cases
Pattern 4: Tool Definition
Define tools with the tool() function and Zod schemas. Use snake_case for tool names.
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const getWeather = tool(
async ({ location }) => {
// Call real weather API here
return `Weather in ${location}: 22C, sunny`;
},
{
name: "get_weather",
description: "Get current weather for a city",
schema: z.object({
location: z.string().describe("City name, e.g. 'San Francisco'"),
}),
},
);
Why good: Zod schema validates input, .describe() guides model's argument generation, snake_case name avoids provider compatibility issues
// BAD: Using DynamicStructuredTool (verbose, legacy pattern)
import { DynamicStructuredTool } from "@langchain/core/tools";
const tool = new DynamicStructuredTool({
name: "getWeather", // camelCase breaks some providers
description: "...",
schema: z.object({ ... }),
func: async (input) => { ... },
});
Why bad: DynamicStructuredTool is verbose compared to tool(), camelCase name causes issues with some providers
See: examples/structured-output-tools.md for binding tools to models and handling tool calls
Pattern 5: Agents with createAgent()
Use createAgent() for agentic workflows. It is backed by LangGraph and handles tool calling loops automatically.
import { createAgent } from "langchain";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const search = tool(async ({ query }) => `Results for: ${query}`, {
name: "search",
description: "Search for information",
schema: z.object({ query: z.string() }),
});
const agent = createAgent({
model: "openai:gpt-4.1",
tools: [search],
systemPrompt: "You are a helpful research assistant.",
});
const stream = await agent.stream({
messages: [{ role: "user", content: "Find info about LangChain" }],
});
for await (const step of stream) {
console.log(step.messages.at(-1));
}
Why good: createAgent handles the tool-call loop, supports streaming, manages state via LangGraph
// BAD: Legacy AgentExecutor pattern
import { AgentExecutor, createToolCallingAgent } from "langchain/agents";
const agent = createToolCallingAgent({ llm, tools, prompt });
const executor = new AgentExecutor({ agent, tools });
Why bad: AgentExecutor is legacy, does not integrate with LangGraph state management, less composable
See: examples/agents.md for chat history, custom state, and middleware patterns
Pattern 6: RAG Pipeline
Load documents, split into chunks, embed, store in a vector store, and retrieve.
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
const CHUNK_SIZE = 1000;
const CHUNK_OVERLAP = 200;
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: CHUNK_SIZE,
chunkOverlap: CHUNK_OVERLAP,
});
const chunks = await splitter.splitDocuments(docs);
const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
const vectorStore = new MemoryVectorStore(embeddings);
await vectorStore.addDocuments(chunks);
// Retrieve
const results = await vectorStore.similaritySearch("query", 3);
Why good: Named constants for chunk parameters, explicit embedding model, MemoryVectorStore for prototyping
See: examples/rag.md for full RAG chains, agent-based RAG, and production vector stores
Pattern 7: Streaming
All Runnables support .stream(). Streaming propagates through LCEL chains.
const chain = prompt.pipe(model).pipe(parser);
const stream = await chain.stream({ text: "Explain quantum computing." });
for await (const chunk of stream) {
process.stdout.write(chunk);
}
Why good: Streaming propagates through the entire chain, progressive output for better UX
// BAD: Collecting all output then displaying
const result = await chain.invoke({ text: "..." });
console.log(result); // User waits for full response
Why bad: User waits for full generation before seeing anything, bad UX for long responses
See: examples/streaming.md for model streaming, stream events, agent streaming
Pattern 8: LangSmith Tracing
Enable tracing by setting environment variables. No code changes needed.
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=lsv2_...
LANGCHAIN_PROJECT=my-project
# Recommended for non-serverless environments:
LANGCHAIN_CALLBACKS_BACKGROUND=true
Why good: Zero-code setup, traces every chain/model/tool invocation, LANGCHAIN_CALLBACKS_BACKGROUND=true reduces latency in long-running processes
See: reference.md for all environment variables
<decision_framework>
Decision Framework
When to Use LangChain vs Direct SDK
Do you need multi-step LLM workflows (prompt -> model -> parser -> ...)?
+-- YES -> Use LangChain (LCEL chains)
+-- NO -> Do you need to swap between LLM providers?
+-- YES -> Use LangChain (unified chat model interface)
+-- NO -> Do you need RAG or agent tool calling?
+-- YES -> Use LangChain
+-- NO -> Use the provider SDK directly (simpler, fewer deps)
Which Chat Model Class
Which provider?
+-- OpenAI -> ChatOpenAI from @langchain/openai
+-- Anthropic -> ChatAnthropic from @langchain/anthropic
+-- Google -> ChatGoogleGenerativeAI from @langchain/google-genai
+-- Runtime selection -> initChatModel("provider:model") from langchain
+-- Other -> Check @langchain/community
LCEL vs createAgent
Does the model need to autonomously decide when to call tools?
+-- YES -> createAgent() (handles tool-call loops, state management)
+-- NO -> Is it a fixed sequence of steps?
+-- YES -> LCEL chain (prompt.pipe(model).pipe(parser))
+-- NO -> RunnableSequence.from() with branching
Legacy Chain vs LCEL
Are you writing new code?
+-- YES -> ALWAYS use LCEL (.pipe()) -- never legacy chains
+-- NO -> Is the existing code using LLMChain/ConversationChain?
+-- YES -> Migrate to LCEL when touching the code
+-- NO -> Keep as-is if it works
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Using legacy chains (
LLMChain,ConversationChain,SequentialChain) instead of LCEL -- these are deprecated - Mismatched
@langchain/coreversions across packages -- causesinstanceofchecks to fail silently, methods to be undefined, and type errors - Hardcoding API keys instead of using environment variables
- Manually parsing JSON from LLM text output instead of using
withStructuredOutput() - Using
AgentExecutorfor new code instead ofcreateAgent()
Medium Priority Issues:
- Using camelCase tool names (
getWeather) instead of snake_case (get_weather) -- some providers reject camelCase - Not adding
.describe()to Zod schema fields for tools -- model gets no guidance on argument format - Using
BufferMemory/ConversationSummaryMemory-- these are deprecated, use LangGraph checkpointing orRunnableWithMessageHistory - Not setting
LANGCHAIN_CALLBACKS_BACKGROUND=truein non-serverless environments -- adds latency to every LLM call when tracing is on - Importing from
langchain/(main package) when the import should come from@langchain/core/or a provider package
Common Mistakes:
- Installing
langchainwithout@langchain/core--@langchain/coreis a required peer dependency - Mixing
@langchain/corev0.x withlangchainv1.x -- all packages must be on compatible versions - Using
RunnableLambdain a chain and expecting.stream()to work -- lambda functions do not propagate streaming by default; subclassRunnableand implementtransforminstead - Forgetting that
ChatPromptTemplate.fromTemplate()creates a single user message -- useChatPromptTemplate.fromMessages()for multi-message prompts with system/assistant/user roles - Using
MemoryVectorStorein production -- it is in-memory only, all data is lost on restart; use a persistent vector store
Gotchas & Edge Cases:
@langchain/coreis a peer dependency, not a transitive dependency. You must install it explicitly:npm install @langchain/core. If you see "cannot resolve @langchain/core" orinstanceofchecks failing, you likely have duplicate core versions -- runnpm ls @langchain/coreto check.withStructuredOutput()uses function calling under the hood, not JSON mode. Not all models support it -- check provider docs. If the model does not support function calling, useJsonOutputParserwith a prompt instead.ChatPromptTemplate.fromMessages()uses tuple syntax["system", "..."]or["human", "..."]-- the role names aresystem,human,ai, notdeveloper,user,assistant.tool()from@langchain/core/toolsvstool()fromlangchain-- both exist. Thelangchainre-export is a convenience wrapper. Use whichever matches your import pattern but be consistent.initChatModel()requires the provider package to be installed. If you callinitChatModel("anthropic:claude-sonnet-4-5-20250929")without@langchain/anthropicinstalled, you get a confusing module resolution error, not a clear "package not installed" message.- Zod v4 works with
StateSchemaandcreateAgent, butwithStructuredOutput()may have partial Zod v4 support -- test with your version and fall back to Zod v3.x if schema validation fails. RecursiveCharacterTextSplitternow lives in@langchain/textsplitters(separate package), notlangchain/text_splitter.- When using streaming with
createAgent(), usestreamMode: "values"to get full state at each step, or omit for incremental updates.
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use LCEL pipe composition (prompt.pipe(model).pipe(parser)) for all chains -- never use legacy LLMChain, ConversationChain, or SequentialChain)
(You MUST ensure all @langchain/* packages depend on the same version of @langchain/core -- version mismatches cause cryptic runtime errors)
(You MUST use withStructuredOutput(zodSchema) for structured LLM responses -- never manually parse JSON from completion text)
(You MUST use createAgent() from langchain for new agent code -- AgentExecutor and createToolCallingAgent are legacy patterns)
(You MUST never hardcode API keys -- use environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.))
Failure to follow these rules will produce fragile, hard-to-debug LLM applications with version conflicts and untyped outputs.
</critical_reminders>
More from agents-inc/skills
web-animation-css-animations
CSS Animation patterns - transitions, keyframes, scroll-driven animations, @property, GPU-accelerated properties, accessibility with prefers-reduced-motion
20web-testing-playwright-e2e
Playwright E2E testing patterns - test structure, Page Object Model, locator strategies, assertions, network mocking, visual regression, parallel execution, fixtures, and configuration
18web-animation-view-transitions
View Transitions API patterns - same-document transitions, cross-document MPA transitions, shared element animations, pseudo-element styling, accessibility
17web-animation-framer-motion
Motion (formerly Framer Motion) animation patterns - motion components, variants, gestures, layout animations, scroll-linked animations, accessibility
17web-styling-cva
Class Variance Authority - type-safe component variant styling with cva(), compound variants, and VariantProps
16web-i18n-next-intl
Type-safe i18n for Next.js App Router
16