ask-core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -2
- data/lib/ask/content.rb +200 -0
- data/lib/ask/conversation.rb +114 -13
- data/lib/ask/version.rb +1 -1
- data/lib/ask.rb +2 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 07cf04e5db86d1aa2ed63aad10f47608619f320e00af7d730b0cd5b950f347ff
|
|
4
|
+
data.tar.gz: 99ef57dd70f631d3bfa024eb03b63279df7d251cab2afa68988cecd66140bc92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62ca489c64a4b6ef8e89603e9a4d0442d7b9a4c5c54e61cbb46dc3803046d1b120e2b5e061c6826ee0ad1b2fc8cec9618f6fe3ec5587970efdd99a29d46b89af
|
|
7
|
+
data.tar.gz: c228dc1e27f86a24eddc30b60e928dbdf2ca59ee45feb441954b07c91787ba55c825d3d537fb6cca7fba312908100d220ab63ab283d5b6736bb0fe60d1248ba3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.7.0] — 2026-07-26
|
|
2
2
|
|
|
3
3
|
### Added
|
|
4
4
|
|
|
5
|
-
-
|
|
5
|
+
- **Multi-modal content types** — `Ask::Content` module with `Text`, `Image`, `Audio`, `Video`, and `File` value objects. Each is frozen, comparable, and serializable via `#to_h`.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
Ask::Content::Text.new("What's in this image?")
|
|
9
|
+
Ask::Content::Image.new(url: "https://example.com/photo.jpg", mime_type: "image/jpeg")
|
|
10
|
+
Ask::Content::Audio.new(url: "https://example.com/audio.mp3", mime_type: "audio/mpeg")
|
|
11
|
+
Ask::Content::Video.new(url: "https://example.com/video.mp4", mime_type: "video/mp4")
|
|
12
|
+
Ask::Content::File.new(data: "file content", mime_type: "text/plain", filename: "notes.txt")
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **`Ask::Message#content_blocks`** — `Message` now accepts an Array of `Ask::Content` objects as `content:`. The `#content` accessor still returns the plain text for backward compatibility. `#content_blocks` returns the structured blocks, `#multimodal?` checks for non-text blocks.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
msg = Ask::Message.new(role: :user, content: [
|
|
19
|
+
Ask::Content::Text.new("What's in this image?"),
|
|
20
|
+
Ask::Content::Image.new(url: "https://example.com/photo.jpg", mime_type: "image/jpeg")
|
|
21
|
+
])
|
|
22
|
+
msg.multimodal? # => true
|
|
23
|
+
msg.content_blocks # => [Text("What's in this image?"), Image(...)]
|
|
24
|
+
msg.content # => "What's in this image?"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **`Ask::Conversation`** — `#user` and `#system` now accept arrays of content blocks directly. `#dup` correctly handles content blocks.
|
|
28
|
+
|
|
29
|
+
- **`Ask::Content::Block`** — shared base module for all content types. Content blocks respond to `#to_h` for serialization.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- `Ask::Message#to_h` now serializes content blocks as an array of typed hashes (`{ type: "text", text: "..." }`, `{ type: "image", url: "...", mime_type: "..." }`, etc.) when the message has content_blocks.
|
|
34
|
+
- `Ask::Message#inspect` updated to show `multimodal`, `rich`, or `text` label.
|
|
35
|
+
|
|
36
|
+
### Tested
|
|
37
|
+
|
|
38
|
+
- 311 tests, 611 assertions, 0 failures
|
|
39
|
+
- 69 new tests for Content types, multi-modal messages, serialization, equality, and Conversation integration
|
|
40
|
+
|
|
41
|
+
## [0.6.0] — 2026-07-26: loaded by document loaders, split by text splitters, embedded and retrieved by vector stores. Immutable, equality-checked by content+metadata, and JSON-serializable.
|
|
6
42
|
|
|
7
43
|
```ruby
|
|
8
44
|
doc = Ask::Document.new(
|
data/lib/ask/content.rb
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
# Content types for multi-modal messages.
|
|
5
|
+
#
|
|
6
|
+
# These are frozen value objects representing different kinds of content
|
|
7
|
+
# that can appear in an {Ask::Message}. A message's {Message#content_blocks
|
|
8
|
+
# content_blocks} may contain multiple content objects of different types,
|
|
9
|
+
# allowing text, images, audio, video, and files to be interleaved naturally.
|
|
10
|
+
#
|
|
11
|
+
# All content types are frozen value objects with structural equality.
|
|
12
|
+
# They carry the minimum fields needed for provider-agnostic use; each
|
|
13
|
+
# provider serializes them to its own wire format.
|
|
14
|
+
#
|
|
15
|
+
# @example Text
|
|
16
|
+
# Ask::Content::Text.new("What's in this image?")
|
|
17
|
+
#
|
|
18
|
+
# @example Image from URL
|
|
19
|
+
# Ask::Content::Image.new(url: "https://example.com/photo.jpg",
|
|
20
|
+
# mime_type: "image/jpeg")
|
|
21
|
+
#
|
|
22
|
+
# @example Multi-modal message
|
|
23
|
+
# msg = Ask::Message.new(role: :user, content: [
|
|
24
|
+
# Ask::Content::Text.new("What's in this image?"),
|
|
25
|
+
# Ask::Content::Image.new(url: "https://example.com/photo.jpg",
|
|
26
|
+
# mime_type: "image/jpeg")
|
|
27
|
+
# ])
|
|
28
|
+
#
|
|
29
|
+
module Content
|
|
30
|
+
# Base module for all content types.
|
|
31
|
+
module Block
|
|
32
|
+
def to_h
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def inspect
|
|
37
|
+
"#<#{self.class.name}#{inspect_fields}>"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def inspect_fields
|
|
43
|
+
""
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A text block within a multi-modal message.
|
|
48
|
+
class Text
|
|
49
|
+
include Block
|
|
50
|
+
|
|
51
|
+
# @return [String] the text content
|
|
52
|
+
attr_reader :text
|
|
53
|
+
|
|
54
|
+
# @param text [String] the text content
|
|
55
|
+
def initialize(text)
|
|
56
|
+
@text = text
|
|
57
|
+
freeze
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ==(other)
|
|
61
|
+
other.is_a?(Text) && @text == other.text
|
|
62
|
+
end
|
|
63
|
+
alias eql? ==
|
|
64
|
+
|
|
65
|
+
def hash
|
|
66
|
+
@text.hash
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_h
|
|
70
|
+
{ type: "text", text: @text }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def inspect_fields
|
|
76
|
+
" #{@text.inspect}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Base class for media content types (Image, Audio, Video).
|
|
81
|
+
# All have optional +url+, +base64+, +mime_type+, and +file_id+ fields.
|
|
82
|
+
class Media
|
|
83
|
+
include Block
|
|
84
|
+
|
|
85
|
+
# @return [String, nil] URL of the media
|
|
86
|
+
attr_reader :url
|
|
87
|
+
|
|
88
|
+
# @return [String, nil] Base64-encoded media data
|
|
89
|
+
attr_reader :base64
|
|
90
|
+
|
|
91
|
+
# @return [String, nil] MIME type
|
|
92
|
+
attr_reader :mime_type
|
|
93
|
+
|
|
94
|
+
# @return [String, nil] Provider-managed file ID
|
|
95
|
+
attr_reader :file_id
|
|
96
|
+
|
|
97
|
+
# @param url [String, nil] URL of the media
|
|
98
|
+
# @param base64 [String, nil] Base64-encoded data
|
|
99
|
+
# @param mime_type [String, nil] MIME type
|
|
100
|
+
# @param file_id [String, nil] Provider-managed file ID
|
|
101
|
+
def initialize(url: nil, base64: nil, mime_type: nil, file_id: nil)
|
|
102
|
+
@url = url
|
|
103
|
+
@base64 = base64
|
|
104
|
+
@mime_type = mime_type
|
|
105
|
+
@file_id = file_id
|
|
106
|
+
freeze
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def ==(other)
|
|
110
|
+
other.is_a?(Media) && @url == other.url && @base64 == other.base64 &&
|
|
111
|
+
@mime_type == other.mime_type && @file_id == other.file_id
|
|
112
|
+
end
|
|
113
|
+
alias eql? ==
|
|
114
|
+
|
|
115
|
+
def hash
|
|
116
|
+
[@url, @base64, @mime_type, @file_id].hash
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def to_h
|
|
120
|
+
h = { type: media_type }
|
|
121
|
+
h[:url] = @url if @url
|
|
122
|
+
h[:base64] = @base64 if @base64
|
|
123
|
+
h[:mime_type] = @mime_type if @mime_type
|
|
124
|
+
h[:file_id] = @file_id if @file_id
|
|
125
|
+
h
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def media_type
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# An image block within a multi-modal message.
|
|
136
|
+
class Image < Media
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def media_type
|
|
140
|
+
"image"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# An audio block within a multi-modal message.
|
|
145
|
+
class Audio < Media
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def media_type
|
|
149
|
+
"audio"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# A video block within a multi-modal message.
|
|
154
|
+
class Video < Media
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
def media_type
|
|
158
|
+
"video"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# An inline file block within a multi-modal message.
|
|
163
|
+
class File
|
|
164
|
+
include Block
|
|
165
|
+
|
|
166
|
+
# @return [String] the file content
|
|
167
|
+
attr_reader :data
|
|
168
|
+
|
|
169
|
+
# @return [String, nil] MIME type
|
|
170
|
+
attr_reader :mime_type
|
|
171
|
+
|
|
172
|
+
# @return [String, nil] original filename
|
|
173
|
+
attr_reader :filename
|
|
174
|
+
|
|
175
|
+
# @param data [String] the file content
|
|
176
|
+
# @param mime_type [String, nil] MIME type
|
|
177
|
+
# @param filename [String, nil] original filename
|
|
178
|
+
def initialize(data:, mime_type: nil, filename: nil)
|
|
179
|
+
@data = data
|
|
180
|
+
@mime_type = mime_type
|
|
181
|
+
@filename = filename
|
|
182
|
+
freeze
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def ==(other)
|
|
186
|
+
other.is_a?(File) && @data == other.data &&
|
|
187
|
+
@mime_type == other.mime_type && @filename == other.filename
|
|
188
|
+
end
|
|
189
|
+
alias eql? ==
|
|
190
|
+
|
|
191
|
+
def hash
|
|
192
|
+
[@data, @mime_type, @filename].hash
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def to_h
|
|
196
|
+
{ type: "file", data: @data, mime_type: @mime_type, filename: @filename }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
data/lib/ask/conversation.rb
CHANGED
|
@@ -9,9 +9,16 @@ module Ask
|
|
|
9
9
|
# @return [Symbol] message role (:system, :user, :assistant, :tool)
|
|
10
10
|
attr_reader :role
|
|
11
11
|
|
|
12
|
-
# @return [String, nil] message text content
|
|
12
|
+
# @return [String, nil] message text content. For multi-modal messages
|
|
13
|
+
# this is the concatenation of all text blocks. See {#content_blocks}
|
|
14
|
+
# for the full structured content.
|
|
13
15
|
attr_reader :content
|
|
14
16
|
|
|
17
|
+
# @return [Array<Ask::Content::Text, Ask::Content::Image, ...>, nil]
|
|
18
|
+
# structured content blocks for multi-modal messages. +nil+ when the
|
|
19
|
+
# message was created with a plain string +content:+ (backward compatible).
|
|
20
|
+
attr_reader :content_blocks
|
|
21
|
+
|
|
15
22
|
# @return [String, nil] optional participant name (for multi-agent scenarios)
|
|
16
23
|
attr_reader :name
|
|
17
24
|
|
|
@@ -24,17 +31,45 @@ module Ask
|
|
|
24
31
|
# @return [Hash] arbitrary metadata attached to this message
|
|
25
32
|
attr_reader :metadata
|
|
26
33
|
|
|
34
|
+
# Create a new message.
|
|
35
|
+
#
|
|
36
|
+
# @param role [Symbol, String] message role
|
|
37
|
+
# @param content [String, Array<Ask::Content>, nil] message text or array
|
|
38
|
+
# of content blocks. When an Array is given, each element must be one of
|
|
39
|
+
# the {Ask::Content} types. +content+ will be set to the plain text
|
|
40
|
+
# representation; use {#content_blocks} to access the structured blocks.
|
|
41
|
+
# @param name [String, nil] participant name
|
|
42
|
+
# @param tool_call_id [String, nil] tool call ID this message responds to
|
|
43
|
+
# @param tool_calls [Array<Hash>, nil] tool call invocations
|
|
44
|
+
# @param metadata [Hash] arbitrary metadata
|
|
27
45
|
def initialize(role:, content: nil, name: nil, tool_call_id: nil, tool_calls: nil, metadata: {})
|
|
28
46
|
@role = normalize_role!(role)
|
|
29
|
-
@content = content
|
|
30
47
|
@name = normalize_name(name)
|
|
31
48
|
@tool_call_id = tool_call_id
|
|
32
49
|
@tool_calls = tool_calls
|
|
33
50
|
@metadata = metadata.dup.freeze
|
|
51
|
+
|
|
52
|
+
if content.is_a?(Array)
|
|
53
|
+
validate_content_blocks!(content)
|
|
54
|
+
@content_blocks = content.map(&:freeze).freeze
|
|
55
|
+
@content = extract_text_from_blocks(@content_blocks)
|
|
56
|
+
else
|
|
57
|
+
@content = content
|
|
58
|
+
@content_blocks = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
34
61
|
validate!
|
|
35
62
|
freeze
|
|
36
63
|
end
|
|
37
64
|
|
|
65
|
+
# @return [Boolean] true if this message contains non-text content blocks
|
|
66
|
+
# (images, audio, video, or files).
|
|
67
|
+
def multimodal?
|
|
68
|
+
return false unless @content_blocks
|
|
69
|
+
|
|
70
|
+
@content_blocks.any? { |b| !b.is_a?(Content::Text) && b.is_a?(Content::Block) }
|
|
71
|
+
end
|
|
72
|
+
|
|
38
73
|
# @return [Boolean] true if this message contains tool calls
|
|
39
74
|
def tool_call? = @tool_calls&.any? == true
|
|
40
75
|
|
|
@@ -54,16 +89,24 @@ module Ask
|
|
|
54
89
|
def tool? = @role == :tool
|
|
55
90
|
|
|
56
91
|
# Convert to a hash suitable for provider wire format serialization.
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
92
|
+
# For multi-modal messages, +:content+ is an array of content block hashes.
|
|
93
|
+
# For plain text messages, +:content+ is a string (backward compatible).
|
|
94
|
+
#
|
|
95
|
+
# Each content block hash includes a +:type+ key for discrimination.
|
|
96
|
+
# Providers can use these to build their wire format.
|
|
97
|
+
#
|
|
60
98
|
# @return [Hash]
|
|
61
99
|
def to_h
|
|
62
100
|
base = { role: @role }
|
|
63
|
-
base[:content] = @content if @content
|
|
64
101
|
base[:name] = @name if @name
|
|
65
102
|
base[:tool_call_id] = @tool_call_id if @tool_call_id
|
|
66
103
|
|
|
104
|
+
if @content_blocks
|
|
105
|
+
base[:content] = @content_blocks.map(&method(:serialize_content_block))
|
|
106
|
+
else
|
|
107
|
+
base[:content] = @content if @content
|
|
108
|
+
end
|
|
109
|
+
|
|
67
110
|
if @tool_calls
|
|
68
111
|
base[:tool_calls] = @tool_calls.is_a?(Array) ? @tool_calls : @tool_calls.map do |id_val, tc|
|
|
69
112
|
tc_id = if tc.respond_to?(:id)
|
|
@@ -98,27 +141,62 @@ module Ask
|
|
|
98
141
|
base
|
|
99
142
|
end
|
|
100
143
|
|
|
101
|
-
# @return [Boolean] true if role, content, name, and tool metadata all match
|
|
144
|
+
# @return [Boolean] true if role, content/block, name, and tool metadata all match
|
|
102
145
|
def ==(other)
|
|
103
146
|
return false unless other.is_a?(Message)
|
|
104
147
|
|
|
105
148
|
@role == other.role && @content == other.content &&
|
|
149
|
+
@content_blocks == other.content_blocks &&
|
|
106
150
|
@name == other.name && @tool_call_id == other.tool_call_id &&
|
|
107
151
|
@tool_calls == other.tool_calls
|
|
108
152
|
end
|
|
109
153
|
alias eql? ==
|
|
110
154
|
|
|
111
155
|
def hash
|
|
112
|
-
[@role, @content, @name, @tool_call_id, @tool_calls].hash
|
|
156
|
+
[@role, @content, @content_blocks, @name, @tool_call_id, @tool_calls].hash
|
|
113
157
|
end
|
|
114
158
|
|
|
115
159
|
# @return [String]
|
|
116
160
|
def inspect
|
|
117
|
-
|
|
161
|
+
label = multimodal? ? "multimodal" : (@content_blocks ? "rich" : "text")
|
|
162
|
+
preview = if @content_blocks
|
|
163
|
+
blk = @content_blocks.find { |b| b.is_a?(Content::Text) }
|
|
164
|
+
blk ? preview_text(blk.text) : label
|
|
165
|
+
elsif @content
|
|
166
|
+
@content.length > 57 ? "#{@content[0,57]}..." : @content
|
|
167
|
+
end
|
|
168
|
+
"#<Ask::Message role=#{@role.inspect} #{label} #{preview.inspect}>"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def preview_text(text)
|
|
172
|
+
text.length > 57 ? "#{text[0, 57]}..." : text
|
|
118
173
|
end
|
|
119
174
|
|
|
120
175
|
private
|
|
121
176
|
|
|
177
|
+
def serialize_content_block(block)
|
|
178
|
+
block.respond_to?(:to_h) ? block.to_h : { type: "unknown", data: block.to_s }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def validate_content_blocks!(blocks)
|
|
182
|
+
raise ArgumentError, "Content blocks must be an Array" unless blocks.is_a?(Array)
|
|
183
|
+
raise ArgumentError, "Content blocks cannot be empty" if blocks.empty?
|
|
184
|
+
|
|
185
|
+
blocks.each_with_index do |block, idx|
|
|
186
|
+
next if block.is_a?(Content::Block)
|
|
187
|
+
|
|
188
|
+
raise ArgumentError,
|
|
189
|
+
"Invalid content block at index #{idx}: " \
|
|
190
|
+
"expected an Ask::Content type (Text, Image, Audio, Video, or File), " \
|
|
191
|
+
"got #{block.class}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def extract_text_from_blocks(blocks)
|
|
196
|
+
texts = blocks.select { |b| b.is_a?(Content::Text) }.map(&:text)
|
|
197
|
+
texts.empty? ? nil : texts.join("\n")
|
|
198
|
+
end
|
|
199
|
+
|
|
122
200
|
def normalize_role!(role)
|
|
123
201
|
sym = role.to_s.downcase.to_sym
|
|
124
202
|
raise InvalidRole, "Invalid role: #{role.inspect}. Valid: #{VALID_ROLES.join(', ')}" unless VALID_ROLES.include?(sym)
|
|
@@ -164,15 +242,23 @@ module Ask
|
|
|
164
242
|
alias add <<
|
|
165
243
|
|
|
166
244
|
# Add a system message.
|
|
167
|
-
# @param text [String] message content
|
|
245
|
+
# @param text [String, Array<Ask::Content>] message content or content blocks
|
|
168
246
|
# @return [self]
|
|
169
247
|
def system(text, **options)
|
|
170
248
|
self << Message.new(role: :system, content: text, **options)
|
|
171
249
|
end
|
|
172
250
|
|
|
173
251
|
# Add a user message.
|
|
174
|
-
# @param text [String] message content
|
|
252
|
+
# @param text [String, Array<Ask::Content>] message content or content blocks.
|
|
253
|
+
# Pass an Array of {Ask::Content} objects for multi-modal messages.
|
|
175
254
|
# @return [self]
|
|
255
|
+
# @example Plain text
|
|
256
|
+
# conv.user("Hello")
|
|
257
|
+
# @example Multi-modal
|
|
258
|
+
# conv.user([
|
|
259
|
+
# Ask::Content::Text.new("What's in this image?"),
|
|
260
|
+
# Ask::Content::Image.new(url: "https://example.com/photo.jpg", mime_type: "image/jpeg")
|
|
261
|
+
# ])
|
|
176
262
|
def user(text, **options)
|
|
177
263
|
self << Message.new(role: :user, content: text, **options)
|
|
178
264
|
end
|
|
@@ -260,7 +346,16 @@ module Ask
|
|
|
260
346
|
# Deep copy of this conversation.
|
|
261
347
|
# @return [Ask::Conversation]
|
|
262
348
|
def dup
|
|
263
|
-
Conversation.new(@messages.map { |m|
|
|
349
|
+
Conversation.new(@messages.map { |m|
|
|
350
|
+
opts = { role: m.role, name: m.name, tool_call_id: m.tool_call_id,
|
|
351
|
+
tool_calls: m.tool_calls, metadata: m.metadata }
|
|
352
|
+
if m.content_blocks
|
|
353
|
+
opts[:content] = m.content_blocks.dup
|
|
354
|
+
else
|
|
355
|
+
opts[:content] = m.content
|
|
356
|
+
end
|
|
357
|
+
Message.new(**opts)
|
|
358
|
+
})
|
|
264
359
|
end
|
|
265
360
|
|
|
266
361
|
# @return [String]
|
|
@@ -271,7 +366,13 @@ module Ask
|
|
|
271
366
|
private
|
|
272
367
|
|
|
273
368
|
def build_message(attrs)
|
|
274
|
-
attrs.is_a?(Hash)
|
|
369
|
+
if attrs.is_a?(Hash)
|
|
370
|
+
Message.new(**attrs)
|
|
371
|
+
elsif attrs.is_a?(Array)
|
|
372
|
+
Message.new(role: :user, content: attrs)
|
|
373
|
+
else
|
|
374
|
+
Message.new(role: :user, content: attrs.to_s)
|
|
375
|
+
end
|
|
275
376
|
end
|
|
276
377
|
end
|
|
277
378
|
end
|
data/lib/ask/version.rb
CHANGED
data/lib/ask.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "ask/version"
|
|
|
6
6
|
#
|
|
7
7
|
# Ask::Provider is the abstract base class for LLM providers.
|
|
8
8
|
# Ask::Conversation is a message container with role normalization.
|
|
9
|
+
# Ask::Content provides multi-modal content types (Text, Image, Audio, Video, File).
|
|
9
10
|
# Ask::Stream provides streaming primitives for incremental responses.
|
|
10
11
|
# Ask::ModelCatalog resolves model names to provider metadata.
|
|
11
12
|
# Ask::Document is a text+metadata value object for RAG pipelines.
|
|
@@ -16,6 +17,7 @@ module Ask
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
require_relative "ask/errors"
|
|
20
|
+
require_relative "ask/content"
|
|
19
21
|
require_relative "ask/tool_def"
|
|
20
22
|
require_relative "ask/result"
|
|
21
23
|
require_relative "ask/stream"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- LICENSE
|
|
65
65
|
- README.md
|
|
66
66
|
- lib/ask.rb
|
|
67
|
+
- lib/ask/content.rb
|
|
67
68
|
- lib/ask/conversation.rb
|
|
68
69
|
- lib/ask/document.rb
|
|
69
70
|
- lib/ask/errors.rb
|