Learnixo

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.

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)