intelligence 0.6.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +555 -0
  3. data/intelligence.gemspec +1 -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} +8 -125
  11. data/lib/intelligence/adapters/cerebras.rb +17 -17
  12. data/lib/intelligence/adapters/generic/chat_methods.rb +12 -5
  13. data/lib/intelligence/adapters/generic.rb +1 -1
  14. data/lib/intelligence/adapters/google/adapter.rb +33 -22
  15. data/lib/intelligence/adapters/google/chat_request_methods.rb +233 -0
  16. data/lib/intelligence/adapters/google/chat_response_methods.rb +236 -0
  17. data/lib/intelligence/adapters/groq.rb +27 -28
  18. data/lib/intelligence/adapters/hyperbolic.rb +13 -13
  19. data/lib/intelligence/adapters/legacy/chat_methods.rb +1 -2
  20. data/lib/intelligence/adapters/mistral.rb +18 -18
  21. data/lib/intelligence/adapters/open_ai/adapter.rb +39 -32
  22. data/lib/intelligence/adapters/open_ai/chat_request_methods.rb +186 -0
  23. data/lib/intelligence/adapters/open_ai/{chat_methods.rb → chat_response_methods.rb} +60 -162
  24. data/lib/intelligence/adapters/open_ai.rb +1 -1
  25. data/lib/intelligence/adapters/open_router.rb +18 -18
  26. data/lib/intelligence/adapters/samba_nova.rb +13 -13
  27. data/lib/intelligence/adapters/together_ai.rb +21 -19
  28. data/lib/intelligence/conversation.rb +11 -10
  29. data/lib/intelligence/message.rb +44 -28
  30. data/lib/intelligence/message_content/base.rb +2 -9
  31. data/lib/intelligence/message_content/binary.rb +3 -3
  32. data/lib/intelligence/message_content/file.rb +3 -3
  33. data/lib/intelligence/message_content/text.rb +2 -2
  34. data/lib/intelligence/message_content/tool_call.rb +8 -4
  35. data/lib/intelligence/message_content/tool_result.rb +11 -6
  36. data/lib/intelligence/tool.rb +139 -0
  37. data/lib/intelligence/version.rb +1 -1
  38. data/lib/intelligence.rb +2 -1
  39. metadata +15 -10
  40. data/lib/intelligence/adapter/class_methods/construction.rb +0 -17
  41. data/lib/intelligence/adapter/module_methods/construction.rb +0 -43
  42. data/lib/intelligence/adapters/google/chat_methods.rb +0 -393
@@ -1,46 +1,48 @@
1
- require_relative 'chat_methods'
1
+ require_relative 'chat_request_methods'
2
+ require_relative 'chat_response_methods'
2
3
 
3
4
  module Intelligence
4
5
  module Anthropic
5
6
  class Adapter < Adapter::Base
6
7
 
7
- configuration do
8
+ schema do
8
9
 
9
10
  # normalized properties for all endpoints
10
- parameter :key
11
+ key
11
12
 
12
13
  # anthropic specific properties for all endpoints
13
- parameter :version, String, default: '2023-06-01'
14
+ version String, default: '2023-06-01'
14
15
 
15
- group :chat_options do
16
+ chat_options do
16
17
 
17
18
  # normalized properties for anthropic generative text endpoint
18
- parameter :model, String
19
- parameter :max_tokens, Integer
20
- parameter :temperature, Float
21
- parameter :top_k, Integer
22
- parameter :top_p, Float
23
- parameter :stop, String, array: true, as: :stop_sequences
24
- parameter :stream, [ TrueClass, FalseClass ]
19
+ model String
20
+ max_tokens Integer
21
+ temperature Float
22
+ top_k Integer
23
+ top_p Float
24
+ stop String, array: true, as: :stop_sequences
25
+ stream [ TrueClass, FalseClass ]
25
26
 
26
27
  # anthropic variant of normalized properties for anthropic generative text endpoints
27
- parameter :stop_sequences, String, array: true
28
+ stop_sequences String, array: true
28
29
 
29
30
  # anthropic specific properties for anthropic generative text endpoints
30
- parameter :tool_choice do
31
- parameter :type, String
31
+ tool_choice do
32
+ type String
32
33
  # the name parameter should only be set if type = 'tool'
