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,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