LLM.Message (LLM v0.1.3)

Copy Markdown View Source

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_id linking back to the call)
  • :system — system instructions (set via the :system option, 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

t()

@type t() :: %LLM.Message{
  content: String.t() | nil,
  name: String.t() | nil,
  role: :system | :developer | :user | :assistant | :tool,
  thinking: thinking() | nil,
  tool_call_id: String.t() | nil,
  tools: [tool_call()] | nil,
  usage: LLM.Usage.t() | nil
}

thinking()

@type thinking() :: String.t() | %{text: String.t(), signature: String.t()}

tool_call()

@type tool_call() :: %{id: String.t(), name: String.t(), args: map()}

Functions

new(text)

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"})

new(role, content)

Create a message with explicit role and string content.