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,393 +0,0 @@
1
- require 'uri'
2
-
3
- module Intelligence
4
- module Google
5
- module ChatMethods
6
-
7
- GENERATIVE_LANGUAGE_URI = "https://generativelanguage.googleapis.com/v1beta/models/"
8
-
9
- SUPPORTED_BINARY_MEDIA_TYPES = %w[ text ]
10
-
11
- SUPPORTED_BINARY_CONTENT_TYPES = %w[
12
- image/png image/jpeg image/webp image/heic image/heif
13
- audio/aac audio/flac audio/mp3 audio/m4a audio/mpeg audio/mpga audio/mp4 audio/opus
14
- audio/pcm audio/wav audio/webm
15
- application/pdf
16
- ]
17
-
18
- SUPPORTED_FILE_MEDIA_TYPES = %w[ text ]
19
-
20
- SUPPORTED_CONTENT_TYPES = %w[
21
- image/png image/jpeg image/webp image/heic image/heif
22
- video/x-flv video/quicktime video/mpeg video/mpegps video/mpg video/mp4 video/webm
23
- video/wmv video/3gpp
24
- audio/aac audio/flac audio/mp3 audio/m4a audio/mpeg audio/mpga audio/mp4 audio/opus
25
- audio/pcm audio/wav audio/webm
26
- application/pdf
27
- ]
28
-
29
- def chat_request_uri( options )
30
- options = options ? self.class.configure( options ) : {}
31
- options = @options.merge( options )
32
-
33
- key = options[ :key ]
34
- gc = options[ :generationConfig ] || {}
35
- model = gc[ :model ]
36
- stream = gc.key?( :stream ) ? gc[ :stream ] : false
37
-
38
- raise ArgumentError.new( "A Google API key is required to build a Google chat request." ) \
39
- if key.nil?
40
- raise ArgumentError.new( "A Google model is required to build a Google chat request." ) \
41
- if model.nil?
42
-
43
- uri = URI( GENERATIVE_LANGUAGE_URI )
44
- path = File.join( uri.path, model )
45
- path += stream ? ':streamGenerateContent' : ':generateContent'
46
- uri.path = path
47
- query = { key: key }
48
- query[ :alt ] = 'sse' if stream
49
- uri.query = URI.encode_www_form( query )
50
-
51
- uri.to_s
52
- end
53
-
54
- def chat_request_headers( options = {} )
55
- { 'Content-Type' => 'application/json' }
56
- end
57
-
58
- def chat_request_body( conversation, options = {} )
59
- options = options ? self.class.configure( options ) : {}
60
- options = @options.merge( options )
61
-
62
- gc = options[ :generationConfig ]
63
- # discard properties not part of the google generationConfig schema
64
- gc.delete( :model )
65
- gc.delete( :stream )
66
-
67
- result = {}
68
- result[ :generationConfig ] = gc
69
-
70
- # construct the system prompt in the form of the google schema
71
- system_instructions = translate_system_message( conversation[ :system_message ] )
72
- result[ :systemInstruction ] = system_instructions if system_instructions
73
-
74
- result[ :contents ] = []
75
- conversation[ :messages ]&.each do | message |
76
-
77
- result_message = { role: message[ :role ] == :user ? 'user' : 'model' }
78
- result_message_parts = []
79
-
80
- message[ :contents ]&.each do | content |
81
- case content[ :type ]
82
- when :text
83
- result_message_parts << { text: content[ :text ] }
84
- when :binary
85
- content_type = content[ :content_type ]
86
- bytes = content[ :bytes ]
87
- if content_type && bytes
88
- mime_type = MIME::Types[ content_type ].first
89
- if SUPPORTED_BINARY_MEDIA_TYPES.include?( mime_type&.media_type ) ||
90
- SUPPORTED_BINARY_CONTENT_TYPES.include?( content_type )
91
- result_message_parts << {
92
- inline_data: {
93
- mime_type: content_type,
94
- data: Base64.strict_encode64( bytes )
95
- }
96
- }
97
- else
98
- raise UnsupportedContentError.new(
99
- :google,
100
- "does not support #{content_type} content type"
101
- )
102
- end
103
- else
104
- raise UnsupportedContentError.new(
105
- :google,
106
- 'requires binary content to include content type and ( packed ) bytes'
107
- )
108
- end
109
- when :file
110
- content_type = content[ :content_type ]
111
- uri = content[ :uri ]
112
- if content_type && uri
113
- mime_type = MIME::Types[ content_type ].first
114
- if SUPPORTED_FILE_MEDIA_TYPES.include?( mime_type&.media_type ) ||
115
- SUPPORTED_FILE_CONTENT_TYPES.include?( content_type )
116
- result_message_parts << {
117
- file_data: {
118
- mime_type: content_type,
119
- file_uri: uri
120
- }
121
- }
122
- else
123
- raise UnsupportedContentError.new(
124
- :google,
125
- "does not support #{content_type} content type"
126
- )
127
- end
128
- else
129
- raise UnsupportedContentError.new(
130
- :google,
131
- 'requires file content to include content type and uri'
132
- )
133
- end
134
-
135
- else
136
- raise InvalidContentError.new( :google )
137
- end
138
- end
139
-
140
- result_message[ :parts ] = result_message_parts
141
- result[ :contents ] << result_message
142
-
143
- end
144
-
145
- JSON.generate( result )
146
-
147
- end
148
-
149
- def chat_result_attributes( response )
150
-
151
- return nil unless response.success?
152
-
153
- response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
154
- return nil \
155
- if response_json.nil? || response_json[ :candidates ].nil?
156
-
157
- result = {}
158
- result[ :choices ] = []
159
-
160
- response_json[ :candidates ]&.each do | response_choice |
161
-
162
- end_reason = translate_finish_reason( response_choice[ :finishReason ] )
163
-
164
- role = nil
165
- contents = []
166
-
167
- response_content = response_choice[ :content ]
168
- if response_content
169
- role = ( response_content[ :role ] == 'model' ) ? 'assistant' : 'user'
170
-
171
- contents = []
172
- response_content[ :parts ]&.each do | response_content_part |
173
- if response_content_part.key?( :text )
174
- contents.push( {
175
- type: 'text', text: response_content_part[ :text ]
176
- } )
177
- end
178
- end
179
- end
180
-
181
- result_message = nil
182
- if role
183
- result_message = { role: role }
184
- result_message[ :contents ] = contents
185
- end
186
-
187
- result[ :choices ].push( { end_reason: end_reason, message: result_message } )
188
-
189
- end
190
-
191
- metrics_json = response_json[ :usageMetadata ]
192
- unless metrics_json.nil?
193
-
194
- metrics = {}
195
- metrics[ :input_tokens ] = metrics_json[ :promptTokenCount ]
196
- metrics[ :output_tokens ] = metrics_json[ :candidatesTokenCount ]
197
- metrics = metrics.compact
198
-
199
- result[ :metrics ] = metrics unless metrics.empty?
200
-
201
- end
202
-
203
- result
204
-
205
- end
206
-
207
- def chat_result_error_attributes( response )
208
-
209
- error_type, error_description = translate_error_response_status( response.status )
210
- result = { error_type: error_type.to_s, error_description: error_description }
211
-
212
- response_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
213
- if response_body && response_body[ :error ]
214
- error_details_reason = response_body[ :error ][ :details ]&.first&.[]( :reason )
215
- # a special case for authentication
216
- error_type = :authentication_error if error_details_reason == 'API_KEY_INVALID'
217
- result = {
218
- error_type: error_type.to_s,
219
- error: error_details_reason || response_body[ :error ][ :status ] || error_type,
220
- error_description: response_body[ :error ][ :message ]
221
- }
222
- end
223
- result
224
-
225
- end
226
-
227
- def stream_result_chunk_attributes( context, chunk )
228
- #---------------------------------------------------
229
-
230
- context ||= {}
231
- buffer = context[ :buffer ] || ''
232
- metrics = context[ :metrics ] || {
233
- input_tokens: 0,
234
- output_tokens: 0
235
- }
236
- choices = context[ :choices ] || Array.new( 1 , { message: {} } )
237
-
238
- choices.each do | choice |
239
- choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
240
- case content[ :type ]
241
- when :text
242
- content[ :text ] = ''
243
- else
244
- content.clear
245
- end
246
- content
247
- end
248
- end
249
-
250
- buffer += chunk
251
- while ( eol_index = buffer.index( "\n" ) )
252
-
253
- line = buffer.slice!( 0..eol_index )
254
- line = line.strip
255
- next if line.empty? || !line.start_with?( 'data:' )
256
- line = line[ 6..-1 ]
257
-
258
- data = JSON.parse( line, symbolize_names: true )
259
- if data.is_a?( Hash )
260
-
261
- data[ :candidates ]&.each do | data_candidate |
262
-
263
- data_candidate_index = data_candidate[ :index ] || 0
264
- data_candidate_content = data_candidate[ :content ]
265
- data_candidate_finish_reason = data_candidate[ :finishReason ]
266
- choices.fill( { message: { role: 'assistant' } }, choices.size, data_candidate_index + 1 ) \
267
- if choices.size <= data_candidate_index
268
- contents = choices[ data_candidate_index ][ :message ][ :contents ] || []
269
- last_content = contents&.last
270
-
271
- if data_candidate_content&.include?( :parts )
272
- data_candidate_content_parts = data_candidate_content[ :parts ]
273
- data_candidate_content_parts&.each do | data_candidate_content_part |
274
- if data_candidate_content_part.key?( :text )
275
- if last_content.nil? || last_content[ :type ] != :text
276
- contents.push( { type: :text, text: data_candidate_content_part[ :text ] } )
277
- else
278
- last_content[ :text ] =
279
- ( last_content[ :text ] || '' ) + data_candidate_content_part[ :text ]
280
- end
281
- end
282
- end
283
- end
284
- choices[ data_candidate_index ][ :message ][ :contents ] = contents
285
- choices[ data_candidate_index ][ :end_reason ] =
286
- translate_finish_reason( data_candidate_finish_reason )
287
- end
288
-
289
- if usage = data[ :usageMetadata ]
290
- metrics[ :input_tokens ] = usage[ :promptTokenCount ]
291
- metrics[ :output_tokens ] = usage[ :candidatesTokenCount ]
292
- end
293
-
294
- end
295
-
296
- end
297
-
298
- context[ :buffer ] = buffer
299
- context[ :metrics ] = metrics
300
- context[ :choices ] = choices
301
-
302
- [ context, choices.empty? ? nil : { choices: choices.dup } ]
303
-
304
- end
305
-
306
- def stream_result_attributes( context )
307
- #--------------------------------------
308
-
309
- choices = context[ :choices ]
310
- metrics = context[ :metrics ]
311
-
312
- choices = choices.map do | choice |
313
- { end_reason: choice[ :end_reason ] }
314
- end
315
-
316
- { choices: choices, metrics: context[ :metrics ] }
317
-
318
- end
319
-
320
- alias_method :stream_result_error_attributes, :chat_result_error_attributes
321
-
322
- private; def translate_system_message( system_message )
323
- # -----------------------------------------------------
324
-
325
- return nil if system_message.nil?
326
-
327
- text = ''
328
- system_message[ :contents ].each do | content |
329
- text += content[ :text ] if content[ :type ] == :text
330
- end
331
-
332
- return nil if text.empty?
333
-
334
- {
335
- role: 'user',
336
- parts: [
337
- { text: text }
338
- ]
339
- }
340
-
341
- end
342
-
343
- private; def translate_finish_reason( finish_reason )
344
- # ---------------------------------------------------
345
- case finish_reason
346
- when 'STOP'
347
- :ended
348
- when 'MAX_TOKENS'
349
- :token_limit_exceeded
350
- when 'SAFETY', 'RECITATION', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII'
351
- :filtered
352
- else
353
- nil
354
- end
355
- end
356
-
357
- private; def translate_error_response_status( status )
358
- case status
359
- when 400
360
- [ :invalid_request_error,
361
- "There was an issue with the format or content of your request." ]
362
- when 403
363
- [ :permission_error,
364
- "Your API key does not have permission to use the specified resource." ]
365
- when 404
366
- [ :not_found_error,
367
- "The requested resource was not found." ]
368
- when 413
369
- [ :request_too_large,
370
- "Request exceeds the maximum allowed number of bytes." ]
371
- when 422
372
- [ :invalid_request_error,
373
- "There was an issue with the format or content of your request." ]
374
- when 429
375
- [ :rate_limit_error,
376
- "Your account has hit a rate limit." ]
377
- when 500, 502, 503
378
- [ :api_error,
379
- "An unexpected error has occurred internal to the providers systems." ]
380
- when 529
381
- [ :overloaded_error,
382
- "The providers server is temporarily overloaded." ]
383
- else
384
- [ :unknown_error, "
385
- An unknown error occurred." ]
386
- end
387
- end
388
-
389
- end
390
-
391
- end
392
-
393
- end
@@ -1,11 +0,0 @@
1
- require_relative '../generic/adapter'
2
- require_relative 'chat_methods'
3
-
4
- module Intelligence
5
- module Legacy
6
- class Adapter < Generic::Adapter
7
- include ChatMethods
8
- end
9
- end
10
- end
11
-
@@ -1,54 +0,0 @@
1
- module Intelligence
2
- module Legacy
3
- module ChatMethods
4
-
5
- def chat_request_body( conversation, options = {} )
6
- options = options ? self.class.configure( options ) : {}
7
- options = @options.merge( options )
8
-
9
- result = options[ :chat_options ]&.compact || {}
10
- result[ :messages ] = []
11
-
12
- system_message = system_message_to_s( conversation[ :system_message ] )
13
- result[ :messages ] << { role: 'system', content: system_message } if system_message
14
-
15
- # detect if the conversation has any non-text content; this handles the sittuation
16
- # where non-vision models only support the legacy message schema while the vision
17
- # models only support the modern message schema
18
- has_non_text_content = conversation[ :messages ]&.find do | message |
19
- message[ :contents ]&.find do | content |
20
- content[ :type ] != nil && content[ :type ] != :text
21
- end
22
- end
23
-
24
- if has_non_text_content
25
- conversation[ :messages ]&.each do | message |
26
- result[ :messages ] << chat_request_message_attributes( message )
27
- end
28
- else
29
- conversation[ :messages ]&.each do | message |
30
- result[ :messages ] << chat_request_legacy_message_attributes( message )
31
- end
32
- end
33
- JSON.generate( result )
34
- end
35
-
36
- def chat_request_legacy_message_attributes( message )
37
- result_message = { role: message[ :role ] }
38
- result_message_content = ""
39
-
40
- message[ :contents ]&.each do | content |
41
- case content[ :type ]
42
- when :text
43
- result_message_content += content[ :text ]
44
- end
45
- end
46
-
47
- result_message[ :content ] = result_message_content
48
- result_message
49
- end
50
-
51
- end
52
- end
53
- end
54
-