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