LangChain has become the default framework for building LLM applications. But it's also one of the most over-engineered tools in the ecosystem — layers of abstraction that hide what's happening and make debugging painful. Here's how to evaluate when to use it versus building directly.
What LangChain Actually Provides
LangChain solves real problems:
- Chain composition: Link LLM calls, tool calls, and transforms
- Memory: Conversation history management
- Retrieval: Vector store integration for RAG
- Agents: Loop-based tool use with multiple LLM providers
- Streaming: Unified streaming interface
The question is whether you need the abstraction or whether it's adding cost.
The Direct API Approach
For most applications, the Anthropic SDK is sufficient:
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic()
// Simple chat
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
})
// With tools
const toolResponse = await client.messages.create({
model: 'claude-sonnet-4-6',
tools: myTools,
messages,
})
// Streaming
const stream = client.messages.stream({ model, messages })
for await (const event of stream) { /* ... */ }
150 lines of direct SDK code is often clearer than 20 lines of LangChain that hides what's happening.
When LangChain Makes Sense
Multi-provider apps: If you genuinely need to swap between OpenAI, Anthropic, and Mistral with one interface:
import { ChatAnthropic } from '@langchain/anthropic'
import { ChatOpenAI } from '@langchain/openai'
// Swap providers without changing downstream code
const model = process.env.AI_PROVIDER === 'openai'
? new ChatOpenAI({ model: 'gpt-4o' })
: new ChatAnthropic({ model: 'claude-sonnet-4-6' })
Complex RAG pipelines: LangChain's retrieval chains handle chunking, embedding, and context injection:
import { createRetrievalChain } from 'langchain/chains/retrieval'
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents'
const questionAnswerChain = await createStuffDocumentsChain({ llm, prompt })
const ragChain = await createRetrievalChain({
retriever: vectorStore.asRetriever(),
combineDocsChain: questionAnswerChain,
})
const result = await ragChain.invoke({ input: question })
When to Build Direct
Build directly with the provider SDK when:
- Single provider (90% of apps)
- Custom tool execution logic
- You need to inspect and debug every step
- Performance is critical (each abstraction layer adds latency)
- The team needs to understand the code
LangSmith for Observability
LangChain's observability platform works regardless of whether you use LangChain:
import { Client } from 'langsmith'
import { traceable } from 'langsmith/traceable'
// Wrap any function to trace it
const tracedAnalyze = traceable(
async (text: string) => {
return anthropic.messages.create({ /* ... */ })
},
{ name: 'analyze-text', project_name: 'my-app' }
)
LangSmith traces every LLM call, shows token usage, latency, and errors. Worth using even if you skip LangChain.
The Honest Assessment
LangChain is best used as a starting point for exploration, not a production dependency:
- Prototype fast with LangChain abstractions
- Replace with direct SDK calls where you hit friction
- Keep LangSmith for observability throughout
Most production AI applications I've seen eventually strip out LangChain and go direct once the team understands the patterns.
The AI SaaS Starter at whoffagents.com uses the Anthropic SDK directly — no LangChain abstraction — with a clean streaming chat hook, tool execution loop, and conversation persistence. $99 one-time.