33
- parameter :name, String
34
+ name String
34
35
  end
35
- group :metadata do
36
- parameter :user_id, String
36
+ metadata do
37
+ user_id String
37
38
  end
38
39
 
39
40
  end
40
41
 
41
42
  end
42
43
 
43
- include ChatMethods
44
+ include ChatRequestMethods
45
+ include ChatResponseMethods
44
46
 
45
47
  end
46
48
  end
@@ -0,0 +1,189 @@
1
+ module Intelligence
2
+ module Anthropic
3
+ module ChatRequestMethods
4
+
5
+ CHAT_REQUEST_URI = "https://api.anthropic.com/v1/messages"
6
+
7
+ def chat_request_uri( options )
8
+ CHAT_REQUEST_URI
9
+ end
10
+
11
+ def chat_request_headers( options = nil )
12
+ options = @options.merge( build_options( options ) )
13
+ result = {}
14
+
15
+ key = options[ :key ]
16
+ version = options[ :version ] || "2023-06-01"
17
+
18
+ raise ArgumentError.new(
19
+ "An Anthropic key is required to build an Anthropic chat request."
20
+ ) if key.nil?
21
+
22
+ result[ 'content-type' ] = 'application/json'
23
+ result[ 'x-api-key' ] = "#{key}"
24
+ result[ 'anthropic-version' ] = version unless version.nil?
25
+
26
+ result
27
+ end
28
+
29
+ def chat_request_body( conversation, options = nil )
30
+ options = @options.merge( build_options( options ) )
31
+ result = options[ :chat_options ]&.compact || {}
32
+
33
+ system_message = to_anthropic_system_message( conversation[ :system_message ] )
34
+ result[ :system ] = system_message unless system_message.nil?
35
+ result[ :messages ] = []
36
+
37
+ messages = conversation[ :messages ]
38
+ length = messages&.length || 0
39
+ index = 0; while index < length
40
+
41
+ message = messages[ index ]
42
+ unless message.nil?
43
+
44
+ # The Anthropic API will not accept a sequence of messages where the role of two
45
+ # sequentian messages is the same.
46
+ #
47
+ # The purpose of this code is to identify such occurences and coalece them such
48
+ # that the first message in the sequence aggregates the contents of all subsequent
49
+ # messages with the same role.
50
+ look_ahead_index = index + 1; while look_ahead_index < length
51
+ ahead_message = messages[ look_ahead_index ]
52
+ unless ahead_message.nil?
53
+ if ahead_message[ :role ] == message[ :role ]
54
+ message[ :contents ] =
55
+ ( message[ :contents ] || [] ) +
56
+ ( ahead_message[ :contents ] || [] )
57
+ messages[ look_ahead_index ] = nil
58
+ look_ahead_index += 1
59
+ else
60
+ break
61
+ end
62
+ end
63
+ end
64
+
65
+ result_message = { role: message[ :role ] }
66
+ result_message_content = []
67
+
68
+ message[ :contents ]&.each do | content |
69
+ case content[ :type ]
70
+ when :text
71
+ result_message_content << { type: 'text', text: content[ :text ] }
72
+ when :binary
73
+ content_type = content[ :content_type ]
74
+ bytes = content[ :bytes ]
75
+ if content_type && bytes
76
+ mime_type = MIME::Types[ content_type ].first
77
+ if mime_type&.media_type == 'image'
78
+ result_message_content << {
79
+ type: 'image',
80
+ source: {
81
+ type: 'base64',
82
+ media_type: content_type,
83
+ data: Base64.strict_encode64( bytes )
84
+ }
85
+ }
86
+ else
87
+ raise UnsupportedContentError.new(
88
+ :anthropic,
89
+ 'only support content of type image/*'
90
+ )
91
+ end
92
+ else
93
+ raise UnsupportedContentError.new(
94
+ :anthropic,
95
+ 'requires file content to include content type and (packed) bytes'
96
+ )
97
+ end
98
+ when :tool_call
99
+ result_message_content << {
100
+ type: 'tool_use',
101
+ id: content[ :tool_call_id ],
102
+ name: content[ :tool_name ],
103
+ input: content[ :tool_parameters ] || {}
104
+ }
105
+ when :tool_result
106
+ result_message_content << {
107
+ type: 'tool_result',
108
+ tool_use_id: content[ :tool_call_id ],
109
+ content: content[ :tool_result ]
110
+ }
111
+ else
112
+ raise InvalidContentError.new( :anthropic )
113
+ end
114
+ end
115
+
116
+ result_message[ :content ] = result_message_content
117
+ result[ :messages ] << result_message
118
+
119
+ end
120
+
121
+ index += 1
122
+
123
+ end
124
+
125
+ tools_attributes = chat_request_tools_attributes( conversation[ :tools ] )
126
+ result[ :tools ] = tools_attributes if tools_attributes && tools_attributes.length > 0
127
+
128
+ JSON.generate( result )
129
+ end
130
+
131
+ def chat_request_tools_attributes( tools )
132
+ properties_array_to_object = lambda do | properties |
133
+ return nil unless properties&.any?
134
+ object = {}
135
+ required = []
136
+ properties.each do | property |
137
+ name = property.delete( :name )
138
+ required << name if property.delete( :required )
139
+ if property[ :properties ]&.any?
140
+ property_properties, property_required =
141
+ properties_array_to_object.call( property[ :properties ] )
142
+ property[ :properties ] = property_properties
143
+ property[ :required ] = property_required if property_required.any?
144
+ end
145
+ object[ name ] = property
146
+ end
147
+ [ object, required.compact ]
148
+ end
149
+
150
+ tools&.map do | tool |
151
+ tool_attributes = {
152
+ name: tool[ :name ],
153
+ description: tool[ :description ],
154
+ input_schema: { type: 'object' }
155
+ }
156
+
157
+ if tool[ :properties ]&.any?
158
+ properties_object, properties_required =
159
+ properties_array_to_object.call( tool[ :properties ] )
160
+ input_schema = {
161
+ type: 'object',
162
+ properties: properties_object
163
+ }
164
+ input_schema[ :required ] = properties_required if properties_required.any?
165
+ tool_attributes[ :input_schema ] = input_schema
166
+ end
167
+ tool_attributes
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def to_anthropic_system_message( system_message )
174
+ return nil if system_message.nil?
175
+
176
+ # note: the current version of the anthropic api simply takes a string as the
177
+ # system message but the beta version requires an array of objects akin
178
+ # to message contents.
179
+ result = ''
180
+ system_message[ :contents ].each do | content |
181
+ result += content[ :text ] if content[ :type ] == :text
182
+ end
183
+
184
+ result.empty? ? nil : result
185
+ end
186
+
187
+ end
188
+ end
189
+ end
@@ -1,113 +1,6 @@
1
1
  module Intelligence
