intelligence 0.6.0 → 0.7.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +555 -0
  3. data/intelligence.gemspec +1 -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} +8 -125
  11. data/lib/intelligence/adapters/cerebras.rb +17 -17
  12. data/lib/intelligence/adapters/generic/chat_methods.rb +12 -5
  13. data/lib/intelligence/adapters/generic.rb +1 -1
  14. data/lib/intelligence/adapters/google/adapter.rb +33 -22
  15. data/lib/intelligence/adapters/google/chat_request_methods.rb +233 -0
  16. data/lib/intelligence/adapters/google/chat_response_methods.rb +236 -0
  17. data/lib/intelligence/adapters/groq.rb +27 -28
  18. data/lib/intelligence/adapters/hyperbolic.rb +13 -13
  19. data/lib/intelligence/adapters/legacy/chat_methods.rb +1 -2
  20. data/lib/intelligence/adapters/mistral.rb +18 -18
  21. data/lib/intelligence/adapters/open_ai/adapter.rb +39 -32
  22. data/lib/intelligence/adapters/open_ai/chat_request_methods.rb +186 -0
  23. data/lib/intelligence/adapters/open_ai/{chat_methods.rb → chat_response_methods.rb} +60 -162
  24. data/lib/intelligence/adapters/open_ai.rb +1 -1
  25. data/lib/intelligence/adapters/open_router.rb +18 -18
  26. data/lib/intelligence/adapters/samba_nova.rb +13 -13
  27. data/lib/intelligence/adapters/together_ai.rb +21 -19
  28. data/lib/intelligence/conversation.rb +11 -10
  29. data/lib/intelligence/message.rb +44 -28
  30. data/lib/intelligence/message_content/base.rb +2 -9
  31. data/lib/intelligence/message_content/binary.rb +3 -3
  32. data/lib/intelligence/message_content/file.rb +3 -3
  33. data/lib/intelligence/message_content/text.rb +2 -2
  34. data/lib/intelligence/message_content/tool_call.rb +8 -4
  35. data/lib/intelligence/message_content/tool_result.rb +11 -6
  36. data/lib/intelligence/tool.rb +139 -0
  37. data/lib/intelligence/version.rb +1 -1
  38. data/lib/intelligence.rb +2 -1
  39. metadata +15 -10
  40. data/lib/intelligence/adapter/class_methods/construction.rb +0 -17
  41. data/lib/intelligence/adapter/module_methods/construction.rb +0 -43
  42. data/lib/intelligence/adapters/google/chat_methods.rb +0 -393
@@ -1,60 +1,67 @@
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 OpenAi
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, String
11
+ key String
11
12
 
12
13
  # openai properties for all endpoints
13
- parameter :organization
14
- parameter :project
14
+ organization String
15
+ project String
15
16
 
16
17
  # properties for generative text endpoints
17
- group :chat_options do
18
+ chat_options do
18
19
 
19
20
  # normalized properties for openai generative text endpoint
20
- parameter :model, String
21
- parameter :n, Integer
22
- parameter :max_tokens, Integer, as: :max_completion_tokens
23
- parameter :temperature, Float
24
- parameter :top_p, Float
25
- parameter :seed, Integer
26
- parameter :stop, String, array: true
27
- parameter :stream, [ TrueClass, FalseClass ]
28
-
29
- parameter :frequency_penalty, Float
30
- parameter :presence_penalty, Float
21
+ model String, requried: true
22
+ n Integer
23
+ max_tokens Integer, as: :max_completion_tokens
24
+ temperature Float
25
+ top_p Float
26
+ seed Integer
27
+ stop String, array: true
28
+ stream [ TrueClass, FalseClass ]
29
+
30
+ frequency_penalty Float
31
+ presence_penalty Float
31
32
 
32
33
  # openai variant of normalized properties for openai generative text endpoints
33
- parameter :max_completion_tokens, Integer
34
+ max_completion_tokens Integer
34
35
 
35
36
  # openai properties for openai generative text endpoint
