RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /LangChain for Local AI
  6. /Ch. 4
LangChain for Local AI

04. ChatPromptTemplate

Chapter 4 of 18 · 25 min
KEY INSIGHT

`ChatPromptTemplate` builds role-labeled message sequences that chat models consume natively, contrasting with `PromptTemplate`'s single-string approach.

ChatPromptTemplate constructs prompts made of discrete message objects rather than a single string. This matches how chat models actually process input—they see role-labeled messages (system, user, assistant), not raw concatenation. Using the right template type matters because the model interprets positional cues differently depending on whether it receives a single prompt string or a structured message history.

LangChain defines three message role types in langchain_core.messages:

from langchain_core.messages import (
    SystemMessage,
    HumanMessage,
    AIMessage,
    ToolMessage,
    ChatMessage,
)

# SystemMessage: global instructions (always first or interleaved depending on model)
# HumanMessage: user input turns
# AIMessage: previous model responses (for conversation memory)
# ToolMessage: output from tool/function calls
# ChatMessage: a generic role message (rarely needed)

Build a ChatPromptTemplate with explicit message tuples:

from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate

chat_template = ChatPromptTemplate.from_messages([
    ("system", "You are a {assistant_role} assistant. Always answer in {language}."),
    ("human", "The user said: {user_input}\n\nWhat is a good response?"),
])

rendered = chat_template.invoke({
    "assistant_role": "helpful",
    "language": "Spanish",
    "user_input": "Where's the nearest coffee shop?"
})

for msg in rendered.to_messages():
    print(f"[{msg.type}] {msg.content}")
# [system] You are a helpful assistant. Always answer in Spanish.
# [human] The user said: Where's the nearest coffee shop?
# What is a good response?

The ChatPromptTemplate also accepts message objects directly, which is useful when constructing responses from stored histories:

# Pre-populate with a conversation history
from langchain_core.messages import AIMessage

history_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful code reviewer."),
    AIMessage(content="I've written this function. Can you review it?"),
    ("human", "{new_code}"),
])

When you inspect the rendered PromptValue from a ChatPromptTemplate, you get a list of BaseMessage objects. These are what ChatOllama.invoke() accepts natively:

response = llm.invoke(rendered)
print(response.content)

The connection between template and model is direct: ChatPromptTemplate → PromptValue → list of BaseMessage → ChatOllama.

Failure mode with message ordering: some operators insert HumanMessage objects into a ChatPromptTemplate expecting them to be treated differently from hardcoded strings. They are not. The template renders all messages at invoke time, not at model call time. If you want to append a user's new message to an existing message history, concatenate the message lists before passing to the model—not inside the template construction.

Another failure mode: SystemMessage injection. Ollama models do not all handle system messages consistently. Some models trained with Llama 3-style instruction tuning expect system prompts to be prepended, while others ignore them entirely. Test your specific model with a system prompt that includes a verifiable token (e.g., "Always end responses with the word BANANA."). If the model ignores it, fall back to including the instruction in the first HumanMessage or using the ChatOllama system parameter directly.

EXERCISE

Construct a ChatPromptTemplate with a system message defining a translator role, two human messages (the first expressing a subject, the second asking for a different translation style), and a render that captures both full message sequences. Print the role and content of each message in the rendered output.

← Chapter 3
Prompt Templates
Chapter 5 →
LLMChain Basics