Lesson 1 of 714 min2 challenges

What an LLM App Actually Does

The model is stateless, the three message roles, how tokens are billed, and what LangChain is really for.

Before you start: this track assumes you're comfortable with Python basics — variables, functions, dictionaries, exceptions. If any of those are shaky, go do the first 7 lessons of the Python track first. You'll have a much better time here.

What an LLM app actually does

What people imagine an AI app is: a very smart model.

What an AI app actually is: a pipeline that gathers material, hands it to a model, and turns the model's reply back into data your program can use.

The model itself is a black box behind an HTTP endpoint. You send text, it sends text back. That's the whole interface. Every product that feels clever is clever outside the box — in how it assembles context, constrains the output, connects tools, and handles failure.

That's good news for you. The part you control is the part that's engineering.

Before LangChain, look at the raw shape

Here's a full LLM app. No framework.

from openai import OpenAI

client = OpenAI(api_key="sk-...")   # reads from OPENAI_API_KEY if you omit it

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "You are a terse assistant. Answer in two sentences or fewer."},
        {"role": "user", "content": "What is an API?"},
    ],
)
print(resp.choices[0].message.content)

Running this prints a short answer, something like: "An API is a defined way for one program to ask another program to do something. It's a contract: send this shape of request, get that shape of response."

Take a second to notice what's in that call. A model name. A list of dictionaries. That's it. Everything else in this track is about what goes into that list.

The three roles, and what each is for:

  • system — the persona and the rules. Highest priority, and the user never sees it.
  • user — what the person typed.
  • assistant — what the model said earlier. You include these when you want a multi-turn conversation.

The key fact: the model has no memory

Every call starts from nothing. The server keeps no record of your last request. So how do chat apps "remember"?

They resend everything.

messages = [
    {"role": "system", "content": "You are an assistant."},
    {"role": "user", "content": "My name is Maya."},
    {"role": "assistant", "content": "Nice to meet you, Maya."},
    {"role": "user", "content": "What's my name?"},   # only answerable because of the two lines above
]

Send that list and the model replies "Maya." Delete the middle two dictionaries and send it again — now it says it doesn't know. Same model, same key, different context.

So "memory features" are, without exception, code that manages this list for you. Hold on to that sentence. When you reach the Memory lesson in this track, it will save you from thinking there's something mysterious going on.

Tokens and what they cost

Models bill by token, not by character or word. A token is a chunk of text — roughly a common word, or a piece of a longer one. Rough rule of thumb: one English word is about 1.3 tokens.

Input and output are priced separately, and output is usually several times more expensive.

Two engineering rules fall straight out of that:

  1. Don't dump everything into the context "just in case." It costs more, it's slower, and — this is the part people miss — it makes quality worse. A model given ten paragraphs where one was relevant has to find the signal, and sometimes it doesn't.
  2. Context has a hard ceiling. Every model has a maximum context length. A long enough conversation will hit it and the API will return an error. You need a plan for trimming before that day arrives.

So what is LangChain for?

That raw openai snippet above already works. Why add a framework?

Because in a real app you hit the same handful of chores over and over:

  • Switching model providers means rewriting your call code.
  • Interpolating variables into prompts, safely.
  • Turning a prose reply into JSON your code can trust.
  • Keeping conversation history per user.
  • Chopping documents into pieces and searching them.
  • Letting the model call your functions.

LangChain packages these as standard parts with a shared interface, and you snap them together. The real payoff isn't any single part — it's that once two things share an interface, you can compose them, and things like streaming and batching come along for free. You'll see that concretely in the next lesson.

The cost is a layer of abstraction between you and the HTTP call. So the right mindset while learning is: remember that underneath it is the snippet above. When the abstraction leaks — and it will — you'll know where to look.

Setting up

pip install langchain langchain-openai python-dotenv

Put your API key in a .env file. Never hardcode it in source. Push a key to GitHub and it's public: there are bots that scan commits for keys within minutes.

OPENAI_API_KEY=sk-xxxx
from dotenv import load_dotenv
load_dotenv()        # loads .env into environment variables

load_dotenv() returns True if it found a file. If your very first API call fails with an auth error, print that return value — nine times out of ten the .env is in a different directory than you thought.

Add .env to your .gitignore right now, before you forget.

Don't have a key yet? You can still work through most of this track. Keep the model call behind a small function, and you can swap in a fake while you develop:

def ask(messages):
    return "FAKE REPLY"      # swap for the real call once you have a key

print(ask([{"role": "user", "content": "hi"}]))   # -> FAKE REPLY

Structuring your code this way isn't a learning crutch — it's how you make an LLM app testable. Tests that hit a live model are slow, flaky, and cost money.

A word about versions

LangChain moves fast, and a large share of tutorials online are written against old versions. This track uses the LCEL style — the one where you join components with |.

If you find code containing LLMChain(...), initialize_agent(...), or ConversationBufferMemory(...), that's an older interface. Don't copy it.

More generally: names in this ecosystem move between packages. When a symbol in these lessons doesn't import, don't assume it's gone — check the current docs for where it lives now. That's a normal part of working here, not a sign you did something wrong.

Ten-second check

Why can a model "remember" what you said a moment ago?

Because your code sent the whole history again. The model is stateless; memory is something the application layer builds.

Ad slot (AdSense not configured; ads appear here in production)

Hands-on

Reading it isn't knowing it. Writing it is.

0/2 passed

Challenges and grading are completely free, and a wrong answer costs you nothing. Stuck? Hit "Ask the AI": it can see the code you are writing.

Level 01Multiple choice+10 pts · +15 XP

Where does the model’s "memory" come from?

Why can a model "remember" what you said in the previous turn?

  • Might be multiple choice. Wrong answers cost nothing, so just try.
Level 02Multiple choice+10 pts · +15 XP

What is the system role for?

Which statements about the three message roles are correct? (Select all that apply.)

  • Might be multiple choice. Wrong answers cost nothing, so just try.

Finished? Mark it

Marking it done saves your progress, gives you 20 XP and keeps your streak alive.