36
- parameter :logit_bias
37
- parameter :logprobs, [ TrueClass, FalseClass ]
38
- parameter :parallel_tool_calls, [ TrueClass, FalseClass ]
39
- group :response_format do
37
+ audio do
38
+ voice String
39
+ format String
40
+ end
41
+ logit_bias
42
+ logprobs [ TrueClass, FalseClass ]
43
+ modalities String, array: true
44
+ # the parallel_tool_calls parameter is only allowed when 'tools' are specified
45
+ parallel_tool_calls [ TrueClass, FalseClass ]
46
+ response_format do
40
47
  # 'text' and 'json_schema' are the only supported types
41
- parameter :type, String
42
- parameter :json_schema
48
+ type Symbol, in: [ :text, :json_schema ]
49
+ json_schema
43
50
  end
44
- parameter :service_tier, String
45
- group :stream_options do
46
- parameter :include_usage, [ TrueClass, FalseClass ]
51
+ service_tier String
52
+ stream_options do
53
+ include_usage [ TrueClass, FalseClass ]
47
54
  end
48
- parameter :tool_choice
49
- # the parallel_tool_calls parameter is only allowed when 'tools' are specified
50
- parameter :top_logprobs, Integer
51
- parameter :user
55
+ tool_choice
56
+ top_logprobs Integer
57
+ user
52
58
 
53
59
  end
54
60
 
55
61
  end
56
62
 
57
- include ChatMethods
63
+ include ChatRequestMethods
64
+ include ChatResponseMethods
58
65
 
59
66
  end
60
67
  end
