LangChain Message Types
In LangChain, all conversations are passed through Message objects. Understanding the purpose of various message types is the foundation for writing Agents.
Four Core Message Types
LangChain defines four core message types, corresponding to different roles in a conversation:
| Type | Role | Description | Typical Content |
|---|---|---|---|
| HumanMessage | User | Message sent by the user | "What's the weather like today?" |
| AIMessage | AI Assistant | Model's reply, may include tool_calls | "It's sunny in Hangzhou today, 25°C" |
| SystemMessage | System | System instruction, defining the AI's role and behavior rules | "You are a professional weather assistant" |
| ToolMessage | Tool | Return result after tool execution | "Sunny, 25°C, humidity 60%" |
HumanMessage — User Message
HumanMessage represents a message sent by the user to the AI. It is the most common message type and the starting point of a conversation.
Example
from langchain.chat_models import init_chat_model
# Create a user message
msg = HumanMessage(content="What is Python?")
print(f"Type: {msg.type}") # human
print(f"Content: {msg.content}") # What is Python?
print(f"Role: {msg.role}") # user
# Create a message list (representing multi-turn conversation history)
messages = [
HumanMessage(content="Hello"),
HumanMessage(content="What courses does Example offer?"),
HumanMessage(content="Is the Python course suitable for beginners with zero foundation?"),
]
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
response = model.invoke(messages)
print(f"\n"Model reply: {response.content}")
Output:
Type: human Content: What is Python? Role: user Model reply: Python is a high-level, interpreted programming language known for its clear and readable syntax, widely used across many fields. Its rich ecosystem and beginner-friendly design make it an excellent choice for those starting from zero.
Quick Creation Methods for HumanMessage
When building message lists, you can use tuples or dictionaries as shortcuts:
Example
# Method 1: Standard construction
msg1 = HumanMessage(content="Hello")
# Method 2: Tuple shortcut (role, content)
msg2 = ("user", "Hello")
msg3 = ("human", "Hello")
# Method 3: Dictionary shortcut
msg4 = {"role": "user", "content": "Hello"}
# All four methods are equivalent and will be converted to HumanMessage internally in the Agent.
print(type(msg1)) # <class 'langchain_core.messages.human.HumanMessage'>
AIMessage — AI Reply
AIMessage represents the model's reply. Unlike ordinary text, AIMessage may includetool_calls(tool call requests).
Example
# Normal AI reply (no tool call)
ai_msg = AIMessage(content="Example is a programming learning platform")
# AI reply containing a tool call
ai_with_tools = AIMessage(
content="", # When calling a tool, content is usually empty
tool_calls=[
{
"name": "get_weather",
"args": {"city": "Hangzhou"},
"id": "call_abc123",
"type": "tool_call",
}
]
)
print("=== Normal AI message ===")
print(f"content: {ai_msg.content}")
print(f"tool_calls: {ai_msg.tool_calls}") # []
print("\n"=== AI message with tool call ===")
print(f"content: {ai_with_tools.content}")
print(f"tool_calls: {ai_with_tools.tool_calls}")
# [{'name': 'get_weather', 'args': {'city': 'Hangzhou'}, ...}]
Additional Information for AIMessage
Example
model = init_chat_model("deepseek:deepseek-v4-flash")
response = model.invoke("Introduce Python")
# AIMessage contains rich metadata
print(f"Content: {response.content}")
print(f"Message ID: {response.id}")
print(f"Model name: {response.response_metadata.get('model_name')}")
print(f"Finish reason: {response.response_metadata.get('finish_reason')}")
# usage_metadata contains token usage information
if response.usage_metadata:
print(f"Input tokens: {response.usage_metadata.get('input_tokens')}")
print(f"Output tokens: {response.usage_metadata.get('output_tokens')}")
print(f"Total tokens: {response.usage_metadata.get('total_tokens')}")
Output:
Content: Example is a free programming learning platform for beginners... Message ID: msg_abc123def456 Model name: deepseek-v4-flash Finish reason: stop Input tokens: 19 Output tokens: 47 Total tokens: 66
SystemMessage — System Instruction
SystemMessage is used to set the AI's behavior, role, and constraints. It is placed at the beginning of the message list and guides how the model replies.
Example
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0.7)
# Reply without a system instruction
messages_no_system = [HumanMessage(content="Introduce Python")]
response = model.invoke(messages_no_system)
print(f"No system instruction: {response.content[:80]}...")
# Reply with a system instruction
messages_with_system = [
SystemMessage(content="You are a Xiaohongshu-style blogger; replies should be lively, use emoji, and include topic tags."),
HumanMessage(content="Introduce Python")
]
response = model.invoke(messages_with_system)
print(f"\n"With system instruction: {response.content}")
Output:
Without system prompt: Example is an online programming learning platform for beginners, offering rich tutorials and examples... With system prompt: Amazing site! Recommending Example to every beginner who wants to learn programming! Comprehensive free tutorials covering everything from HTML to Python! Plenty of examples and quick to get started - a perfect entry point for programming novices! #ProgrammingLearning #FreeTutorials #ZeroBasics #Example
ToolMessage — Tool Return Result
ToolMessage contains the result returned after tool execution. It must be associated with the corresponding tool_call.
Example
from langchain.chat_models import init_chat_model
# Simulate a complete round of tool-calling conversation
messages = [
HumanMessage(content="What's the weather like in Hangzhou?"),
# The model requests to call a tool
AIMessage(
content="",
tool_calls=[
{"name": "get_weather", "args": {"city": "Hangzhou"},
"id": "call_abc", "type": "tool_call"}
]
),
# Tool return result (must include tool_call_id corresponding to the id above)
ToolMessage(
content="Sunny, 25°C, humidity 60%",
tool_call_id="call_abc", # Corresponds to the id of tool_call
name="get_weather", # Tool name
),
]
model = init_chat_model("deepseek:deepseek-v4-flash")
response = model.invoke(messages)
print(f"Model reply based on tool result: {response.content}")
Output:
Model reply based on tool result: Hangzhou is sunny today, 25 C, humidity 60%, great for outdoor activities.
ToolMessage's tool_call_id must exactly match the id of tool_call in the AIMessage. If they do not match, the model may ignore the tool result or produce confusing behavior.
AIMessageChunk — Streaming Output Message Chunk
When you use stream() for streaming output, each arriving chunk is an AIMessageChunk rather than a complete AIMessage:
Example
model = init_chat_model("deepseek:deepseek-v4-flash")
print("Streaming output process:")
# stream() returns an AIMessageChunk iterator
for chunk in model.stream("Introduce Python in one sentence"):
# Each chunk is a small piece of text
print(chunk.content, end="", flush=True)
print() # Newline
Output:
Streaming output: Example is a free online learning platform for programming beginners, offering rich tutorials and examples.
Message Type Quick Reference Table
| Message Type | type attribute | role attribute | Key fields | When to use |
|---|---|---|---|---|
| HumanMessage | human | user | content | User input |
| AIMessage | ai | assistant | content, tool_calls, usage_metadata | Model reply |
| AIMessageChunk | ai | assistant | content (delta) | Streaming output chunk |
| SystemMessage | system | system | content | Set AI role |
| ToolMessage | tool | tool | content, tool_call_id, name | Tool execution result |
ContentBlock — Structured Message Content
So far, the message content we've used has been plain strings. But in reality, each message's content can be multipleContentBlock(content blocks) forming a list.
The three most common types of content blocks:
| Type | Description | Purpose |
|---|---|---|
| PlainTextContentBlock | Plain text content | Ordinary text message |
| ImageContentBlock | Image content (base64 or URL) | Image input for multimodal models |
| ToolCall | Tool call request | AI requests to call a tool |
Example
from langchain.messages import PlainTextContentBlock, ImageContentBlock
# Build messages using content blocks
# content can be a plain string (simple scenario)
simple_msg = HumanMessage(content=Hello)
# content can also be a list of ContentBlock (complex scenario)
complex_msg = HumanMessage(content=[
PlainTextContentBlock(text="What is in this picture?"),
# The image can be a URL or base64 encoding
ImageContentBlock(
url="https://example.com/photo.jpg"
),
])
print(f"Simple message content type: {type(simple_msg.content)}")
# Output: <class 'str'>
print(f"Complex message content type: {type(complex_msg.content)}")
# Output: <class 'list'>
print(f"Number of content blocks: {len(complex_msg.content)}")
# Output: 2
When you only need to send plain text, you can directly pass a string, and LangChain will handle it automatically. Only when you need to mix text and images in a single message do you need to manually construct a ContentBlock list.
Multimodal Messages — Letting the Model "See" Images
If your model supports multimodal input (such as GPT-4o, Claude 3+), you can let it analyze image content:
Example
from pathlib import Path
from langchain.messages import HumanMessage
from langchain.chat_models import init_chat_model
# Encode the local image as base64
def encode_image(image_path: str) -> str:
Read image file and convert to base64 encoding
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
# Create a multimodal model (must support image input)
model = init_chat_model("deepseek:deepseek-v4-flash")
# Build a message containing an image
# Note: content uses list format, containing text blocks and image blocks
image_data = encode_image("screenshot.png")
messages = [
HumanMessage(content=[
{"type": "text", "text": Please describe the content of this screenshot of the Python official website.},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_data}",
"detail": "auto" # Optional: low, high, auto
}
}
])
]
response = model.invoke(messages)
print(fImage analysis result: {response.content})
If your message contains only an image and no text, you can directly use:
Example
model = init_chat_model("deepseek:deepseek-v4-flash")
# Use a URL to directly reference an online image
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": What does this image describe?},
{
"type": "image_url",
"image_url": {"url": "https://www.example.com/wp-content/uploads/2024/01/logo.png"}
}
]
}
]
response = model.invoke(messages)
print(response.content)
Not all models support multimodal input. OpenAI's GPT-4o series, Anthropic's Claude 3+, and Google's Gemini series support it. If you send an image using an unsupported model, you'll get an error.
ToolCall — Tool Call Message
The tool_calls field in AIMessage is a list of ToolCall objects, where each ToolCall represents a request by the model to call a tool:
Example
from langchain.messages.tool import ToolCall
# Manually construct a ToolCall
tool_call = ToolCall(
name="get_weather", # Tool name
args={"city": Hangzhou}, # Call arguments
id="call_abc123", # Unique identifier
type="tool_call", # Fixed value
)
# Create an AIMessage containing tool_calls
ai_message = AIMessage(
content="", # When tool_calls is present, content is usually empty
tool_calls=[tool_call],
)
print(fTool name: {ai_message.tool_calls)
print(fCall parameters: {ai_message.tool_calls)
print(fCall ID: {ai_message.tool_calls)
Check Whether AIMessage Contains Tool Calls
Example
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# After binding tools, the model may return tool_calls
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": Query weather,
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": City name}
},
"required": ["city"]
}
}
}]
model_with_tools = model.bind_tools(tools)
# Question that triggers a tool call
response = model_with_tools.invoke(How is the weather in Hangzhou?)
# Two ways to determine whether there is a tool call
# Method 1: Check whether the tool_calls list is non-empty
if response.tool_calls:
print(Model requests tool call)
# Method 2: Check whether content is empty
# (For most models, content is empty when tool_calls is present)
if not response.content:
print(The model's content is empty, indicating it wants to call a tool instead of replying directly.)
trim_messages() — Trimming Message History
When the conversation gets longer, the message list may exceed the model's context window.trim_messages()The function helps you intelligently trim message history.
Example
HumanMessage, AIMessage, SystemMessage, trim_messages
)
from langchain.chat_models import init_chat_model
# Simulate a long conversation history
messages = [
SystemMessage(content=You are the AI assistant of EXAMPLE tutorial.),
HumanMessage(content=How to get started with Python?),
AIMessage(content=Getting started with Python can begin with basic knowledge...),
HumanMessage(content=Any recommended IDE?),
AIMessage(content=Recommend VS Code or PyCharm...),
HumanMessage(content=How to install third-party libraries?),
AIMessage(content=Use the pip install command...),
HumanMessage(content=What is NumPy?),
AIMessage(content=NumPy is a scientific computing library...),
HumanMessage(content=What is the difference between pandas and NumPy?),
]
model = init_chat_model("deepseek:deepseek-v4-flash")
# Trim messages to fit the model's context window (at most 1000 tokens)
# strategy="last" retains the last system message and the most recent conversation
trimmed = trim_messages(
messages,
max_tokens=1000, # Keep at most 1000 tokens
strategy="last", # Keep the last system message + most recent conversation
token_counter=model, # Use the model's token counting method
include_system=True, # Always keep SystemMessage
start_on="human", # After trimming, start with a human message
)
print(fBefore trimming: {len(messages)} messages)
print(fAfter trimming: {len(trimmed)} messages)
for msg in trimmed:
snippet = msg.content[:50] if isinstance(msg.content, str) else str(msg.content)[:50]
print(f" [{msg.type}] {snippet}...")
Execution result:
Before trimming: 10 messages After trimming: 5 messages [system] You are the AI assistant of Example... [human] How do I install third-party libraries?... [ai] Use the pip install command... [human] What is NumPy?... [ai] NumPy is a scientific computing library...
| Strategy | Description | Applicable scenarios |
|---|---|---|
| strategy="last" | Keep system message + most recent conversation | In long conversations, only the latest context matters |
| strategy="first" | Keep system message + earliest conversation | Ensure key context is not trimmed |
start_on="human" ensures that the trimmed message list starts with a user message (rather than an AI message), preventing the model from receiving a list that starts with an isolated AI reply.
RemoveMessage — Deleting Specific Messages
In some advanced scenarios, you may need to delete specific messages from the message history (such as cleaning sensitive content, regenerating replies, etc.):
Example
# Assume there is a conversation
messages = [
HumanMessage(content=Hello, id="msg_1"),
AIMessage(content=Hello! How can I help you?, id="msg_2"),
HumanMessage(content=Check the weather for me, id="msg_3"),
]
# Use RemoveMessage to delete a specific message (by ID)
# RemoveMessage is used with the add_messages reducer
# When updating Agent state, RemoveMessage removes the message with the corresponding ID from the list
removal = RemoveMessage(id="msg_3")
print(fMessage ID to delete: {removal.id})
print(fType: {removal.type}) # remove
RemoveMessage is usually used together with the add_messages reducer of AgentState. Returning RemoveMessage in middleware or after_model hooks can dynamically clean up message history.
Common Methods for Message Properties
All message types inherit from BaseMessage and share some common methods:
Example
msg = HumanMessage(content=Hello, Example)
# Basic attributes
print(f"content: {msg.content}") # Message content
print(f"type: {msg.type}") # Message type (human/ai/system/tool)
print(f"id: {msg.id}") # Auto-generated unique ID
# text attribute: if it is text content, return the text; otherwise return ""
print(f"text: {msg.text}")
# pretty_repr(): formatted printing, suitable for debugging
print(f"Pretty output:\n{msg.pretty_repr()}")
Running result:
content: Hello, world type: human id: 00000000-0000-4000-8000-000000000000 text: Hello, world Pretty output: ================================ Human Message ================================= Hello, worldOther extensions