2
2
  module Anthropic
3
- module ChatMethods
4
-
5
- CHAT_REQUEST_URI = "https://api.anthropic.com/v1/messages"
6
-
7
- def chat_request_uri( options )
8
- CHAT_REQUEST_URI
9
- end
10
-
11
- def chat_request_headers( options = nil )
12
- options = options ? self.class.configure( options ) : {}
13
- options = @options.merge( options )
14
- result = {}
15
-
16
- key = options[ :key ]
17
- version = options[ :version ] || "2023-06-01"
18
-
19
- raise ArgumentError.new(
20
- "An Anthropic key is required to build an Anthropic chat request."
21
- ) if key.nil?
22
-
23
- result[ 'content-type' ] = 'application/json'
24
- result[ 'x-api-key' ] = "#{key}"
25
- result[ 'anthropic-version' ] = version unless version.nil?
26
-
27
- result
28
- end
29
-
30
- def chat_request_body( conversation, options = nil )
31
- options = options ? self.class.configure( options ) : {}
32
- options = @options.merge( options )
33
- result = options[ :chat_options ]&.compact || {}
34
-
35
- system_message = translate_system_message( conversation[ :system_message ] )
36
- result[ :system ] = system_message unless system_message.nil?
37
- result[ :messages ] = []
38
-
39
- messages = conversation[ :messages ]
40
- length = messages&.length || 0
41
- index = 0; while index < length
42
-
43
- message = messages[ index ]
44
- unless message.nil?
45
-
46
- # The Anthropic API will not accept a sequence of messages where the role of two
47
- # sequentian messages is the same.
48
- #
49
- # The purpose of this code is to identify such occurences and coalece them such
50
- # that the first message in the sequence aggregates the contents of all subsequent
51
- # messages with the same role.
52
- look_ahead_index = index + 1; while look_ahead_index < length
53
- ahead_message = messages[ look_ahead_index ]
54
- unless ahead_message.nil?
55
- if ahead_message[ :role ] == message[ :role ]
56
- message[ :contents ] =
57
- ( message[ :contents ] || [] ) +
58
- ( ahead_message[ :contents ] || [] )
59
- messages[ look_ahead_index ] = nil
60
- look_ahead_index += 1
61
- else
62
- break
63
- end
64
- end
65
- end
66
-
67
- result_message = { role: message[ :role ] }
68
- result_message_content = []
69
-
70
- message[ :contents ]&.each do | content |
71
- case content[ :type ]
72
- when :text
73
- result_message_content << { type: 'text', text: content[ :text ] }
74
- when :binary
75
- content_type = content[ :content_type ]
76
- bytes = content[ :bytes ]
77
- if content_type && bytes
78
- mime_type = MIME::Types[ content_type ].first
79
- if mime_type&.media_type == 'image'
80
- result_message_content << {
81
- type: 'image',
82
- source: {
83
- type: 'base64',
84
- media_type: content_type,
85
- data: Base64.strict_encode64( bytes )
86
- }
87
- }
88
- else
89
- raise UnsupportedContentError.new(
90
- :anthropic,
91
- 'only support content of type image/*'
92
- )
93
- end
94
- else
95
- raise InvalidContentError.new( :anthropic )
96
- end
97
- end
98
- end
99
-
100
- result_message[ :content ] = result_message_content
101
- result[ :messages ] << result_message
102
-
103
- end
104
-
105
- index += 1
106
-
107
- end
108
-
109
- JSON.generate( result )
110
- end
3
+ module ChatResponseMethods
111
4
 
