intelligence 0.6.0 → 0.8.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/README.md +576 -0
- data/intelligence.gemspec +2 -1
- data/lib/intelligence/adapter/base.rb +13 -6
- data/lib/intelligence/adapter/class_methods.rb +15 -0
- data/lib/intelligence/adapter/module_methods.rb +41 -0
- data/lib/intelligence/adapter.rb +2 -2
- data/lib/intelligence/adapters/anthropic/adapter.rb +21 -19
- data/lib/intelligence/adapters/anthropic/chat_request_methods.rb +189 -0
- data/lib/intelligence/adapters/anthropic/{chat_methods.rb → chat_response_methods.rb} +13 -137
- data/lib/intelligence/adapters/cerebras.rb +19 -19
- data/lib/intelligence/adapters/generic/adapter.rb +4 -2
- data/lib/intelligence/adapters/generic/chat_request_methods.rb +221 -0
- data/lib/intelligence/adapters/generic/chat_response_methods.rb +234 -0
- data/lib/intelligence/adapters/generic.rb +1 -1
- data/lib/intelligence/adapters/google/adapter.rb +33 -22
- data/lib/intelligence/adapters/google/chat_request_methods.rb +234 -0
- data/lib/intelligence/adapters/google/chat_response_methods.rb +236 -0
- data/lib/intelligence/adapters/groq.rb +29 -49
- data/lib/intelligence/adapters/hyperbolic.rb +13 -39
- data/lib/intelligence/adapters/mistral.rb +21 -42
- data/lib/intelligence/adapters/open_ai/adapter.rb +39 -32
- data/lib/intelligence/adapters/open_ai/chat_request_methods.rb +186 -0
- data/lib/intelligence/adapters/open_ai/chat_response_methods.rb +239 -0
- data/lib/intelligence/adapters/open_ai.rb +1 -1
- data/lib/intelligence/adapters/open_router.rb +18 -18
- data/lib/intelligence/adapters/samba_nova.rb +16 -18
- data/lib/intelligence/adapters/together_ai.rb +25 -23
- data/lib/intelligence/conversation.rb +11 -10
- data/lib/intelligence/message.rb +45 -29
- data/lib/intelligence/message_content/base.rb +2 -9
- data/lib/intelligence/message_content/binary.rb +3 -3
- data/lib/intelligence/message_content/file.rb +3 -3
- data/lib/intelligence/message_content/text.rb +10 -2
- data/lib/intelligence/message_content/tool_call.rb +61 -5
- data/lib/intelligence/message_content/tool_result.rb +11 -6
- data/lib/intelligence/tool.rb +139 -0
- data/lib/intelligence/version.rb +1 -1
- data/lib/intelligence.rb +3 -1
- metadata +31 -13
- data/lib/intelligence/adapter/class_methods/construction.rb +0 -17
- data/lib/intelligence/adapter/module_methods/construction.rb +0 -43
- data/lib/intelligence/adapters/generic/chat_methods.rb +0 -355
- data/lib/intelligence/adapters/google/chat_methods.rb +0 -393
- data/lib/intelligence/adapters/legacy/adapter.rb +0 -11
- data/lib/intelligence/adapters/legacy/chat_methods.rb +0 -54
- data/lib/intelligence/adapters/open_ai/chat_methods.rb +0 -345
@@ -1,46 +1,48 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'chat_request_methods'
|
2
|
+
require_relative 'chat_response_methods'
|
2
3
|
|
3
4
|
module Intelligence
|
4
5
|
module Anthropic
|
5
6
|
class Adapter < Adapter::Base
|
6
7
|
|
7
|
-
|
8
|
+
schema do
|
8
9
|
|
9
10
|
# normalized properties for all endpoints
|
10
|
-
|
11
|
+
key
|
11
12
|
|
12
13
|
# anthropic specific properties for all endpoints
|
13
|
-
|
14
|
+
version String, default: '2023-06-01'
|
14
15
|
|
15
|
-
|
16
|
+
chat_options do
|
16
17
|
|
17
18
|
# normalized properties for anthropic generative text endpoint
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
model String
|
20
|
+
max_tokens Integer
|
21
|
+
temperature Float
|
22
|
+
top_k Integer
|
23
|
+
top_p Float
|
24
|
+
stop String, array: true, as: :stop_sequences
|
25
|
+
stream [ TrueClass, FalseClass ]
|
25
26
|
|
26
27
|
# anthropic variant of normalized properties for anthropic generative text endpoints
|
27
|
-
|
28
|
+
stop_sequences String, array: true
|
28
29
|
|
29
30
|
# anthropic specific properties for anthropic generative text endpoints
|
30
|
-
|
31
|
-
|
31
|
+
tool_choice do
|
32
|
+
type String
|
32
33
|
# the name parameter should only be set if type = 'tool'
|
33
|
-
|
34
|
+
name String
|
34
35
|
end
|
35
|
-
|
36
|
-
|
36
|
+
metadata do
|
37
|
+
user_id String
|
37
38
|
end
|
38
39
|
|
39
40
|
end
|
40
41
|
|
41
42
|
end
|
42
43
|
|
43
|
-
include
|
44
|
+
include ChatRequestMethods
|
45
|
+
include ChatResponseMethods
|
44
46
|
|
45
47
|
end
|
46
48
|
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module Intelligence
|
2
|
+
module Anthropic
|
3
|
+
module ChatRequestMethods
|
4
|
+
|
5
|
+
CHAT_REQUEST_URI = "https://api.anthropic.com/v1/messages"
|
6
|
+
|
7
|
+
def chat_request_uri( options )
|
8
|
+
CHAT_REQUEST_URI
|
9
|
+
end
|
10
|
+
|
11
|
+
def chat_request_headers( options = nil )
|
12
|
+
options = @options.merge( build_options( options ) )
|
13
|
+
result = {}
|
14
|
+
|
15
|
+
key = options[ :key ]
|
16
|
+
version = options[ :version ] || "2023-06-01"
|
17
|
+
|
18
|
+
raise ArgumentError.new(
|
19
|
+
"An Anthropic key is required to build an Anthropic chat request."
|
20
|
+
) if key.nil?
|
21
|
+
|
22
|
+
result[ 'content-type' ] = 'application/json'
|
23
|
+
result[ 'x-api-key' ] = "#{key}"
|
24
|
+
result[ 'anthropic-version' ] = version unless version.nil?
|
25
|
+
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def chat_request_body( conversation, options = nil )
|
30
|
+
options = @options.merge( build_options( options ) )
|
31
|
+
result = options[ :chat_options ]&.compact || {}
|
32
|
+
|
33
|
+
system_message = to_anthropic_system_message( conversation[ :system_message ] )
|
34
|
+
result[ :system ] = system_message unless system_message.nil?
|
35
|
+
result[ :messages ] = []
|
36
|
+
|
37
|
+
messages = conversation[ :messages ]
|
38
|
+
length = messages&.length || 0
|
39
|
+
index = 0; while index < length
|
40
|
+
|
41
|
+
message = messages[ index ]
|
42
|
+
unless message.nil?
|
43
|
+
|
44
|
+
# The Anthropic API will not accept a sequence of messages where the role of two
|
45
|
+
# sequentian messages is the same.
|
46
|
+
#
|
47
|
+
# The purpose of this code is to identify such occurences and coalece them such
|
48
|
+
# that the first message in the sequence aggregates the contents of all subsequent
|
49
|
+
# messages with the same role.
|
50
|
+
look_ahead_index = index + 1; while look_ahead_index < length
|
51
|
+
ahead_message = messages[ look_ahead_index ]
|
52
|
+
unless ahead_message.nil?
|
53
|
+
if ahead_message[ :role ] == message[ :role ]
|
54
|
+
message[ :contents ] =
|
55
|
+
( message[ :contents ] || [] ) +
|
56
|
+
( ahead_message[ :contents ] || [] )
|
57
|
+
messages[ look_ahead_index ] = nil
|
58
|
+
look_ahead_index += 1
|
59
|
+
else
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
result_message = { role: message[ :role ] }
|
66
|
+
result_message_content = []
|
67
|
+
|
68
|
+
message[ :contents ]&.each do | content |
|
69
|
+
case content[ :type ]
|
70
|
+
when :text
|
71
|
+
result_message_content << { type: 'text', text: content[ :text ] }
|
72
|
+
when :binary
|
73
|
+
content_type = content[ :content_type ]
|
74
|
+
bytes = content[ :bytes ]
|
75
|
+
if content_type && bytes
|
76
|
+
mime_type = MIME::Types[ content_type ].first
|
77
|
+
if mime_type&.media_type == 'image'
|
78
|
+
result_message_content << {
|
79
|
+
type: 'image',
|
80
|
+
source: {
|
81
|
+
type: 'base64',
|
82
|
+
media_type: content_type,
|
83
|
+
data: Base64.strict_encode64( bytes )
|
84
|
+
}
|
85
|
+
}
|
86
|
+
else
|
87
|
+
raise UnsupportedContentError.new(
|
88
|
+
:anthropic,
|
89
|
+
'only support content of type image/*'
|
90
|
+
)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
raise UnsupportedContentError.new(
|
94
|
+
:anthropic,
|
95
|
+
'requires file content to include content type and (packed) bytes'
|
96
|
+
)
|
97
|
+
end
|
98
|
+
when :tool_call
|
99
|
+
result_message_content << {
|
100
|
+
type: 'tool_use',
|
101
|
+
id: content[ :tool_call_id ],
|
102
|
+
name: content[ :tool_name ],
|
103
|
+
input: content[ :tool_parameters ] || {}
|
104
|
+
}
|
105
|
+
when :tool_result
|
106
|
+
result_message_content << {
|
107
|
+
type: 'tool_result',
|
108
|
+
tool_use_id: content[ :tool_call_id ],
|
109
|
+
content: content[ :tool_result ]
|
110
|
+
}
|
111
|
+
else
|
112
|
+
raise InvalidContentError.new( :anthropic )
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
result_message[ :content ] = result_message_content
|
117
|
+
result[ :messages ] << result_message
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
index += 1
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
tools_attributes = chat_request_tools_attributes( conversation[ :tools ] )
|
126
|
+
result[ :tools ] = tools_attributes if tools_attributes && tools_attributes.length > 0
|
127
|
+
|
128
|
+
JSON.generate( result )
|
129
|
+
end
|
130
|
+
|
131
|
+
def chat_request_tools_attributes( tools )
|
132
|
+
properties_array_to_object = lambda do | properties |
|
133
|
+
return nil unless properties&.any?
|
134
|
+
object = {}
|
135
|
+
required = []
|
136
|
+
properties.each do | property |
|
137
|
+
name = property.delete( :name )
|
138
|
+
required << name if property.delete( :required )
|
139
|
+
if property[ :properties ]&.any?
|
140
|
+
property_properties, property_required =
|
141
|
+
properties_array_to_object.call( property[ :properties ] )
|
142
|
+
property[ :properties ] = property_properties
|
143
|
+
property[ :required ] = property_required if property_required.any?
|
144
|
+
end
|
145
|
+
object[ name ] = property
|
146
|
+
end
|
147
|
+
[ object, required.compact ]
|
148
|
+
end
|
149
|
+
|
150
|
+
tools&.map do | tool |
|
151
|
+
tool_attributes = {
|
152
|
+
name: tool[ :name ],
|
153
|
+
description: tool[ :description ],
|
154
|
+
input_schema: { type: 'object' }
|
155
|
+
}
|
156
|
+
|
157
|
+
if tool[ :properties ]&.any?
|
158
|
+
properties_object, properties_required =
|
159
|
+
properties_array_to_object.call( tool[ :properties ] )
|
160
|
+
input_schema = {
|
161
|
+
type: 'object',
|
162
|
+
properties: properties_object
|
163
|
+
}
|
164
|
+
input_schema[ :required ] = properties_required if properties_required.any?
|
165
|
+
tool_attributes[ :input_schema ] = input_schema
|
166
|
+
end
|
167
|
+
tool_attributes
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def to_anthropic_system_message( system_message )
|
174
|
+
return nil if system_message.nil?
|
175
|
+
|
176
|
+
# note: the current version of the anthropic api simply takes a string as the
|
177
|
+
# system message but the beta version requires an array of objects akin
|
178
|
+
# to message contents.
|
179
|
+
result = ''
|
180
|
+
system_message[ :contents ].each do | content |
|
181
|
+
result += content[ :text ] if content[ :type ] == :text
|
182
|
+
end
|
183
|
+
|
184
|
+
result.empty? ? nil : result
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -1,113 +1,6 @@
|
|
1
1
|
module Intelligence
|
2
2
|
module Anthropic
|
3
|
-
module
|
4
|
-
|
5
|
-
CHAT_REQUEST_URI = "https://api.anthropic.com/v1/messages"
|
6
|
-
|
7
|
-
def chat_request_uri( options )
|
8
|
-
CHAT_REQUEST_URI
|
9
|
-
end
|
10
|
-
|
11
|
-
def chat_request_headers( options = nil )
|
12
|
-
options = options ? self.class.configure( options ) : {}
|
13
|
-
options = @options.merge( options )
|
14
|
-
result = {}
|
15
|
-
|
16
|
-
key = options[ :key ]
|
17
|
-
version = options[ :version ] || "2023-06-01"
|
18
|
-
|
19
|
-
raise ArgumentError.new(
|
20
|
-
"An Anthropic key is required to build an Anthropic chat request."
|
21
|
-
) if key.nil?
|
22
|
-
|
23
|
-
result[ 'content-type' ] = 'application/json'
|
24
|
-
result[ 'x-api-key' ] = "#{key}"
|
25
|
-
result[ 'anthropic-version' ] = version unless version.nil?
|
26
|
-
|
27
|
-
result
|
28
|
-
end
|
29
|
-
|
30
|
-
def chat_request_body( conversation, options = nil )
|
31
|
-
options = options ? self.class.configure( options ) : {}
|
32
|
-
options = @options.merge( options )
|
33
|
-
result = options[ :chat_options ]&.compact || {}
|
34
|
-
|
35
|
-
system_message = translate_system_message( conversation[ :system_message ] )
|
36
|
-
result[ :system ] = system_message unless system_message.nil?
|
37
|
-
result[ :messages ] = []
|
38
|
-
|
39
|
-
messages = conversation[ :messages ]
|
40
|
-
length = messages&.length || 0
|
41
|
-
index = 0; while index < length
|
42
|
-
|
43
|
-
message = messages[ index ]
|
44
|
-
unless message.nil?
|
45
|
-
|
46
|
-
# The Anthropic API will not accept a sequence of messages where the role of two
|
47
|
-
# sequentian messages is the same.
|
48
|
-
#
|
49
|
-
# The purpose of this code is to identify such occurences and coalece them such
|
50
|
-
# that the first message in the sequence aggregates the contents of all subsequent
|
51
|
-
# messages with the same role.
|
52
|
-
look_ahead_index = index + 1; while look_ahead_index < length
|
53
|
-
ahead_message = messages[ look_ahead_index ]
|
54
|
-
unless ahead_message.nil?
|
55
|
-
if ahead_message[ :role ] == message[ :role ]
|
56
|
-
message[ :contents ] =
|
57
|
-
( message[ :contents ] || [] ) +
|
58
|
-
( ahead_message[ :contents ] || [] )
|
59
|
-
messages[ look_ahead_index ] = nil
|
60
|
-
look_ahead_index += 1
|
61
|
-
else
|
62
|
-
break
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
result_message = { role: message[ :role ] }
|
68
|
-
result_message_content = []
|
69
|
-
|
70
|
-
message[ :contents ]&.each do | content |
|
71
|
-
case content[ :type ]
|
72
|
-
when :text
|
73
|
-
result_message_content << { type: 'text', text: content[ :text ] }
|
74
|
-
when :binary
|
75
|
-
content_type = content[ :content_type ]
|
76
|
-
bytes = content[ :bytes ]
|
77
|
-
if content_type && bytes
|
78
|
-
mime_type = MIME::Types[ content_type ].first
|
79
|
-
if mime_type&.media_type == 'image'
|
80
|
-
result_message_content << {
|
81
|
-
type: 'image',
|
82
|
-
source: {
|
83
|
-
type: 'base64',
|
84
|
-
media_type: content_type,
|
85
|
-
data: Base64.strict_encode64( bytes )
|
86
|
-
}
|
87
|
-
}
|
88
|
-
else
|
89
|
-
raise UnsupportedContentError.new(
|
90
|
-
:anthropic,
|
91
|
-
'only support content of type image/*'
|
92
|
-
)
|
93
|
-
end
|
94
|
-
else
|
95
|
-
raise InvalidContentError.new( :anthropic )
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
result_message[ :content ] = result_message_content
|
101
|
-
result[ :messages ] << result_message
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
index += 1
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
JSON.generate( result )
|
110
|
-
end
|
3
|
+
module ChatResponseMethods
|
111
4
|
|
112
5
|
def chat_result_attributes( response )
|
113
6
|
return nil unless response.success?
|
@@ -130,6 +23,13 @@ module Intelligence
|
|
130
23
|
response_json[ :content ].each do | content |
|
131
24
|
if content[ :type ] == 'text'
|
132
25
|
result_content.push( { type: 'text', text: content[ :text ] } )
|
26
|
+
elsif content[ :type ] == 'tool_use'
|
27
|
+
result_content.push( {
|
28
|
+
type: :tool_call,
|
29
|
+
tool_call_id: content[ :id ],
|
30
|
+
tool_name: content[ :name ],
|
31
|
+
tool_parameters: content[ :input ]
|
32
|
+
} )
|
133
33
|
end
|
134
34
|
end
|
135
35
|
|
@@ -189,15 +89,8 @@ module Intelligence
|
|
189
89
|
output_tokens: 0
|
190
90
|
}
|
191
91
|
|
192
|
-
contents.
|
193
|
-
|
194
|
-
when :text
|
195
|
-
content[ :text ] = ''
|
196
|
-
when :tool_call
|
197
|
-
content[ :tool_parameters ] = ''
|
198
|
-
else
|
199
|
-
content.clear
|
200
|
-
end
|
92
|
+
contents.map! do | content |
|
93
|
+
{ type: content[ :type ] }
|
201
94
|
end
|
202
95
|
|
203
96
|
buffer += chunk
|
@@ -216,7 +109,7 @@ module Intelligence
|
|
216
109
|
metrics[ :output_tokens ] += data[ 'message' ]&.[]( 'usage' )&.[]( 'output_tokens' ) || 0
|
217
110
|
when 'content_block_start'
|
218
111
|
index = data[ 'index' ]
|
219
|
-
contents.fill( {}, contents.size
|
112
|
+
contents.fill( {}, contents.size..index ) if contents.size <= index
|
220
113
|
if content_block = data[ 'content_block' ]
|
221
114
|
if content_block[ 'type' ] == 'text'
|
222
115
|
contents[ index ] = {
|
@@ -234,7 +127,7 @@ module Intelligence
|
|
234
127
|
end
|
235
128
|
when 'content_block_delta'
|
236
129
|
index = data[ 'index' ]
|
237
|
-
contents.fill( {}, contents.size
|
130
|
+
contents.fill( {}, contents.size..index ) if contents.size <= index
|
238
131
|
if delta = data[ 'delta' ]
|
239
132
|
if delta[ 'type' ] == 'text_delta'
|
240
133
|
contents[ index ][ :type ] = :text
|
@@ -242,7 +135,7 @@ module Intelligence
|
|
242
135
|
elsif delta[ 'type' ] == 'input_json_delta'
|
243
136
|
contents[ index ][ :type ] = :tool_call
|
244
137
|
contents[ index ][ :tool_parameters ] =
|
245
|
-
( contents[ index ][ :tool_parameters ] || '' ) + delta[ '
|
138
|
+
( contents[ index ][ :tool_parameters ] || '' ) + delta[ 'partial_json' ]
|
246
139
|
end
|
247
140
|
end
|
248
141
|
when 'message_delta'
|
@@ -289,23 +182,6 @@ module Intelligence
|
|
289
182
|
|
290
183
|
private
|
291
184
|
|
292
|
-
def translate_system_message( system_message )
|
293
|
-
|
294
|
-
return nil if system_message.nil?
|
295
|
-
|
296
|
-
# note: the current version of the anthropic api simply takes a string as the
|
297
|
-
# system message but the beta version requires an array of objects akin
|
298
|
-
# to message contents.
|
299
|
-
|
300
|
-
result = ''
|
301
|
-
system_message[ :contents ].each do | content |
|
302
|
-
result += content[ :text ] if content[ :type ] == :text
|
303
|
-
end
|
304
|
-
|
305
|
-
result.empty? ? nil : result
|
306
|
-
|
307
|
-
end
|
308
|
-
|
309
185
|
def translate_end_result( end_result )
|
310
186
|
case end_result
|
311
187
|
when 'end_turn'
|
@@ -1,31 +1,31 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'generic/adapter'
|
2
2
|
|
3
3
|
module Intelligence
|
4
4
|
module Cerebras
|
5
5
|
|
6
|
-
class Adapter <
|
6
|
+
class Adapter < Generic::Adapter
|
7
7
|
|
8
8
|
chat_request_uri "https://api.cerebras.ai/v1/chat/completions"
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
schema do
|
11
|
+
key String
|
12
|
+
chat_options do
|
13
|
+
model String
|
14
|
+
max_tokens Integer
|
15
|
+
response_format do
|
16
|
+
type String, default: 'json_schema'
|
17
|
+
json_schema
|
18
18
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
seed Integer
|
20
|
+
stop array: true
|
21
|
+
stream [ TrueClass, FalseClass ]
|
22
|
+
temperature Float
|
23
|
+
top_p Float
|
24
|
+
tool_choice do
|
25
|
+
type String
|
26
|
+
mame String
|
27
27
|
end
|
28
|
-
|
28
|
+
user
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require_relative '../../adapter'
|
2
|
-
require_relative '
|
2
|
+
require_relative 'chat_request_methods'
|
3
|
+
require_relative 'chat_response_methods'
|
3
4
|
|
4
5
|
module Intelligence
|
5
6
|
module Generic
|
6
7
|
class Adapter < Adapter::Base
|
7
|
-
include
|
8
|
+
include ChatRequestMethods
|
9
|
+
include ChatResponseMethods
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|