@@ -0,0 +1,186 @@
1
+ module Intelligence
2
+ module OpenAi
3
+ module ChatRequestMethods
4
+
5
+ CHAT_REQUEST_URI = "https://api.openai.com/v1/chat/completions"
6
+
7
+ SUPPORTED_CONTENT_TYPES = [ 'image/jpeg', 'image/png' ]
8
+
9
+ def chat_request_uri( options )
10
+ CHAT_REQUEST_URI
11
+ end
12
+
13
+ def chat_request_headers( options = {} )
14
+ options = @options.merge( build_options( options ) )
15
+ result = {}
16
+
17
+ key = options[ :key ]
18
+ organization = options[ :organization ]
19
+ project = options[ :project ]
20
+
21
+ raise ArgumentError.new( "An OpenAI key is required to build an OpenAI chat request." ) \
22
+ if key.nil?
23
+
24
+ result[ 'Content-Type' ] = 'application/json'
25
+ result[ 'Authorization' ] = "Bearer #{key}"
26
+ result[ 'OpenAI-Organization' ] = organization unless organization.nil?
27
+ result[ 'OpenAI-Project' ] = project unless project.nil?
28
+
29
+ result
30
+ end
31
+
32
+ def chat_request_body( conversation, options = {} )
33
+ options = @options.merge( build_options( options ) )
34
+ result = options[ :chat_options ]&.compact || {}
35
+ result[ :messages ] = []
36
+
37
+ system_message = to_open_ai_system_message( conversation[ :system_message ] )
38
+ result[ :messages ] << { role: 'system', content: system_message } if system_message
39
+
40
+ conversation[ :messages ]&.each do | message |
41
+
42
+ result_message = { role: message[ :role ] }
43
+ result_message_content = []
44
+
45
+ message[ :contents ]&.each do | content |
46
+ case content[ :type ]
47
+ when :text
48
+ result_message_content << { type: 'text', text: content[ :text ] }
49
+ when :binary
50
+ content_type = content[ :content_type ]
51
+ bytes = content[ :bytes ]
52
+ if content_type && bytes
53
+ if SUPPORTED_CONTENT_TYPES.include?( content_type )
54
+ result_message_content << {
55
+ type: 'image_url',
56
+ image_url: {
57
+ url: "data:#{content_type};base64,#{Base64.strict_encode64( bytes )}".freeze
58
+ }
59
+ }
60
+ else
61
+ raise UnsupportedContentError.new(
62
+ :open_ai,
63
+ "only supports content of type #{SUPPORTED_CONTENT_TYPES.join( ', ' )}"
64
+ )
65
+ end
66
+ else
67
+ raise UnsupportedContentError.new(
68
+ :open_ai,
69
+ 'requires binary content to include content type and ( packed ) bytes'
70
+ )
71
+ end
72
+ when :file
73
+ content_type = content[ :content_type ]
74
+ uri = content[ :uri ]
75
+ if content_type && uri
76
+ if SUPPORTED_CONTENT_TYPES.include?( content_type )
77
+ result_message_content << {
78
+ type: 'image_url',
79
+ image_url: {
80
+ url: uri
81
+ }
82
+ }
83
+ else
84
+ raise UnsupportedContentError.new(
85
+ :open_ai,
86
+ "only supports content of type #{SUPPORTED_CONTENT_TYPES.join( ', ' )}"
87
+ )
88
+ end
89
+ else
90
+ raise UnsupportedContentError.new(
91
+ :open_ai,
92
+ 'requires file content to include content type and uri'
93
+ )
94
+ end
95
+ when :tool_call
96
+ tool_calls = result_message[ :tool_calls ] || []
97
+ function = {
98
+ name: content[ :tool_name ]
99
+ }
100
+ function[ :arguments ] = JSON.generate( content[ :tool_parameters ] || {} )
101
+ tool_calls << { id: content[ :tool_call_id ], type: 'function', function: function }
102
+ result_message[ :tool_calls ] = tool_calls
103
+ when :tool_result
104
+ # open-ai returns tool results as a message with a role of 'tool'
105
+ result[ :messages ] << {
106
+ role: :tool,
107
+ tool_call_id: content[ :tool_call_id ],
108
+ content: content[ :tool_result ]
109
+ }
110
+ else
111
+ raise InvalidContentError.new( :open_ai )
112
+ end
113
+
114
+ end
115
+
116
+ result_message[ :content ] = result_message_content
117
+ result[ :messages ] << result_message \
118
+ if result_message[ :content ]&.any? || result_message[ :tool_calls ]&.any?
119
+ result
120
+
121
+ end
122
+
123
+ tools_attributes = chat_request_tools_attributes( conversation[ :tools ] )
124
+ result[ :tools ] = tools_attributes if tools_attributes && tools_attributes.length > 0
125
+
126
+ JSON.generate( result )
127
+ end
128
+
129
+ def chat_request_tools_attributes( tools )
130
+ properties_array_to_object = lambda do | properties |
131
+ return nil unless properties&.any?
132
+ object = {}
133
+ required = []
134
+ properties.each do | property |
135
+ name = property.delete( :name )
136
+ required << name if property.delete( :required )
137
+ if property[ :properties ]&.any?
138
+ property_properties, property_required =
139
+ properties_array_to_object.call( property[ :properties ] )
140
+ property[ :properties ] = property_properties
141
+ property[ :required ] = property_required if property_required.any?
142
+ end
143
+ object[ name ] = property
144
+ end
145
+ [ object, required.compact ]
146
+ end
147
+
148
+ tools&.map do | tool |
149
+ function = {
150
+ type: 'function',
151
+ function: {
152
+ name: tool[ :name ],
153
+ description: tool[ :description ],
154
+ }
155
+ }
156
+
157
+ if tool[ :properties ]&.any?
158
+ properties_object, properties_required =
159
+ properties_array_to_object.call( tool[ :properties ] )
160
+ function[ :function ][ :parameters ] = {
161
+ type: 'object',
162
+ properties: properties_object
163
+ }
164
+ function[ :function ][ :parameters ][ :required ] = properties_required \
165
+ if properties_required.any?
166
+ end
167
+ function
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def to_open_ai_system_message( system_message )
174
+ return nil if system_message.nil?
175
+
176
+ result = ''
177
+ system_message[ :contents ].each do | content |
178
+ result += content[ :text ] if content[ :type ] == :text
179
+ end
180
+
181
+ result.empty? ? nil : result
182
+ end
183
+
184
+ end
185
+ end
186
+ end
@@ -1,116 +1,9 @@
1
1
  module Intelligence
