intelligence 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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