LangChain Prompts and Prompt Templates

The quality of your AI application depends heavily on how you phrase your instructions to the model. A well-designed prompt produces accurate, consistent, useful output. A poorly designed one produces vague, incorrect, or unpredictable responses. LangChain provides a Prompt Template system that makes crafting, reusing, and managing prompts much easier than building strings by hand.

What Is a Prompt

A prompt is the instruction or question you send to an AI model. It is the text that tells the model what to do, what role to play, what format to use, and what the user wants. Everything the model generates comes from interpreting your prompt.

The Recipe Card Analogy

A prompt template is like a recipe card. The recipe card describes the dish (the task), lists ingredients with blank spaces where you fill in specific values (like "1 cup of ___"), and provides steps (the format you want). Every time you cook, you use the same card but fill in different ingredients. LangChain's Prompt Templates work the same way — fixed structure, variable inputs.

Recipe Card (Template):
┌────────────────────────────────────────────────┐
│ Dish: ___[dish_name]___                        │
│ Main ingredient: ___[ingredient]___            │
│ Serves: ___[servings]___                       │
│ Bake at 180°C for 30 minutes.                  │
└────────────────────────────────────────────────┘

Filled in for Lasagna:
┌────────────────────────────────────────────────┐
│ Dish: Lasagna                                  │
│ Main ingredient: Beef mince                    │
│ Serves: 6                                      │
│ Bake at 180°C for 30 minutes.                  │
└────────────────────────────────────────────────┘

Filled in for Cake:
┌────────────────────────────────────────────────┐
│ Dish: Chocolate Cake                           │
│ Main ingredient: Cocoa powder                  │
│ Serves: 10                                     │
│ Bake at 180°C for 30 minutes.                  │
└────────────────────────────────────────────────┘

Why Hard-Coded Prompts Create Problems

Beginners often build prompts by joining strings with the plus operator or using Python f-strings directly in their code. This works for small experiments but creates problems as your application grows.

Hard-coded approach (fragile):

user_topic = "Python"
prompt = "Write a beginner tutorial about " + user_topic + ". Keep it under 200 words."

# Issues:
# - Logic is scattered throughout the code
# - Hard to test the prompt independently
# - Difficult to update when requirements change
# - No input validation or formatting control

LangChain's Prompt Templates solve these problems by treating prompts as first-class objects with clear inputs, consistent formatting, and easy testing.

PromptTemplate for Single Text Inputs

The most basic template type works with plain text prompts — the kind you send to older completion-style models or use as a single instruction.

from langchain_core.prompts import PromptTemplate

# Define the template with placeholders in curly braces
template = PromptTemplate.from_template(
    "Write a short explanation of {topic} for a {audience} audience. "
    "Keep it under 100 words."
)

# Fill in the template with specific values
filled_prompt = template.format(topic="machine learning", audience="10-year-old")

print(filled_prompt)
# Output:
# "Write a short explanation of machine learning for a 10-year-old audience.
#  Keep it under 100 words."

The curly brace placeholders ({topic} and {audience}) are called input variables. LangChain automatically identifies them from the template string. When you call .format(), you pass values for each variable and get back the completed prompt text.

ChatPromptTemplate for Chat Models

Modern AI applications use Chat Models, which take a list of messages rather than a single text block. LangChain provides ChatPromptTemplate for this purpose. It lets you define templates for each message in the conversation, including the system message, user message, and (if needed) example AI responses.

from langchain_core.prompts import ChatPromptTemplate

# Create a template for a chat conversation
chat_template = ChatPromptTemplate.from_messages([
    ("system", "You are an expert in {subject}. Give clear, accurate answers."),
    ("human", "{user_question}")
])

# Fill in the variables
messages = chat_template.format_messages(
    subject="nutrition",
    user_question="How much protein should I eat per day?"
)

# messages is now a list ready to send to a Chat Model
print(messages)

The output is a list containing a SystemMessage and a HumanMessage, each with the variables filled in. You pass this list directly to your model's invoke() method.

Diagram: How ChatPromptTemplate Works

Template Definition:
┌─────────────────────────────────────────────────────────┐
│ system:  "You are an expert in {subject}."              │
│ human:   "{user_question}"                              │
└────────────────────┬────────────────────────────────────┘
                     │
          format_messages(subject="nutrition",
                          user_question="How much protein?")
                     │
                     ▼
Filled Messages List:
┌─────────────────────────────────────────────────────────┐
│ SystemMessage: "You are an expert in nutrition."        │
│ HumanMessage:  "How much protein should I eat per day?" │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
              model.invoke(messages)
                     │
                     ▼
              AIMessage: "Most adults need 0.8g per kg..."

Using Multiple Human and AI Messages

Some templates include example back-and-forth exchanges to show the model the style of conversation you want. This technique is called few-shot prompting.

few_shot_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant that answers in exactly one sentence."),
    ("human", "What is the speed of light?"),
    ("ai", "The speed of light in a vacuum is approximately 299,792 kilometers per second."),
    ("human", "What is the boiling point of water?"),
    ("ai", "Water boils at 100 degrees Celsius at standard atmospheric pressure."),
    ("human", "{user_question}")
])

The two example pairs (human/ai) demonstrate the exact response format you want: one complete sentence, precise, no extra words. The model learns the pattern from the examples and applies it to the actual user question.

MessagesPlaceholder for Dynamic Conversation History

When you build a chatbot with memory, the conversation history grows over time. You cannot hard-code the history into the template because it changes with every turn. LangChain provides MessagesPlaceholder to insert a dynamic list of messages into a fixed template position.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