2
2
  module OpenAi
3
- module ChatMethods
4
-
5
- CHAT_REQUEST_URI = "https://api.openai.com/v1/chat/completions"
6
-
7
- SUPPORTED_CONTENT_TYPES = [ 'image/jpeg', 'image/png' ]
8
-
9
- def chat_request_uri( options )
10
- CHAT_REQUEST_URI
11
- end
12
-
13
- def chat_request_headers( options = {} )
14
- options = options ? self.class.configure( options ) : {}
15
- options = @options.merge( options )
16
- result = {}
17
-
18
- key = options[ :key ]
19
- organization = options[ :organization ]
20
- project = options[ :project ]
21
-
22
- raise ArgumentError.new( "An OpenAI key is required to build an OpenAI chat request." ) \
23
- if key.nil?
24
-
25
- result[ 'Content-Type' ] = 'application/json'
26
- result[ 'Authorization' ] = "Bearer #{key}"
27
- result[ 'OpenAI-Organization' ] = organization unless organization.nil?
28
- result[ 'OpenAI-Project' ] = project unless project.nil?
29
-
30
- result
31
- end
32
-
33
- def chat_request_body( conversation, options = {} )
34
- options = options ? self.class.configure( options ) : {}
35
- options = @options.merge( options )
36
- result = options[ :chat_options ]&.compact || {}
37
- result[ :messages ] = []
38
-
39
- system_message = translate_system_message( conversation[ :system_message ] )
40
- result[ :messages ] << { role: 'system', content: system_message } if system_message
41
-
42
- conversation[ :messages ]&.each do | message |
43
-
44
- result_message = { role: message[ :role ] }
45
- result_message_content = []
46
-
47
- message[ :contents ]&.each do | content |
48
- case content[ :type ]
49
- when :text
50
- result_message_content << { type: 'text', text: content[ :text ] }
51
- when :binary
52
- content_type = content[ :content_type ]
53
- bytes = content[ :bytes ]
54
- if content_type && bytes
55
- if SUPPORTED_CONTENT_TYPES.include?( content_type )
56
- result_message_content << {
57
- type: 'image_url',
58
- image_url: {
59
- url: "data:#{content_type};base64,#{Base64.strict_encode64( bytes )}".freeze
60
- }
61
- }
62
- else
63
- raise UnsupportedContentError.new(
64
- :open_ai,
65
- "only supports content of type #{SUPPORTED_CONTENT_TYPES.join( ', ' )}"
66
- )
67
- end
68
- else
69
- raise UnsupportedContentError.new(
70
- :open_ai,
71
- 'requires binary content to include content type and ( packed ) bytes'
72
- )
73
- end
74
- when :file
75
- content_type = content[ :content_type ]
76
- uri = content[ :uri ]
77
- if content_type && uri
78
- if SUPPORTED_CONTENT_TYPES.include?( content_type )
79
- result_message_content << {
80
- type: 'image_url',
81
- image_url: {
82
- url: uri
83
- }
84
- }
85
- else
86
- raise UnsupportedContentError.new(
87
- :open_ai,
88
- "only supports content of type #{SUPPORTED_CONTENT_TYPES.join( ', ' )}"
89
- )
90
- end
91
- else
92
- raise UnsupportedContentError.new(
93
- :open_ai,
94
- 'requires file content to include content type and uri'
95
- )
96
- end
97
- else
98
- raise InvalidContentError.new( :open_ai )
99
- end
100
-
101
- end
102
-
103
- result_message[ :content ] = result_message_content
104
- result[ :messages ] << result_message
105
-
106
- end
107
-
108
- JSON.generate( result )
109
- end
3
+ module ChatResponseMethods
110
4
 
111
5
  def chat_result_attributes( response )
112
6
  return nil unless response.success?
113
-
114
7
  response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
115
8
  return nil \
116
9
  if response_json.nil? || response_json[ :choices ].nil?
@@ -120,15 +13,31 @@ module Intelligence
120
13
 
121
14
  ( response_json[ :choices ] || [] ).each do | json_choice |
