intelligence 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,12 +11,64 @@ module Intelligence
11
11
 
12
12
  attr_reader :tool_call_id
13
13
  attr_reader :tool_name
14
- attr_reader :tool_parameters
14
+
15
+ def tool_parameters( options = nil )
16
+ return @tool_parameters if @tool_parameters.nil? || @tool_parameters.is_a?( Hash )
17
+
18
+ options = options || {}
19
+ parse = options.key?( :parse ) ? options[ :parse ] : true
20
+
21
+ return @parsed_tool_parameters if @parsed_tool_parameters && parse
22
+
23
+ tool_parameters = ( options.key?( :repair ) ? options[ :repair ] : true ) ?
24
+ JSON.repair( @tool_parameters ) : @tool_parameters
25
+
26
+ if parse
27
+ @parsed_tool_parameters = tool_parameters =
28
+ JSON.parse(
29
+ tool_parameters,
30
+ parse.is_a?( Hash ) ? parse : { symbolize_names: true }
31
+ )
32
+ end
33
+
34
+ tool_parameters
35
+ end
15
36
 
16
37
  def valid?
17
38
  tool_name && !tool_name.empty?
18
39
  end
19
40
 
41
+ def merge( other_tool_call )
42
+ other_tool_call_id = other_tool_call.tool_call_id
43
+ other_tool_name = other_tool_call.tool_name
44
+ other_tool_parameters = other_tool_call.tool_parameters( repair: false, parse: false )
45
+
46
+ raise ArgumentError,
47
+ "The given tool call parameters are incompative with this tool's parameters." \
48
+ unless @tool_parameters.nil? || other_tool_parameters.nil? ||
49
+ @tool_parameters.is_a?( other_tool_parameters.class )
50
+
51
+ tool_call_id = other_tool_call_id.nil? ?
52
+ @tool_call_id : @tool_call_id || '' + other_tool_call_id
53
+ tool_name = other_tool_name.nil? ?
54
+ @tool_name : @tool_name || '' + other_tool_name
55
+ tool_parameters = @tool_parameters
56
+
57
+ unless other_tool_parameters.nil?
58
+ if other_tool_parameters.is_a?( Hash )
59
+ tool_parameters = ( tool_parameters || {} ).merge( other_tool_parameters )
60
+ else
61
+ tool_parameters = ( tool_parameters || '' ) + other_tool_parameters
62
+ end
63
+ end
64
+
65
+ self.class.new(
66
+ tool_call_id: tool_call_id,
67
+ tool_name: tool_name,
68
+ tool_parameters: tool_parameters
69
+ )
70
+ end
71
+
20
72
  def to_h
