AI Systemsintermediate
PromptTemplate and ChatPromptTemplate
Master LangChain prompt templates: PromptTemplate vs ChatPromptTemplate, partial variables, template composition, format instructions, and prompt version control.
Asma Hafeez KhanMay 16, 20265 min read
LangChainPromptTemplateChatPromptTemplatePromptsTemplates
Two Types of Prompts
LangChain has two prompt classes for two model paradigms:
- PromptTemplate ā for completion/text models (legacy LLMs, text-in text-out)
- ChatPromptTemplate ā for chat models (message-in message-out, the modern standard)
In practice, you'll use ChatPromptTemplate almost exclusively since GPT-4o, Claude, and Gemini are all chat models.
Python
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
# PromptTemplate: single string with {variables}
text_prompt = PromptTemplate(
input_variables=["drug", "condition"],
template="What is the dose of {drug} for {condition}?",
)
formatted_text = text_prompt.format(drug="warfarin", condition="AFib")
# "What is the dose of warfarin for AFib?"
# ChatPromptTemplate: list of (role, content) tuples
chat_prompt = ChatPromptTemplate.from_messages([
("system", "You are a clinical pharmacist."),
("human", "What is the dose of {drug} for {condition}?"),
])
formatted_chat = chat_prompt.format_messages(drug="warfarin", condition="AFib")
# [SystemMessage(content="You are..."), HumanMessage(content="What is the dose...")]ChatPromptTemplate Construction Patterns
Python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
# Pattern 1: from_messages with tuples (most common)
prompt_a = ChatPromptTemplate.from_messages([
("system", "You are a {role}."),
("human", "{question}"),
])
# Pattern 2: from_messages with Message objects
prompt_b = ChatPromptTemplate.from_messages([
SystemMessage(content="You are a clinical pharmacist."),
HumanMessage(content="Explain {drug}."),
])
# Pattern 3: from_template (single human message)
prompt_c = ChatPromptTemplate.from_template("Explain {drug} in simple terms.")
# Equivalent to: ChatPromptTemplate.from_messages([("human", "Explain {drug}...")])
# Pattern 4: from_messages with placeholder (for chat history)
prompt_d = ChatPromptTemplate.from_messages([
("system", "You are a clinical pharmacist."),
("placeholder", "{chat_history}"), # Insert list of messages here
("human", "{question}"),
])
# Used in conversational chains ā chat_history expands to a list of messages
# Pattern 5: with few-shot examples
prompt_e = ChatPromptTemplate.from_messages([
("system", "Answer drug questions precisely."),
("human", "What is the mechanism of aspirin?"),
("ai", "Aspirin inhibits COX-1 and COX-2 enzymes, reducing prostaglandin synthesis."),
("human", "What is the mechanism of {drug}?"), # Template variable in last message
])Input Variables and Validation
Python
prompt = ChatPromptTemplate.from_messages([
("system", "You are a {specialty} specialist."),
("human", "Patient: {patient_age} years old. Drug: {drug}. {question}"),
])
# Check required variables
print(prompt.input_variables)
# ["specialty", "patient_age", "drug", "question"]
# LangChain raises if required variables are missing
try:
prompt.format_messages(drug="warfarin", question="dosing?")
# Raises: KeyError: 'specialty' and 'patient_age'
except Exception as e:
print(f"Error: {e}")
# Correct invocation
messages = prompt.format_messages(
specialty="clinical pharmacology",
patient_age=72,
drug="warfarin",
question="What is the appropriate starting dose?",
)Partial Variables
"Bake in" some variables upfront, leaving others for runtime:
Python
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
model = ChatOpenAI(model="gpt-4o", temperature=0)
base_prompt = ChatPromptTemplate.from_messages([
("system", "You are a {specialty} specialist. Today is {date}."),
("human", "{question}"),
])
# Partial: fix specialty and date, leave question for runtime
import datetime
clinical_pharmacist_prompt = base_prompt.partial(
specialty="clinical pharmacist",
date=datetime.date.today().isoformat(),
)
# Now only needs "question"
print(clinical_pharmacist_prompt.input_variables) # ["question"]
chain = clinical_pharmacist_prompt | model | parser
result = chain.invoke({"question": "What is the warfarin dose for AFib?"})Template Composition
Build complex prompts from smaller parts:
Python
from langchain_core.prompts import PipelinePromptTemplate
# Compose prompts from reusable parts
system_part = PromptTemplate.from_template(
"You are a {role}. Your guidelines follow {standard}."
)
safety_part = PromptTemplate.from_template(
"Safety rule: {safety_rule}"
)
question_part = PromptTemplate.from_template("{question}")
# Full prompt pipeline
full_template = PromptTemplate.from_template(
"{system}\n\n{safety}\n\nQuestion: {question_text}"
)
pipeline_prompt = PipelinePromptTemplate(
final_prompt=full_template,
pipeline_prompts=[
("system", system_part),
("safety", safety_part),
("question_text", question_part),
],
)
result_prompt = pipeline_prompt.format(
role="clinical pharmacist",
standard="ASHP guidelines",
safety_rule="Always recommend professional consultation for specific patient cases.",
question="What is warfarin dosing?",
)
print(result_prompt)Format Instructions for Structured Output
Python
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
class DrugProfile(BaseModel):
name: str = Field(description="Generic drug name")
drug_class: str = Field(description="Pharmacological class")
mechanism: str = Field(description="Mechanism of action in one sentence")
half_life: str = Field(description="Elimination half-life")
monitoring: list[str] = Field(description="Required clinical monitoring")
# PydanticOutputParser generates format instructions
parser = PydanticOutputParser(pydantic_object=DrugProfile)
# Inject format instructions into prompt
structured_prompt = ChatPromptTemplate.from_messages([
("system", "Extract drug information. {format_instructions}"),
("human", "Drug: {drug}"),
]).partial(format_instructions=parser.get_format_instructions())
# The format instructions tell the model exactly what JSON to return
print(structured_prompt.format_messages(drug="warfarin")[0].content[:200])
# "Extract drug information. The output should be formatted as a JSON instance..."
chain = structured_prompt | ChatOpenAI(model="gpt-4o") | parser
profile = chain.invoke({"drug": "warfarin"})
print(profile.name) # str
print(profile.monitoring) # list[str]Dynamic Templates
Build templates programmatically based on runtime conditions:
Python
def build_clinical_prompt(
specialty: str,
include_safety_warning: bool = True,
response_style: str = "technical",
) -> ChatPromptTemplate:
"""Build a clinical prompt customized to context."""
messages = [
("system", f"You are a {specialty} specialist."),
]
if include_safety_warning:
messages.append((
"system",
"Important: For all clinical recommendations, note that individual patient assessment is required. "
"This is for educational purposes only."
))
if response_style == "technical":
messages.append(("system", "Use precise medical terminology and cite evidence levels."))
elif response_style == "patient_friendly":
messages.append(("system", "Use simple language suitable for patients. Avoid jargon."))
messages.append(("human", "{question}"))
return ChatPromptTemplate.from_messages(messages)
# Build for different use cases
clinician_prompt = build_clinical_prompt("clinical pharmacology", include_safety_warning=False)
patient_prompt = build_clinical_prompt("pharmacist", response_style="patient_friendly")
clinician_chain = clinician_prompt | model | parser
patient_chain = patient_prompt | model | parserPrompt Best Practices
Python
# 1. Use system messages for role, constraints, and output format
# 2. Use human messages for the actual query
# 3. Keep {variables} minimal ā every variable is a possible injection point
# 4. Use .partial() for configuration, not .invoke() with fixed values
# 5. Validate input variables before building chains
def safe_invoke(chain, inputs: dict, required_keys: list[str]) -> str:
"""Validate inputs before invoking chain."""
missing = [k for k in required_keys if k not in inputs or not inputs[k]]
if missing:
raise ValueError(f"Missing required inputs: {missing}")
return chain.invoke(inputs)
# 6. For multi-turn: use MessagesPlaceholder (placeholder) for chat history
from langchain_core.prompts import MessagesPlaceholder
conversational_prompt = ChatPromptTemplate.from_messages([
("system", "You are a clinical pharmacist."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{question}"),
])Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.