conversational_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant named Alex."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{current_question}")
])

The MessagesPlaceholder marked as "history" accepts whatever list of messages you pass in. This means your template works whether the conversation has 2 messages or 50 messages — the placeholder expands to accommodate all of them.

Partial Prompts: Filling Variables in Stages

Sometimes you know some variable values when the application starts but other values only come at runtime when a user makes a request. LangChain supports partial prompts — templates with some variables already filled in and others still waiting.

from langchain_core.prompts import PromptTemplate
from datetime import datetime

# Create template with two variables
template = PromptTemplate.from_template(
    "Today is {date}. Answer this question: {user_question}"
)

# Fill in the date now (known at startup)
partial_template = template.partial(date=datetime.now().strftime("%B %d, %Y"))

# Later, fill in the user's question when it arrives
final_prompt = partial_template.format(user_question="What day is it?")
print(final_prompt)
# "Today is January 15, 2025. Answer this question: What day is it?"

This pattern keeps your prompts organized. Constants like the current date, the user's account type, or the application language get filled in at startup. Dynamic values from the user get filled in per request.

Prompt Templates in a Chain

The real power of Prompt Templates emerges when you combine them with other LangChain components. Using the pipe operator (|), you chain a template directly into a model call. The template formats the input, the model generates a response.

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

load_dotenv()

model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

template = ChatPromptTemplate.from_messages([
    ("system", "You explain complex topics in simple language."),
    ("human", "Explain {concept} as if I am 10 years old.")
])

# Chain the template to the model with the pipe operator
chain = template | model

# Invoke the chain with just the input variables
response = chain.invoke({"concept": "quantum entanglement"})
print(response.content)

Notice that you call chain.invoke() instead of calling the template and model separately. LangChain pipes the output of the template (a list of messages) directly into the model's input. This is the foundation of LangChain Expression Language (LCEL), covered in depth later in this course.

Diagram: Template in a Chain

chain = template | model

Input:
  {"concept": "quantum entanglement"}
         │
         ▼
  template  (formats the input)
         │
         │ [SystemMessage, HumanMessage] 
         ▼
  model  (sends to AI, gets response)
         │
         │ AIMessage
         ▼
  Output:
  "Imagine two magic coins..."

Inspecting a Template

LangChain templates expose useful properties you can check programmatically. This helps during development and debugging.

template = ChatPromptTemplate.from_messages([
    ("system", "You are an expert in {domain}."),
    ("human", "{question}")
])

print(template.input_variables)
# ['domain', 'question']

print(template.messages)
# [SystemMessagePromptTemplate(...), HumanMessagePromptTemplate(...)]

Printing input_variables shows you every placeholder the template expects. If you forget to provide one of these when calling the chain, LangChain raises a clear error telling you exactly which variable is missing.

Best Practices for Writing Prompts

Be Specific About the Task

Vague prompts produce vague results. "Explain databases" gives a generic answer. "Explain what a database index is and why it makes queries faster, using a phone contacts book as an analogy" gives a precise, useful answer.

Specify the Output Format

Tell the model exactly what format you want. "Return a JSON object with keys 'title', 'summary', and 'rating'" is much better than "give me information about this article."

Set the Tone in the System Message

The system message controls the model's personality and approach for the entire conversation. Invest time in writing a clear, specific system message. "You are a senior Python developer who explains concepts using code examples and never uses jargon without defining it first" produces better responses than "You are a helpful assistant."

Use Examples When Consistency Matters

If your application requires a specific output format that is hard to describe with words, include one or two examples in the template. The model follows the pattern more reliably when it sees a concrete example.

Keep Variables Meaningful

Name your template variables clearly. Use {customer_name} instead of {n}. Use {product_category} instead of {cat}. Clear variable names make your templates self-documenting.

Common Prompt Patterns

Summarization

template = ChatPromptTemplate.from_messages([
    ("system", "You summarize text concisely without losing key information."),
    ("human", "Summarize this in {num_sentences} sentences:\n\n{text}")
])

Classification

template = ChatPromptTemplate.from_messages([
    ("system", "Classify the sentiment of text. Reply with only one word: positive, negative, or neutral."),
    ("human", "{customer_review}")
])

Data Extraction

template = ChatPromptTemplate.from_messages([
    ("system", "Extract structured data from text. Return only valid JSON, nothing else."),
    ("human", "Extract the name, email, and phone number from: {raw_text}")
])

Translation

template = ChatPromptTemplate.from_messages([
    ("system", "You are a professional translator. Preserve tone and meaning."),
    ("human", "Translate this from {source_language} to {target_language}:\n\n{text}")
])

Testing Your Templates

Before connecting a template to a live model, test the formatted output by calling format_messages() and printing the result. This catches typos in variable names, missing placeholders, and formatting issues without spending API credits.

# Test without calling the model
messages = template.format_messages(
    source_language="English",
    target_language="French",
    text="Good morning, how are you?"
)

for msg in messages:
    print(f"{msg.type}: {msg.content}")

# system: You are a professional translator. Preserve tone and meaning.
# human: Translate this from English to French:
#        Good morning, how are you?

Verify the output looks exactly right before running it against the API. This practice saves time and money during development.

Summary

Prompt Templates separate your instruction logic from your application code, making prompts reusable, testable, and maintainable. PromptTemplate handles single text inputs while ChatPromptTemplate structures full conversation message lists. MessagesPlaceholder handles dynamic conversation histories. Partial templates allow staged filling of variables. Templates connect directly to models using the pipe operator, forming the foundation of LangChain chains.

Leave a Comment