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
@@ -0,0 +1,234 @@
1
+ require 'uri'
2
+
3
+ module Intelligence
4
+ module Google
5
+ module ChatRequestMethods
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.merge( build_options( options ) )
31
+
32
+ key = options[ :key ]
33
+ gc = options[ :generationConfig ] || {}
34
+ model = gc[ :model ]
35
+ stream = gc.key?( :stream ) ? gc[ :stream ] : false
36
+
37
+ raise ArgumentError.new( "A Google API key is required to build a Google chat request." ) \
38
+ if key.nil?
39
+ raise ArgumentError.new( "A Google model is required to build a Google chat request." ) \
40
+ if model.nil?
41
+
42
+ uri = URI( GENERATIVE_LANGUAGE_URI )
43
+ path = File.join( uri.path, model )
44
+ path += stream ? ':streamGenerateContent' : ':generateContent'
45
+ uri.path = path
46
+ query = { key: key }
47
+ query[ :alt ] = 'sse' if stream
48
+ uri.query = URI.encode_www_form( query )
49
+
50
+ uri.to_s
51
+ end
52
+
53
+ def chat_request_headers( options = {} )
54
+ { 'Content-Type' => 'application/json' }
55
+ end
56
+
57
+ def chat_request_body( conversation, options = {} )
58
+ options = @options.merge( build_options( options ) )
59
+
60
+ gc = options[ :generationConfig ]
61
+ # discard properties not part of the google generationConfig schema
62
+ gc.delete( :model )
63
+ gc.delete( :stream )
64
+
65
+ # googlify tool configuration
66
+ if tool_config = gc.delete( :tool_config )
67
+ mode = tool_config[ :function_calling_config ]&.[]( :mode )
68
+ tool_config[ :function_calling_config ][ :mode ] = mode.to_s.upcase if mode
69
+ end
70
+
71
+ result = {}
72
+ result[ :generationConfig ] = gc
73
+ result[ :tool_config ] = tool_config if tool_config
74
+
75
+ # construct the system prompt in the form of the google schema
76
+ system_instructions = to_google_system_message( conversation[ :system_message ] )
77
+ result[ :systemInstruction ] = system_instructions if system_instructions
78
+
79
+ result[ :contents ] = []
80
+ conversation[ :messages ]&.each do | message |
81
+
82
+ result_message = { role: message[ :role ] == :user ? 'user' : 'model' }
83
+ result_message_parts = []
84
+
85
+ message[ :contents ]&.each do | content |
86
+ case content[ :type ]
87
+ when :text
88
+ result_message_parts << { text: content[ :text ] }
89
+ when :binary
90
+ content_type = content[ :content_type ]
91
+ bytes = content[ :bytes ]
92
+ if content_type && bytes
93
+ mime_type = MIME::Types[ content_type ].first
94
+ if SUPPORTED_BINARY_MEDIA_TYPES.include?( mime_type&.media_type ) ||
95
+ SUPPORTED_BINARY_CONTENT_TYPES.include?( content_type )
96
+ result_message_parts << {
97
+ inline_data: {
98
+ mime_type: content_type,
99
+ data: Base64.strict_encode64( bytes )
100
+ }
101
+ }
102
+ else
103
+ raise UnsupportedContentError.new(
104
+ :google,
105
+ "does not support #{content_type} content type"
106
+ )
107
+ end
108
+ else
109
+ raise UnsupportedContentError.new(
110
+ :google,
111
+ 'requires binary content to include content type and ( packed ) bytes'
112
+ )
113
+ end
114
+ when :file
115
+ content_type = content[ :content_type ]
116
+ uri = content[ :uri ]
117
+ if content_type && uri
118
+ mime_type = MIME::Types[ content_type ].first
119
+ if SUPPORTED_FILE_MEDIA_TYPES.include?( mime_type&.media_type ) ||
120
+ SUPPORTED_FILE_CONTENT_TYPES.include?( content_type )
121
+ result_message_parts << {
122
+ file_data: {
123
+ mime_type: content_type,
124
+ file_uri: uri
125
+ }
126
+ }
127
+ else
128
+ raise UnsupportedContentError.new(
129
+ :google,
130
+ "does not support #{content_type} content type"
131
+ )
132
+ end
133
+ else
134
+ raise UnsupportedContentError.new(
135
+ :google,
136
+ 'requires file content to include content type and uri'
137
+ )
138
+ end
139
+ when :tool_call
140
+ result_message_parts << {
141
+ functionCall: {
142
+ name: content[ :tool_name ],
143
+ args: content[ :tool_parameters ]
144
+ }
145
+ }
146
+ when :tool_result
147
+ result_message_parts << {
148
+ functionResponse: {
149
+ name: content[ :tool_name ],
150
+ response: {
151
+ name: content[ :tool_name ],
152
+ content: content[ :tool_result ]
153
+ }
154
+ }
155
+ }
156
+ else
157
+ raise InvalidContentError.new( :google )
158
+ end
159
+ end
160
+
161
+ result_message[ :parts ] = result_message_parts
162
+ result[ :contents ] << result_message
163
+
164
+ end
165
+
166
+ tools_attributes = to_google_tools( conversation[ :tools ] )
167
+ result[ :tools ] = [ { function_declarations: tools_attributes } ] \
168
+ if tools_attributes&.any?
169
+
170
+ JSON.generate( result )
171
+ end
172
+
173
+ private
174
+
175
+ def to_google_system_message( system_message )
176
+ return nil if system_message.nil?
177
+
178
+ text = ''
179
+ system_message[ :contents ].each do | content |
180
+ text += content[ :text ] if content[ :type ] == :text
181
+ end
182
+
183
+ return nil if text.empty?
184
+
185
+ {
186
+ role: 'user',
187
+ parts: [
188
+ { text: text }
189
+ ]
190
+ }
191
+ end
192
+
193
+ def to_google_tools( tools )
194
+ properties_array_to_object = lambda do | properties |
195
+ return nil unless properties&.any?
196
+ object = {}
197
+ required = []
198
+ properties.each do | property |
199
+ name = property.delete( :name )
200
+ required << name if property.delete( :required )
201
+ if property[ :properties ]&.any?
202
+ property_properties, property_required =
203
+ properties_array_to_object.call( property[ :properties ] )
204
+ property[ :properties ] = property_properties
205
+ property[ :required ] = property_required if property_required.any?
206
+ end
207
+ object[ name ] = property
208
+ end
209
+ [ object, required.compact ]
210
+ end
211
+
212
+ return tools&.map { | tool |
213
+ function = {
214
+ name: tool[ :name ],
215
+ description: tool[ :description ],
216
+ }
217
+ if tool[ :properties ]&.any?
218
+ properties_object, properties_required =
219
+ properties_array_to_object.call( tool[ :properties ] )
220
+ function[ :parameters ] = {
221
+ type: 'object',
222
+ properties: properties_object
223
+ }
224
+ function[ :parameters ][ :required ] = properties_required if properties_required.any?
225
+ end
226
+ function
227
+ }
228
+ end
229
+
230
+ end
231
+
232
+ end
233
+
234
+ end
@@ -0,0 +1,236 @@
1
+ require 'uri'
2
+
3
+ module Intelligence
4
+ module Google
5
+ module ChatResponseMethods
6
+
7
+ def chat_result_attributes( response )
8
+
9
+ return nil unless response.success?
10
+
11
+ response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
12
+ return nil if response_json.nil? || response_json[ :candidates ].nil?
13
+
14
+ result = {}
15
+ result[ :choices ] = []
16
+
17
+ response_json[ :candidates ]&.each do | response_choice |
18
+
19
+ end_reason = translate_finish_reason( response_choice[ :finishReason ] )
20
+
21
+ role = nil
22
+ contents = []
23
+
24
+ response_content = response_choice[ :content ]
25
+ if response_content
26
+ role = ( response_content[ :role ] == 'model' ) ? 'assistant' : 'user'
27
+ contents = []
28
+ response_content[ :parts ]&.each do | response_content_part |
29
+ if response_content_part.key?( :text )
30
+ contents.push( {
31
+ type: 'text', text: response_content_part[ :text ]
32
+ } )
33
+ elsif function_call = response_content_part[ :functionCall ]
34
+ contents.push( {
35
+ type: :tool_call,
36
+ tool_name: function_call[ :name ],
37
+ tool_parameters: function_call[ :args ]
38
+ } )
39
+ # google does not indicate there is tool call in the stop reason so
40
+ # we will synthesize this end reason
41
+ end_reason = :tool_called if end_reason == :ended
42
+ end
43
+ end
44
+ end
45
+
46
+ result_message = nil
47
+ if role
48
+ result_message = { role: role }
49
+ result_message[ :contents ] = contents
50
+ end
51
+
52
+ result[ :choices ].push( { end_reason: end_reason, message: result_message } )
53
+
54
+ end
55
+
56
+ metrics_json = response_json[ :usageMetadata ]
57
+ unless metrics_json.nil?
58
+
59
+ metrics = {}
60
+ metrics[ :input_tokens ] = metrics_json[ :promptTokenCount ]
61
+ metrics[ :output_tokens ] = metrics_json[ :candidatesTokenCount ]
62
+ metrics = metrics.compact
63
+
64
+ result[ :metrics ] = metrics unless metrics.empty?
65
+
66
+ end
67
+
68
+ result
69
+
70
+ end
71
+
72
+ def chat_result_error_attributes( response )
73
+
74
+ error_type, error_description = translate_error_response_status( response.status )
75
+ result = { error_type: error_type.to_s, error_description: error_description }
76
+
77
+ response_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
78
+ if response_body && response_body[ :error ]
79
+ error_details_reason = response_body[ :error ][ :details ]&.first&.[]( :reason )
80
+ # a special case for authentication
81
+ error_type = :authentication_error if error_details_reason == 'API_KEY_INVALID'
82
+ result = {
83
+ error_type: error_type.to_s,
84
+ error: error_details_reason || response_body[ :error ][ :status ] || error_type,
85
+ error_description: response_body[ :error ][ :message ]
86
+ }
87
+ end
88
+ result
89
+
90
+ end
91
+
92
+ def stream_result_chunk_attributes( context, chunk )
93
+
94
+ context ||= {}
95
+ buffer = context[ :buffer ] || ''
96
+ metrics = context[ :metrics ] || {
97
+ input_tokens: 0,
98
+ output_tokens: 0
99
+ }
100
+ choices = context[ :choices ] || Array.new( 1 , { message: {} } )
101
+
102
+ choices.each do | choice |
103
+ choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
104
+ case content[ :type ]
105
+ when :text
106
+ content[ :text ] = ''
107
+ else
108
+ content.clear
109
+ end
110
+ content
111
+ end
112
+ end
113
+
114
+ buffer += chunk
115
+ while ( eol_index = buffer.index( "\n" ) )
116
+
117
+ line = buffer.slice!( 0..eol_index )
118
+ line = line.strip
119
+ next if line.empty? || !line.start_with?( 'data:' )
120
+ line = line[ 6..-1 ]
121
+
122
+ data = JSON.parse( line, symbolize_names: true )
123
+ if data.is_a?( Hash )
124
+
125
+ data[ :candidates ]&.each do | data_candidate |
126
+
127
+ data_candidate_index = data_candidate[ :index ] || 0
128
+ data_candidate_content = data_candidate[ :content ]
129
+ data_candidate_finish_reason = data_candidate[ :finishReason ]
130
+ choices.fill( { message: { role: 'assistant' } }, choices.size, data_candidate_index + 1 ) \
131
+ if choices.size <= data_candidate_index
132
+ contents = choices[ data_candidate_index ][ :message ][ :contents ] || []
133
+ last_content = contents&.last
134
+
135
+ if data_candidate_content&.include?( :parts )
136
+ data_candidate_content_parts = data_candidate_content[ :parts ]
137
+ data_candidate_content_parts&.each do | data_candidate_content_part |
138
+ if data_candidate_content_part.key?( :text )
139
+ if last_content.nil? || last_content[ :type ] != :text
140
+ contents.push( { type: :text, text: data_candidate_content_part[ :text ] } )
141
+ else
142
+ last_content[ :text ] =
143
+ ( last_content[ :text ] || '' ) + data_candidate_content_part[ :text ]
144
+ end
145
+ end
146
+ end
147
+ end
148
+ choices[ data_candidate_index ][ :message ][ :contents ] = contents
149
+ choices[ data_candidate_index ][ :end_reason ] =
150
+ translate_finish_reason( data_candidate_finish_reason )
151
+ end
152
+
153
+ if usage = data[ :usageMetadata ]
154
+ metrics[ :input_tokens ] = usage[ :promptTokenCount ]
155
+ metrics[ :output_tokens ] = usage[ :candidatesTokenCount ]
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
+
168
+ end
169
+
170
+ def stream_result_attributes( context )
171
+
172
+ choices = context[ :choices ]
173
+ metrics = context[ :metrics ]
174
+
175
+ choices = choices.map do | choice |
176
+ { end_reason: choice[ :end_reason ] }
177
+ end
178
+
179
+ { choices: choices, metrics: context[ :metrics ] }
180
+
181
+ end
182
+
183
+ alias_method :stream_result_error_attributes, :chat_result_error_attributes
184
+
185
+ private
186
+
187
+ def translate_finish_reason( finish_reason )
188
+ case finish_reason
189
+ when 'STOP'
190
+ :ended
191
+ when 'MAX_TOKENS'
192
+ :token_limit_exceeded
193
+ when 'SAFETY', 'RECITATION', 'BLOCKLIST', 'PROHIBITED_CONTENT', 'SPII'
194
+ :filtered
195
+ else
196
+ nil
197
+ end
198
+ end
199
+
200
+ def translate_error_response_status( status )
201
+ case status
202
+ when 400
203
+ [ :invalid_request_error,
204
+ "There was an issue with the format or content of your request." ]
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
+
234
+ end
235
+
236
+ end
@@ -1,66 +1,46 @@
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
 
