Represents a message in a conversation with an AI model.
Messages support multi-modal content, tool calls, and various roles following OpenAI's standard message format while providing Elixir-native validation and type safety.
Message Roles
:system- System instructions and context:user- User input and queries:assistant- AI model responses:tool- Tool execution results
Examples
# Simple text messages
iex> msg = Message.system("You are a helpful assistant")
iex> {msg.role, msg.content}
{:system, "You are a helpful assistant"}
iex> msg = Message.user("Hello!")
iex> {msg.role, msg.content}
{:user, "Hello!"}
# Multi-modal user message: parts are flattened into `content` and the
# original ContentPart list is kept under `metadata.content_parts`.
iex> msg = Message.user([
...> ContentPart.text("What's in this image?"),
...> ContentPart.image_url("https://example.com/image.jpg")
...> ])
iex> msg.content
"What's in this image?[Image: https://example.com/image.jpg]"
iex> Enum.map(msg.metadata.content_parts, & &1.type)
[:text, :image_url]
# Assistant message with tool calls
iex> msg = Message.assistant("Let me search for that", tool_calls: [
...> %{id: "call_123", name: "search", arguments: %{"query" => "elixir"}}
...> ])
iex> {msg.role, Message.has_tool_calls?(msg)}
{:assistant, true}
Summary
Functions
Create an assistant message.
Extract text content from a message.
Check if message is from assistant.
Convert from legacy tuple format.
Check if message is from user.
Get message content as ContentPart list.
Get metadata from a message.
Check if message has tool calls.
Check if message is system instruction.
Check if message is tool-related (tool call or tool result).
Create a new message.
Create a new message, raising on validation failure.
Add metadata to a message.
Split messages into {system_prompt, other_messages}.
Create a system message.
Convert message content to plain text representation.
Create a tool result message.
Create a user message.
Types
Functions
@spec assistant( String.t() | [Nous.Message.ContentPart.t()], keyword() ) :: t()
Create an assistant message.
Assistant messages contain AI model responses, including tool calls.
Examples
iex> msg = Message.assistant("Hello there!")
iex> {msg.role, msg.content}
{:assistant, "Hello there!"}
iex> msg = Message.assistant("Let me search", tool_calls: [
...> %{id: "call_1", name: "search", arguments: %{"query" => "elixir"}}
...> ])
iex> msg.tool_calls
[%{id: "call_1", name: "search", arguments: %{"query" => "elixir"}}]
Extract text content from a message.
Examples
iex> message = Message.user("Hello world")
iex> Message.extract_text(message)
"Hello world"
iex> parts = [ContentPart.text("Hi"), ContentPart.image_url("https://example.com/i.png")]
iex> Message.extract_text(%Message{role: :user, content: parts})
"Hi"
Check if message is from assistant.
Examples
iex> Message.from_assistant?(Message.assistant("hello"))
true
iex> Message.from_assistant?(Message.user("hi"))
false
Convert from legacy tuple format.
Examples
iex> msg = Message.from_legacy({:user_prompt, "Hello"})
iex> {msg.role, msg.content}
{:user, "Hello"}
iex> msg = Message.from_legacy({:system_prompt, "Instructions"})
iex> {msg.role, msg.content}
{:system, "Instructions"}
iex> msg = Message.from_legacy({:tool_return, %{call_id: "call_1", result: "42"}})
iex> {msg.role, msg.tool_call_id, msg.content}
{:tool, "call_1", "42"}
Check if message is from user.
Examples
iex> Message.from_user?(Message.user("hello"))
true
iex> Message.from_user?(Message.assistant("hi"))
false
@spec get_content_parts(t()) :: [Nous.Message.ContentPart.t()]
Get message content as ContentPart list.
Always returns a list, converting string content to text parts.
Examples
iex> Message.get_content_parts(Message.user("Hello"))
[%ContentPart{type: :text, content: "Hello"}]
Get metadata from a message.
Examples
iex> message = Message.user("hello", metadata: %{source: "api"})
iex> Message.get_metadata(message, :source)
"api"
Check if message has tool calls.
Examples
iex> message = Message.assistant("Hello")
iex> Message.has_tool_calls?(message)
false
iex> message = Message.assistant("Search", tool_calls: [%{id: "call_1", name: "search"}])
iex> Message.has_tool_calls?(message)
true
Check if message is system instruction.
Examples
iex> Message.is_system?(Message.system("You are helpful"))
true
iex> Message.is_system?(Message.user("hi"))
false
@spec new(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
Create a new message.
Returns {:ok, message} on success or {:error, changeset} on validation failure.
Examples
iex> {:ok, msg} = Message.new(%{role: :user, content: "Hello"})
iex> {msg.role, msg.content}
{:user, "Hello"}
iex> {:error, changeset} = Message.new(%{role: :invalid})
iex> changeset.valid?
false
Create a new message, raising on validation failure.
Examples
iex> msg = Message.new!(%{role: :user, content: "Hello"})
iex> {msg.role, msg.content}
{:user, "Hello"}
Add metadata to a message.
Examples
iex> message = Message.user("hello")
iex> message = Message.put_metadata(message, :source, "web_ui")
iex> message.metadata
%{source: "web_ui"}
Split messages into {system_prompt, other_messages}.
Providers that take the system prompt out-of-band (Anthropic, Gemini) use
this before converting the remaining messages. Multiple system messages are
joined with blank lines; no system messages yields nil.
Examples
iex> {system, rest} = Message.split_system([Message.system("Be helpful"), Message.user("Hi")])
iex> {system, Enum.map(rest, & &1.content)}
{"Be helpful", ["Hi"]}
iex> {system, rest} = Message.split_system([Message.user("Hi")])
iex> {system, length(rest)}
{nil, 1}
@spec system( String.t() | [Nous.Message.ContentPart.t()], keyword() ) :: t()
Create a system message.
System messages provide instructions and context for the AI model.
Examples
iex> msg = Message.system("You are a helpful assistant")
iex> {msg.role, msg.content}
{:system, "You are a helpful assistant"}
Convert message content to plain text representation.
Examples
iex> message = Message.user([
...> ContentPart.text("Check this out: "),
...> ContentPart.image_url("https://example.com/img.jpg")
...> ])
iex> Message.to_text(message)
"Check this out: [Image: https://example.com/img.jpg]"
Create a tool result message.
Tool messages contain the results of tool/function executions.
Examples
iex> msg = Message.tool("call_123", "Search results: 42", name: "search")
iex> {msg.role, msg.tool_call_id, msg.name, msg.content}
{:tool, "call_123", "search", "Search results: 42"}
@spec user( String.t() | [Nous.Message.ContentPart.t()], keyword() ) :: t()
Create a user message.
User messages contain input, queries, and multi-modal content.
Examples
iex> msg = Message.user("Hello!")
iex> {msg.role, msg.content}
{:user, "Hello!"}
iex> msg = Message.user([ContentPart.text("Hi "), ContentPart.image_url("https://example.com/i.png")])
iex> msg.content
"Hi [Image: https://example.com/i.png]"
iex> Enum.map(msg.metadata.content_parts, & &1.type)
[:text, :image_url]