Skills api-ai-langchain
LangChain.js patterns for building LLM applications — chat models, LCEL chains, prompt templates, structured output, agents, tools, RAG, streaming, and LangSmith tracing
git clone https://github.com/agents-inc/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/dist/plugins/api-ai-langchain/skills/api-ai-langchain" ~/.claude/skills/agents-inc-skills-api-ai-langchain && rm -rf "$T"
dist/plugins/api-ai-langchain/skills/api-ai-langchain/SKILL.mdLangChain.js Patterns
Quick Guide: Use LangChain.js (v1.x) to build composable LLM applications. Use LCEL (
) for all chain composition -- never use legacyprompt.pipe(model).pipe(parser). UseLLMChainfor typed responses. UsewithStructuredOutput(zodSchema)(LangGraph-backed) for agentic workflows --createAgent()is legacy. AllAgentExecutorpackages must share the same@langchain/*version or you get cryptic type errors at runtime.@langchain/core
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use LCEL pipe composition (
) for all chains -- never use legacy prompt.pipe(model).pipe(parser)
, LLMChain
, or ConversationChain
)SequentialChain
(You MUST ensure all
packages depend on the same version of @langchain/*
-- version mismatches cause cryptic runtime errors)@langchain/core
(You MUST use
for structured LLM responses -- never manually parse JSON from completion text)withStructuredOutput(zodSchema)
(You MUST use
from createAgent()
for new agent code -- langchain
and AgentExecutor
are legacy patterns)createToolCallingAgent
(You MUST never hardcode API keys -- use environment variables (
, OPENAI_API_KEY
, etc.))ANTHROPIC_API_KEY
</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
and.pipe()RunnableSequence - Prompt templates (
,ChatPromptTemplate
)MessagesPlaceholder - Structured output with
and Zod schemaswithStructuredOutput() - Tool definition with
function and Zod schemastool() - Agent creation with
(LangGraph-backed)createAgent() - 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
) -- use a framework-integrated AI SDKuseCompletion - 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 --
, tool definition, binding tools to modelswithStructuredOutput - Agents --
, tool-calling agents, chat history, streaming agentscreateAgent - 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>
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:
. Each step is independently testable and replaceable. Legacy chain classes (prompt.pipe(model).pipe(parser)
,LLMChain
) are deprecated.ConversationChain - Provider-agnostic models -- Chat models (
,ChatOpenAI
,ChatAnthropic
) share a common interface. Swap providers by changing one import and model name. UseChatGoogleGenerativeAI
for runtime provider selection.initChatModel() - Type-safe structured output --
constrains LLM responses to your schema. No manual JSON parsing.model.withStructuredOutput(zodSchema) - Split package architecture --
holds abstractions, provider packages (@langchain/core
,@langchain/openai
) hold implementations,@langchain/anthropic
holds higher-level composables. All must share the samelangchain
version.@langchain/core - Observability built in -- Set
and every chain/agent/tool call is traced to LangSmith automatically.LANGCHAIN_TRACING_V2=true
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
<patterns>
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-4o", 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-20250514" }); // Or use initChatModel for runtime provider selection import { initChatModel } from "langchain"; const model = await initChatModel("openai:gpt-4o", { 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-4o" }); 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-4o", }).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()
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-4o", 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
</patterns><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
) instead of LCEL -- these are deprecatedSequentialChain - Mismatched
versions across packages -- causes@langchain/core
checks to fail silently, methods to be undefined, and type errorsinstanceof - Hardcoding API keys instead of using environment variables
- Manually parsing JSON from LLM text output instead of using
withStructuredOutput() - Using
for new code instead ofAgentExecutorcreateAgent()
Medium Priority Issues:
- Using camelCase tool names (
) instead of snake_case (getWeather
) -- some providers reject camelCaseget_weather - Not adding
to Zod schema fields for tools -- model gets no guidance on argument format.describe() - Using
/BufferMemory
-- these are deprecated, use LangGraph checkpointing orConversationSummaryMemoryRunnableWithMessageHistory - Not setting
in non-serverless environments -- adds latency to every LLM call when tracing is onLANGCHAIN_CALLBACKS_BACKGROUND=true - Importing from
(main package) when the import should come fromlangchain/
or a provider package@langchain/core/
Common Mistakes:
- Installing
withoutlangchain
--@langchain/core
is a required peer dependency@langchain/core - Mixing
v0.x with@langchain/core
v1.x -- all packages must be on compatible versionslangchain - Using
in a chain and expectingRunnableLambda
to work -- lambda functions do not propagate streaming by default; subclass.stream()
and implementRunnable
insteadtransform - Forgetting that
creates a single user message -- useChatPromptTemplate.fromTemplate()
for multi-message prompts with system/assistant/user rolesChatPromptTemplate.fromMessages() - Using
in production -- it is in-memory only, all data is lost on restart; use a persistent vector storeMemoryVectorStore
Gotchas & Edge Cases:
is a peer dependency, not a transitive dependency. You must install it explicitly:@langchain/core
. If you see "cannot resolve @langchain/core" ornpm install @langchain/core
checks failing, you likely have duplicate core versions -- runinstanceof
to check.npm ls @langchain/core
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, usewithStructuredOutput()
with a prompt instead.JsonOutputParser
uses tuple syntaxChatPromptTemplate.fromMessages()
or["system", "..."]
-- the role names are["human", "..."]
,system
,human
, notai
,developer
,user
.assistant
fromtool()
vs@langchain/core/tools
fromtool()
-- both exist. Thelangchain
re-export is a convenience wrapper. Use whichever matches your import pattern but be consistent.langchain
requires the provider package to be installed. If you callinitChatModel()
withoutinitChatModel("anthropic:claude-sonnet-4-20250514")
installed, you get a confusing module resolution error, not a clear "package not installed" message.@langchain/anthropic- Zod v4 is NOT fully supported by
as of early 2026 -- stick with Zod v3.x for now.withStructuredOutput()
now lives inRecursiveCharacterTextSplitter
(separate package), not@langchain/textsplitters
.langchain/text_splitter- When using streaming with
, usecreateAgent()
to get full state at each step, or omit for incremental updates.streamMode: "values"
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use LCEL pipe composition (
) for all chains -- never use legacy prompt.pipe(model).pipe(parser)
, LLMChain
, or ConversationChain
)SequentialChain
(You MUST ensure all
packages depend on the same version of @langchain/*
-- version mismatches cause cryptic runtime errors)@langchain/core
(You MUST use
for structured LLM responses -- never manually parse JSON from completion text)withStructuredOutput(zodSchema)
(You MUST use
from createAgent()
for new agent code -- langchain
and AgentExecutor
are legacy patterns)createToolCallingAgent
(You MUST never hardcode API keys -- use environment variables (
, OPENAI_API_KEY
, etc.))ANTHROPIC_API_KEY
Failure to follow these rules will produce fragile, hard-to-debug LLM applications with version conflicts and untyped outputs.
</critical_reminders>