skip to content

In LangChain, when do you use PromptTemplate versus ChatPromptTemplate?

level: juniorimportance: must knowfreq 78%

answer

  1. one renders text, one renders roles
  2. system, human, ai
  3. from_template versus from_messages
  4. chat models are the default target
  5. flattening a conversation loses tool metadata

basics

~20 s

PromptTemplate formats one string with declared variables and suits plain text-completion models. ChatPromptTemplate formats an ordered list of role-tagged messages (system, human, ai) and is what you use with chat models, which is nearly always today.

solid answer

~40 s

Both are prompt objects that declare their input variables instead of relying on ad-hoc string concatenation, but they produce different shapes. `PromptTemplate.from_template("Summarize: {doc}")` renders a single string — the right shape for a legacy text-completion model. `ChatPromptTemplate.from_messages([...])` renders a list of messages, each carrying a role: you pass `("system", "...")`, `("human", "{input}")`, `("ai", "...")` tuples, or message objects and placeholders. Because every mainstream provider now exposes a chat-completions surface, ChatPromptTemplate is the default choice; the roles are semantically meaningful to the model and to provider features like system-prompt caching. Practically, reach for PromptTemplate when you need a plain string — a sub-prompt embedded elsewhere, a text-model call, or a template you render yourself — and ChatPromptTemplate for anything you feed a chat model.

code

python · 11 lines
python
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate

text_prompt = PromptTemplate.from_template("Summarize: {doc}")
print(text_prompt.input_variables)      # ['doc']
print(text_prompt.format(doc="hello"))  # 'Summarize: hello'

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a terse assistant."),
    ("human", "Summarize: {doc}"),
])
print(chat_prompt.format_messages(doc="hello"))

go deeper

for a junior

Be able to say that PromptTemplate produces one string and ChatPromptTemplate produces a list of role-tagged messages, and show from_template and from_messages in code.

for a middle

Explain how from_messages interprets tuples versus concrete message objects, where input_variables come from, and why the system role is a distinct slot rather than a prefix.

for a senior

Show the production consequence: flattening a conversation into one string breaks tool-call round-tripping and provider-side handling of stable system content. Argue for chat prompts as the default in a real service.

for a principal

Own the convention across a codebase: one prompt module, chat prompts everywhere, prompts as reviewed and tested artifacts rather than inline strings, so prompt changes are diffable and attributable.

## Why prompts are objects at all The first thing a LangChain prompt buys you is a declared interface. A template names its variables, so the framework can tell you at format time that `{doc}` was never supplied, rather than silently sending a prompt with a hole in it. It also makes the prompt a value you can pass around, version, serialize, and compose — instead of an f-string buried three call sites deep. That is the whole pitch: templated prompts with declared variables versus string concatenation scattered across a codebase. ## PromptTemplate — one string out `PromptTemplate` renders a single string. ``` from langchain_core.prompts import PromptTemplate p = PromptTemplate.from_template("Summarize this in one line: {doc}") p.input_variables # ['doc'] p.format(doc="...") # -> str ``` `from_template` infers `input_variables` by parsing the template; the explicit constructor lets you pass `template`, `input_variables`, `partial_variables`, and `template_format`. Calling `.format(**kwargs)` returns the finished string, while `.invoke({...})` returns a `StringPromptValue` wrapper (see the PromptValue question in this topic). Text-completion endpoints are largely legacy, so a PromptTemplate today usually shows up as a building block: the body of one message, a piece of text you inject into a retrieval chain, or a template rendered for a non-LLM consumer. ## ChatPromptTemplate — a message list out `ChatPromptTemplate` renders an ordered `list[BaseMessage]`. ``` from langchain_core.prompts import ChatPromptTemplate chat = ChatPromptTemplate.from_messages([ ("system", "You are a terse assistant. Answer in one sentence."), ("human", "Summarize: {doc}"), ]) chat.format_messages(doc="...") # -> [SystemMessage(...), HumanMessage(...)] ``` `from_messages` accepts several element shapes: a `(role, template_string)` tuple where the role is `"system"`, `"human"`/`"user"`, `"ai"`/`"assistant"`; an already-built message object such as `SystemMessage("...")`, whose content is treated as literal, not templated; a message-prompt-template object; or a placeholder for a whole run of messages. Variables are collected across all message templates, so `input_variables` is the union. A detail worth knowing: a plain string element is interpreted as a human message template, and `ChatPromptTemplate.from_template("...")` builds a single-human-message chat prompt. That is convenient but it hides the role structure, so most production code spells the messages out. ## Why the distinction matters beyond formatting Roles are not decoration. Providers treat the system message differently — it is where instructions, persona, and safety framing belong, and some providers price or cache it separately from the turn-by-turn content. Chat models also expect prior turns as distinct assistant/user messages so tool calls and their results line up correctly. If you flatten a conversation into one giant string with `"User: ...\nAssistant: ..."` prefixes, you lose all of that: the model sees one blob of user text, tool-call metadata cannot round-trip, and the provider cannot separate stable instructions from volatile input. Conversely, forcing a chat prompt where a string is wanted is equally awkward. If you are producing text that is not a model call — a section of a larger prompt, a rendered report — `PromptTemplate` is the honest type. ## Interop Both types share a base class, so both expose `input_variables`, `partial_variables`, `.partial()`, `.format_prompt()`, `.invoke()`, and both are Runnables that can sit at the head of a chain. A chat model accepts either — hand it a string prompt value and it wraps the text in a single human message. A text model handed a chat prompt value flattens it via `to_string()`. So the wrong choice usually still runs; it just quietly loses structure, which is exactly the kind of defect interviewers probe for. ## Choosing Default to `ChatPromptTemplate` for every model call, and give it a real system message rather than smuggling instructions into the human turn. Use `PromptTemplate` for string-shaped work. If you are targeting both a chat and a text model from the same prompt, write the chat version and rely on `to_string()` rather than maintaining two templates that drift apart.

  • What happens if you pass a ChatPromptTemplate's output to a plain text-completion model?
    The chat prompt value is flattened with `to_string()`, which renders the messages as prefixed lines such as `System: ...` / `Human: ...`. It runs, but the role structure the model never sees is now just text, so any provider-side handling of system content is lost. If the target is genuinely a text model, write a `PromptTemplate` so the flattening is explicit and reviewable.
  • If you put an already-constructed SystemMessage into from_messages, are its braces templated?
    No. A concrete message object is inserted literally — its content is not parsed for variables, so braces inside it are safe. Only the string-based forms, such as `("system", "...")` tuples and message prompt templates, are treated as templates. That makes a real message object a quick way to include text containing braces without escaping.
  • How do you tell what variables a prompt expects before you run it?
    Read `prompt.input_variables`, which lists the names it still needs, and `prompt.partial_variables` for the ones already bound. For a chat prompt these are the union across all message templates. Checking that list in a test is a cheap guard against a renamed variable silently becoming a missing-key error at request time.

A PromptTemplate is a letter; a ChatPromptTemplate is a transcript with each speaker labelled.

saying these in an interview costs you the question

  • Claiming ChatPromptTemplate is only for multi-turn chat
  • Putting all instructions in the human message and leaving system empty
  • Treating role tuples as cosmetic string prefixes
  • Saying you must build the message list manually for chat models
  • Believing PromptTemplate can emit role-tagged messages

context