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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +576 -0
  3. data/intelligence.gemspec +2 -1
  4. data/lib/intelligence/adapter/base.rb +13 -6
  5. data/lib/intelligence/adapter/class_methods.rb +15 -0
  6. data/lib/intelligence/adapter/module_methods.rb +41 -0
  7. data/lib/intelligence/adapter.rb +2 -2
  8. data/lib/intelligence/adapters/anthropic/adapter.rb +21 -19
  9. data/lib/intelligence/adapters/anthropic/chat_request_methods.rb +189 -0
  10. data/lib/intelligence/adapters/anthropic/{chat_methods.rb → chat_response_methods.rb} +13 -137
  11. data/lib/intelligence/adapters/cerebras.rb +19 -19
  12. data/lib/intelligence/adapters/generic/adapter.rb +4 -2
  13. data/lib/intelligence/adapters/generic/chat_request_methods.rb +221 -0
  14. data/lib/intelligence/adapters/generic/chat_response_methods.rb +234 -0
  15. data/lib/intelligence/adapters/generic.rb +1 -1
  16. data/lib/intelligence/adapters/google/adapter.rb +33 -22
  17. data/lib/intelligence/adapters/google/chat_request_methods.rb +234 -0
  18. data/lib/intelligence/adapters/google/chat_response_methods.rb +236 -0
  19. data/lib/intelligence/adapters/groq.rb +29 -49
  20. data/lib/intelligence/adapters/hyperbolic.rb +13 -39
  21. data/lib/intelligence/adapters/mistral.rb +21 -42
  22. data/lib/intelligence/adapters/open_ai/adapter.rb +39 -32
  23. data/lib/intelligence/adapters/open_ai/chat_request_methods.rb +186 -0
  24. data/lib/intelligence/adapters/open_ai/chat_response_methods.rb +239 -0
  25. data/lib/intelligence/adapters/open_ai.rb +1 -1
  26. data/lib/intelligence/adapters/open_router.rb +18 -18
  27. data/lib/intelligence/adapters/samba_nova.rb +16 -18
  28. data/lib/intelligence/adapters/together_ai.rb +25 -23
  29. data/lib/intelligence/conversation.rb +11 -10
  30. data/lib/intelligence/message.rb +45 -29
  31. data/lib/intelligence/message_content/base.rb +2 -9
  32. data/lib/intelligence/message_content/binary.rb +3 -3
  33. data/lib/intelligence/message_content/file.rb +3 -3
  34. data/lib/intelligence/message_content/text.rb +10 -2
  35. data/lib/intelligence/message_content/tool_call.rb +61 -5
  36. data/lib/intelligence/message_content/tool_result.rb +11 -6
  37. data/lib/intelligence/tool.rb +139 -0
  38. data/lib/intelligence/version.rb +1 -1
  39. data/lib/intelligence.rb +3 -1
  40. metadata +31 -13
  41. data/lib/intelligence/adapter/class_methods/construction.rb +0 -17
  42. data/lib/intelligence/adapter/module_methods/construction.rb +0 -43
  43. data/lib/intelligence/adapters/generic/chat_methods.rb +0 -355
  44. data/lib/intelligence/adapters/google/chat_methods.rb +0 -393
  45. data/lib/intelligence/adapters/legacy/adapter.rb +0 -11
  46. data/lib/intelligence/adapters/legacy/chat_methods.rb +0 -54
  47. data/lib/intelligence/adapters/open_ai/chat_methods.rb +0 -345
@@ -1,46 +1,48 @@
1
- require_relative 'chat_methods'
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
- configuration do
8
+ schema do
8
9
 
9
10
  # normalized properties for all endpoints
10
- parameter :key
11
+ key
11
12
 
12
13
  # anthropic specific properties for all endpoints
13
- parameter :version, String, default: '2023-06-01'
14
+ version String, default: '2023-06-01'
14
15
 
15
- group :chat_options do
16
+ chat_options do
16
17
 
17
18
  # normalized properties for anthropic generative text endpoint
18
- parameter :model, String
19
- parameter :max_tokens, Integer
20
- parameter :temperature, Float
21
- parameter :top_k, Integer
22
- parameter :top_p, Float
23
- parameter :stop, String, array: true, as: :stop_sequences
24
- parameter :stream, [ TrueClass, FalseClass ]
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
- parameter :stop_sequences, String, array: true
28
+ stop_sequences String, array: true
28
29
 
29
30
  # anthropic specific properties for anthropic generative text endpoints
30
- parameter :tool_choice do
31
- parameter :type, String
31
+ tool_choice do
32
+ type String
32
33
  # the name parameter should only be set if type = 'tool'
33
- parameter :name, String
34
+ name String
34
35
  end
35
- group :metadata do
36
- parameter :user_id, String
36
+ metadata do
37
+ user_id String
37
38
  end
38
39
 
39
40
  end
40
41
 
41
42
  end
42
43
 
43
- include ChatMethods
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 ChatMethods
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.each do | content |
193
- case content[ :type ]
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, index + 1 ) if contents.size <= index
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, index + 1 ) if contents.size <= index
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[ 'input_json_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 'legacy/adapter'
1
+ require_relative 'generic/adapter'
2
2
 
3
3
  module Intelligence
4
4
  module Cerebras
5
5
 
6
- class Adapter < Legacy::Adapter
6
+ class Adapter < Generic::Adapter
7
7
 
8
8
  chat_request_uri "https://api.cerebras.ai/v1/chat/completions"
9
9
 
10
- configuration do
11
- parameter :key
12
- group :chat_options do
13
- parameter :model, String
14
- parameter :max_tokens, Integer
15
- parameter :response_format do
16
- parameter :type, String, default: 'json_schema'
17
- parameter :json_schema
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
- parameter :seed, Integer
20
- parameter :stop, array: true
21
- parameter :stream, [ TrueClass, FalseClass ]
22
- parameter :temperature, Float
23
- parameter :top_p, Float
24
- parameter :tool_choice do
25
- parameter :type, String
26
- parameter :mame, String
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
- parameter :user
28
+ user
29
29
  end
30
30
  end
31
31
 
@@ -1,10 +1,12 @@
1
1
  require_relative '../../adapter'
2
- require_relative 'chat_methods'
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 ChatMethods
8
+ include ChatRequestMethods
9
+ include ChatResponseMethods
8
10
  end
9
11
  end
10
12
  end