System Prompt in LangChain: Templates and Message Roles

LangChain passes system instructions through a SystemMessage or a ChatPromptTemplate. Here is how to write templates, use variables, and test that your instructions behave correctly.

Updated 5 min read

LangChain passes system instructions to a language model through a SystemMessage object or a ChatPromptTemplate that includes a system component. The model receives that message before the user's input and applies it as standing instructions for the conversation.

The flexibility of LangChain templates makes it easy to write prompts with variable slots, but that flexibility also creates new failure modes: variables that produce unexpected output when filled at runtime, or system messages that conflict with the model's behavior in ways that only show up in production. Tools like fenbs store approved prompt content and the reasons behind each rule, so the tested version is available as the project evolves.

The SystemMessage class

In LangChain, a SystemMessage is the direct equivalent of passing {"role": "system", "content": "..."} to a chat API. You create one with the standing instructions as a string, then pass it as the first message in a list to the model.

LangChain: SystemMessage basic usage
from langchain_core.messages import SystemMessage, HumanMessage

messages = [
    SystemMessage(content="You are a technical writing assistant. Help the user write clear documentation for software engineers. Use US English. Define any technical term the first time it appears."),
    HumanMessage(content="Write an introduction for our REST API documentation.")
]

response = model.invoke(messages)

For a one-off system message, SystemMessage is the simplest approach. When the same system message is reused across multiple chains or needs variables filled at runtime, a ChatPromptTemplate is more practical.

Using ChatPromptTemplate for system instructions

ChatPromptTemplate lets you define the structure of a prompt, including the system component, with named variables you fill at runtime. The from_messages method accepts a list of role-content tuples.

LangChain: ChatPromptTemplate with system message
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a {role} for {company}. Help users with {scope}. Respond in US English. Keep responses under 200 words. If a request is outside {scope}, explain that and offer to escalate."),
    ("human", "{user_input}")
])

chain = prompt | model
response = chain.invoke({
    "role": "customer support assistant",
    "company": "Acme Software",
    "scope": "Acme Software's desktop application",
    "user_input": "How do I reset my password?"
})

Variable substitution happens at runtime when you call invoke. Each {variable} in the template is replaced with the value you pass. The model never sees the template syntax; it receives the filled string.

Testing variable substitution

Variables in system prompts create a new category of failure: the template may look correct, but a particular combination of values at runtime produces instructions that are unclear or contradictory.

  • Test with the real values you will pass in production, not with placeholder text. Short variable values may not produce the same behavior as long ones.
  • Check that sentence structure is correct after substitution. A variable containing a company name that includes a comma or a conjunction can produce a malformed sentence.
  • Test the edge case where a variable value is empty or unexpected. What does the model do if a variable slot receives an empty string?
  • Test that the filled system message still produces correct behavior on out-of-scope user messages.

See prompt engineering for broader guidance on testing AI instructions, and context engineering vs prompt engineering for when a system prompt alone is not enough.

Common mistakes in LangChain system prompts

  • Putting request-specific context in the system message: the system message should hold standing instructions, not information that changes per request. Move request-specific context to the human message or inject it as a variable.
  • Overly long templates: a system message that runs to several paragraphs competes with the actual user message for the model's attention. Keep standing instructions focused on the rules that matter across all requests.
  • Not testing after model changes: LangChain supports many model providers. When you switch from one to another, the same system message may produce different behavior. Test on the model you are actually deploying with.
  • Ignoring template injection risk: if a user-controlled value is substituted into a system message template variable, a malicious input can alter the instructions. Never put unsanitized user input into a system message variable. See indirect prompt injection for more on this risk.

Versioning and storing prompt content

LangChain prompt templates are Python code. Version-controlling the template in your repository is straightforward, but the reasoning behind a rule in the template is usually in someone's head or in a comment. When the template needs changing months later, the comment is gone and the reason is unclear.

A separate record that pairs each system prompt rule with the decision behind it makes future revisions safer. When a constraint was added for a specific reason, that reason shows whether the constraint should change when circumstances change.

LangChain also offers a LangSmith hub for managing and versioning prompts. Whether you use that or a simpler approach, the goal is the same: the current tested version is findable, and the reasoning behind each rule is accessible.

How fenbs helps

Keep your LangChain prompt content in a shared record

Add your tested system prompt content to fenbs as a skill, with the reasoning behind any significant rule recorded as a Decision. When the template needs revising, the record shows what was deliberate and what was a default. Connected AI apps can read the current approved version without digging through code.

Create a free account

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

How to do this with fenbs

  1. Create a free account and add a project for the LangChain application you are building.
  2. Write the system prompt content: role, positive rules, negative constraints, fallback for out-of-scope requests.
  3. Build the ChatPromptTemplate or SystemMessage from the content. Test with your real production variables.
  4. Run the precedence tests: out-of-scope requests, requests that try to override the rules, edge cases for each variable.
  5. Add the tested prompt content as a skill in fenbs. Record the reasoning behind any rule as a Decision.
  6. When the template needs updating, check the Decision record first to understand what was deliberate before changing it.

See what is LangChain for an overview of where the system prompt fits in a LangChain application, and ChatGPT prompts for work for user-turn prompt examples that pair with your system instructions.

When your LangChain application handles multiple types of requests with different standing instructions, consider a separate chain per instruction type rather than one chain with a very long combined system message. Shorter, focused instructions tend to be followed more consistently than long ones that try to cover every case in a single block.

Questions people ask.

How do I add a system prompt in LangChain?

Use a SystemMessage object for a fixed system message, or a ChatPromptTemplate with a "system" tuple for a reusable template with variables. Pass the system message as the first item in the messages list you send to the model.

What is the difference between SystemMessage and ChatPromptTemplate in LangChain?

SystemMessage is a fixed string passed directly to the model. ChatPromptTemplate lets you define the message structure with named variable slots you fill at runtime. For reusable prompts that need runtime values, ChatPromptTemplate is the practical choice.

How do variables work in a LangChain system prompt?

Variables are written as {variable_name} in the template string. When you call invoke with a dictionary of values, LangChain substitutes each variable before sending the message to the model. Test with your real production values, not placeholder text, to catch issues that appear only at runtime.

Can I version-control LangChain system prompts?

Yes. The template is Python code and belongs in version control with the rest of your application. For managing and sharing prompts across teams, LangSmith hub is one option. Keeping the reasoning behind each rule in a separate shared record helps when revisions are needed months later.

Does the system message work the same across LangChain model integrations?

Mostly, but with differences. LangChain normalizes messages across model APIs, but some models handle the system role differently. Test your system message specifically with the model you are deploying with; do not assume behavior from one model carries to another.

More in Prompts and skills for AI work

All Prompts and skills for AI work articles

Start with one thing.

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

There is nothing to set up first. Write one line and you’ve started.