Normalized message format shared across all providers.
A message has a role, content, and optional metadata fields.
Roles
:user— messages from the human user (default when creating from a string):assistant— responses from the model (may contain text, tool calls, thinking):tool— tool call results (requires:tool_call_idlinking back to the call):system— system instructions (set via the:systemoption, not as a message):developer— developer-specific instructions (Anthropic-specific, merged into system)
Assistant message fields
Assistant responses use dedicated fields rather than a mixed content list:
:content— the model's text reply (String.t() | nil):tools— tool calls made by the model ([tool_call()] | nil):thinking— reasoning/thinking produced before the reply (thinking() | nil):usage— token usage for this turn (LLM.Usage.t() | nil), set on assistant messages during auto-tool loops
Multi-turn conversation
Pass previous messages via the :messages option to continue a conversation:
messages = [
LLM.Message.new(:user, "What is Elixir?"),
LLM.Message.new(:assistant, "Elixir is a functional language."),
LLM.Message.new(:user, "What is the BEAM?")
]
{:ok, response} = LLM.generate("Tell me more",
provider: :openai,
model: "gpt-4",
messages: messages
)Tool call sequence
When a model calls a tool, the message flow is:
# 1. Assistant message with tool call
%LLM.Message{
role: :assistant,
content: "Let me search for that.",
tools: [%{id: "call_abc123", name: "search", args: %{"query" => "elixir"}}]
}
# 2. Tool result message (links back via tool_call_id)
%LLM.Message{
role: :tool,
tool_call_id: "call_abc123",
name: "search",
content: "Elixir is a functional language..."
}Examples
# Simple user message
LLM.Message.new("Hello")
#=> %LLM.Message{role: :user, content: "Hello"}
# Assistant message with explicit role
LLM.Message.new(:assistant, "Hi there!")
# Assistant message with thinking and tool calls
%LLM.Message{
role: :assistant,
content: "I'll check the weather for you.",
thinking: "The user wants weather info. I should call get_weather.",
tools: [%{id: "call_1", name: "get_weather", args: %{"city" => "Paris"}}]
}
# Tool result message
%LLM.Message{
role: :tool,
tool_call_id: "call_abc123",
name: "get_weather",
content: "72°F, sunny"
}See the Messages, Roles, and Tool Calls guide for the full lifecycle of messages, roles, and tool calls across providers.
Summary
Functions
Create a message from a string, keyword list, or map.
Create a message with explicit role and string content.
Types
Functions
Create a message from a string, keyword list, or map.
A plain string creates a user message:
LLM.Message.new("Hello")
#=> %LLM.Message{role: :user, content: "Hello"}A keyword list or map sets struct fields directly:
LLM.Message.new(role: :assistant, content: "Hi!")
LLM.Message.new(%{role: :tool, tool_call_id: "abc", content: "result"})
Create a message with explicit role and string content.