Class: Ask::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/conversation.rb

Overview

A single message in a conversation. Immutable after creation. Valid roles are: :system, :user, :assistant, :tool.

Constant Summary collapse

VALID_ROLES =
%i[system user assistant tool].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(role:, content: nil, name: nil, tool_call_id: nil, tool_calls: nil, metadata: {}) ⇒ Message

Create a new message.

Parameters:

  • role (Symbol, String)

    message role

  • content (String, Array<Ask::Content>, nil) (defaults to: nil)

    message text or array of content blocks. When an Array is given, each element must be one of the Content types. content will be set to the plain text representation; use #content_blocks to access the structured blocks.

  • name (String, nil) (defaults to: nil)

    participant name

  • tool_call_id (String, nil) (defaults to: nil)

    tool call ID this message responds to

  • tool_calls (Array<Hash>, nil) (defaults to: nil)

    tool call invocations

  • metadata (Hash) (defaults to: {})

    arbitrary metadata



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ask/conversation.rb', line 45

def initialize(role:, content: nil, name: nil, tool_call_id: nil, tool_calls: nil, metadata: {})
  @role = normalize_role!(role)
  @name = normalize_name(name)
  @tool_call_id = tool_call_id
  @tool_calls = tool_calls
  @metadata = .dup.freeze

  if content.is_a?(Array)
    validate_content_blocks!(content)
    @content_blocks = content.map(&:freeze).freeze
    @content = extract_text_from_blocks(@content_blocks)
  else
    @content = content
    @content_blocks = nil
  end

  validate!
  freeze
end

Instance Attribute Details

#contentString? (readonly)

Returns message text content. For multi-modal messages this is the concatenation of all text blocks. See #content_blocks for the full structured content.

Returns:

  • (String, nil)

    message text content. For multi-modal messages this is the concatenation of all text blocks. See #content_blocks for the full structured content.



15
16
17
# File 'lib/ask/conversation.rb', line 15

def content
  @content
end

#content_blocksArray<Ask::Content::Text, Ask::Content::Image, ...>? (readonly)

Returns structured content blocks for multi-modal messages. nil when the message was created with a plain string content: (backward compatible).

Returns:

  • (Array<Ask::Content::Text, Ask::Content::Image, ...>, nil)

    structured content blocks for multi-modal messages. nil when the message was created with a plain string content: (backward compatible).



20
21
22
# File 'lib/ask/conversation.rb', line 20

def content_blocks
  @content_blocks
end

#metadataHash (readonly)

Returns arbitrary metadata attached to this message.

Returns:

  • (Hash)

    arbitrary metadata attached to this message



32
33
34
# File 'lib/ask/conversation.rb', line 32

def 
  @metadata
end

#nameString? (readonly)

Returns optional participant name (for multi-agent scenarios).

Returns:

  • (String, nil)

    optional participant name (for multi-agent scenarios)



23
24
25
# File 'lib/ask/conversation.rb', line 23

def name
  @name
end

#roleSymbol (readonly)

Returns message role (:system, :user, :assistant, :tool).

Returns:

  • (Symbol)

    message role (:system, :user, :assistant, :tool)



10
11
12
# File 'lib/ask/conversation.rb', line 10

def role
  @role
end

#tool_call_idString? (readonly)

Returns tool call ID this message is responding to.

Returns:

  • (String, nil)

    tool call ID this message is responding to



26
27
28
# File 'lib/ask/conversation.rb', line 26

def tool_call_id
  @tool_call_id
end

#tool_callsArray<Hash>? (readonly)

Returns tool calls included in this message.

Returns:

  • (Array<Hash>, nil)

    tool calls included in this message



29
30
31
# File 'lib/ask/conversation.rb', line 29

def tool_calls
  @tool_calls
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Returns true if role, content/block, name, and tool metadata all match.

Returns:

  • (Boolean)

    true if role, content/block, name, and tool metadata all match



145
146
147
148
149
150
151
152
# File 'lib/ask/conversation.rb', line 145

def ==(other)
  return false unless other.is_a?(Message)

  @role == other.role && @content == other.content &&
    @content_blocks == other.content_blocks &&
    @name == other.name && @tool_call_id == other.tool_call_id &&
    @tool_calls == other.tool_calls
end

#assistant?Boolean

Returns true if role is :assistant.

Returns:

  • (Boolean)

    true if role is :assistant



