Learnixo
Back to blog
AI Systemsintermediate

HumanMessage, AIMessage, SystemMessage

Understand LangChain's message types: HumanMessage, AIMessage, SystemMessage, ToolMessage, and FunctionMessage. How they map to provider APIs and flow through chains.

Asma Hafeez KhanMay 16, 20265 min read
LangChainMessagesHumanMessageAIMessageSystemMessageChat
Share:š•

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.

Python
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.

Python
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 API

HumanMessage: The user's input.

Python
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 API

AIMessage: The model's response.

Python
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 API

Calling Models with Messages

Python
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:

Python
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 answer

AIMessageChunk: Streaming

When streaming, the model emits AIMessageChunk objects that accumulate into a full AIMessage:

Python
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 response

Message Conversion and Utilities

Python
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

Python
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 |

Python
# 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)

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.