LangChain Mastery · Lesson 13 of 33
HumanMessage, AIMessage, SystemMessage
The Message Type System
Chat models communicate via structured messages, not raw strings. LangChain's message classes model this conversation structure and map to each provider's API format.
from langchain_core.messages import (
HumanMessage, # User input
AIMessage, # Model output
SystemMessage, # System instructions (pre-conversation)
ToolMessage, # Result from a tool call
FunctionMessage, # Legacy — use ToolMessage instead
AIMessageChunk, # Streaming chunk of AIMessage
BaseMessage, # Abstract base class
)Core Message Types
SystemMessage: Instructions that set the model's behavior. Sent before the conversation.
from langchain_core.messages import SystemMessage
system = SystemMessage(
content="You are a clinical pharmacist at a hospital. "
"Answer questions with clinical precision. "
"Always note when professional consultation is required."
)
# Maps to: {"role": "system", "content": "..."} in OpenAI APIHumanMessage: The user's input.
from langchain_core.messages import HumanMessage
human = HumanMessage(
content="What is the recommended dose of warfarin for atrial fibrillation?",
# Optional: add metadata
additional_kwargs={"user_id": "clinician_42"},
)
# Maps to: {"role": "user", "content": "..."} in OpenAI APIAIMessage: The model's response.
from langchain_core.messages import AIMessage
ai_response = AIMessage(
content="The typical warfarin dose for AFib starts at 2-5mg daily...",
# Tool calls (if model called tools)
tool_calls=[],
# Usage metadata
response_metadata={
"model": "gpt-4o",
"usage": {"prompt_tokens": 120, "completion_tokens": 85},
},
)
# Maps to: {"role": "assistant", "content": "..."} in OpenAI APICalling Models with Messages
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
openai_model = ChatOpenAI(model="gpt-4o", temperature=0)
claude_model = ChatAnthropic(model="claude-sonnet-4-6", temperature=0)
# Build a conversation manually
messages = [
SystemMessage(content="You are a clinical pharmacist."),
HumanMessage(content="What is warfarin?"),
]
# Both models use the same interface
response_openai = openai_model.invoke(messages)
response_claude = claude_model.invoke(messages)
print(type(response_openai)) # AIMessage
print(response_openai.content) # str
# Continue the conversation
messages.append(response_openai) # Add AI response to history
messages.append(HumanMessage(content="What dose should I use for AFib?"))
follow_up = openai_model.invoke(messages)
print(follow_up.content)ToolMessage: Results from Tool Calls
When a model calls a tool, the tool's result comes back as a ToolMessage:
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
@tool
def get_drug_dose(drug_name: str, indication: str) -> str:
"""Look up standard drug dosing for a given indication."""
db = {
("warfarin", "afib"): "2-10mg daily, INR-guided",
("metformin", "t2dm"): "500-2550mg daily, in divided doses",
}
key = (drug_name.lower(), indication.lower())
return db.get(key, "Consult clinical reference for this drug/indication combination.")
model = ChatOpenAI(model="gpt-4o")
model_with_tools = model.bind_tools([get_drug_dose])
# 1. Model decides to call a tool
messages = [HumanMessage(content="What's the warfarin dose for AFib?")]
response = model_with_tools.invoke(messages)
# response.tool_calls = [{"name": "get_drug_dose", "args": {...}, "id": "call_xyz"}]
# 2. Execute the tool
if response.tool_calls:
for tool_call in response.tool_calls:
tool_result = get_drug_dose.invoke(tool_call["args"])
# 3. Add both AI response (with tool call) and ToolMessage to history
messages.append(response) # AI message with tool_calls
messages.append(ToolMessage(
content=tool_result,
tool_call_id=tool_call["id"], # Links result to the specific tool call
))
# 4. Model generates final response using tool result
final_response = model_with_tools.invoke(messages)
print(final_response.content) # Uses tool result in answerAIMessageChunk: Streaming
When streaming, the model emits AIMessageChunk objects that accumulate into a full AIMessage:
from langchain_core.messages import AIMessageChunk
# Stream response
full_content = ""
for chunk in openai_model.stream([HumanMessage(content="Explain warfarin.")]):
# chunk is AIMessageChunk
print(type(chunk)) # AIMessageChunk
print(chunk.content) # "" or partial text
full_content += chunk.content
# Chunks can be concatenated with +
# (AIMessageChunk + AIMessageChunk → AIMessageChunk)
all_chunks = []
for chunk in openai_model.stream(messages):
all_chunks.append(chunk)
combined = all_chunks[0]
for c in all_chunks[1:]:
combined = combined + c # Concatenate chunks
print(combined.content) # Full responseMessage Conversion and Utilities
from langchain_core.messages import (
messages_to_dict,
messages_from_dict,
convert_to_messages,
get_buffer_string,
)
# Serialize messages for storage
conversation = [
SystemMessage(content="You are a pharmacist."),
HumanMessage(content="What is warfarin?"),
AIMessage(content="Warfarin is an anticoagulant..."),
]
# Convert to dict (for JSON storage)
serialized = messages_to_dict(conversation)
# [{"type": "system", "data": {"content": "..."}}, ...]
# Restore from dict
restored = messages_from_dict(serialized)
# Convert from tuples or strings (convenient shortcuts)
messages = convert_to_messages([
("system", "You are a pharmacist."),
("human", "What is aspirin?"),
# Tuples → SystemMessage/HumanMessage/AIMessage based on role
])
# Format as readable string (for display or debug)
text = get_buffer_string(conversation)
# "System: You are a pharmacist.\nHuman: What is warfarin?\nAI: Warfarin is..."
print(text)Messages in ChatPromptTemplate
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# MessagesPlaceholder inserts a list of messages at a specific position
prompt = ChatPromptTemplate.from_messages([
("system", "You are a clinical pharmacist."),
MessagesPlaceholder(variable_name="chat_history"), # List of messages goes here
("human", "{question}"),
])
# Use with conversation history
chat_history = [
HumanMessage(content="What is warfarin?"),
AIMessage(content="Warfarin is an anticoagulant used to prevent blood clots..."),
]
messages = prompt.format_messages(
chat_history=chat_history,
question="What dose should I use for AFib?",
)
# [SystemMessage, HumanMessage("What is warfarin?"), AIMessage("..."), HumanMessage("What dose...")]
response = openai_model.invoke(messages)Provider API Mapping
LangChain messages map to each provider's format:
| LangChain Class | OpenAI role | Anthropic role | Purpose | |---|---|---|---| | SystemMessage | "system" | "system" | Pre-conversation instructions | | HumanMessage | "user" | "user" | User input | | AIMessage | "assistant" | "assistant" | Model output | | ToolMessage | "tool" | "tool" | Tool call result |
# LangChain handles the translation automatically
# Your code uses the same message classes regardless of provider
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
messages = [
SystemMessage(content="Be concise."),
HumanMessage(content="Explain warfarin."),
]
# OpenAI API call (handled internally):
# {"role": "system", "content": "..."}, {"role": "user", "content": "..."}
# Anthropic API call (handled internally):
# system="Be concise.", messages=[{"role": "user", "content": "..."}]
# Your code is identical for both:
openai_result = ChatOpenAI(model="gpt-4o").invoke(messages)
claude_result = ChatAnthropic(model="claude-sonnet-4-6").invoke(messages)