Sagents.Message.DisplayHelpers (Sagents v0.8.0-rc.5)

Copy Markdown

Utilities for extracting displayable content from LangChain Messages.

These helpers bridge the gap between LangChain Message structs and application-specific display schemas. They handle the complexity of:

  • Extracting text, thinking, tool_calls, and tool_results
  • Proper sequencing when a single Message contains multiple display items
  • Converting structs to maps with string keys (JSON-compatible)

Usage in Generated Code

Mix task templates should demonstrate this pattern:

# In your LiveView or context module
def persist_message(%Message{} = message, conversation_id) do
  message
  |> DisplayHelpers.extract_display_items()
  |> Enum.with_index()
  |> Enum.map(fn {item, sequence} ->
    attrs = Map.put(item, "sequence", sequence)
    create_display_message(conversation_id, attrs)
  end)
end

This gives users full control over their schema while providing library utilities that handle the extraction complexity.

Summary

Functions

Extracts all displayable items from a Message.

Functions

extract_display_items(message)

@spec extract_display_items(LangChain.Message.t()) :: [map()]

Extracts all displayable items from a Message.

Returns a list of maps, each representing one displayable item. A single Message can produce multiple items (e.g., text + tool_calls).

Return Format

Each map contains atom keys:

  • :type - One of: :text, :thinking, :tool_call, :tool_result
  • :message_type - Role-based: :user, :assistant, :tool, :system
  • :content - Map with type-specific content (string keys for JSONB storage)

The order of items in the list represents the display order. The caller should assign sequence numbers (0, 1, 2, ...) when persisting.

Note: No mixed maps - top-level keys are atoms, content payload uses string keys.

Examples

# Assistant message with text and tool calls
message = Message.new_assistant!(%{
  content: [ContentPart.text!("Let me search...")],
  tool_calls: [
    ToolCall.new!(%{call_id: "1", name: "search", arguments: %{q: "elixir"}}),
    ToolCall.new!(%{call_id: "2", name: "weather", arguments: %{city: "NYC"}})
  ]
})

DisplayHelpers.extract_display_items(message)
# => [
#   %{type: :text, message_type: :assistant, content: %{"text" => "Let me search..."}},
#   %{type: :tool_call, message_type: :assistant, content: %{"call_id" => "1", "name" => "search", "arguments" => %{q: "elixir"}}},
#   %{type: :tool_call, message_type: :assistant, content: %{"call_id" => "2", "name" => "weather", "arguments" => %{city: "NYC"}}}
# ]

# Tool result message with multiple results
message = Message.new_tool_result!(%{
  tool_results: [
    ToolResult.new!(%{tool_call_id: "1", name: "search", content: "Found...", is_error: false}),
    ToolResult.new!(%{tool_call_id: "2", name: "weather", content: "Sunny", is_error: false})
  ]
})

DisplayHelpers.extract_display_items(message)
# => [
#   %{type: :tool_result, message_type: :tool, content: %{"tool_call_id" => "1", "name" => "search", "content" => "Found...", "is_error" => false}},
#   %{type: :tool_result, message_type: :tool, content: %{"tool_call_id" => "2", "name" => "weather", "content" => "Sunny", "is_error" => false}}
# ]