intelligence 0.7.1 → 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.
@@ -0,0 +1,234 @@
1
+ module Intelligence
2
+ module Generic
3
+ module ChatResponseMethods
4
+
5
+ def chat_result_attributes( response )
6
+ return nil unless response.success?
7
+ response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
8
+ return nil if response_json.nil? || response_json[ :choices ].nil?
9
+
10
+ result = {}
11
+ result[ :choices ] = []
12
+
13
+ ( response_json[ :choices ] || [] ).each do | json_choice |
14
+ if ( json_message = json_choice[ :message ] )
15
+ result_message = { role: json_message[ :role ] }
16
+ if json_message[ :content ]
17
+ result_message[ :contents ] = [ { type: :text, text: json_message[ :content ] } ]
18
+ end
19
+ if json_message[ :tool_calls ] && !json_message[ :tool_calls ].empty?
20
+ result_message[ :contents ] ||= []
21
+ json_message[ :tool_calls ].each do | json_message_tool_call |
22
+ result_message_tool_call_parameters =
23
+ JSON.parse( json_message_tool_call[ :function ][ :arguments ], symbolize_names: true ) \
24
+ rescue json_message_tool_call[ :function ][ :arguments ]
25
+ result_message[ :contents ] << {
26
+ type: :tool_call,
27
+ tool_call_id: json_message_tool_call[ :id ],
28
+ tool_name: json_message_tool_call[ :function ][ :name ],
29
+ tool_parameters: result_message_tool_call_parameters
30
+ }
31
+ end
32
+ end
33
+ end
34
+ result[ :choices ].push( {
35
+ end_reason: to_end_reason( json_choice[ :finish_reason ] ),
36
+ message: result_message
37
+ } )
38
+ end
39
+
40
+ metrics_json = response_json[ :usage ]
41
+ unless metrics_json.nil?
42
+
43
+ metrics = {}
44
+ metrics[ :input_tokens ] = metrics_json[ :prompt_tokens ]
45
+ metrics[ :output_tokens ] = metrics_json[ :completion_tokens ]
46
+ metrics = metrics.compact
47
+
48
+ result[ :metrics ] = metrics unless metrics.empty?
49
+
50
+ end
51
+
52
+ result
53
+ end
54
+
55
+ def chat_result_error_attributes( response )
56
+ error_type, error_description = to_error_response( response.status )
57
+ error = error_type
58
+
59
+ parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
60
+ if parsed_body && parsed_body.respond_to?( :[] )
61
+ if parsed_body[ :error ].respond_to?( :[] )
62
+ error = parsed_body[ :error ][ :code ] || error_type
63
+ error_description = parsed_body[ :error ][ :message ] || error_description
64
+ elsif parsed_body[ :object ] == 'error'
65
+ error = parsed_body[ :type ] || error_type
66
+ error_description = parsed_body[ :detail ] || parsed_body[ :message ]
67
+ end
68
+ end
69
+
70
+ { error_type: error_type.to_s, error: error.to_s, error_description: error_description }
71
+ end
72
+
73
+ def stream_result_chunk_attributes( context, chunk )
74
+ context ||= {}
75
+ buffer = context[ :buffer ] || ''
76
+ metrics = context[ :metrics ] || {
77
+ input_tokens: 0,
78
+ output_tokens: 0
79
+ }
80
+ choices = context[ :choices ] || Array.new( 1 , { message: {} } )
81
+
82
+ choices.each do | choice |
83
+ choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
84
+ { type: content[ :type ] }
85
+ end
86
+ end
87
+
88
+ buffer += chunk
89
+ while ( eol_index = buffer.index( "\n" ) )
90
+ line = buffer.slice!( 0..eol_index )
91
+ line = line.strip
92
+ next if line.empty? || !line.start_with?( 'data:' )
93
+ line = line[ 6..-1 ]
94
+ next if line.end_with?( '[DONE]' )
95
+
96
+ data = JSON.parse( line ) rescue nil
97
+ if data.is_a?( Hash )
98
+ data[ 'choices' ]&.each do | data_choice |
99
+
100
+ data_choice_index = data_choice[ 'index' ]
101
+ data_choice_delta = data_choice[ 'delta' ]
102
+ data_choice_finish_reason = data_choice[ 'finish_reason' ]
103
+
104
+ choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
105
+ if choices.size <= data_choice_index
106
+ contents = choices[ data_choice_index ][ :message ][ :contents ] || []
107
+
108
+ text_content = contents.first&.[]( :type ) == :text ? contents.first : nil
109
+ if data_choice_content = data_choice_delta[ 'content' ]
110
+ if text_content.nil?
111
+ contents.unshift( text_content = { type: :text, text: data_choice_content } )
112
+ else
113
+ text_content[ :text ] = ( text_content[ :text ] || '' ) + data_choice_content
114
+ end
115
+ end
116
+ if data_choice_tool_calls = data_choice_delta[ 'tool_calls' ]
117
+ data_choice_tool_calls.each_with_index do | data_choice_tool_call, data_choice_tool_call_index |
118
+ if data_choice_tool_call_function = data_choice_tool_call[ 'function' ]
119
+ data_choice_tool_index = data_choice_tool_call[ 'index' ] || data_choice_tool_call_index
120
+ data_choice_tool_id = data_choice_tool_call[ 'id' ]
121
+ data_choice_tool_name = data_choice_tool_call_function[ 'name' ]
122
+ data_choice_tool_parameters = data_choice_tool_call_function[ 'arguments' ]
123
+
124
+ tool_call_content_index = ( text_content.nil? ? 0 : 1 ) + data_choice_tool_index
125
+ if tool_call_content_index >= contents.length
126
+ contents.push( {
127
+ type: :tool_call,
128
+ tool_call_id: data_choice_tool_id,
129
+ tool_name: data_choice_tool_name,
130
+ tool_parameters: data_choice_tool_parameters
131
+ } )
132
+ else
133
+ tool_call = contents[ tool_call_content_index ]
134
+ tool_call[ :tool_call_id ] = ( tool_call[ :tool_call_id ] || '' ) + data_choice_tool_id \
135
+ if data_choice_tool_id
136
+ tool_call[ :tool_name ] = ( tool_call[ :tool_name ] || '' ) + data_choice_tool_name \
137
+ if data_choice_tool_name
138
+ tool_call[ :tool_parameters ] = ( tool_call[ :tool_parameters ] || '' ) + data_choice_tool_parameters \
139
+ if data_choice_tool_parameters
140
+ end
141
+ end
142
+ end
143
+ end
144
+ choices[ data_choice_index ][ :message ][ :contents ] = contents
145
+ choices[ data_choice_index ][ :end_reason ] ||=
146
+ to_end_reason( data_choice_finish_reason )
147
+ end
148
+
149
+ if usage = data[ 'usage' ]
150
+ # note: A number of providers will resend the input tokens as part of their usage
151
+ # payload.
152
+ metrics[ :input_tokens ] = usage[ 'prompt_tokens' ] \
153
+ if usage.include?( 'prompt_tokens' )
154
+ metrics[ :output_tokens ] += usage[ 'completion_tokens' ] \
155
+ if usage.include?( 'completion_tokens' )
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+
162
+ context[ :buffer ] = buffer
163
+ context[ :metrics ] = metrics
164
+ context[ :choices ] = choices
165
+
166
+ [ context, choices.empty? ? nil : { choices: choices.dup } ]
167
+ end
168
+
169
+ def stream_result_attributes( context )
170
+ choices = context[ :choices ]
171
+ metrics = context[ :metrics ]
172
+
173
+ choices = choices.map do | choice |
174
+ { end_reason: choice[ :end_reason ] }
175
+ end
176
+
177
+ { choices: choices, metrics: context[ :metrics ] }
178
+ end
179
+
180
+ alias_method :stream_result_error_attributes, :chat_result_error_attributes
181
+
182
+ def to_end_reason( finish_reason )
183
+ case finish_reason
184
+ when 'stop'
185
+ :ended
186
+ when 'length'
187
+ :token_limit_exceeded
188
+ when 'tool_calls'
189
+ :tool_called
190
+ when 'content_filter'
191
+ :filtered
192
+ else
193
+ nil
194
+ end
195
+ end
196
+
197
+ def to_error_response( status )
198
+ case status
199
+ when 400
200
+ [ :invalid_request_error,
201
+ "There was an issue with the format or content of your request." ]
202
+ when 401
203
+ [ :authentication_error,
204
+ "There's an issue with your API key." ]
205
+ when 403
206
+ [ :permission_error,
207
+ "Your API key does not have permission to use the specified resource." ]
208
+ when 404
209
+ [ :not_found_error,
210
+ "The requested resource was not found." ]
211
+ when 413
212
+ [ :request_too_large,
213
+ "Request exceeds the maximum allowed number of bytes." ]
214
+ when 422
215
+ [ :invalid_request_error,
216
+ "There was an issue with the format or content of your request." ]
217
+ when 429
218
+ [ :rate_limit_error,
219
+ "Your account has hit a rate limit." ]
220
+ when 500, 502, 503
221
+ [ :api_error,
222
+ "An unexpected error has occurred internal to the providers systems." ]
223
+ when 529
224
+ [ :overloaded_error,
225
+ "The providers server is temporarily overloaded." ]
226
+ else
227
+ [ :unknown_error, "
228
+ An unknown error occurred." ]
229
+ end
230
+ end
231
+
232
+ end
233
+ end
234
+ end
@@ -164,8 +164,9 @@ module Intelligence
164
164
  end
165
165
 
166
166
  tools_attributes = to_google_tools( conversation[ :tools ] )
167
- result[ :tools ] = tools_attributes if tools_attributes && tools_attributes.length > 0
168
-
167
+ result[ :tools ] = [ { function_declarations: tools_attributes } ] \
168
+ if tools_attributes&.any?
169
+
169
170
  JSON.generate( result )
170
171
  end
171
172
 
@@ -208,7 +209,7 @@ module Intelligence
208
209
  [ object, required.compact ]
209
210
  end
210
211
 
211
- return [ { function_declarations: tools&.map { | tool |
212
+ return tools&.map { | tool |
212
213
  function = {
213
214
  name: tool[ :name ],
214
215
  description: tool[ :description ],
@@ -223,7 +224,7 @@ module Intelligence
223
224
  function[ :parameters ][ :required ] = properties_required if properties_required.any?
224
225
  end
225
226
  function
226
- } } ]
227
+ }
227
228
  end
228
229
 
229
230
  end
@@ -1,9 +1,9 @@
1
- require_relative 'legacy/adapter'
1
+ require_relative 'generic/adapter'
2
2
 
3
3
  module Intelligence
4
4
  module Groq
5
5
 
6
- class Adapter < Legacy::Adapter
6
+ class Adapter < Generic::Adapter
7
7
 
8
8
  chat_request_uri 'https://api.groq.com/openai/v1/chat/completions'
9
9
 
@@ -44,25 +44,6 @@ module Intelligence
44
44
  end
45
45
  end
46
46
 
47
- alias chat_request_generic_message_attributes chat_request_message_attributes
48
-
49
- # groq models only support the legacy Open AI message schema for the assistant
50
- # messages while supporting the modern message schema for user messages
51
- def chat_request_message_attributes( message )
52
- role = message[ :role ]&.to_sym
53
- case role
54
- when :user
55
- chat_request_generic_message_attributes( message )
56
- when :assistant
57
- chat_request_legacy_message_attributes( message )
58
- else
59
- raise UnsupportedContentError.new(
60
- :mistral,
61
- 'only supports user and assistant message roles'
62
- )
63
- end
64
- end
65
-
66
47
  end
67
48
 
68
49
  end
@@ -23,32 +23,6 @@ module Intelligence
23
23
  end
24
24
  end
25
25
 
26
- def chat_result_error_attributes( response )
27
-
28
- error_type, error_description = translate_error_response_status( response.status )
29
- result = {
30
- error_type: error_type.to_s,
31
- error_description: error_description
32
- }
33
- parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
34
- if parsed_body && parsed_body.respond_to?( :include? )
35
- if parsed_body.include?( :error )
36
- result = {
37
- error_type: error_type.to_s,
38
- error: parsed_body[ :error ][ :code ] || error_type.to_s,
39
- error_description: parsed_body[ :error ][ :message ] || error_description
40
- }
41
- elsif parsed_body.include?( :detail )
42
- result[ :error_description ] = parsed_body[ :detail ]
43
- elsif parsed_body[ :object ] == 'error'
44
- result[ :error_description ] = parsed_body[ :message ]
45
- end
46
- end
47
-
48
- result
49
-
50
- end
51
-
52
26
  end
53
27
 
54
28
  end
@@ -1,9 +1,8 @@
1
- require_relative 'legacy/adapter'
1
+ require_relative 'generic/adapter'
2
2
 
3
3
  module Intelligence
4
4
  module Mistral
5
-
6
- class Adapter < Legacy::Adapter
5
+ class Adapter < Generic::Adapter
7
6
 
8
7
  chat_request_uri "https://api.mistral.ai/v1/chat/completions"
9
8
 
@@ -31,27 +30,7 @@ module Intelligence
31
30
  end
32
31
  end
33
32
  end
34
-
35
- alias chat_request_generic_message_attributes chat_request_message_attributes
36
-
37
- # mistral vision models only support the legacy Open AI message schema for the assistant
38
- # messages while supporting the modern message schema for user messages :facepalm:
39
- def chat_request_message_attributes( message )
40
- role = message[ :role ]&.to_sym
41
- case role
42
- when :user
43
- chat_request_generic_message_attributes( message )
44
- when :assistant
45
- chat_request_legacy_message_attributes( message )
46
- else
47
- raise UnsupportedContentError.new(
48
- :mistral,
49
- 'only supports user and assistant message roles'
50
- )
51
- end
52
- end
53
-
33
+
54
34
  end
55
-
56
35
  end
57
36
  end
@@ -18,42 +18,42 @@ module Intelligence
18
18
  chat_options do
19
19
 
20
20
  # normalized properties for openai generative text endpoint
21
- model String, requried: true
22
- n Integer
23
- max_tokens Integer, as: :max_completion_tokens
24
- temperature Float
25
- top_p Float
26
- seed Integer
27
- stop String, array: true
28
- stream [ TrueClass, FalseClass ]
21
+ model String, requried: true
22
+ n Integer
23
+ max_tokens Integer, as: :max_completion_tokens
24
+ temperature Float
25
+ top_p Float
26
+ seed Integer
27
+ stop String, array: true
28
+ stream [ TrueClass, FalseClass ]
29
29
 
30
- frequency_penalty Float
31
- presence_penalty Float
30
+ frequency_penalty Float
31
+ presence_penalty Float
32
32
 
33
33
  # openai variant of normalized properties for openai generative text endpoints
34
- max_completion_tokens Integer
34
+ max_completion_tokens Integer
35
35
 
36
36
  # openai properties for openai generative text endpoint
37
37
  audio do
38
- voice String
39
- format String
38
+ voice String
39
+ format String
40
40
  end
41
41
  logit_bias
42
- logprobs [ TrueClass, FalseClass ]
43
- modalities String, array: true
42
+ logprobs [ TrueClass, FalseClass ]
43
+ modalities String, array: true
44
44
  # the parallel_tool_calls parameter is only allowed when 'tools' are specified
45
- parallel_tool_calls [ TrueClass, FalseClass ]
45
+ parallel_tool_calls [ TrueClass, FalseClass ]
46
46
  response_format do
47
47
  # 'text' and 'json_schema' are the only supported types
48
- type Symbol, in: [ :text, :json_schema ]
48
+ type Symbol, in: [ :text, :json_schema ]
49
49
  json_schema
50
50
  end
51
- service_tier String
51
+ service_tier String
52
52
  stream_options do
53
- include_usage [ TrueClass, FalseClass ]
53
+ include_usage [ TrueClass, FalseClass ]
54
54
  end
55
55
  tool_choice
56
- top_logprobs Integer
56
+ top_logprobs Integer
57
57
  user
58
58
 
59
59
  end
@@ -122,7 +122,7 @@ module Intelligence
122
122
 
123
123
  tools_attributes = chat_request_tools_attributes( conversation[ :tools ] )
124
124
  result[ :tools ] = tools_attributes if tools_attributes && tools_attributes.length > 0
125
-
125
+
126
126
  JSON.generate( result )
127
127
  end
128
128
 
@@ -85,31 +85,21 @@ module Intelligence
85
85
 
86
86
  choices.each do | choice |
87
87
  choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
88
- case content[ :type ]
89
- when :text
90
- content[ :text ] = ''
91
- when :tool_call
92
- content[ :tool_parameters ] = ''
93
- else
94
- content.clear
95
- end
96
- content
88
+ { type: content[ :type ] }
97
89
  end
98
90
  end
99
91
 
100
92
  buffer += chunk
101
93
  while ( eol_index = buffer.index( "\n" ) )
102
-
103
94
  line = buffer.slice!( 0..eol_index )
104
95
  line = line.strip
105
96
  next if line.empty? || !line.start_with?( 'data:' )
106
97
  line = line[ 6..-1 ]
107
98
 
108
99
  next if line.end_with?( '[DONE]' )
109
- data = JSON.parse( line )
100
+ data = JSON.parse( line ) rescue nil
110
101
 
111
102
  if data.is_a?( Hash )
112
-
113
103
  data[ 'choices' ]&.each do | data_choice |
114
104
 
115
105
  data_choice_index = data_choice[ 'index' ]
@@ -119,39 +109,45 @@ module Intelligence
119
109
  choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
120
110
  if choices.size <= data_choice_index
121
111
  contents = choices[ data_choice_index ][ :message ][ :contents ] || []
122
- last_content = contents&.last
123
-
124
- if data_choice_delta.include?( 'content' )
125
- data_choice_content = data_choice_delta[ 'content' ] || ''
126
- if last_content.nil? || last_content[ :type ] == :tool_call
127
- contents.push( { type: :text, text: data_choice_content } )
128
- elsif last_content[ :type ].nil?
129
- last_content[ :type ] = :text
130
- last_content[ :text ] = data_choice_content
131
- elsif last_content[ :type ] == :text
132
- last_content[ :text ] = ( last_content[ :text ] || '' ) + data_choice_content
133
- end
134
- elsif data_choice_delta.include?( 'function_call' )
135
- data_choice_tool_call = data_choice_delta[ 'function_call' ]
136
- data_choice_tool_name = data_choice_tool_call[ 'name' ]
137
- data_choice_tool_parameters = data_choice_tool_call[ 'arguments' ]
138
- if last_content.nil? || last_content[ :type ] == :text
139
- contents.push( {
140
- type: :tool_call,
141
- tool_name: data_choice_tool_name,
142
- tool_parameters: data_choice_tool_parameters
143
- } )
144
- elsif last_content[ :type ].nil?
145
- last_content[ :type ] = :tool_call
146
- last_content[ :tool_name ] = data_choice_tool_name if data_choice_tool_name.present?
147
- last_content[ :tool_parameters ] = tool_parameters
148
- elsif last_content[ :type ] == :tool_call
149
- last_content[ :tool_parameters ] =
150
- ( last_content[ :tool_parameters ] || '' ) + data_choice_tool_parameters
112
+
113
+ text_content = contents.first&.[]( :type ) == :text ? contents.first : nil
114
+ if data_choice_content = data_choice_delta[ 'content' ]
115
+ if text_content.nil?
116
+ contents.unshift( text_content = { type: :text, text: data_choice_content } )
117
+ else
118
+ text_content[ :text ] = ( text_content[ :text ] || '' ) + data_choice_content
151
119
  end
120
+ end
121
+ if data_choice_tool_calls = data_choice_delta[ 'tool_calls' ]
122
+ data_choice_tool_calls.each do | data_choice_tool_call |
123
+ if data_choice_tool_call_function = data_choice_tool_call[ 'function' ]
124
+ data_choice_tool_index = data_choice_tool_call[ 'index' ]
125
+ data_choice_tool_id = data_choice_tool_call[ 'id' ]
126
+ data_choice_tool_name = data_choice_tool_call_function[ 'name' ]
127
+ data_choice_tool_parameters = data_choice_tool_call_function[ 'arguments' ]
128
+
129
+ tool_call_content_index = ( text_content.nil? ? 0 : 1 ) + data_choice_tool_index
130
+ if tool_call_content_index >= contents.length
131
+ contents.push( {
132
+ type: :tool_call,
133
+ tool_call_id: data_choice_tool_id,
134
+ tool_name: data_choice_tool_name,
135
+ tool_parameters: data_choice_tool_parameters
136
+ } )
137
+ else
138
+ tool_call = contents[ tool_call_content_index ]
139
+ tool_call[ :tool_call_id ] = ( tool_call[ :tool_call_id ] || '' ) + data_choice_tool_id \
140
+ if data_choice_tool_id
141
+ tool_call[ :tool_name ] = ( tool_call[ :tool_name ] || '' ) + data_choice_tool_name \
142
+ if data_choice_tool_name
143
+ tool_call[ :tool_parameters ] = ( tool_call[ :tool_parameters ] || '' ) + data_choice_tool_parameters \
144
+ if data_choice_tool_parameters
145
+ end
146
+ end
147
+ end
152
148
  end
153
149
  choices[ data_choice_index ][ :message ][ :contents ] = contents
154
- choices[ data_choice_index ][ :end_reason ] =
150
+ choices[ data_choice_index ][ :end_reason ] ||=
155
151
  translate_end_result( data_choice_finish_reason )
156
152
  end
157
153
 
@@ -1,9 +1,9 @@
1
- require_relative 'legacy/adapter'
1
+ require_relative 'generic/adapter'
2
2
 
3
3
  module Intelligence
4
4
  module SambaNova
5
5
 
6
- class Adapter < Legacy::Adapter
6
+ class Adapter < Generic::Adapter
7
7
 
8
8
  chat_request_uri "https://api.sambanova.ai/v1/chat/completions"
9
9
 
@@ -35,8 +35,7 @@ module Intelligence
35
35
  end
36
36
 
37
37
  def chat_result_error_attributes( response )
38
-
39
- error_type, error_description = translate_error_response_status( response.status )
38
+ error_type, error_description = to_error_response( response.status )
40
39
  result = {
41
40
  error_type: error_type.to_s,
42
41
  error_description: error_description
@@ -55,7 +54,6 @@ module Intelligence
55
54
  end
56
55
 
57
56
  result
58
-
59
57
  end
60
58
 
61
59
  end
@@ -1,9 +1,9 @@
1
- require_relative 'legacy/adapter'
1
+ require_relative 'generic/adapter'
2
2
 
3
3
  module Intelligence
4
4
  module TogetherAi
5
5
 
6
- class Adapter < Legacy::Adapter
6
+ class Adapter < Generic::Adapter
7
7
 
8
8
  chat_request_uri "https://api.together.xyz/v1/chat/completions"
9
9
 
@@ -25,7 +25,7 @@ module Intelligence
25
25
  end
26
26
  end
27
27
 
28
- def translate_end_result( end_result )
28
+ def to_end_reason( end_result )
29
29
  case end_result
30
30
  when 'eos', 'stop'
31
31
  :ended
@@ -35,7 +35,7 @@ module Intelligence
35
35
  # :end_sequence_encountered
36
36
  when 'length'
37
37
  :token_limit_exceeded
38
- when 'function_call'
38
+ when 'tool_calls'
39
39
  :tool_called
40
40
  else
41
41
  nil
@@ -109,7 +109,7 @@ module Intelligence
109
109
  def text
110
110
  result = []
111
111
  each_content do | content |
112
- result << content.text if content.is_a?( MessageContent::Text )
112
+ result << content.text if content.is_a?( MessageContent::Text ) && content.text
113
113
  end
114
114
  result.join( "\n" )
115
115
  end
@@ -13,6 +13,14 @@ module Intelligence
13
13
  ( text || false ) && text.respond_to?( :empty? ) && !text.empty?
14
14
  end
15
15
 
16
+ def merge( other )
17
+ other_text = other.text
18
+ text = @text
19
+ text = ( @text || '' ) + other_text if other_text
20
+
21
+ self.class.new( text: text )
22
+ end
23
+
16
24
  def to_h
17
25
  { type: :text, text: text }
18
26
  end