122
15
  json_message = json_choice[ :message ]
123
- result[ :choices ].push(
124
- {
125
- end_reason: translate_end_result( json_choice[ :finish_reason ] ),
126
- message: {
127
- role: json_message[ :role ],
128
- contents: [ { type: 'text', text: json_message[ :content ] } ]
129
- }
130
- }
131
- )
16
+ result_message = nil
17
+ if ( json_message )
18
+ result_message = { role: json_message[ :role ] }
19
+ if json_message[ :content ]
20
+ result_message[ :contents ] = [ { type: :text, text: json_message[ :content ] } ]
21
+ end
22
+ if json_message[ :tool_calls ] && !json_message[ :tool_calls ].empty?
23
+ result_message[ :contents ] ||= []
24
+ json_message[ :tool_calls ].each do | json_message_tool_call |
25
+ result_message_tool_call_parameters =
26
+ JSON.parse( json_message_tool_call[ :function ][ :arguments ], symbolize_names: true ) \
27
+ rescue json_message_tool_call[ :function ][ :arguments ]
28
+ result_message[ :contents ] << {
29
+ type: :tool_call,
30
+ tool_call_id: json_message_tool_call[ :id ],
31
+ tool_name: json_message_tool_call[ :function ][ :name ],
32
+ tool_parameters: result_message_tool_call_parameters
33
+ }
34
+ end
35
+ end
36
+ end
37
+ result[ :choices ].push( {
38
+ end_reason: translate_end_result( json_choice[ :finish_reason ] ),
39
+ message: result_message
40
+ } )
132
41
  end
133
42
 
134
43
  metrics_json = response_json[ :usage ]
@@ -275,26 +184,15 @@ module Intelligence
275
184
 
276
185
  alias_method :stream_result_error_attributes, :chat_result_error_attributes
277
186
 
278
- private; def translate_system_message( system_message )
279
-
280
- return nil if system_message.nil?
281
-
282
- result = ''
283
- system_message[ :contents ].each do | content |
284
- result += content[ :text ] if content[ :type ] == :text
285
- end
286
-
287
- result.empty? ? nil : result
288
-
289
- end
187
+ private
290
188
 
291
- private; def translate_end_result( end_result )
189
+ def translate_end_result( end_result )
292
190
  case end_result
293
191
  when 'stop'
294
192
  :ended
295
193
  when 'length'
296
194
  :token_limit_exceeded
297
- when 'function_call'
195
+ when 'tool_calls'
298
196
  :tool_called
299
197
  when 'content_filter'
300
198
  :filtered
@@ -305,37 +203,37 @@ module Intelligence
305
203
 
306
204
  def translate_error_response_status( status )
307
205
  case status
