We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide13 min read

LangChain, an LLM application framework for Python and TypeScript

LangChain connects a language model to tools, vector stores, and memory. Version 1.0, create_agent, middleware, RAG, costs, and a LlamaIndex comparison.

LangChain, an LLM application framework for Python and TypeScript

LangChain connects a language model to the rest of your application: prompts, a vector store, tools, and conversation history. Version 1.0 shipped on 22 October 2025 and reduced the core library to a single agent constructor plus a set of middleware layers around it. The hundreds of integrations collected over three years moved into separate packages.

What LangChain does and does not do

The core value is a shared interface across model providers. Instead of writing separate code for OpenAI, Claude, and a local model served by Ollama, you call .invoke() on an object that behaves the same way regardless of what sits underneath. Tool calling, structured output, streaming, and token accounting share one signature.

That shared interface has limits worth knowing up front. The framework unifies the shape of a call, it does not paper over provider differences: rate limits, prompt caching, reasoning parameters, and error formats stay exactly as they are. Swapping one line with a model name moves your code to another provider, while answer quality and cost shift enough that the prompt usually needs retuning. Treat the swap as a starting point for testing, not as a finished result.

LangChain is not a database, a server, or a hosting platform. It stores no vectors, it talks to Chroma, Qdrant, or Postgres. It runs no background processes, that is LangGraph or a plain job queue. It also does not guarantee answer quality, which you measure separately with evaluation tooling. Nor does it handle voice conversation: detecting the end of a turn, interruption mid sentence, and telephony are a separate layer that a platform such as Vapi takes on. When costing one, remember the platform fee is a fraction of the bill, since every minute of conversation also carries speech recognition, the model call, and voice synthesis.

The project started in October 2022 and spent its first two years absorbing criticism about excessive abstraction. The answer was a package split: langchain-core with interfaces only, langchain with agent building blocks, langchain-classic holding legacy code for backwards compatibility, and provider packages such as langchain-openai. The promise attached to 1.0 is explicit: no breaking API changes before 2.0. The current langchain-core release from late July 2026 is 1.5.3.

Installation and your first agent

Python needs two packages:

Code
Bash
pip install langchain langchain-openai
export OPENAI_API_KEY=sk-...

TypeScript works the same way:

Code
Bash
npm install langchain @langchain/openai @langchain/core

A minimal 1.0 agent fits in a few lines:

Code
Python
from langchain.agents import create_agent
from langchain_core.tools import tool

@tool
def exchange_rate(symbol: str) -> str:
    """Returns the currency rate against the Polish zloty."""
    rates = {"EUR": "4.28", "USD": "3.94"}
    return rates.get(symbol.upper(), "no data")

agent = create_agent(
    model="openai:gpt-5",
    tools=[exchange_rate],
    system_prompt="Answer briefly and always name the source of any number."
)

answer = agent.invoke({"messages": [{"role": "user", "content": "What is the euro rate?"}]})
print(answer["messages"][-1].content)

A loop runs underneath: the model receives a tool description generated from the function signature and docstring, decides to call it, the framework executes it, and the result returns to the model as a tool message. The loop ends when the model stops requesting tools. That is the whole mechanism, and it pays to understand it, because your token bill scales with the number of turns.

LCEL, composing chains with the pipe operator

For work that needs no agent, LangChain Expression Language remains. You compose components with a vertical bar, and the result gets three execution methods for free.

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

prompt = ChatPromptTemplate.from_template(
    "Summarise the bug report below in one sentence:\n\n{text}"
)
chain = prompt | ChatOpenAI(model="gpt-5-mini", temperature=0) | StrOutputParser()

chain.invoke({"text": report})
chain.batch([{"text": r} for r in reports])
for piece in chain.stream({"text": report}):
    print(piece, end="")

batch parallelises calls on its own, stream yields tokens as they are generated, and the async variants (ainvoke, astream) work without extra code. If your backend serves a few hundred concurrent users, that single property saves a meaningful amount of concurrency work.

Structured output relies on a schema, usually Pydantic in Python or Zod in TypeScript:

Code
TypeScript
import { z } from 'zod'
import { ChatOpenAI } from '@langchain/openai'

const schema = z.object({
  priority: z.enum(['low', 'medium', 'high']),
  category: z.string(),
  repairHours: z.number().describe('estimate in hours')
})

const model = new ChatOpenAI({ model: 'gpt-5-mini' }).withStructuredOutput(schema)
const result = await model.invoke(`Classify this ticket: ${ticket}`)

The response arrives as an object matching the schema, with no manual JSON parsing and no exception handling around a failed JSON.parse.

