The Hidden Language of AI: How Chat Templates Reveal the Evolution of LLMs
Zhan Shi, Rui Sun, Bing He (The Prompt Architects)
Originally published on Medium, Nov 1, 2025. This is a mirror on the author’s personal site.
A journey through the overlooked architecture that shapes every conversation you have with ChatGPT, Claude, and beyond.
Here’s something most people don’t know: every time you chat with an AI, there’s an invisible formatting layer working behind the scenes. It’s not magic — it’s called a chat template, and it’s arguably one of the most overlooked yet critical pieces of how modern AI actually works.
Think about it. When you type “What’s the weather like?” into ChatGPT, the model doesn’t just see those four words. It sees something more like this:
<|im_start|>user
What's the weather like?<|im_end|>
<|im_start|>assistant
Weird, right? Those cryptic tags, the structured format — they’re the scaffolding that makes AI conversation possible. And the story of how we got here? It’s a fascinating mirror of AI’s entire evolution from dumb text predictors to the sophisticated assistants we use today.
Let me show you how a simple formatting convention became the backbone of the AI revolution.

Act I: The Wild West of Text Completion
When AI Was Just a Really Fancy Autocomplete (2019–2020)
Remember GPT-2? If you were following AI back in 2019, you might recall the headlines about it being “too dangerous to release” (spoiler: it wasn’t). But here’s what’s interesting — GPT-2 and early GPT-3 didn’t “chat.” They just… continued text.
You’d give them a prompt:
"The capital of France is"
And they’d complete it:
" Paris."
Simple, elegant, and completely useless for conversation.
Want to have a Q&A session? You had to get creative:
prompt = """Q: What is the tallest mountain?
A: Mount Everest
Q: What is the capital of France?
A:"""
See what developers were doing? They were faking structure using plain English. Writing “Q:” and “A:” to trick the model into thinking it was in a quiz format.
But this was brittle as hell. Sometimes the model would helpfully continue with another “Q:” instead of answering. Sometimes it would go off on a tangent. You were basically hoping the pattern in your prompt was strong enough to guide the completion.
The core problem? The model had no concept of:
- Who was talking (you or it)
- What was an instruction versus context
- When to stop being helpful and just… stop
It was like trying to have a conversation with someone who thinks you’re both just reading from the same script. Technically functional, but deeply weird.
Act II: The Breakthrough — Teaching AI to Take Turns
How InstructGPT Changed Everything (2021–2022)
Then came InstructGPT in January 2022, and suddenly everything clicked.
OpenAI had a realization: what if we explicitly taught the model about roles? What if instead of pretending everyone’s reading the same script, we labeled who’s speaking?
The innovation seems almost too simple:
"""User: What is the capital of France?
Assistant: The capital of France is Paris.
User: What about Germany?
Assistant:"""
That’s it. Just adding “User:” and “Assistant:” labels.
But this tiny change was revolutionary:
- ✓ The model finally understood who was talking
- ✓ It learned to “stay in character” as the assistant
- ✓ Multi-turn conversations became natural instead of hacky
- ✓ The model knew when to respond versus when to listen
This worked because of RLHF — Reinforcement Learning from Human Feedback. OpenAI trained the model with thousands of examples following this format, with human raters scoring the responses. The model learned: “When I see ‘User:’, that’s my cue to pay attention. When I see ‘Assistant:’, that’s me talking.”
The Secret Weapon: System Prompts
But the real game-changer came next — the system prompt.
Imagine you’re directing a play. The actors need to know their lines (the user messages) but they also need to understand their character and the rules of the scene. That’s what system prompts do:
messages = [
{
"role": "system",
"content": "You are a helpful assistant that speaks like a pirate."
},
{
"role": "user",
"content": "What is the capital of France?"
},
{
"role": "assistant",
"content": "Arr matey! The capital of France be Paris!"
}
]
With this three-role structure (system/user/assistant), developers suddenly had unprecedented control:
- Set the personality (“You are a expert Python programmer”)
- Define boundaries (“Never provide medical advice”)
- Control tone (“Be concise and professional”)
- Establish expertise (“You specialize in 18th-century literature”)
The system prompt became the instruction manual that governed every conversation. It’s why ChatGPT sounds helpful, why Claude is thoughtful, why different AI assistants have distinct personalities despite using similar underlying tech.
This wasn’t just a formatting change. It was the moment AI went from “fancy autocomplete” to “conversational agent.”
Act III: The Template Wars
ChatGPT Sets the Standard (November 2022)
When ChatGPT dropped in November 2022, it didn’t just go viral — it established a de facto standard for how chat templates should work.
OpenAI’s API used clean, elegant JSON:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi! How can I help you today?"},
{"role": "user", "content": "What's the weather like?"}
]
Beautiful, right? Easy to read, easy to use.
But here’s the thing nobody tells you: behind the scenes, this gets converted into something completely different:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
Hello!<|im_end|>
<|im_start|>assistant
Hi! How can I help you today?<|im_end|>
<|im_start|>user
What's the weather like?<|im_end|>
<|im_start|>assistant
Those <|im_start|> and <|im_end|> tokens? They’re special markers that tell the model “here’s where a message begins, here’s where it ends.” The model was literally trained to recognize these specific strings.
Then Open Source Happened
Once Llama, Mistral, and dozens of other open-source models hit the scene, all hell broke loose. Every model decided to do templates slightly differently.
Llama 2 went with:
<s>[INST] <<SYS>>
You are a helpful assistant.
<</SYS>>
What is the capital of France? [/INST]
The capital of France is Paris. </s>
Mistral preferred:
<s>[INST] What is the capital of France? [/INST]
The capital of France is Paris.</s>
Vicuna kept it simple:
USER: What is the capital of France?
ASSISTANT: The capital of France is Paris.</s>
Qwen copied ChatGPT’s homework:
<|im_start|>user
What is the capital of France?<|im_end|>
<|im_start|>assistant
Why the chaos? A few reasons:
- Tokenization efficiency matters. Some tokenizers handle certain formats better. If your special tokens are tokenized efficiently (as single tokens rather than multiple), you save compute and context window space.
- Training data is king. Each model was trained on data formatted a specific way. Use the wrong format at inference time, and the model gets confused. It’s like speaking English to someone who only learned French.
- Legacy is sticky. Once you commit to a format and train a billion-dollar model on it, you’re kinda stuck with it.
- Everyone thinks they’re special. Let’s be real — every AI lab wanted their own special sauce.
The Solution: Jinja2 to the Rescue
This mess became untenable. Imagine being a developer trying to support 20 different models with 20 different templates.
HuggingFace, bless them, came up with a clever solution: standardized chat templates using Jinja2 (a templating language from web development).
# Each model defines its own template once
tokenizer.chat_template = """
{% for message in messages %}
{% if message['role'] == 'system' %}
<|im_start|>system\n{{ message['content'] }}<|im_end|>\n
{% elif message['role'] == 'user' %}
<|im_start|>user\n{{ message['content'] }}<|im_end|>\n
{% elif message['role'] == 'assistant' %}
<|im_start|>assistant\n{{ message['content'] }}<|im_end|>\n
{% endif %}
{% endfor %}
"""
# Developers use one simple interface
formatted = tokenizer.apply_chat_template(messages, tokenize=False)
Now you could write your code once using the standard message format, and the tokenizer would handle the conversion to whatever weird format that specific model needed.
Brilliant. Problem solved. Mostly.
Act IV: When Chat Became Action
The Agent Revolution (2023–2024)
Just when we thought chat templates were settled, AI went and evolved again.
The new challenge? AI assistants needed to DO things, not just TALK about things.
Think about it: What’s more useful — an AI that tells you “It’s probably sunny in Paris” or one that actually checks the weather API and gives you real data?
This required a fundamental rethinking of chat templates. We needed to add a new role: tools.
OpenAI’s Function Calling
OpenAI’s solution looked like this:
messages = [
{
"role": "system",
"content": "You are a helpful assistant with access to functions."
},
{
"role": "user",
"content": "What's the weather in Paris?"
},
{
"role": "assistant",
"content": None,
"function_call": {
"name": "get_weather",
"arguments": '{"location": "Paris"}'
}
},
{
"role": "function",
"name": "get_weather",
"content": '{"temperature": 18, "condition": "sunny"}'
},
{
"role": "assistant",
"content": "The weather in Paris is sunny with a temperature of 18°C."
}
]
See what’s happening? The conversation now has a new flow:
- User asks a question
- AI realizes it needs real data
- AI calls a function (not just talks about calling it)
- Function returns results
- AI incorporates those results into its answer
This is agentic behavior — the AI is taking action, not just generating text.
But Wait, Every Company Did It Differently
Of course they did.
Anthropic (Claude) went XML:
<function_calls>
<invoke>
<tool_name>get_weather</tool_name>
<parameters>
<location>Paris</location>
</parameters>
</invoke>
</function_calls>
Llama 3.1 used JSON-in-tags:
<|start_header_id|>assistant<|end_header_id|>
<function=get_weather>{"location": "Paris"}</function><|eot_id|>
Different formats, but same core idea: the chat template now needed to support a request-response cycle with external tools.
Multi-Modal: Images Enter the Chat
Then vision models arrived, and templates evolved again:
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url", "image_url": {"url": "https://..."}}
]
},
{
"role": "assistant",
"content": "I can see a cat sitting on a windowsill."
}
]
Now content wasn’t just a string — it could be an array of different media types. The template had to be flexible enough to handle text, images, and eventually audio and video.
The Full Agent Experience
Modern templates now support genuinely complex workflows:
messages = [
{
"role": "user",
"content": "Book me a flight to Paris next week"
},
{
"role": "assistant",
"content": "I'll help you with that. Let me check available flights.",
"tool_calls": [{"function": {"name": "search_flights", ...}}]
},
{
"role": "tool",
"content": "[flight options data]"
},
{
"role": "assistant",
"content": "I found several options. Let me check your calendar.",
"tool_calls": [{"function": {"name": "check_calendar", ...}}]
},
{
"role": "tool",
"content": "[calendar data]"
},
{
"role": "assistant",
"content": "Based on your schedule, I recommend..."
}
]
The AI is now:
- Making multiple tool calls
- Tracking which tool response matches which request
- Reasoning across multiple steps
- Maintaining context through complex interactions
This is no longer a chat template. It’s an execution trace.
We’ve gone from “the AI continues your sentence” to “the AI is a autonomous agent that plans, acts, and adapts.”
And it all happened through the evolution of these formatting conventions.
What This All Means: The Technical Reality
Why Getting Templates Wrong Breaks Everything
Here’s the dirty secret: if you use the wrong chat template, your AI will become noticeably dumber.
I’m serious. Use Llama’s [INST] tags with a model trained on <|im_start|> format? Your model will:
- Give worse answers
- Ignore instructions
- Hallucinate more
- Generally act confused
Why? Because the model was trained on millions of examples with a specific format. It learned to recognize those exact patterns. Change the pattern, and you’re essentially speaking a different dialect the model barely understands.
This is the #1 rule: your inference template must exactly match training.
The Token Economics Nobody Talks About
Let’s talk money. Every token you send costs compute. And template formatting? That’s tokens too.
Compare these:
# Inefficient approach
"User: " # → tokenizes to 3 separate tokens
# Efficient approach
"<|im_start|>user\n" # → one special token + efficiency gains
When you’re processing millions of conversations, this adds up. Meta didn’t add [INST] tags to Llama just for fun — they’re optimized in the tokenizer as special tokens for efficiency.
This is why companies obsess over template design. It’s not pedantry — it’s economics.
Security: Prompt Injection is Real
Bad template design makes prompt injection easier. Compare:
# Vulnerable
f"User: {user_input}\nAssistant:"
# User input: "Ignore previous instructions and..."
# Result: Model might actually listen
# More secure
f"<|im_start|>user\n{user_input}<|im_end|>\n<|im_start|>assistant\n"
# The special tokens make "escaping" harder
Those weird special tokens aren’t just formatting — they’re security boundaries. They make it harder for malicious users to inject commands that the model will interpret as system-level instructions.
The Consistency Principle
Everything breaks down to one rule:
Training format = Inference format
Mismatches cause:
- ✗ Response quality drops
- ✗ Instruction following degrades
- ✗ Increased hallucinations
- ✗ Unpredictable behavior
It’s like training someone to respond to a doorbell, then being surprised when they don’t react to a knock.
Practical Advice (Because Theory is Useless Without Practice)
If You’re Using Models
DO:
- ✓ Use the official chat template provided with the model
- ✓ Use
tokenizer.apply_chat_template()when available - ✓ Read the model card — it usually explains the format
- ✓ Test small changes carefully
DON’T:
- ✗ Modify templates unless you’re fine-tuning
- ✗ Assume all models use the same format
- ✗ Mix templates from different models
- ✗ Ignore the documentation
If You’re Building Models
DO:
- ✓ Document your template clearly (please!)
- ✓ Provide Jinja2 template in tokenizer config
- ✓ Include examples in your model card
- ✓ Consider tokenization efficiency early
- ✓ Plan for extensibility (tools, multi-modal, etc.)
DON’T:
- ✗ Create yet another format “because you can”
- ✗ Change formats between model versions unnecessarily
- ✗ Forget backwards compatibility
- ✗ Ignore established standards without good reason
The Bigger Picture
Here’s what fascinates me about this whole story: the evolution of chat templates perfectly mirrors the evolution of our expectations for AI.
In 2019, we were thrilled when GPT-2 could complete sentences coherently. The idea that it needed special formatting for “conversations” seemed absurd — it was just predicting text!
By 2022, we expected AI to actually converse — to understand context, remember what we said, respond appropriately. The three-role system (system/user/assistant) emerged to meet that expectation.
By 2024, we wanted AI to be agents — to use tools, check real data, take actions in the world. Templates evolved to support function calling, multi-modal inputs, and complex workflows.
The template is where theory meets practice. It’s the actual, concrete specification of how we think AI should work. And watching it evolve tells us exactly how quickly our ambitions for AI are growing.
What’s Next?
If I had to bet on the future of chat templates, here’s what I’d predict:
- Multi-agent conversations will become standard. Templates will need to handle not just user ↔ AI, but AI ↔ AI, with multiple models collaborating on tasks.
- Longer context windows will make conversation structure more important, not less. When you’re managing 1M+ token conversations, clear formatting becomes critical for the model to track what’s happening.
- Standardization will (maybe) happen. The industry is chaotic now, but there’s growing pressure for interoperability. Maybe we’ll see an HTTP-for-AI emerge — a universal protocol that everyone actually uses.
- Richer modalities will push templates further. Audio, video, real-time streams — each will require new formatting conventions.
The Hidden Architecture
Next time you chat with ChatGPT or Claude, remember: there’s an invisible architecture working behind the scenes. Those special tokens, those role labels, that careful formatting — they’re not accidents. They’re the carefully designed interface between human thought and machine intelligence.
Chat templates might seem like a boring implementation detail. But they’re actually one of the most revealing windows into how we’re teaching machines to think, act, and ultimately, to be useful.
And that story? That story is just getting started.
Reference
Originally published: Zhan Shi, Rui Sun, Bing He, “The Hidden Language of AI: How Chat Templates Reveal the Evolution of LLMs”, Medium, Nov 1, 2025.