21
73
  {
22
74
  type: :tool_call,
@@ -1,3 +1,3 @@
1
1
  module Intelligence
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.0"
3
3
  end
data/lib/intelligence.rb CHANGED
@@ -4,6 +4,7 @@ require 'base64'
4
4
  require 'faraday'
5
5
  require 'dynamic_schema'
6
6
  require 'mime-types'
7
+ require 'json/repair'
7
8
 
8
9
  require 'intelligence/version'
9
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intelligence
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kristoph Cichocki-Romanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-27 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json-repair
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -123,15 +137,14 @@ files:
123
137
  - lib/intelligence/adapters/cerebras.rb
124
138
  - lib/intelligence/adapters/generic.rb
125
139
  - lib/intelligence/adapters/generic/adapter.rb
126
- - lib/intelligence/adapters/generic/chat_methods.rb
140
+ - lib/intelligence/adapters/generic/chat_request_methods.rb
141
+ - lib/intelligence/adapters/generic/chat_response_methods.rb
127
142
  - lib/intelligence/adapters/google.rb
128
143
  - lib/intelligence/adapters/google/adapter.rb
129
144
  - lib/intelligence/adapters/google/chat_request_methods.rb
130
145
  - lib/intelligence/adapters/google/chat_response_methods.rb
131
146
  - lib/intelligence/adapters/groq.rb
132
147
  - lib/intelligence/adapters/hyperbolic.rb
133
- - lib/intelligence/adapters/legacy/adapter.rb
134
- - lib/intelligence/adapters/legacy/chat_methods.rb
135
148
  - lib/intelligence/adapters/mistral.rb
136
149
  - lib/intelligence/adapters/open_ai.rb
137
150
  - lib/intelligence/adapters/open_ai/adapter.rb
@@ -1,362 +0,0 @@
1
- module Intelligence
2
- module Generic
3
- module ChatMethods
4
-
5
- module ClassMethods
6
- def chat_request_uri( uri = nil )
7
- if uri
8
- @chat_request_uri = uri
9
- else
10
- @chat_request_uri
11
- end
12
- end
13
- end
14
-
15
- def self.included( base )
16
- base.extend( ClassMethods )
17
- end
18
-
19
- def chat_request_uri( options )
20
- self.class.chat_request_uri
21
- end
22
-
23
- def chat_request_headers( options = nil )
24
- options = @options.merge( build_options( options ) )
25
- result = {}
26
-
27
- key = options[ :key ]
28
-
29
- raise ArgumentError.new( "An API key is required to build a chat request." ) \
30
- if key.nil?
31
-
32
- result[ 'Content-Type' ] = 'application/json'
33
- result[ 'Authorization' ] = "Bearer #{key}"
34
-
35
- result
36
- end
37
-
38
- def chat_request_body( conversation, options = nil )
39
- options = @options.merge( build_options( options ) )
40
-
41
- result = options[ :chat_options ]
42
- result[ :messages ] = []
43
-
44
- system_message = system_message_to_s( conversation[ :system_message ] )
45
- result[ :messages ] << { role: 'system', content: system_message } if system_message
46
-
47
- conversation[ :messages ]&.each do | message |
48
- result[ :messages ] << chat_request_message_attributes( message )
49
- end
50
-
51
- JSON.generate( result )
52
- end
53
-
54
- def chat_request_message_attributes( message )
55
- result_message = { role: message[ :role ] }
56
- result_message_content = []
57
- message[ :contents ]&.each do | content |
58
- case content[ :type ]
59
- when :text
60
- result_message_content << { type: 'text', text: content[ :text ] }
61
- when :binary
62
- content_type = content[ :content_type ]
63
- bytes = content[ :bytes ]
64
- if content_type && bytes
65
- mime_type = MIME::Types[ content_type ].first
66
- if mime_type&.media_type == 'image'
67
- result_message_content << {
68
- type: 'image_url',
69
- image_url: {
70
- url: "data:#{content_type};base64,#{Base64.strict_encode64( bytes )}".freeze
71
- }
72
- }
73
- else
74
- raise UnsupportedContentError.new(
75
- :generic,
76
- 'only support content of type image/*'
77
- )
78
- end
79
- else
80
- raise UnsupportedContentError.new(
81
- :generic,
82
- 'requires binary content to include content type and ( packed ) bytes'
83
- )
84
- end
85
- when :file
86
- content_type = content[ :content_type ]
87
- uri = content[ :uri ]
88
- if content_type && uri
89
- mime_type = MIME::Types[ content_type ].first
90
- if mime_type&.media_type == 'image'
91
- result_message_content << {
92
- type: 'image_url',
93
- image_url: { url: uri }
94
- }
95
- else
96
- raise UnsupportedContentError.new(
97
- :generic,
98
- 'only support content of type image/*'
99
- )
100
- end
101
- else
102
- raise UnsupportedContentError.new(
103
- :generic,
104
- 'requires binary content to include content type and ( packed ) bytes'
105
- )
106
- end
107
- end
108
- end
109
- result_message[ :content ] = result_message_content
110
- result_message
111
- end
112
-
113
- def chat_result_attributes( response )
114
-
115
- return nil unless response.success?
116
- response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
117
- return nil if response_json.nil? || response_json[ :choices ].nil?
118
-
119
- result = {}
120
- result[ :choices ] = []
121
-
122
- ( response_json[ :choices ] || [] ).each do | json_choice |
123
- json_message = json_choice[ :message ]
124
- result[ :choices ].push(
125
- {
126
- end_reason: translate_end_result( json_choice[ :finish_reason ] ),
127
- message: {
128
- role: json_message[ :role ],
129
- contents: [ { type: 'text', text: json_message[ :content ] } ]
130
- }
131
- }
132
- )
133
- end
134
-
135
- metrics_json = response_json[ :usage ]
136
- unless metrics_json.nil?
137
-
138
- metrics = {}
139
- metrics[ :input_tokens ] = metrics_json[ :prompt_tokens ]
140
- metrics[ :output_tokens ] = metrics_json[ :completion_tokens ]
141
- metrics = metrics.compact
142
-
143
- result[ :metrics ] = metrics unless metrics.empty?
144
-
145
- end
146
-
147
- result
148
-
149
- end
150
-
151
- def chat_result_error_attributes( response )
152
-
153
- error_type, error_description = translate_error_response_status( response.status )
154
- result = {
155
- error_type: error_type.to_s,
156
- error_description: error_description
157
- }
158
-
159
- parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
160
- if parsed_body && parsed_body.respond_to?( :include? ) && parsed_body.include?( :error )
161
- result = {
162
- error_type: error_type.to_s,
163
- error: parsed_body[ :error ][ :code ] || error_type.to_s,
164
- error_description: parsed_body[ :error ][ :message ] || error_description
165
- }
166
- end
167
-
168
- result
169
-
170
- end
171
-
172
- def stream_result_chunk_attributes( context, chunk )
173
-
174
- context ||= {}
175
- buffer = context[ :buffer ] || ''
176
- metrics = context[ :metrics ] || {
177
- input_tokens: 0,
178
- output_tokens: 0
179
- }
180
- choices = context[ :choices ] || Array.new( 1 , { message: {} } )
181
-
182
- choices.each do | choice |
183
- choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
184
- case content[ :type ]
185
- when :text
186
- content[ :text ] = ''
187
- when :tool_call
188
- content[ :tool_parameters ] = ''
189
- else
190
- content.clear
191
- end
192
- content
193
- end
194
- end
195
-
196
- buffer += chunk
197
- while ( eol_index = buffer.index( "\n" ) )
198
-
199
- line = buffer.slice!( 0..eol_index )
200
- line = line.strip
201
- next if line.empty? || !line.start_with?( 'data:' )
202
- line = line[ 6..-1 ]
203
-
204
- next if line.end_with?( '[DONE]' )
205
- data = JSON.parse( line )
206
- if data.is_a?( Hash )
207
-
208
- data[ 'choices' ]&.each do | data_choice |
209
-
210
- data_choice_index = data_choice[ 'index' ]
211
- data_choice_delta = data_choice[ 'delta' ]
212
- data_choice_finish_reason = data_choice[ 'finish_reason' ]
213
-
214
- choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
215
- if choices.size <= data_choice_index
216
- contents = choices[ data_choice_index ][ :message ][ :contents ] || []
217
- last_content = contents&.last
218
-
219
- if data_choice_delta.include?( 'content' )
220
- data_choice_content = data_choice_delta[ 'content' ] || ''
221
- if last_content.nil? || last_content[ :type ] == :tool_call
222
- contents.push( { type: :text, text: data_choice_content } )
223
- elsif last_content[ :type ] == :text || last_content[ :type ].nil?
224
- last_content[ :type ] = :text
225
- last_content[ :text ] = ( last_content[ :text ] || '' ) + data_choice_content
226
- end
227
- elsif data_choice_delta.include?( 'function_call' )
228
- data_choice_tool_call = data_choice_delta[ 'function_call' ]
229
- data_choice_tool_name = data_choice_tool_call[ 'name' ]
230
- data_choice_tool_parameters = data_choice_tool_call[ 'arguments' ]
231
- if last_content.nil? || last_content[ :type ] == :text
232
- contents.push( {
233
- type: :tool_call,
234
- tool_name: data_choice_tool_name,
235
- tool_parameters: data_choice_tool_parameters
236
- } )
237
- elsif last_content[ :type ].nil?
238
- last_content[ :type ] = :tool_call
239
- last_content[ :tool_name ] = data_choice_tool_name \
240
- if data_choice_tool_name.present?
241
- last_content[ :tool_parameters ] = tool_parameters
242
- elsif last_content[ :type ] == :tool_call
243
- last_content[ :tool_parameters ] =
244
- ( last_content[ :tool_parameters ] || '' ) + data_choice_tool_parameters
245
- end
246
- end
247
- choices[ data_choice_index ][ :message ][ :contents ] = contents
248
- choices[ data_choice_index ][ :end_reason ] ||=
249
- translate_end_result( data_choice_finish_reason )
250
- end
251
-
252
- if usage = data[ 'usage' ]
253
- # note: A number of providers will resend the input tokens as part of their usage
254
- # payload.
255
- metrics[ :input_tokens ] = usage[ 'prompt_tokens' ] \
256
- if usage.include?( 'prompt_tokens' )
257
- metrics[ :output_tokens ] += usage[ 'completion_tokens' ] \
258
- if usage.include?( 'completion_tokens' )
259
- end
260
-
261
- end
262
-
263
- end
264
-
265
- context[ :buffer ] = buffer
266
- context[ :metrics ] = metrics
267
- context[ :choices ] = choices
268
-
269
- [ context, choices.empty? ? nil : { choices: choices.dup } ]
270
-
271
- end
272
-
273
- def stream_result_attributes( context )
274
-
275
- choices = context[ :choices ]
276
- metrics = context[ :metrics ]
277
-
278
- choices = choices.map do | choice |
279
- { end_reason: choice[ :end_reason ] }
280
- end
281
-
282
- { choices: choices, metrics: context[ :metrics ] }
283
-
284
- end
285
-
286
- alias_method :stream_result_error_attributes, :chat_result_error_attributes
287
-
288
- def translate_end_result( end_result )
289
- case end_result
290
- when 'stop'
291
- :ended
292
- when 'length'
293
- :token_limit_exceeded
294
- when 'function_call'
295
- :tool_called
296
- when 'content_filter'
297
- :filtered
298
- else
299
- nil
300
- end
301
- end
302
-
303
- def translate_error_response_status( status )
304
- case status
305
- when 400
306
- [ :invalid_request_error,
307
- "There was an issue with the format or content of your request." ]
308
- when 401
309
- [ :authentication_error,
310
- "There's an issue with your API key." ]
311
- when 403
312
- [ :permission_error,
313
- "Your API key does not have permission to use the specified resource." ]
314
- when 404
315
- [ :not_found_error,
316
- "The requested resource was not found." ]
317
- when 413
318
- [ :request_too_large,
319
- "Request exceeds the maximum allowed number of bytes." ]
320
- when 422
321
- [ :invalid_request_error,
322
- "There was an issue with the format or content of your request." ]
323
- when 429
324
- [ :rate_limit_error,
325
- "Your account has hit a rate limit." ]
326
- when 500, 502, 503
327
- [ :api_error,
328
- "An unexpected error has occurred internal to the providers systems." ]
329
- when 529
330
- [ :overloaded_error,
331
- "The providers server is temporarily overloaded." ]
332
- else
333
- [ :unknown_error, "
334
- An unknown error occurred." ]
335
- end
336
- end
337
-
338
- private
339
-
340
- def to_options( options, &block )
341
- return {} unless options&.any?
342
- @options_builder ||= DynamicSchema::Builder.new.define( &self.class.schema )
343
- @options_builder.build( options, &block )
344
- end
345
-
346
-
347
- def system_message_to_s( system_message )
348
-
349
- return nil if system_message.nil?
350
-
351
- result = ''
352
- system_message[ :contents ].each do | content |
353
- result += content[ :text ] if content[ :type ] == :text
354
- end
355
-
356
- result.empty? ? nil : result
357
-
358
- end
359
-
360
- end
361
- end
362
- 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,53 +0,0 @@
1
- module Intelligence
2
- module Legacy
3
- module ChatMethods
4
-
5
- def chat_request_body( conversation, options = {} )
6
- options = @options.merge( to_options( options ) )
7
-
8
- result = options[ :chat_options ]&.compact || {}
9
- result[ :messages ] = []
10
-
11
- system_message = system_message_to_s( conversation[ :system_message ] )
12
- result[ :messages ] << { role: 'system', content: system_message } if system_message
13
-
14
- # detect if the conversation has any non-text content; this handles the sittuation
15
- # where non-vision models only support the legacy message schema while the vision
16
- # models only support the modern message schema
17
- has_non_text_content = conversation[ :messages ]&.find do | message |
18
- message[ :contents ]&.find do | content |
19
- content[ :type ] != nil && content[ :type ] != :text
20
- end
21
- end
22
-
23
- if has_non_text_content
24
- conversation[ :messages ]&.each do | message |
25
- result[ :messages ] << chat_request_message_attributes( message )
26
- end
27
- else
28
- conversation[ :messages ]&.each do | message |
29
- result[ :messages ] << chat_request_legacy_message_attributes( message )
30
- end
31
- end
32
- JSON.generate( result )
33
- end
34
-
35
- def chat_request_legacy_message_attributes( message )
36
- result_message = { role: message[ :role ] }
37
- result_message_content = ""
38
-
39
- message[ :contents ]&.each do | content |
40
- case content[ :type ]
41
- when :text
42
- result_message_content += content[ :text ]
43
- end
44
- end
45
-
46
- result_message[ :content ] = result_message_content
47
- result_message
48
- end
49
-
50
- end
51
- end
52
- end
53
-