TechByteByByte

Why LangChain Exists (Before You Learn What It Is)

Start with raw API calls, hit the real problems every LLM app runs into, and see exactly why LangChain was built to solve them — with matching OpenAI and Gemini code.

#LangChain#LLM Applications#Python#OpenAI#Gemini

Let’s not start with a definition. Definitions are boring on their own, and they don’t stick unless you already feel why they matter.

So instead, let’s start the way every real LangChain project actually starts: with a single, plain, ordinary call to a model. No LangChain involved yet. Just you, an API key, and a question.

The simplest possible AI app

Here’s the whole app. Two real lines of work. One question goes in, one answer comes out.

OpenAI:

from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY from your environment

response = client.responses.create(
    model="gpt-4o-mini",
    input="What is RAG?"
)

print(response.output_text)

Gemini:

from google import genai

client = genai.Client()  # reads GOOGLE_API_KEY from your environment

response = client.models.generate_content(
    model="gemini-2.0-flash",
    contents="What is RAG?"
)

print(response.text)

Let’s slow down and actually look at what just happened here, piece by piece, because every single thing in this tiny example is a concept we’ll keep meeting for the rest of this course.

  • client is your connection to the AI company’s servers. You’re not running any AI model on your own computer. Your code sends a message across the internet to OpenAI’s or Google’s computers, and their model runs there.
  • model="gpt-4o-mini" or model="gemini-2.0-flash" tells them exactly which specific AI model you want to talk to. Companies offer several models — some faster and cheaper, some slower and more capable.
  • response is the object you get back. It’s not just the plain text answer — it’s a whole package of information, and the actual words the model wrote are tucked inside it (response.output_text for OpenAI, response.text for Gemini).

Run either one, and you get a real, correct answer back. That’s it. That is, genuinely, a complete AI application. Two lines could power a small business.

So here’s the honest, important question this whole module is going to answer:

If it’s really this simple… why does an entire framework called LangChain exist? What problem could possibly be left to solve?

To find the real answer, we’re not going to read a definition. We’re going to deliberately push this tiny app until it breaks, and watch exactly where and why it breaks. That’s where LangChain’s actual reason for existing lives.

Problem 1: a real conversation needs memory

Right now, our little app can only ever answer one isolated question. Ask it something, get an answer, and that’s the end — it has no idea what was said a moment ago.

But think about how you actually talk to ChatGPT or Gemini in your browser. You ask something, then you say “wait, explain that more simply” — and it remembers what “that” refers to. It’s not magic. The model itself doesn’t remember anything between messages at all. Every single time you send a message, the entire conversation so far has to be sent again, from scratch. The “memory” you’re experiencing is really just your app carefully resending the whole history every time.

Let’s build that, by hand, and see what it actually takes.

OpenAI:

from openai import OpenAI

client = OpenAI()

# "history" is a plain Python list. WE are responsible for keeping it updated.
history = [
    {"role": "system", "content": "You are a friendly AI tutor."},
    {"role": "user", "content": "What is RAG?"},
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=history
)

# we must manually add the model's own reply back into the list,
# or the next call won't know it ever happened
history.append({"role": "assistant", "content": response.choices[0].message.content})
history.append({"role": "user", "content": "Can you explain it like I'm 12?"})

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=history
)

print(response.choices[0].message.content)

Gemini:

from google import genai

client = genai.Client()

# Gemini gives us a "chat" object that keeps track of history internally
chat = client.chats.create(model="gemini-2.0-flash")

first = chat.send_message("What is RAG?")
second = chat.send_message("Can you explain it like I'm 12?")

print(second.text)

Now let’s slow down and unpack the important idea buried in that word “role” you saw in the OpenAI example. Every message in a conversation has a role — a label saying who said it:

  • "system" — instructions from you, the developer, telling the model how to behave overall. The user never sees this.
  • "user" — something the human typed.
  • "assistant" — something the model itself said, in an earlier turn.

This three-role structure is how every chat-based AI model represents a conversation, underneath whatever interface you happen to be using.

Now notice the actual difference between our two code blocks. With OpenAI, you are the one responsible for building that growing list and remembering to add the model’s own reply back into it. Forget that one line, and the model will have no idea what it just said a moment ago. With Gemini, the chat object quietly does that bookkeeping for you.

Same underlying job — “remember the conversation” — solved with two genuinely different pieces of code, in two genuinely different shapes. That’s the first real crack in our “AI apps are simple” idea: every provider has its own way of doing the same fundamental thing, and if you ever need to support both, or switch between them, you have to learn and maintain both approaches separately.