112
5
  def chat_result_attributes( response )
113
6
  return nil unless response.success?
@@ -130,6 +23,13 @@ module Intelligence
130
23
  response_json[ :content ].each do | content |
131
24
  if content[ :type ] == 'text'
132
25
  result_content.push( { type: 'text', text: content[ :text ] } )
26
+ elsif content[ :type ] == 'tool_use'
27
+ result_content.push( {
28
+ type: :tool_call,
29
+ tool_call_id: content[ :id ],
30
+ tool_name: content[ :name ],
31
+ tool_parameters: content[ :input ]
32
+ } )
133
33
  end
134
34
  end
135
35
 
@@ -289,23 +189,6 @@ module Intelligence
289
189
 
290
190
  private
291
191
 
292
- def translate_system_message( system_message )
293
-
294
- return nil if system_message.nil?
295
-
296
- # note: the current version of the anthropic api simply takes a string as the
297
- # system message but the beta version requires an array of objects akin
298
- # to message contents.
299
-
300
- result = ''
301
- system_message[ :contents ].each do | content |
302
- result += content[ :text ] if content[ :type ] == :text
303
- end
304
-
305
- result.empty? ? nil : result
306
-
307
- end
308
-
309
192
  def translate_end_result( end_result )
310
193
  case end_result
311
194
  when 'end_turn'
@@ -7,25 +7,25 @@ module Intelligence
7
7
 
8
8
  chat_request_uri "https://api.cerebras.ai/v1/chat/completions"
9
9
 
10
- configuration do
11
- parameter :key
12
- group :chat_options do
13
- parameter :model, String
14
- parameter :max_tokens, Integer
15
- parameter :response_format do
16
- parameter :type, String, default: 'json_schema'
17
- parameter :json_schema
10
+ schema do
11
+ key String
12
+ chat_options do
13
+ model String
14
+ max_tokens Integer
15
+ response_format do
16
+ type String, default: 'json_schema'
17
+ json_schema
18
18
  end
19
- parameter :seed, Integer
20
- parameter :stop, array: true
21
- parameter :stream, [ TrueClass, FalseClass ]
22
- parameter :temperature, Float
23
- parameter :top_p, Float
24
- parameter :tool_choice do
25
- parameter :type, String
26
- parameter :mame, String
19
+ seed Integer
20
+ stop array: true
21
+ stream [ TrueClass, FalseClass ]
22
+ temperature Float
23
+ top_p Float
24
+ tool_choice do
25
+ type String
26
+ mame String
27
27
  end