10
- configuration do
11
- parameter :key, String
12
- group :chat_options do
13
- parameter :frequency_penalty, Float
14
- parameter :logit_bias
15
- parameter :logprobs, [ TrueClass, FalseClass ]
16
- parameter :max_tokens, Integer
17
- parameter :model, String
18
- parameter :n, Integer
19
- # the parallel_tool_calls parameter is only allowed when 'tools' are specified
20
- parameter :parallel_tool_calls, [ TrueClass, FalseClass ]
21
- parameter :presence_penalty, Float
22
- group :response_format do
10
+ schema do
11
+ key String
12
+ chat_options do
13
+ frequency_penalty Float
14
+ logit_bias
15
+ logprobs [ TrueClass, FalseClass ]
16
+ max_tokens Integer
17
+ model String
18
+ # the parallel_tool_calls is only allowed when 'tools' are specified
19
+ parallel_tool_calls [ TrueClass, FalseClass ]
20
+ presence_penalty Float
21
+ response_format do
23
22
  # 'text' and 'json_object' are the only supported types; you must also instruct
24
23
  # the model to output json
25
- parameter :type, String
24
+ type Symbol, in: [ :text, :json_object ]
26
25
  end
27
- parameter :seed, Integer
28
- parameter :stop, String, array: true
29
- parameter :stream, [ TrueClass, FalseClass ]
30
- group :stream_options do
31
- parameter :include_usage, [ TrueClass, FalseClass ]
26
+ seed Integer
27
+ stop String, array: true
28
+ stream [ TrueClass, FalseClass ]
29
+ stream_options do
30
+ include_usage [ TrueClass, FalseClass ]
32
31
  end