Problem 2: real apps need to actually do something, not just talk

A conversation alone often isn’t enough. Picture something small but genuinely useful — a homework helper that needs to look something up before it can answer properly.

The flow now looks like this:

User asks a question

Model reads the question

Model decides: "I don't know this — I need to look something up"

Our app actually runs that lookup (a search, a calculator, a database query)

Model reads the result of that lookup

Model writes the final, informed answer

App shows it to the user

Notice something important here: this is no longer one call to the model. It’s a back-and-forth. The model answers partway, our own code does something in the real world, and then we hand control back to the model to finish the job. This pattern — model, then action, then model again — is called tool calling, and it’s genuinely one of the most important ideas in this entire course, so let’s be precise about it.

A tool is just an ordinary function your own code already knows how to run — a calculator, a weather lookup, a database query. The model itself can never run this code directly. It has no hands. What it can do is read a description of your function and say, in effect, “I’d like you to run this specific function, with these specific arguments.” Your own application code is the one that actually executes it and reports back.

Here’s roughly what building that hand-off looks like, without any help:

response = client.chat.completions.create(model="gpt-4o-mini", messages=history)

# did the model ask to use a tool, instead of just answering directly?
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]

    # now WE have to:
    # 1. figure out which of our functions it's asking for
    # 2. pull out and validate the arguments it wants to use
    # 3. actually run that function ourselves
    # 4. package the result in the exact format the API expects
    # 5. send it back, then call the model AGAIN to get the real, final answer
    ...

Notice how many separate responsibilities landed on us here, just to support one single tool. Real applications rarely need just one — a customer support bot might need to look up an order, check a refund policy, and search a knowledge base, all within a single conversation. Every one of those needs this same five-step hand-off, built and maintained by hand, separately, for every provider you support.

Stepping back: the shape every real LLM app takes

Let’s zoom out from these two specific problems and name the actual pattern hiding underneath both of them. Almost every serious LLM application, no matter what it does, is built from the same repeating shape:

flowchart TD
    A[User] --> B[Prompt]
    B --> C[Model]
    C --> D[Parse the output]
    D --> E[Call a tool]
    E --> C
    C --> F[Retrieve some documents]
    F --> C
    C --> G[Check the output is safe and correct]
    G --> H[Save state or memory]
    H --> I[Return the response]

Look closely at how many times the arrow loops back into the Model box. That’s not a coincidence — it’s the defining shape of real LLM applications. The model rarely does its whole job in one single pass; it does a piece, hands off to your code, and comes back for another pass.

And here is the actual point of this entire module, stated plainly:

Every single arrow in that diagram needs real, working code behind it — code for tracking conversation history, code for detecting and executing tool calls, code for formatting messages the exact way each provider expects — and every developer who has ever built an LLM app from scratch has had to write some version of this same plumbing, for themselves, more than once.

Nobody wants to keep rebuilding the same plumbing for every new project. So a group of engineers did what engineers usually do when they notice the same problem showing up again and again: they built a shared, reusable toolbox — once — so nobody else would have to build it from scratch.

So, what actually is LangChain?

Now we’re ready for the real definition, and it should actually make sense:

LangChain is a software framework — a library of pre-built, reusable Python components — specifically designed to handle the recurring plumbing of LLM applications: sending messages in a consistent format, managing conversation history, detecting and executing tool calls, retrieving documents, and chaining all of these steps together.

A quick, important word on “framework,” since we’ll use it constantly. A framework isn’t a single tool — it’s a whole set of tools, built to work together, that expects you to build your application using its own particular building blocks and conventions, rather than writing everything yourself from raw materials. That’s different from a plain library, which just gives you individual functions to call however you like. LangChain leans toward the framework side: it gives you standard building blocks — models, messages, tools, agents — that are all designed to click together the same way, every time.

It’s genuinely important to be precise about what LangChain does not do, too:

  • It does not make any model smarter or more capable.
  • It does not replace OpenAI’s or Google’s own servers — every request still travels to their computers, exactly as before.
  • It does not invent some new kind of AI. Every concept you already know from your earlier courses — tokens, context windows, tool calling, RAG — is still exactly what’s happening underneath.

What LangChain actually gives you is a shared, consistent shape for all of that plumbing, so you write it once and reuse it, instead of rebuilding it for every project and every provider.