86
# File 'lib/ask/conversation.rb', line 86

def assistant? = @role == :assistant

#hashObject



155
156
157
# File 'lib/ask/conversation.rb', line 155

def hash
  [@role, @content, @content_blocks, @name, @tool_call_id, @tool_calls].hash
end

#inspectString

Returns:

  • (String)


160
161
162
163
164
165
166
167
168
169
# File 'lib/ask/conversation.rb', line 160

def inspect
  label = multimodal? ? "multimodal" : (@content_blocks ? "rich" : "text")
  preview = if @content_blocks
    blk = @content_blocks.find { |b| b.is_a?(Content::Text) }
    blk ? preview_text(blk.text) : label
  elsif @content
    @content.length > 57 ? "#{@content[0,57]}..." : @content
  end
  "#<Ask::Message role=#{@role.inspect} #{label} #{preview.inspect}>"
end

#multimodal?Boolean

Returns true if this message contains non-text content blocks (images, audio, video, or files).

Returns:

  • (Boolean)

    true if this message contains non-text content blocks (images, audio, video, or files).



67
68
69
70
71
# File 'lib/ask/conversation.rb', line 67

def multimodal?
  return false unless @content_blocks

  @content_blocks.any? { |b| !b.is_a?(Content::Text) && b.is_a?(Content::Block) }
end

#preview_text(text) ⇒ Object



171
172
173
# File 'lib/ask/conversation.rb', line 171

def preview_text(text)
  text.length > 57 ? "#{text[0, 57]}..." : text
end

#system?Boolean

Returns true if role is :system.

Returns:

  • (Boolean)

    true if role is :system



80
# File 'lib/ask/conversation.rb', line 80

def system? = @role == :system

#to_hHash

Convert to a hash suitable for provider wire format serialization. For multi-modal messages, :content is an array of content block hashes. For plain text messages, :content is a string (backward compatible).

Each content block hash includes a :type key for discrimination. Providers can use these to build their wire format.

Returns:

  • (Hash)


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ask/conversation.rb', line 99

def to_h
  base = { role: @role }
  base[:name] = @name if @name
  base[:tool_call_id] = @tool_call_id if @tool_call_id

  if @content_blocks
    base[:content] = @content_blocks.map(&method(:serialize_content_block))
  else
    base[:content] = @content if @content
  end

  if @tool_calls
    base[:tool_calls] = @tool_calls.is_a?(Array) ? @tool_calls : @tool_calls.map do |id_val, tc|
      tc_id = if tc.respond_to?(:id)
        tc.id
      elsif tc.is_a?(Hash)
        tc[:id] || tc["id"] || id_val
      else
        id_val
      end

      tc_name = if tc.respond_to?(:name)
        tc.name
      elsif tc.is_a?(Hash)
        tc.dig(:function, :name) || tc.dig("function", "name") || tc[:name] || tc["name"] || id_val
      else
        id_val
      end

      tc_args = if tc.respond_to?(:arguments)
        tc.arguments.is_a?(String) ? tc.arguments : JSON.generate(tc.arguments)
      elsif tc.is_a?(Hash)
        raw = tc.dig(:function, :arguments) || tc.dig("function", "arguments") || tc[:arguments] || tc["arguments"] || "{}"
        raw.is_a?(String) ? raw : JSON.generate(raw)
      else
        "{}"
      end

      { id: tc_id, type: "function", function: { name: tc_name, arguments: tc_args } }
    end
  end

  base
end

#tool?Boolean

Returns true if role is :tool.

Returns:

  • (Boolean)

    true if role is :tool



89
# File 'lib/ask/conversation.rb', line 89

def tool? = @role == :tool

#tool_call?Boolean

Returns true if this message contains tool calls.

Returns:

  • (Boolean)

    true if this message contains tool calls



74
# File 'lib/ask/conversation.rb', line 74

def tool_call? = @tool_calls&.any? == true

#tool_result?Boolean

Returns true if this is a tool result message.

Returns:

  • (Boolean)

    true if this is a tool result message



77
# File 'lib/ask/conversation.rb', line 77

def tool_result? = !@tool_call_id.nil?

#user?Boolean

Returns true if role is :user.

Returns:

  • (Boolean)

    true if role is :user



83
# File 'lib/ask/conversation.rb', line 83

def user? = @role == :user