RAG from document to answer

Retrieval augmented generation is still the most common reason teams reach for this framework. The pipeline has four steps: load documents, split them into chunks, compute embeddings, and write to a vector store.

Code
Python
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

documents = PyPDFLoader("terms.pdf").load()
chunks = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=120
).split_documents(documents)

store = Chroma.from_documents(chunks, OpenAIEmbeddings(model="text-embedding-3-small"))
retriever = store.as_retriever(search_kwargs={"k": 4})

Chunk size decides quality more than model choice does. Chunks of 300 characters lose the context of a sentence, chunks of 4000 characters blur meaning and inflate prompt cost. A sensible starting point for technical documentation is 600 to 1000 characters with a 10 to 15 percent overlap. For contracts and terms of service, split on paragraph boundaries instead, because a sentence from one clause rarely explains another.

The second quality threshold is retrieval itself. Pure vector search misses queries containing version numbers and proper nouns, so production systems combine it with full text search and pass the result through a reranker. Supabase and Neon support both modes on a single Postgres instance, which is often simpler than maintaining a dedicated vector database.

Middleware, control over the agent loop

Version 1.0 introduced layers that hook in before and after each model call. This answers the most common production problem: an agent behaves on a demo, and a week later the message history exceeds the context window.

Code
Python
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware, HumanInTheLoopMiddleware

agent = create_agent(
    model="anthropic:claude-sonnet-4-5",
    tools=[find_order, issue_refund],
    middleware=[
        SummarizationMiddleware(model="openai:gpt-5-mini", max_tokens_before_summary=4000),
        HumanInTheLoopMiddleware(interrupt_on={"issue_refund": True})
    ]
)

The first layer summarises older messages as history grows. The second pauses execution before a tool that moves someone else's money and waits for human approval. Without that second mechanism, every application with write operations is a bet that the model will never make a mistake.

Conversation memory and state across sessions

Short term memory is simply a list of messages passed on every call. Durability comes from a checkpointer, which stores conversation state under a thread identifier and lets you resume after a process restart.

Code
Python
from langgraph.checkpoint.postgres import PostgresSaver

with PostgresSaver.from_conn_string(os.environ["DATABASE_URL"]) as saver:
    agent = create_agent(model="openai:gpt-5", tools=tools, checkpointer=saver)
    agent.invoke(payload, config={"configurable": {"thread_id": "user-42"}})

Long term memory, meaning facts about a user carried between threads, is a separate problem. LangChain provides a store interface for it but does not decide what deserves remembering. You design that yourself, usually as an extra model call that extracts durable facts once a conversation ends.

Debugging when the answer is wrong

A bad answer usually has one of three causes, and separating them beats rewriting the prompt at random.

First: retrieval handed the model the wrong chunks. Two minutes settle it, call the retriever alone and print what came back.

Code
Python
for i, chunk in enumerate(retriever.invoke("how do i file a complaint")):
    print(i, chunk.metadata.get("source"), chunk.page_content[:160])

If none of the four chunks contains the answer, no prompt will fix that. Go back to document splitting, raise the k parameter, or add full text search alongside.

Second: the model received the right context and ignored it, or invented a detail. What helps is an instruction to answer strictly from the supplied chunks and to state plainly when data is missing, plus temperature set to zero for factual work.

Third: the agent picked the wrong tool. The tool description is usually at fault, not the model. A docstring is all the model sees, so "Returns data" decides nothing, while "Returns order status by number in ORD-12345 format, only for orders from the last 90 days" is enough for a correct choice.

To inspect the whole loop, turn on call tracing.

Code
Bash
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__...

Every call then lands in LangSmith as a tree with timings, token counts, and full prompt text. The no signup variant is set_debug(True) from langchain_core.globals, which prints the same information to standard output, just harder to read.

Track cost during testing rather than after it. A token counting callback wrapped around an evaluation run shows what a single conversation actually costs before the invoice does.

Code
Python
from langchain_community.callbacks import get_openai_callback

with get_openai_callback() as counter:
    agent.invoke(payload)
    print(counter.total_tokens, counter.total_cost)

The rule that saves the most time: test the layers separately. Retriever without the model first, prompt without tools next, the full path last.

LangChain against the alternatives

ToolStrengthWeaknessPick it when
LangChainLargest integration set, agents, one interface across providersAn abstraction layer to learn before you can debug itThe app combines several models, tools, and data sources
LlamaIndexIndexing and retrieval, ready made document splitting strategiesWeaker support for complex agentsDocument search is the core of the product
HaystackReadable pipelines, mature evaluationSmaller community, fewer integrationsThe team has classic NLP experience
Provider SDKZero abstraction, full control, fewest dependenciesSwitching providers means rewriting codeThe app makes one model call in total

A practical rule: if your application makes a single model call and returns text, the framework is dead weight. The dependency starts paying off at the third tool, the second provider, or the first vector store.

What it costs

The framework itself is MIT licensed and free. You pay for tokens and, optionally, for LangSmith, the observability layer from the same vendor.

LangSmith planPriceTrace allowanceRetention
Developer0 USD, single seat5,000 per month14 days, extended to 400
Plus39 USD per seat10,000 included, then 2.50 USD per 1,00014 days, extended tier 5 USD per 1,000
Enterprisecustom quotenegotiated14 days, extended to 400

Tokens will dominate the real bill, not the subscription. An agent with five tools and a running conversation can burn 15,000 input tokens on a single user question, because the entire history goes to the model on every loop turn. Two things cut that cost hardest: summarising history and routing simple queries to a smaller model.

The retention column needs one qualification, since the table on its own suggests otherwise. Fourteen days is the baseline on every plan, the free one included, and extension to four hundred days is a separately priced option available everywhere rather than a privilege of a custom quote. If you need traces older than a fortnight for incident analysis or compliance, price that line from the start, because at volume it can exceed the subscription itself.

Common mistakes and when to skip it

The most frequent mistake is reaching for an agent where one prompt with an enforced output schema would do. An agent costs several times more, runs slower, and is harder to test.

The second is the missing iteration limit. A model stuck in a tool calling loop can drain a daily budget in fifteen minutes. Set a hard turn limit and a timeout on the whole call.

The third is installing langchain-community without pinning a version. That package collects integrations of wildly varying quality, some maintained by a single person, and they do break between releases.

The fourth concerns migration. Code written for 0.3 will not run on 1.0 unchanged, but you do not have to rewrite it at once: pip install langchain-classic restores the old imports and lets you migrate module by module.

The fifth is the most expensive: shipping without a fixed set of test questions. Changing a prompt, a model, or a chunk size can improve ten answers and break three others, and without a comparison list nobody notices until a user complains. Thirty questions with expected answers, kept in a JSON file and run through the chain after every change, are enough. That is half a day of work which pays for itself the first time a bad deploy gets rolled back within an hour instead of a week.

LangChain inside a Next.js project

In Next.js the framework plugs into a route handler and streams the response to the browser. Full server side typing survives, because Zod schemas describe both the input and the model output.

TSapp/api/chat/route.ts
TypeScript
// app/api/chat/route.ts
import { ChatAnthropic } from '@langchain/anthropic'

export const runtime = 'nodejs'

export async function POST(req: Request) {
  const { question } = await req.json()
  const model = new ChatAnthropic({ model: 'claude-sonnet-4-5' })
  const stream = await model.stream(question)

  return new Response(
    new ReadableStream({
      async start(controller) {
        for await (const piece of stream) {
          controller.enqueue(new TextEncoder().encode(String(piece.content)))
        }
        controller.close()
      }
    }),
    { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }
  )
}

On Vercel, watch two things. The edge runtime does not support some native dependencies, so keep vector store routes on nodejs. The function timeout on the free plan can cut a longer agent conversation in half, so move long running jobs to a queue and poll for the result.

FAQ

Is LangChain free?

Yes, the library is open source under the MIT licence with no commercial usage limit. The paid parts are the services around it: LangSmith for call tracing and hosted agent deployment. Token costs come from your model provider either way, framework or no framework.

Python or TypeScript?

The Python version ships first and carries more integrations, especially around document processing. The TypeScript version tracks the core closely and covers agents, RAG, and structured output, and it wins when backend and frontend share types in one repository.

LangChain or LangGraph?

LangChain gives you a working agent in one function, LangGraph gives you a graph where you define nodes and transitions yourself. Start with create_agent. Move to LangGraph when you need branching, several cooperating agents, or a process that pauses for hours waiting on approval.

Does the framework slow the application down?

Framework overhead is measured in milliseconds and disappears against model latency, which runs into hundreds of milliseconds or seconds. Real delays come from the number of agent loop turns and from sequential calls that could have run through batch.

How do I migrate from 0.3 to 1.0?

Install langchain-classic, swap imports in one module, run the tests, move to the next one. Agents take the most work, since AgentExecutor gave way to the create_agent function. Chains built on LCEL usually pass through untouched.

Framework documentation lives at docs.langchain.com, and the source code sits in the GitHub repository.