flowchart LR
    A["Your own repeated,\nprovider-specific plumbing code"] -->|LangChain| B["One shared, consistent\nset of building blocks"]

Seeing it for real: the same conversation, the LangChain way

Let’s return to Problem 1 — remembering a conversation — and solve it one final time, now using LangChain. Watch closely for what disappears compared to our earlier, by-hand version.

from langchain.chat_models import init_chat_model
from langchain.messages import SystemMessage, HumanMessage

# this one line decides which provider we're actually talking to
model = init_chat_model("openai:gpt-4o-mini")
# model = init_chat_model("google_genai:gemini-2.0-flash")

messages = [
    SystemMessage(content="You are a friendly AI tutor."),
    HumanMessage(content="What is RAG?"),
]

first_reply = model.invoke(messages)
messages.append(first_reply)  # LangChain's own reply object — an AIMessage
messages.append(HumanMessage(content="Can you explain it like I'm 12?"))

second_reply = model.invoke(messages)
print(second_reply.content)

A few things are genuinely worth pausing on here:

  • init_chat_model("openai:gpt-4o-mini") replaces the entire, provider-specific setup code from both of our earlier examples. The text before the colon — openai or google_genai — is the only thing that changes if you switch providers. Everything below this line stays exactly the same either way.
  • SystemMessage and HumanMessage replace the plain Python dictionaries ({"role": "user", ...}) we typed by hand earlier. They do the exact same job — labeling who said what — but LangChain gives every provider the same object, instead of each one expecting its own particular format.
  • model.invoke(messages) is the one method you’ll call, over and over, for the rest of this entire course, regardless of which provider, which task, or which component you’re using. It always means the same thing: “run this input through, and give me the result.”

Change one string — swap "openai:gpt-4o-mini" for "google_genai:gemini-2.0-flash" — and this entire program keeps working, completely unchanged otherwise. That single, easy swap is a direct, working demonstration of the exact plumbing problem we spent this whole module uncovering, now solved.

This isn’t a toy problem — here’s what it looks like at real scale

Everything in this module so far has used small, deliberately simple examples. It’s worth seeing, briefly, that the exact same plumbing problem — and the exact same fix — shows up at genuinely enormous scale in real companies, not just in tutorials.

Klarna, the payments and shopping company, built an AI Assistant that now handles customer support for over 85 million active users, and has processed more than 2.5 million real conversations — work the company itself has described as equivalent to 700 full-time employees. Klarna’s CEO, Sebastian Siemiatkowski, put it directly: “LangChain has been a great partner in helping us realize our vision for an AI-powered assistant, scaling support and delivering superior customer experiences across the globe.”

Notice what Klarna actually needed, underneath the marketing language: a system that routes different kinds of requests (a refund, a payment question, an escalation) to the right handling logic, keeps track of an ongoing conversation across many turns, and calls real backend systems to actually resolve the customer’s issue — the exact “prompt → model → tool → model → respond” loop from this module’s diagram, just running at a scale of millions of real conversations instead of one. The plumbing problem you just watched break a two-line script is the same plumbing problem a fintech company serving 85 million people had to solve for real.

What you should genuinely take away from this module

Not the dictionary-style sentence from the very top of this page. Take away this instead, because you now understand why it’s true, not just that it’s true:

LangChain exists because real LLM applications are pipelines — models looping with tools, memory, and retrieval — not single API calls. Every developer who built one by hand kept rewriting the same underlying plumbing for every provider. LangChain is the shared, reusable set of building blocks that plumbing gets replaced with.

You already know Python. You already understand how LLMs, RAG, and agents work conceptually, from your earlier courses. What you’re about to learn, module by module, is exactly which LangChain building block replaces which specific piece of hand-written plumbing — and, just as importantly, when it’s genuinely worth using one.

A quick, honest note before you keep going

You’ll sometimes come across older tutorials online using things like LLMChain or an older-style AgentExecutor. Don’t copy those patterns into new work. LangChain went through a deliberate, major cleanup at version 1.0, and that older code has been moved into a separate langchain-classic package — kept available only so existing older projects don’t break. Every example in this course uses the current, officially recommended approach.

Where this goes next

In the next module, you’ll get a proper mental map of LangChain — the actual boxes and arrows that make up a real LangChain application, and exactly where LangChain’s own responsibility ends and its sibling project, LangGraph, takes over.

After that, you’ll set up a real project folder from scratch and make your very first genuine LangChain call.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed