Atlas

Roadmap

Fundamentals

LangChain: Why You Need a Framework

Mar 26, 2026

The Problem with Raw LLM Calls

Calling an LLM API directly works fine for a single prompt. But real applications quickly get complicated:

  • You need to chain multiple LLM calls (summarize → translate → format)
  • You want the LLM to use tools (search the web, query a database)
  • You need to maintain conversation history
  • You're building RAG — retrieval + generation together

Without a framework, you end up writing the same plumbing code over and over. LangChain provides that plumbing.

What is LangChain?

LangChain is a Python (and JavaScript) framework for building LLM-powered applications. It provides abstractions for the most common patterns:

Your App → LangChain → LLM (OpenAI / Anthropic / local)
                     → Tools (search, database, calculator)
                     → Memory (conversation history)
                     → Vector stores (RAG)

Core Concepts

1. Model

Wraps any LLM behind a unified interface:

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0)
response = llm.invoke("What is RAG?")
print(response.content)
python

Switch models by changing one line — your chain code stays the same.

2. Prompt Template

Separates your prompt structure from the variable content:

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant that explains {topic} clearly."),
    ("human", "{question}"),
])

filled = prompt.invoke({"topic": "databases", "question": "What is an index?"})
python

3. Chain (LCEL)

LangChain Expression Language (LCEL) connects components with |:

chain = prompt | llm

response = chain.invoke({
    "topic": "databases",
    "question": "What is an index?"
})
print(response.content)
python

prompt | llm means: fill the prompt → send to LLM → return response. Each | passes the output of the left side as input to the right side.

4. Output Parser

By default, the LLM returns a message object. Add a parser to extract just the string:

from langchain_core.output_parsers import StrOutputParser

chain = prompt | llm | StrOutputParser()

response = chain.invoke({"topic": "databases", "question": "What is an index?"})
print(response)  # plain string, not a message object
python

A Complete Example

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-4o")

prompt = ChatPromptTemplate.from_messages([
    ("system", "Explain the concept in 2-3 sentences, suitable for a beginner."),
    ("human", "Explain: {concept}"),
])

chain = prompt | llm | StrOutputParser()

print(chain.invoke({"concept": "vector embeddings"}))
python

LangChain vs. Calling the API Directly

Direct API callLangChain
Simple one-off prompt✅ simplerOverkill
Chaining multiple stepsManual plumbing✅ clean
Swapping LLM providersChange lots of codeChange one line
Building RAGBuild everything yourself✅ built-in integrations
Adding memoryManual✅ built-in

Use the raw API when you're making a single call and don't need any of LangChain's abstractions. Use LangChain when you're building a real application with multiple steps.

Key Questions

Q: What problem does LangChain solve?

LangChain provides abstractions for common LLM application patterns — chaining multiple calls, managing prompts as templates, integrating tools and memory, and building RAG pipelines. Without it, developers repeat the same plumbing code across projects.

Q: What is LCEL?

LangChain Expression Language — a way to compose chains using the | operator. Each component (prompt, model, parser) implements a standard interface (invoke, stream, batch), so they can be chained together. It replaces older LangChain APIs that required subclassing.

Q: When would you NOT use LangChain?

For simple, single LLM calls where the overhead isn't justified. Also in production systems where you need tight control over every token and API call — LangChain's abstractions can hide what's actually happening, making debugging harder.