33
- parameter :temperature, Float
34
- group :tool_choice do
32
+ temperature Float
33
+ tool_choice do
35
34
  # one of 'auto', 'none' or 'function'
36
- parameter :type, String
37
- # the function group is required if you specify a type of 'function'
38
- group :function do
39
- parameter :name, String
35
+ type Symbol, in: [ :auto, :none, :function ]
36
+ # the function parameters is required if you specify a type of 'function'
37
+ function do
38
+ name String
40
39
  end
41
40
  end
42
- parameter :top_logprobs, Integer
43
- parameter :top_p, Float
44
- parameter :user, String
45
- end
46
- end
47
-
48
- alias chat_request_generic_message_attributes chat_request_message_attributes
49
-
50
- # groq models only support the legacy Open AI message schema for the assistant
51
- # messages while supporting the modern message schema for user messages
52
- def chat_request_message_attributes( message )
53
- role = message[ :role ]&.to_sym
54
- case role
55
- when :user
56
- chat_request_generic_message_attributes( message )
57
- when :assistant
58
- chat_request_legacy_message_attributes( message )
59
- else
60
- raise UnsupportedContentError.new(
61
- :mistral,
62
- 'only supports user and assistant message roles'
63
- )
41
+ top_logprobs Integer
42
+ top_p Float
43
+ user String
64
44
  end
65
45
  end
66
46
 
@@ -7,48 +7,22 @@ module Intelligence
7
7
 
8
8
  chat_request_uri "https://api.hyperbolic.xyz/v1/chat/completions"
9
9
 
10
- configuration do
11
- parameter :key, String
12
- group :chat_options do
13
- parameter :model, String
14
- parameter :temperature, Float
15
- parameter :top_p, Float
16
- parameter :n, Integer
17
- parameter :max_tokens, Integer
18
- parameter :stop, String, array: true
19
- parameter :stream, [ TrueClass, FalseClass ]
20
- parameter :frequency_penalty, Float
21
- parameter :presence_penalty, Float
22
- parameter :user, String
10
+ schema do
11
+ key String
12
+ chat_options do
13
+ model String
14
+ temperature Float
15
+ top_p Float
16
+ n Integer
17
+ max_tokens Integer
18
+ stop String, array: true
19
+ stream [ TrueClass, FalseClass ]
20
+ frequency_penalty Float
21
+ presence_penalty Float
22
+ user String
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