28
- parameter :user
28
+ user
29
29
  end
30
30
  end
31
31
 
@@ -21,8 +21,7 @@ module Intelligence
21
21
  end
22
22
 
23
23
  def chat_request_headers( options = nil )
24
- options = options ? self.class.configure( options ) : {}
25
- options = @options.merge( options )
24
+ options = @options.merge( build_options( options ) )
26
25
  result = {}
27
26
 
28
27
  key = options[ :key ]
@@ -37,8 +36,7 @@ module Intelligence
37
36
  end
38
37
 
39
38
  def chat_request_body( conversation, options = nil )
40
- options = options ? self.class.configure( options ) : {}
41
- options = @options.merge( options )
39
+ options = @options.merge( build_options( options ) )
42
40
 
43
41
  result = options[ :chat_options ]
44
42
  result[ :messages ] = []
@@ -337,7 +335,16 @@ module Intelligence
337
335
  end
338
336
  end
339
337
 
340
- private; def system_message_to_s( system_message )
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 )
341
348
 
342
349
  return nil if system_message.nil?
343
350
 
@@ -1,2 +1,2 @@
1
1
  require_relative '../adapter'
2
- require_relative 'generic/adapter'
2
+ require_relative 'generic/adapter'
@@ -1,44 +1,55 @@
1
- require_relative 'chat_methods'
1
+ require_relative 'chat_request_methods'
2
+ require_relative 'chat_response_methods'
2
3
 
3
4
  module Intelligence
4
5
  module Google
5
6
  class Adapter < Adapter::Base
6
7
 
7
- configuration do
8
+ schema do
8
9
 
9
10
  # normalized properties for all endpoints
10
- parameter :key, String
11
+ key String
11
12
 
12
- group :chat_options, as: :generationConfig do
13
+ chat_options as: :generationConfig do
13
14
 
14
15
  # normalized properties for google generative text endpoint
15
- parameter :model, String
16
- parameter :max_tokens, Integer, as: :maxOutputTokens
17
- parameter :n, Integer, as: :candidateCount
18
- parameter :temperature, Float
19
- parameter :top_k, Integer, as: :topK
20
- parameter :top_p, Float, as: :topP
21
- parameter :seed, Integer
22
- parameter :stop, String, array: true, as: :stopSequences
23
- parameter :stream, [ TrueClass, FalseClass ]
24
-
25
- parameter :frequency_penalty, Float, as: :frequencyPenalty
26
- parameter :presence_penalty, Float, as: :presencePenalty
16
+ model String
17
+ max_tokens Integer, as: :maxOutputTokens
18
+ n Integer, as: :candidateCount
19
+ temperature Float
20
+ top_k Integer, as: :topK
21
+ top_p Float, as: :topP
22
+ seed Integer
23
+ stop String, array: true, as: :stopSequences
24
+ stream [ TrueClass, FalseClass ]
25
+
26
+ frequency_penalty Float, as: :frequencyPenalty
27
+ presence_penalty Float, as: :presencePenalty
27
28
 
28
29
  # google variant of normalized properties for google generative text endpoints
29
- parameter :candidate_count, Integer, as: :candidateCount
30
- parameter :max_output_tokens, Integer, as: :maxOutputTokens
31
- parameter :stop_sequences, String, array: true, as: :stopSequences
30
+ candidate_count Integer, as: :candidateCount
31
+ max_output_tokens Integer, as: :maxOutputTokens
32
+ stop_sequences String, array: true, as: :stopSequences
32
33
 
33
34
  # google specific properties for google generative text endpoints
34
- parameter :response_mime_type, String, as: :responseMimeType
35
- parameter :response_schema, as: :responseSchema
35
+ response_mime_type String, as: :responseMimeType
36
+ response_schema as: :responseSchema
37
+
38
+ # google specific tool configuration
39
+ tool_configuration as: :tool_config do
40
+ function_calling as: :function_calling_config do
41
+ mode Symbol, in: [ :auto, :any, :none ]
42
+ allowed_function_names String, array: true
43
+ end
44
+ end
45
+
36
46
 
37
47
  end
38
48
 
39
49
  end
40
50
 
41
- include ChatMethods
51
+ include ChatRequestMethods
52
+ include ChatResponseMethods
42
53
 
43
54
  end
44
55