308
- when 400
309
- [ :invalid_request_error,
310
- "There was an issue with the format or content of your request." ]
311
- when 401
312
- [ :authentication_error,
313
- "There's an issue with your API key." ]
314
- when 403
315
- [ :permission_error,
316
- "Your API key does not have permission to use the specified resource." ]
317
- when 404
318
- [ :not_found_error,
319
- "The requested resource was not found." ]
320
- when 413
321
- [ :request_too_large,
322
- "Request exceeds the maximum allowed number of bytes." ]
323
- when 422
324
- [ :invalid_request_error,
325
- "There was an issue with the format or content of your request." ]
326
- when 429
327
- [ :rate_limit_error,
328
- "Your account has hit a rate limit." ]
329
- when 500, 502, 503
330
- [ :api_error,
331
- "An unexpected error has occurred internal to the providers systems." ]
332
- when 529
333
- [ :overloaded_error,
334
- "The providers server is temporarily overloaded." ]
335
- else
336
- [ :unknown_error, "
337
- An unknown error occurred." ]
338
- end
206
+ when 400
207
+ [ :invalid_request_error,
208
+ "There was an issue with the format or content of your request." ]
209
+ when 401
210
+ [ :authentication_error,
211
+ "There's an issue with your API key." ]
212
+ when 403
213
+ [ :permission_error,
214
+ "Your API key does not have permission to use the specified resource." ]
215
+ when 404
216
+ [ :not_found_error,
217
+ "The requested resource was not found." ]
218
+ when 413
219
+ [ :request_too_large,
220
+ "Request exceeds the maximum allowed number of bytes." ]
221
+ when 422
222
+ [ :invalid_request_error,
223
+ "There was an issue with the format or content of your request." ]
224
+ when 429
225
+ [ :rate_limit_error,
226
+ "Your account has hit a rate limit." ]
227
+ when 500, 502, 503
228
+ [ :api_error,
229
+ "An unexpected error has occurred internal to the providers systems." ]
230
+ when 529
231
+ [ :overloaded_error,
232
+ "The providers server is temporarily overloaded." ]
233
+ else
234
+ [ :unknown_error, "
235
+ An unknown error occurred." ]
236
+ end
339
237
  end
340
238
 
341
239
  end
@@ -1,2 +1,2 @@
1
1
  require_relative '../adapter'
2
- require_relative 'open_ai/adapter'
2
+ require_relative 'open_ai/adapter'
@@ -7,25 +7,25 @@ module Intelligence
7
7
 
8
8
  chat_request_uri "https://openrouter.ai/api/v1/chat/completions"
9
9
 
10
- configuration do
11
- parameter :key, String
12
- group :chat_options do
13
- parameter :model, String
14
- parameter :temperature, Float
15
- parameter :top_k, Integer
16
- parameter :top_p, Float
17
- parameter :max_tokens, Integer
18
- parameter :seed, Integer
19
- parameter :stop, String, array: true
20
- parameter :stream, [ TrueClass, FalseClass ]
21
- parameter :frequency_penalty, Float
22
- parameter :repetition_penalty, Float
23
- parameter :presence_penalty, Float
10
+ schema do
11
+ key String
12
+ chat_options do
13
+ model String
14
+ temperature Float
15
+ top_k Integer
16
+ top_p Float
17
+ max_tokens Integer
18
+ seed Integer
19
+ stop String, array: true
20
+ stream [ TrueClass, FalseClass ]
21
+ frequency_penalty Float
22
+ repetition_penalty Float
23
+ presence_penalty Float
24
24
 
25
- group :provider do
26
- parameter :order, String, array: true
27
- parameter :require_parameters, [ TrueClass, FalseClass ]
28
- parameter :allow_fallbacks, [ TrueClass, FalseClass ]
25
+ provider do
26
+ order String, array: true
27
+ require_parameters [ TrueClass, FalseClass ]
28
+ allow_fallbacks [ TrueClass, FalseClass ]
29
29
  end
30
30
  end
31
31
  end
@@ -7,27 +7,27 @@ module Intelligence
7
7
 
8
8
  chat_request_uri "https://api.sambanova.ai/v1/chat/completions"
9
9
 
10
- configuration do
10
+ schema do
11
11
 
12
12
  # normalized properties for all endpoints
13
- parameter :key, String
13
+ key String
14
14
 
15
15
  # properties for generative text endpoints
16
- group :chat_options do
16
+ chat_options do
17
17
 
18
18
  # normalized properties for samba nova generative text endpoint
19
- parameter :model, String
20
- parameter :max_tokens, Integer
21
- parameter :temperature, Float
22
- parameter :top_p, Float
23
- parameter :top_k, Float
24
- parameter :stop, String, array: true
25
- parameter :stream, [ TrueClass, FalseClass ]
19
+ model String
20
+ max_tokens Integer
21
+ temperature Float
22
+ top_p Float
23
+ top_k Float
24
+ stop String, array: true
25
+ stream [ TrueClass, FalseClass ]
26
26
 
27
27
  # samba nova properties for samba nova generative text endpoint
28
- parameter :repetition_penalty, Float
29
- group :stream_options do
30
- parameter :include_usage, [ TrueClass, FalseClass ]
28
+ repetition_penalty Float
29
+ stream_options do
30
+ include_usage [ TrueClass, FalseClass ]
31
31
  end
32
32
 
33
33
  end