intelligence 0.5.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/intelligence.gemspec +47 -0
- data/lib/intelligence/adapter/base.rb +7 -0
- data/lib/intelligence/adapter/construction_methods.rb +37 -0
- data/lib/intelligence/adapter.rb +8 -0
- data/lib/intelligence/adapter_error.rb +8 -0
- data/lib/intelligence/adapters/anthropic/adapter.rb +59 -0
- data/lib/intelligence/adapters/anthropic/chat_methods.rb +358 -0
- data/lib/intelligence/adapters/anthropic.rb +2 -0
- data/lib/intelligence/adapters/cerebras.rb +35 -0
- data/lib/intelligence/adapters/generic/adapter.rb +21 -0
- data/lib/intelligence/adapters/generic/chat_methods.rb +331 -0
- data/lib/intelligence/adapters/generic.rb +2 -0
- data/lib/intelligence/adapters/google/adapter.rb +60 -0
- data/lib/intelligence/adapters/google/chat_methods.rb +346 -0
- data/lib/intelligence/adapters/google.rb +2 -0
- data/lib/intelligence/adapters/groq.rb +51 -0
- data/lib/intelligence/adapters/hyperbolic.rb +55 -0
- data/lib/intelligence/adapters/legacy/adapter.rb +13 -0
- data/lib/intelligence/adapters/legacy/chat_methods.rb +37 -0
- data/lib/intelligence/adapters/open_ai/adapter.rb +75 -0
- data/lib/intelligence/adapters/open_ai/chat_methods.rb +314 -0
- data/lib/intelligence/adapters/open_ai.rb +2 -0
- data/lib/intelligence/adapters/samba_nova.rb +64 -0
- data/lib/intelligence/adapters/together_ai.rb +46 -0
- data/lib/intelligence/chat_error_result.rb +11 -0
- data/lib/intelligence/chat_metrics.rb +32 -0
- data/lib/intelligence/chat_request.rb +117 -0
- data/lib/intelligence/chat_result.rb +32 -0
- data/lib/intelligence/chat_result_choice.rb +26 -0
- data/lib/intelligence/conversation.rb +48 -0
- data/lib/intelligence/error.rb +3 -0
- data/lib/intelligence/error_result.rb +24 -0
- data/lib/intelligence/invalid_content_error.rb +3 -0
- data/lib/intelligence/message.rb +53 -0
- data/lib/intelligence/message_content/base.rb +18 -0
- data/lib/intelligence/message_content/binary.rb +24 -0
- data/lib/intelligence/message_content/text.rb +17 -0
- data/lib/intelligence/message_content/tool_call.rb +20 -0
- data/lib/intelligence/message_content/tool_result.rb +20 -0
- data/lib/intelligence/message_content.rb +16 -0
- data/lib/intelligence/unsupported_content_error.rb +3 -0
- data/lib/intelligence/version.rb +3 -0
- data/lib/intelligence.rb +24 -0
- metadata +181 -0
@@ -0,0 +1,314 @@
|
|
1
|
+
module Intelligence
|
2
|
+
module OpenAi
|
3
|
+
module ChatMethods
|
4
|
+
|
5
|
+
CHAT_REQUEST_URI = "https://api.openai.com/v1/chat/completions"
|
6
|
+
|
7
|
+
def chat_request_uri( options )
|
8
|
+
CHAT_REQUEST_URI
|
9
|
+
end
|
10
|
+
|
11
|
+
def chat_request_headers( options = {} )
|
12
|
+
result = {}
|
13
|
+
|
14
|
+
key = options[ :key ] || self.key
|
15
|
+
organization = options[ :organization ] || self.organization
|
16
|
+
project = options[ :project ] || self.project
|
17
|
+
|
18
|
+
raise ArgumentError.new( "An OpenAI key is required to build an OpenAI chat request." ) \
|
19
|
+
if key.nil?
|
20
|
+
|
21
|
+
result[ 'Content-Type' ] = 'application/json'
|
22
|
+
result[ 'Authorization' ] = "Bearer #{key}"
|
23
|
+
result[ 'OpenAI-Organization' ] = organization unless organization.nil?
|
24
|
+
result[ 'OpenAI-Project' ] = project unless project.nil?
|
25
|
+
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def chat_request_body( conversation, options = {} )
|
30
|
+
result = self.chat_options.merge( options ).compact
|
31
|
+
result[ :messages ] = []
|
32
|
+
|
33
|
+
system_message = translate_system_message( conversation[ :system_message ] )
|
34
|
+
result[ :messages ] << { role: 'system', content: system_message } if system_message
|
35
|
+
|
36
|
+
conversation[ :messages ]&.each do | message |
|
37
|
+
|
38
|
+
result_message = { role: message[ :role ] }
|
39
|
+
result_message_content = []
|
40
|
+
|
41
|
+
message[ :contents ]&.each do | content |
|
42
|
+
case content[ :type ]
|
43
|
+
when :text
|
44
|
+
result_message_content << { type: 'text', text: content[ :text ] }
|
45
|
+
when :binary
|
46
|
+
content_type = content[ :content_type ]
|
47
|
+
bytes = content[ :bytes ]
|
48
|
+
if content_type && bytes
|
49
|
+
mime_type = MIME::Types[ content_type ].first
|
50
|
+
if mime_type&.media_type == 'image'
|
51
|
+
result_message_content << {
|
52
|
+
type: 'image_url',
|
53
|
+
image_url: {
|
54
|
+
url: "data:#{content_type};base64,#{Base64.strict_encode64( bytes )}".freeze
|
55
|
+
}
|
56
|
+
}
|
57
|
+
else
|
58
|
+
raise UnsupportedContentError.new(
|
59
|
+
:open_ai,
|
60
|
+
'only supports content of type image/*'
|
61
|
+
)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise UnsupportedContentError.new(
|
65
|
+
:open_ai,
|
66
|
+
'requires binary content to include content type and ( packed ) bytes'
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
result_message[ :content ] = result_message_content
|
73
|
+
result[ :messages ] << result_message
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
JSON.generate( result )
|
78
|
+
end
|
79
|
+
|
80
|
+
def chat_result_attributes( response )
|
81
|
+
return nil unless response.success?
|
82
|
+
|
83
|
+
response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
|
84
|
+
return nil \
|
85
|
+
if response_json.nil? || response_json[ :choices ].nil?
|
86
|
+
|
87
|
+
result = {}
|
88
|
+
result[ :choices ] = []
|
89
|
+
|
90
|
+
( response_json[ :choices ] || [] ).each do | json_choice |
|
91
|
+
json_message = json_choice[ :message ]
|
92
|
+
result[ :choices ].push(
|
93
|
+
{
|
94
|
+
end_reason: translate_end_result( json_choice[ :finish_reason ] ),
|
95
|
+
message: {
|
96
|
+
role: json_message[ :role ],
|
97
|
+
contents: [ { type: 'text', text: json_message[ :content ] } ]
|
98
|
+
}
|
99
|
+
}
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
metrics_json = response_json[ :usage ]
|
104
|
+
unless metrics_json.nil?
|
105
|
+
|
106
|
+
metrics = {}
|
107
|
+
metrics[ :input_tokens ] = metrics_json[ :prompt_tokens ]
|
108
|
+
metrics[ :output_tokens ] = metrics_json[ :completion_tokens ]
|
109
|
+
metrics = metrics.compact
|
110
|
+
|
111
|
+
result[ :metrics ] = metrics unless metrics.empty?
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
result
|
116
|
+
end
|
117
|
+
|
118
|
+
def chat_result_error_attributes( response )
|
119
|
+
error_type, error_description = translate_error_response_status( response.status )
|
120
|
+
result = {
|
121
|
+
error_type: error_type.to_s,
|
122
|
+
error_description: error_description
|
123
|
+
}
|
124
|
+
|
125
|
+
parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
|
126
|
+
if parsed_body && parsed_body.respond_to?( :include? ) && parsed_body.include?( :error )
|
127
|
+
result = {
|
128
|
+
error_type: error_type.to_s,
|
129
|
+
error: parsed_body[ :error ][ :code ] || error_type.to_s,
|
130
|
+
error_description: parsed_body[ :error ][ :message ] || error_description
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
def stream_result_chunk_attributes( context, chunk )
|
138
|
+
context ||= {}
|
139
|
+
buffer = context[ :buffer ] || ''
|
140
|
+
metrics = context[ :metrics ] || {
|
141
|
+
input_tokens: 0,
|
142
|
+
output_tokens: 0
|
143
|
+
}
|
144
|
+
choices = context[ :choices ] || Array.new( 1 , { message: {} } )
|
145
|
+
|
146
|
+
choices.each do | choice |
|
147
|
+
choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
|
148
|
+
case content[ :type ]
|
149
|
+
when :text
|
150
|
+
content[ :text ] = ''
|
151
|
+
when :tool_call
|
152
|
+
content[ :tool_parameters ] = ''
|
153
|
+
else
|
154
|
+
content.clear
|
155
|
+
end
|
156
|
+
content
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
buffer += chunk
|
161
|
+
while ( eol_index = buffer.index( "\n" ) )
|
162
|
+
|
163
|
+
line = buffer.slice!( 0..eol_index )
|
164
|
+
line = line.strip
|
165
|
+
next if line.empty? || !line.start_with?( 'data:' )
|
166
|
+
line = line[ 6..-1 ]
|
167
|
+
|
168
|
+
next if line.end_with?( '[DONE]' )
|
169
|
+
data = JSON.parse( line )
|
170
|
+
|
171
|
+
if data.is_a?( Hash )
|
172
|
+
|
173
|
+
data[ 'choices' ]&.each do | data_choice |
|
174
|
+
|
175
|
+
data_choice_index = data_choice[ 'index' ]
|
176
|
+
data_choice_delta = data_choice[ 'delta' ]
|
177
|
+
data_choice_finish_reason = data_choice[ 'finish_reason' ]
|
178
|
+
|
179
|
+
choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
|
180
|
+
if choices.size <= data_choice_index
|
181
|
+
contents = choices[ data_choice_index ][ :message ][ :contents ] || []
|
182
|
+
last_content = contents&.last
|
183
|
+
|
184
|
+
if data_choice_delta.include?( 'content' )
|
185
|
+
data_choice_content = data_choice_delta[ 'content' ] || ''
|
186
|
+
if last_content.nil? || last_content[ :type ] == :tool_call
|
187
|
+
contents.push( { type: :text, text: data_choice_content } )
|
188
|
+
elsif last_content[ :type ].nil?
|
189
|
+
last_content[ :type ] = :text
|
190
|
+
last_content[ :text ] = data_choice_content
|
191
|
+
elsif last_content[ :type ] == :text
|
192
|
+
last_content[ :text ] = ( last_content[ :text ] || '' ) + data_choice_content
|
193
|
+
end
|
194
|
+
elsif data_choice_delta.include?( 'function_call' )
|
195
|
+
data_choice_tool_call = data_choice_delta[ 'function_call' ]
|
196
|
+
data_choice_tool_name = data_choice_tool_call[ 'name' ]
|
197
|
+
data_choice_tool_parameters = data_choice_tool_call[ 'arguments' ]
|
198
|
+
if last_content.nil? || last_content[ :type ] == :text
|
199
|
+
contents.push( {
|
200
|
+
type: :tool_call,
|
201
|
+
tool_name: data_choice_tool_name,
|
202
|
+
tool_parameters: data_choice_tool_parameters
|
203
|
+
} )
|
204
|
+
elsif last_content[ :type ].nil?
|
205
|
+
last_content[ :type ] = :tool_call
|
206
|
+
last_content[ :tool_name ] = data_choice_tool_name if data_choice_tool_name.present?
|
207
|
+
last_content[ :tool_parameters ] = tool_parameters
|
208
|
+
elsif last_content[ :type ] == :tool_call
|
209
|
+
last_content[ :tool_parameters ] =
|
210
|
+
( last_content[ :tool_parameters ] || '' ) + data_choice_tool_parameters
|
211
|
+
end
|
212
|
+
end
|
213
|
+
choices[ data_choice_index ][ :message ][ :contents ] = contents
|
214
|
+
choices[ data_choice_index ][ :end_reason ] =
|
215
|
+
translate_end_result( data_choice_finish_reason )
|
216
|
+
end
|
217
|
+
|
218
|
+
if usage = data[ 'usage' ]
|
219
|
+
metrics[ :input_tokens ] += usage[ 'prompt_tokens' ]
|
220
|
+
metrics[ :output_tokens ] += usage[ 'completion_tokens' ]
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
context[ :buffer ] = buffer
|
228
|
+
context[ :metrics ] = metrics
|
229
|
+
context[ :choices ] = choices
|
230
|
+
|
231
|
+
[ context, choices.empty? ? nil : { choices: choices.dup } ]
|
232
|
+
end
|
233
|
+
|
234
|
+
def stream_result_attributes( context )
|
235
|
+
choices = context[ :choices ]
|
236
|
+
metrics = context[ :metrics ]
|
237
|
+
|
238
|
+
choices = choices.map do | choice |
|
239
|
+
{ end_reason: choice[ :end_reason ] }
|
240
|
+
end
|
241
|
+
|
242
|
+
{ choices: choices, metrics: context[ :metrics ] }
|
243
|
+
end
|
244
|
+
|
245
|
+
alias_method :stream_result_error_attributes, :chat_result_error_attributes
|
246
|
+
|
247
|
+
private; def translate_system_message( system_message )
|
248
|
+
|
249
|
+
return nil if system_message.nil?
|
250
|
+
|
251
|
+
result = ''
|
252
|
+
system_message[ :contents ].each do | content |
|
253
|
+
result += content[ :text ] if content[ :type ] == :text
|
254
|
+
end
|
255
|
+
|
256
|
+
result.empty? ? nil : result
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
private; def translate_end_result( end_result )
|
261
|
+
case end_result
|
262
|
+
when 'stop'
|
263
|
+
:ended
|
264
|
+
when 'length'
|
265
|
+
:token_limit_exceeded
|
266
|
+
when 'function_call'
|
267
|
+
:tool_called
|
268
|
+
when 'content_filter'
|
269
|
+
:filtered
|
270
|
+
else
|
271
|
+
nil
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def translate_error_response_status( status )
|
276
|
+
case status
|
277
|
+
when 400
|
278
|
+
[ :invalid_request_error,
|
279
|
+
"There was an issue with the format or content of your request." ]
|
280
|
+
when 401
|
281
|
+
[ :authentication_error,
|
282
|
+
"There's an issue with your API key." ]
|
283
|
+
when 403
|
284
|
+
[ :permission_error,
|
285
|
+
"Your API key does not have permission to use the specified resource." ]
|
286
|
+
when 404
|
287
|
+
[ :not_found_error,
|
288
|
+
"The requested resource was not found." ]
|
289
|
+
when 413
|
290
|
+
[ :request_too_large,
|
291
|
+
"Request exceeds the maximum allowed number of bytes." ]
|
292
|
+
when 422
|
293
|
+
[ :invalid_request_error,
|
294
|
+
"There was an issue with the format or content of your request." ]
|
295
|
+
when 429
|
296
|
+
[ :rate_limit_error,
|
297
|
+
"Your account has hit a rate limit." ]
|
298
|
+
when 500, 502, 503
|
299
|
+
[ :api_error,
|
300
|
+
"An unexpected error has occurred internal to the providers systems." ]
|
301
|
+
when 529
|
302
|
+
[ :overloaded_error,
|
303
|
+
"The providers server is temporarily overloaded." ]
|
304
|
+
else
|
305
|
+
[ :unknown_error, "
|
306
|
+
An unknown error occurred." ]
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'legacy/adapter'
|
2
|
+
|
3
|
+
module Intelligence
|
4
|
+
module SambaNova
|
5
|
+
|
6
|
+
class Adapter < Legacy::Adapter
|
7
|
+
|
8
|
+
chat_request_uri "https://api.sambanova.ai/v1/chat/completions"
|
9
|
+
|
10
|
+
configuration do
|
11
|
+
|
12
|
+
# normalized properties for all endpoints
|
13
|
+
parameter :key, String, required: true
|
14
|
+
|
15
|
+
# properties for generative text endpoints
|
16
|
+
group :chat_options do
|
17
|
+
|
18
|
+
# normalized properties for samba nova generative text endpoint
|
19
|
+
parameter :model, String
|
20
|
+
parameter :max_tokens, Integer
|
21
|
+
parameter :temperature, Float
|
22
|
+
parameter :top_p, Float
|
23
|
+
parameter :top_k, Float
|
24
|
+
parameter :stop, String, array: true
|
25
|
+
parameter :stream, [ TrueClass, FalseClass ]
|
26
|
+
|
27
|
+
# samba nova properties for samba nova generative text endpoint
|
28
|
+
parameter :repetition_penalty, Float
|
29
|
+
group :stream_options do
|
30
|
+
parameter :include_usage, [ TrueClass, FalseClass ]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def chat_result_error_attributes( response )
|
38
|
+
|
39
|
+
error_type, error_description = translate_error_response_status( response.status )
|
40
|
+
result = {
|
41
|
+
error_type: error_type.to_s,
|
42
|
+
error_description: error_description
|
43
|
+
}
|
44
|
+
|
45
|
+
parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
|
46
|
+
if parsed_body && parsed_body.respond_to?( :include? ) && parsed_body.include?( :error )
|
47
|
+
result = {
|
48
|
+
error_type: error_type.to_s,
|
49
|
+
error: parsed_body[ :error ][ :code ] || error_type.to_s,
|
50
|
+
error_description: parsed_body[ :error ][ :message ] || error_description
|
51
|
+
}
|
52
|
+
elsif response.headers[ 'content-type' ].start_with?( 'text/plain' ) &&
|
53
|
+
response.body && response.body.length > 0
|
54
|
+
result[ :error_description ] = response.body
|
55
|
+
end
|
56
|
+
|
57
|
+
result
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'legacy/adapter'
|
2
|
+
|
3
|
+
module Intelligence
|
4
|
+
module TogetherAi
|
5
|
+
|
6
|
+
class Adapter < Legacy::Adapter
|
7
|
+
|
8
|
+
chat_request_uri "https://api.together.xyz/v1/chat/completions"
|
9
|
+
|
10
|
+
configuration do
|
11
|
+
parameter :key, String, required: true
|
12
|
+
group :chat_options do
|
13
|
+
parameter :model, String
|
14
|
+
parameter :temperature, Float
|
15
|
+
parameter :top_p, Float
|
16
|
+
parameter :top_k, Integer
|
17
|
+
parameter :n, Integer
|
18
|
+
parameter :max_tokens, Float
|
19
|
+
parameter :stop, String, array: true
|
20
|
+
parameter :stream, [ TrueClass, FalseClass ]
|
21
|
+
parameter :frequency_penalty, Float
|
22
|
+
parameter :presence_penalty, Float
|
23
|
+
parameter :repetition_penalty, Float
|
24
|
+
parameter :user, String
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def translate_end_result( end_result )
|
29
|
+
case end_result
|
30
|
+
when 'eos'
|
31
|
+
:ended
|
32
|
+
when 'length'
|
33
|
+
:token_limit_exceeded
|
34
|
+
when 'stop'
|
35
|
+
:end_sequence_encountered
|
36
|
+
when 'function_call'
|
37
|
+
:tool_called
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Intelligence
|
2
|
+
|
3
|
+
#
|
4
|
+
# class. ChatMetrics
|
5
|
+
#
|
6
|
+
# The ChatMetrics class encapsulates metrics information. These metrics include the number of
|
7
|
+
# input tokens consumed, the number of output tokens generated, the total number of tokens,
|
8
|
+
# and the duration of the request in milliseconds.
|
9
|
+
#
|
10
|
+
class ChatMetrics
|
11
|
+
# ---------------
|
12
|
+
|
13
|
+
attr_reader :duration
|
14
|
+
|
15
|
+
attr_reader :input_tokens
|
16
|
+
attr_reader :output_tokens
|
17
|
+
|
18
|
+
def initialize( attributes )
|
19
|
+
attributes.each do | key, value |
|
20
|
+
instance_variable_set( "@#{key}", value ) if self.respond_to?( "#{key}" )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def total_tokens
|
25
|
+
@total_tokens = @input_tokens + @output_tokens \
|
26
|
+
if @total_tokens.nil? && @input_tokens && @output_tokens
|
27
|
+
@total_tokens
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Intelligence
|
2
|
+
|
3
|
+
#
|
4
|
+
# module. ChatRequestMethods
|
5
|
+
#
|
6
|
+
# The ChatRequestMethods module extends a Faraday request, adding the +receive_result+ method.
|
7
|
+
#
|
8
|
+
module ChatRequestMethods
|
9
|
+
|
10
|
+
def receive_result( &block )
|
11
|
+
@_intelligence_result_callback = block
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# module. ChatResponseMethods
|
18
|
+
#
|
19
|
+
# The ChatResponseMethods module extends a Farada reponse, adding the +result+ method.
|
20
|
+
#
|
21
|
+
module ChatResponseMethods
|
22
|
+
|
23
|
+
def result
|
24
|
+
@_intelligence_result
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# class. ChatRequest
|
31
|
+
#
|
32
|
+
class ChatRequest
|
33
|
+
|
34
|
+
DEFAULT_CONNECTION = Faraday.new { | builder | builder.adapter Faraday.default_adapter }
|
35
|
+
|
36
|
+
def initialize( connection: nil, adapter: , **options )
|
37
|
+
@connection = connection || DEFAULT_CONNECTION
|
38
|
+
@adapter = adapter
|
39
|
+
@options = options || {}
|
40
|
+
|
41
|
+
raise ArgumentError.new( 'An adapter must be configured before a request is constructed.' ) \
|
42
|
+
if @adapter.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def chat( conversation, options = {} )
|
46
|
+
|
47
|
+
options = @options.merge( options )
|
48
|
+
|
49
|
+
uri = @adapter.chat_request_uri( options )
|
50
|
+
headers = @adapter.chat_request_headers( @options.merge( options ) )
|
51
|
+
payload = @adapter.chat_request_body(
|
52
|
+
conversation.to_h,
|
53
|
+
options
|
54
|
+
)
|
55
|
+
|
56
|
+
result_callback = nil
|
57
|
+
response = @connection.post( uri ) do | request |
|
58
|
+
headers.each { | key, value | request.headers[ key ] = value }
|
59
|
+
request.body = payload
|
60
|
+
yield request.extend( ChatRequestMethods ) if block_given?
|
61
|
+
result_callback = request.instance_variable_get( "@_intelligence_result_callback" )
|
62
|
+
end
|
63
|
+
|
64
|
+
result = nil
|
65
|
+
if response.success?
|
66
|
+
chat_result_attributes = @adapter.chat_result_attributes( response )
|
67
|
+
result = ChatResult.new( chat_result_attributes )
|
68
|
+
else
|
69
|
+
error_result_attributes = @adapter.chat_result_error_attributes( response )
|
70
|
+
result = ChatErrorResult.new( error_result_attributes )
|
71
|
+
end
|
72
|
+
|
73
|
+
response.instance_variable_set( "@_intelligence_result", result )
|
74
|
+
response.extend( ChatResponseMethods )
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def stream( conversation, options = {} )
|
79
|
+
|
80
|
+
options = @options.merge( options )
|
81
|
+
|
82
|
+
uri = @adapter.chat_request_uri( options )
|
83
|
+
headers = @adapter.chat_request_headers( @options.merge( options ) )
|
84
|
+
payload = @adapter.chat_request_body( conversation.to_h, options )
|
85
|
+
|
86
|
+
context = nil
|
87
|
+
response = @connection.post( uri ) do | request |
|
88
|
+
|
89
|
+
headers.each { | key, value | request.headers[ key ] = value }
|
90
|
+
request.body = payload
|
91
|
+
yield request.extend( ChatRequestMethods )
|
92
|
+
|
93
|
+
result_callback = request.instance_variable_get( "@_intelligence_result_callback" )
|
94
|
+
request.options.on_data = Proc.new do | chunk, received_bytes |
|
95
|
+
context, attributes = @adapter.stream_result_chunk_attributes( context, chunk )
|
96
|
+
result_callback.call( ChatResult.new( attributes ) ) unless attributes.nil?
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
result = nil
|
102
|
+
if response.success?
|
103
|
+
stream_result_attributes = @adapter.stream_result_attributes( context )
|
104
|
+
result = ChatResult.new( stream_result_attributes )
|
105
|
+
else
|
106
|
+
error_result_attributes = @adapter.stream_result_error_attributes( response )
|
107
|
+
result = ChatErrorResult.new( error_result_attributes )
|
108
|
+
end
|
109
|
+
|
110
|
+
response.instance_variable_set( "@_intelligence_result", result )
|
111
|
+
response.extend( ChatResponseMethods )
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Intelligence
|
2
|
+
|
3
|
+
#
|
4
|
+
# class. ChatResult
|
5
|
+
#
|
6
|
+
# The ChatResult class encapsulates a successful result to a chat or stream request. A result
|
7
|
+
# includes an array of choices ( it is an array even if there is a single choice ) and the
|
8
|
+
# metrics associated with the generation of all result choices.
|
9
|
+
#
|
10
|
+
class ChatResult
|
11
|
+
|
12
|
+
attr_reader :choices
|
13
|
+
attr_reader :metrics
|
14
|
+
|
15
|
+
def initialize( chat_attributes )
|
16
|
+
|
17
|
+
@choices = []
|
18
|
+
chat_attributes[ :choices ]&.each do | json_choice |
|
19
|
+
@choices.push( ChatResultChoice.new( json_choice ) )
|
20
|
+
end
|
21
|
+
@metrics = ChatMetrics.new( chat_attributes[ :metrics ] ) \
|
22
|
+
if chat_attributes.key?( :metrics )
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
return nil if @choices.empty?
|
27
|
+
@choices.first.message
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Intelligence
|
2
|
+
|
3
|
+
class ChatResultChoice
|
4
|
+
|
5
|
+
attr_reader :message
|
6
|
+
attr_reader :end_reason
|
7
|
+
attr_reader :end_sequence
|
8
|
+
|
9
|
+
def initialize( chat_choice_attributes )
|
10
|
+
@end_reason = chat_choice_attributes[ :end_reason ]
|
11
|
+
@end_sequence = chat_choice_attributes[ :end_sequence ]
|
12
|
+
@message = build_message( chat_choice_attributes[ :message ] ) \
|
13
|
+
if chat_choice_attributes[ :message ]
|
14
|
+
end
|
15
|
+
|
16
|
+
private; def build_message( json_message )
|
17
|
+
message = Message.new( json_message[ :role ]&.to_sym || :assistant )
|
18
|
+
json_message[ :contents ]&.each do | json_content |
|
19
|
+
message << MessageContent.build( json_content[ :type ], json_content )
|
20
|
+
end
|
21
|
+
message
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Intelligence
|
2
|
+
|
3
|
+
#
|
4
|
+
# class. Conversation
|
5
|
+
#
|
6
|
+
class Conversation
|
7
|
+
|
8
|
+
attr_reader :system_message
|
9
|
+
attr_reader :messages
|
10
|
+
attr_reader :tools
|
11
|
+
|
12
|
+
def initialize( attributes = {} )
|
13
|
+
@system_message = attributes[ :system_message ]&.dup
|
14
|
+
@messages = attributes[ :messages ]&.dup || []
|
15
|
+
@tools = attributes[ :tools ]&.dup || []
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_system_message?
|
19
|
+
( @system_message || false ) && !@system_message.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_messages?
|
23
|
+
!@messages.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_tools?
|
27
|
+
!@tools.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def system_message=( message )
|
31
|
+
raise ArgumentError, "The system message must be a Intelligence::Message." \
|
32
|
+
unless message.is_a?( Intelligence::Message )
|
33
|
+
raise ArgumentError, "The system message MUST have a role of 'system'." \
|
34
|
+
unless message.role == :system
|
35
|
+
@system_message = message
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_h
|
39
|
+
result = {}
|
40
|
+
result[ :system_message ] = @system_message.to_h if @system_message
|
41
|
+
result[ :messages ] = @messages.map { | m | m.to_h }
|
42
|
+
result[ :tools ] = @tools.map { | t | t.to_h }
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|