intelligence 0.6.0 → 0.7.1

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.
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