Current section
Files
Jump to
Current section
Files
lib/condukt/message.ex
defmodule Condukt.Message do
@moduledoc """
Represents a message in the conversation history.
Messages can be from the user, the assistant, or represent tool results.
Assistant messages may include tool calls and their results.
## Message Roles
- `:user` - A message from the user
- `:assistant` - A response from the LLM
- `:tool_result` - The result of a tool execution
## Content Blocks
Assistant messages can contain multiple content blocks:
- `{:text, string}` - Plain text
- `{:thinking, string}` - Model's reasoning (if thinking enabled)
- `{:tool_call, id, name, args}` - A tool invocation
"""
@enforce_keys [:role, :content]
defstruct [:role, :content, :tool_call_id, images: [], timestamp: nil]
@doc """
Creates a new user message.
## Examples
Message.user("Hello!")
Message.user("What's in this image?", [%{type: :base64, media_type: "image/png", data: "..."}])
"""
def user(text, images \\ []) do
%__MODULE__{
role: :user,
content: text,
images: images,
timestamp: DateTime.utc_now()
}
end
@doc """
Creates a new assistant message.
Content can be a string or a list of content blocks.
"""
def assistant(content) do
%__MODULE__{
role: :assistant,
content: content,
timestamp: DateTime.utc_now()
}
end
@doc """
Creates a tool result message.
The content will be JSON-encoded if not a string.
"""
def tool_result(tool_call_id, content) do
%__MODULE__{
role: :tool_result,
content: content,
tool_call_id: tool_call_id,
timestamp: DateTime.utc_now()
}
end
@doc """
Extracts plain text from a message's content.
Returns the concatenated text blocks for assistant messages,
or the content directly for user messages.
"""
def text(%__MODULE__{content: content}) when is_binary(content), do: content
def text(%__MODULE__{content: blocks}) when is_list(blocks) do
blocks
|> Enum.filter(&match?({:text, _}, &1))
|> Enum.map_join("", fn {:text, text} -> text end)
end
def text(%__MODULE__{content: content}), do: inspect(content)
@doc """
Extracts the content from a tool result message for display.
"""
def tool_result_content(%__MODULE__{role: :tool_result, content: content}), do: content
@doc """
Checks if the message contains any tool calls.
"""
def has_tool_calls?(%__MODULE__{content: blocks}) when is_list(blocks) do
Enum.any?(blocks, &match?({:tool_call, _, _, _}, &1))
end
def has_tool_calls?(_), do: false
@doc """
Extracts tool calls from a message.
Returns a list of `{id, name, args}` tuples.
"""
def tool_calls(%__MODULE__{content: blocks}) when is_list(blocks) do
blocks
|> Enum.filter(&match?({:tool_call, _, _, _}, &1))
|> Enum.map(fn {:tool_call, id, name, args} -> {id, name, args} end)
end
def tool_calls(_), do: []
@doc """
Extracts thinking content from a message.
"""
def thinking(%__MODULE__{content: blocks}) when is_list(blocks) do
blocks
|> Enum.filter(&match?({:thinking, _}, &1))
|> Enum.map_join("", fn {:thinking, text} -> text end)
|> case do
"" -> nil
text -> text
end
end
def thinking(_), do: nil
end