Prompts & Templates
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page’s examples target Python 3.12 with LangChain 1.4.3, langchain-core 1.6.5, langchain-classic 1.0.8 and
langsmith 0.14.1, as checked on 2026-09-29, using DocsAssistant (the retrieval-augmented support assistant built
on the Vector Databases & RAG pages, with Ollama llama3.1:8b and
nomic-embed-text). The snippets were executed against the real packages with a fake chat model
(GenericFakeChatModel) because no Ollama server was available, so the model outputs shown are canned; the
rendered prompts, which are the subject of this page, are real.
A prompt template turns a fixed piece of instruction text plus a handful of variables into the list of messages a chat model receives. Writing good prompt text is covered in Prompt Engineering Fundamentals and Prompting Techniques; this page is about the LangChain objects that hold that text, fill it in, and let it be versioned. Templates are runnables, so the result plugs straight into a chain (see LCEL & Runnables) and the messages they produce are the ones described in Chat Models & Messages.
system + placeholder + human"] -->|"partial(product=...)"| P["Partially filled template"] E["Example selector
few-shot examples"] --> T P -->|"invoke({context, question, history})"| M["Messages"] M --> LLM["Chat model"] T -->|"push_prompt"| H["LangSmith prompt hub
commits and tags"] H -->|"pull_prompt('name:production')"| T
ChatPromptTemplate
ChatPromptTemplate builds a list of messages from (role, template) pairs. Variables are written {name}
(Python f-string syntax by default), and invoke returns a ChatPromptValue holding the messages. The template
below is DocsAssistant’s answer prompt:
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are DocsAssistant for the {product} docs. Answer only from the context."),
MessagesPlaceholder("history", optional=True),
("human", "Context:\n{context}\n\nQuestion: {question}"),
])
print(prompt.input_variables, prompt.optional_variables)
value = prompt.invoke({
"product": "vector-rag",
"context": "HNSW has ef_search.",
"question": "What is ef_search?",
"history": [HumanMessage("hi"), AIMessage("hello")],
})
for m in value.to_messages():
print(type(m).__name__, repr(m.content))
Output:
['context', 'product', 'question'] ['history']
SystemMessage 'You are DocsAssistant for the vector-rag docs. Answer only from the context.'
HumanMessage 'hi'
AIMessage 'hello'
HumanMessage 'Context:\nHNSW has ef_search.\n\nQuestion: What is ef_search?'
See ChatPromptTemplate in the API reference.
A few behaviours are worth knowing:
-
Roles. The first element of each pair is a role such as
"system","human"or"ai"; a message object can be used instead of a tuple when the content needs no variables. -
Missing variables fail loudly. Invoking the template without
productandcontextraises aKeyErrorwhose message reads "Input to ChatPromptTemplate is missing variables" and lists the expected names, which is exactly what a broken call site should do. -
from_template.ChatPromptTemplate.from_template("Summarise {text}")is the shortcut for a single human message, useful for one-shot chains. -
Template formats. The default is f-string; passing
template_format="mustache"switches to{{name}}placeholders, which is the other format LangSmith supports for prompts stored in the hub (see LangSmith — Prompt template format). Because f-string templates treat literal braces as variables, JSON examples inside a prompt need doubled braces ({{and}}), or the mustache format.
MessagesPlaceholder and conversation history
MessagesPlaceholder reserves a slot for a list of messages supplied at call time. It is how a template carries
chat history without turning it into one big string. The slot is required by default; optional=True lets the
caller omit it, as the first turn of a conversation does. n_messages keeps only the last few messages, and the
tuple shorthand ("placeholder", "{history}") is equivalent to an optional placeholder.
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# no history supplied: the placeholder is simply skipped
print(len(prompt.invoke({"product": "x", "context": "c", "question": "q"}).to_messages()))
# keep only the last two history messages
last2 = ChatPromptTemplate.from_messages(
[("system", "s"), MessagesPlaceholder("history", n_messages=2), ("human", "{q}")]
)
hist = [HumanMessage(str(i)) for i in range(5)]
print([m.content for m in last2.invoke({"history": hist, "q": "q"}).to_messages()])
# tuple shorthand for an optional placeholder
short = ChatPromptTemplate.from_messages([("system", "s"), ("placeholder", "{history}"), ("human", "{q}")])
print(short.optional_variables)
Output:
2
['s', '3', '4', 'q']
['history']
See MessagesPlaceholder in the API reference.
n_messages is a blunt cap. Production chat history is trimmed by token budget, filtered by type and summarised
rather than cut by count, and it is persisted per user thread, not passed in by hand; those concerns belong to
Short-Term Memory & Checkpointers. In an agent
created with create_agent, the system prompt and the message history are handled by the agent itself (see
Agents with create_agent), so templates matter most in
fixed-shape chains such as classification, extraction and single-shot generation.
Partial variables
Some values are known before the rest: the product a deployment serves, the current date, the tone. partial
fixes them and returns a template that only asks for the remaining variables. A partial value can also be a
zero-argument callable, which is evaluated on every render, so a date is never stale.
from datetime import date
from langchain_core.prompts import ChatPromptTemplate
p2 = prompt.partial(product="vector-rag")
print(p2.input_variables)
p3 = ChatPromptTemplate.from_messages([
("system", "Today is {today}. Product {product}."),
("human", "{question}"),
]).partial(today=lambda: date.today().isoformat())
print(p3.invoke({"product": "p", "question": "q"}).to_messages()[0].content)
Output:
['context', 'question']
Today is 2026-09-29. Product p.
See ChatPromptTemplate in the API reference. The partially filled template is what DocsAssistant wires into its chain, together with a model and an output parser:
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import StrOutputParser
model = GenericFakeChatModel(messages=iter([AIMessage("ef_search controls the HNSW query beam width.")]))
chain = prompt.partial(product="vector-rag") | model | StrOutputParser()
print(chain.invoke({"context": "ef_search is the query-time candidate list size.",
"question": "What is ef_search?"}))
Output (canned by the fake model):
ef_search controls the HNSW query beam width.
Swap the fake model for a real one from init_chat_model (see
Chat Models & Messages) and nothing else changes. How the
retrieved chunks are formatted into context is covered in
Prompt Augmentation & Generation. See
LangChain reference — RunnableSequence.
Few-shot templates
A few-shot prompt shows the model worked examples before the real input. For chat models the natural form is
message pairs, which FewShotChatMessagePromptTemplate renders from a list of example dictionaries and an
example_prompt. Here DocsAssistant classifies a question into the documentation component it belongs to:
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
examples = [
{"question": "How do I tune HNSW recall?", "label": "vector-rag"},
{"question": "How do I add a Spring AI ChatClient?", "label": "spring-ai"},
]
example_prompt = ChatPromptTemplate.from_messages([("human", "{question}"), ("ai", "{label}")])
few = FewShotChatMessagePromptTemplate(examples=examples, example_prompt=example_prompt)
final = ChatPromptTemplate.from_messages([
("system", "Classify the question into a docs component. Reply with the label only."),
few,
("human", "{question}"),
])
for m in final.invoke({"question": "What is ef_search?"}).to_messages():
print(type(m).__name__, m.content)
Output:
SystemMessage Classify the question into a docs component. Reply with the label only.
HumanMessage How do I tune HNSW recall?
AIMessage vector-rag
HumanMessage How do I add a Spring AI ChatClient?
AIMessage spring-ai
HumanMessage What is ef_search?
See FewShotChatMessagePromptTemplate in the API reference.
Static examples are appended for every request. When the example pool is large, an example selector picks the
most relevant few per request instead. SemanticSimilarityExampleSelector (from langchain_core.example_selectors)
embeds the examples into a vector store and returns the nearest ones to the input; pass it as example_selector
in place of examples, and declare input_variables so the template knows what to feed it.
from langchain_core.embeddings import Embeddings
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.vectorstores import InMemoryVectorStore
class WordEmbeddings(Embeddings):
"""Tiny bag-of-words stand-in for nomic-embed-text, so the snippet runs offline."""
vocab = ["hnsw", "recall", "spring", "chatclient", "ef_search", "tune"]
def embed_documents(self, texts):
return [self.embed_query(t) for t in texts]
def embed_query(self, text):
words = text.lower().replace("?", "").split()
return [float(w in words) + 0.01 for w in self.vocab]
selector = SemanticSimilarityExampleSelector.from_examples(
examples, WordEmbeddings(), InMemoryVectorStore, k=1
)
few2 = FewShotChatMessagePromptTemplate(
example_selector=selector, example_prompt=example_prompt, input_variables=["question"]
)
final2 = ChatPromptTemplate.from_messages([("system", "Classify."), few2, ("human", "{question}")])
for m in final2.invoke({"question": "How do I tune HNSW recall?"}).to_messages():
print(type(m).__name__, m.content)
Output:
SystemMessage Classify.
HumanMessage How do I tune HNSW recall?
AIMessage vector-rag
HumanMessage How do I tune HNSW recall?
See Example selectors in the API
reference. InMemoryVectorStore needs numpy installed for its similarity search; DocsAssistant would use the
pgvector store and OllamaEmbeddings with nomic-embed-text instead, as shown in
Integrating with LangChain. How many examples help, and
how to choose and order them, is covered in
Prompting Techniques.
Prompts that carry an output schema
StructuredPrompt (in langchain_core.prompts.structured) is a template that also carries a JSON schema, built
with StructuredPrompt.from_messages_and_schema. LangSmith documents it for storing a template and its output
shape together in the prompt hub. It renders like any other chat prompt; the schema travels with it so that the
model call can be bound to it. Structured output itself is covered in
Structured Output.
from langchain_core.prompts.structured import StructuredPrompt
sp = StructuredPrompt.from_messages_and_schema(
[("system", "Extract metadata."), ("human", "{page_text}")],
schema={"title": "PageMetadata", "type": "object",
"properties": {"component": {"type": "string"}}, "required": ["component"]},
)
print(type(sp).__name__, sp.input_variables)
Output:
StructuredPrompt ['page_text']
See LangSmith — Manage prompts programmatically
for how a StructuredPrompt is pushed to the hub.
Prompts as versioned code
A template is an ordinary object, so it can be tested and versioned like any other code. The simplest discipline
needs no service: keep the templates in a module (or a prompts/ directory) under version control, review changes
as pull requests, and pin their rendered output with a test that needs no model call. This is the
golden-set idea from Managing Prompts applied to a LangChain
template.
from langchain_core.load import dumpd
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "You are DocsAssistant for the {product} docs."),
("human", "{question}"),
])
def test_prompt_mentions_product_and_question():
text = prompt.invoke({"product": "vector-rag", "question": "What is ef_search?"}).to_string()
assert "vector-rag docs" in text and "What is ef_search?" in text
test_prompt_mentions_product_and_question()
manifest = dumpd(prompt)
print(manifest["id"], manifest["kwargs"]["input_variables"])
print(manifest["kwargs"]["messages"][0]["id"][-1])
Output:
['langchain', 'prompts', 'chat', 'ChatPromptTemplate'] ['product', 'question']
SystemMessagePromptTemplate
See langchain_core.prompts in the API reference.
dumpd produces the JSON manifest that the hub stores; the same shape appears in LangSmith’s own documentation
of prompt-commit webhooks.
The LangSmith prompt hub
When prompts are edited by more people than can review a code change, or should change without a deploy, LangSmith stores them as versioned prompt commits in a workspace, the "prompt hub". The relevant ideas, from the LangSmith — Manage prompts page:
-
Commits. Every save creates a new commit with a hash, and two commits can be compared as a diff.
-
Commit tags. A tag such as
prod-2026-09points to exactly one commit and can be moved to another. Code that pulls by tag picks up a new version without a code change. -
Environments.
stagingandproductionare reserved tags managed through the promotion flow in the UI, with a rollback history per environment. -
Webhooks. A prompt commit can trigger a webhook, which is how a team mirrors prompts into Git (see LangSmith — Sync prompts with GitHub).
In Python, langsmith.Client is the full-featured entry point. push_prompt creates or updates a prompt and
returns its URL; pull_prompt returns it as a LangChain template, optionally with the model it was stored with
(include_model=True), and accepts a commit hash or tag after a colon. The API key comes from the
LANGSMITH_API_KEY environment variable and is never written in code.
from langsmith import Client
client = Client() # reads LANGSMITH_API_KEY from the environment
url = client.push_prompt("docsassistant-answer", object=prompt) # creates a new commit; returns a URL
latest = client.pull_prompt("docsassistant-answer") # newest commit
pinned = client.pull_prompt("docsassistant-answer:production") # the commit the tag points to
exact = client.pull_prompt("docsassistant-answer:12344e88") # a specific commit hash
chain = client.pull_prompt("docsassistant-answer-with-model", include_model=True) # prompt | model
These calls need a LangSmith account and were not executed for this page (there was no API key or network access
to a workspace); the method names and keyword arguments were confirmed against the installed langsmith 0.14.1
signatures and the documentation page below. See
LangSmith — Manage prompts programmatically.
Operational notes that follow from the same page:
-
Pin in production. Pull by tag or hash, not by "latest", so a hub edit cannot silently change production behaviour. Moving the tag is then an explicit, reversible release step.
-
Cache and skip the cache. The client caches pulled prompts; a pull can bypass the cache with
skip_cache=True, useful right after an edit in the UI. -
Public prompts are third-party input. A prompt pulled from another author’s handle (
owner/name) is text you did not review that will be sent to your model, and the manifest can embed a model configuration. The installedlangsmithclient therefore refuses such a pull unless you passdangerously_pull_public_prompt=True. Read the prompt first, pin a commit, and prefer prompts your own workspace owns. -
Stored models and secrets. A pushed chain stores model settings; API keys are referenced by environment variable name, not stored as values.
-
The old
hub. Code written for LangChain 0.x usedlangchain.hub; in LangChain 1.x it lives inlangchain_classic(from langchain_classic import hub), where itspullandpushare marked deprecated in favour of the LangSmith SDK, and it is not part of thelangchainpackage proper; see the migration digest in Ecosystem & Packages.
The hub is one option among several; the trade-offs against prompts in Git, and where evaluation fits, are discussed in Managing Prompts. Running a golden set against a stored prompt on every commit is what turns the hub from storage into a release process (see Evaluation), and tracing shows which prompt version produced a given run (see Observability with LangSmith). Where prompt text ends and context assembly begins is the subject of Context Engineering for Agents; the rest of this section is reachable from LangChain.