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,345 +0,0 @@
1
- module Intelligence
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
110
-
111
- def chat_result_attributes( response )
112
- return nil unless response.success?
113
-
114
- response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
115
- return nil \
116
- if response_json.nil? || response_json[ :choices ].nil?
117
-
118
- result = {}
119
- result[ :choices ] = []
120
-
121
- ( response_json[ :choices ] || [] ).each do | json_choice |
122
- 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
- )
132
- end
133
-
134
- metrics_json = response_json[ :usage ]
135
- unless metrics_json.nil?
136
-
137
- metrics = {}
138
- metrics[ :input_tokens ] = metrics_json[ :prompt_tokens ]
139
- metrics[ :output_tokens ] = metrics_json[ :completion_tokens ]
140
- metrics = metrics.compact
141
-
142
- result[ :metrics ] = metrics unless metrics.empty?
143
-
144
- end
145
-
146
- result
147
- end
148
-
149
- def chat_result_error_attributes( response )
150
- error_type, error_description = translate_error_response_status( response.status )
151
- result = {
152
- error_type: error_type.to_s,
153
- error_description: error_description
154
- }
155
-
156
- parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
157
- if parsed_body && parsed_body.respond_to?( :include? ) && parsed_body.include?( :error )
158
- result = {
159
- error_type: error_type.to_s,
160
- error: parsed_body[ :error ][ :code ] || error_type.to_s,
161
- error_description: parsed_body[ :error ][ :message ] || error_description
162
- }
163
- end
164
-
165
- result
166
- end
167
-
168
- def stream_result_chunk_attributes( context, chunk )
169
- context ||= {}
170
- buffer = context[ :buffer ] || ''
171
- metrics = context[ :metrics ] || {
172
- input_tokens: 0,
173
- output_tokens: 0
174
- }
175
- choices = context[ :choices ] || Array.new( 1 , { message: {} } )
176
-
177
- choices.each do | choice |
178
- choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
179
- case content[ :type ]
180
- when :text
181
- content[ :text ] = ''
182
- when :tool_call
183
- content[ :tool_parameters ] = ''
184
- else
185
- content.clear
186
- end
187
- content
188
- end
189
- end
190
-
191
- buffer += chunk
192
- while ( eol_index = buffer.index( "\n" ) )
193
-
194
- line = buffer.slice!( 0..eol_index )
195
- line = line.strip
196
- next if line.empty? || !line.start_with?( 'data:' )
197
- line = line[ 6..-1 ]
198
-
199
- next if line.end_with?( '[DONE]' )
200
- data = JSON.parse( line )
201
-
202
- if data.is_a?( Hash )
203
-
204
- data[ 'choices' ]&.each do | data_choice |
205
-
206
- data_choice_index = data_choice[ 'index' ]
207
- data_choice_delta = data_choice[ 'delta' ]
208
- data_choice_finish_reason = data_choice[ 'finish_reason' ]
209
-
210
- choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
211
- if choices.size <= data_choice_index
212
- contents = choices[ data_choice_index ][ :message ][ :contents ] || []
213
- last_content = contents&.last
214
-
215
- if data_choice_delta.include?( 'content' )
216
- data_choice_content = data_choice_delta[ 'content' ] || ''
217
- if last_content.nil? || last_content[ :type ] == :tool_call
218
- contents.push( { type: :text, text: data_choice_content } )
219
- elsif last_content[ :type ].nil?
220
- last_content[ :type ] = :text
221
- last_content[ :text ] = data_choice_content
222
- elsif last_content[ :type ] == :text
223
- last_content[ :text ] = ( last_content[ :text ] || '' ) + data_choice_content
224
- end
225
- elsif data_choice_delta.include?( 'function_call' )
226
- data_choice_tool_call = data_choice_delta[ 'function_call' ]
227
- data_choice_tool_name = data_choice_tool_call[ 'name' ]
228
- data_choice_tool_parameters = data_choice_tool_call[ 'arguments' ]
229
- if last_content.nil? || last_content[ :type ] == :text
230
- contents.push( {
231
- type: :tool_call,
232
- tool_name: data_choice_tool_name,
233
- tool_parameters: data_choice_tool_parameters
234
- } )
235
- elsif last_content[ :type ].nil?
236
- last_content[ :type ] = :tool_call
237
- last_content[ :tool_name ] = data_choice_tool_name if data_choice_tool_name.present?
238
- last_content[ :tool_parameters ] = tool_parameters
239
- elsif last_content[ :type ] == :tool_call
240
- last_content[ :tool_parameters ] =
241
- ( last_content[ :tool_parameters ] || '' ) + data_choice_tool_parameters
242
- end
243
- end
244
- choices[ data_choice_index ][ :message ][ :contents ] = contents
245
- choices[ data_choice_index ][ :end_reason ] =
246
- translate_end_result( data_choice_finish_reason )
247
- end
248
-
249
- if usage = data[ 'usage' ]
250
- metrics[ :input_tokens ] += usage[ 'prompt_tokens' ]
251
- metrics[ :output_tokens ] += usage[ 'completion_tokens' ]
252
- end
253
-
254
- end
255
-
256
- end
257
-
258
- context[ :buffer ] = buffer
259
- context[ :metrics ] = metrics
260
- context[ :choices ] = choices
261
-
262
- [ context, choices.empty? ? nil : { choices: choices.dup } ]
263
- end
264
-
265
- def stream_result_attributes( context )
266
- choices = context[ :choices ]
267
- metrics = context[ :metrics ]
268
-
269
- choices = choices.map do | choice |
270
- { end_reason: choice[ :end_reason ] }
271
- end
272
-
273
- { choices: choices, metrics: context[ :metrics ] }
274
- end
275
-
276
- alias_method :stream_result_error_attributes, :chat_result_error_attributes
277
-
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
290
-
291
- private; def translate_end_result( end_result )
292
- case end_result
293
- when 'stop'
294
- :ended
295
- when 'length'
296
- :token_limit_exceeded
297
- when 'function_call'
298
- :tool_called
299
- when 'content_filter'
300
- :filtered
301
- else
302
- nil
303
- end
304
- end
305
-
306
- def translate_error_response_status( status )
307
- 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
339
- end
340
-
341
- end
342
-
343
- end
344
-
345
- end