intelligence 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/intelligence.gemspec +1 -1
  3. data/lib/intelligence/adapter/base.rb +15 -2
  4. data/lib/intelligence/adapter/class_methods/construction.rb +17 -0
  5. data/lib/intelligence/adapter/module_methods/construction.rb +43 -0
  6. data/lib/intelligence/adapter.rb +2 -2
  7. data/lib/intelligence/adapters/anthropic/adapter.rb +4 -15
  8. data/lib/intelligence/adapters/anthropic/chat_methods.rb +9 -8
  9. data/lib/intelligence/adapters/cerebras.rb +3 -3
  10. data/lib/intelligence/adapters/generic/adapter.rb +1 -12
  11. data/lib/intelligence/adapters/generic/chat_methods.rb +34 -10
  12. data/lib/intelligence/adapters/google/adapter.rb +2 -15
  13. data/lib/intelligence/adapters/google/chat_methods.rb +68 -21
  14. data/lib/intelligence/adapters/groq.rb +20 -1
  15. data/lib/intelligence/adapters/hyperbolic.rb +1 -1
  16. data/lib/intelligence/adapters/legacy/adapter.rb +0 -2
  17. data/lib/intelligence/adapters/legacy/chat_methods.rb +23 -6
  18. data/lib/intelligence/adapters/mistral.rb +57 -0
  19. data/lib/intelligence/adapters/open_ai/adapter.rb +2 -16
  20. data/lib/intelligence/adapters/open_ai/chat_methods.rb +39 -8
  21. data/lib/intelligence/adapters/open_router.rb +62 -0
  22. data/lib/intelligence/adapters/samba_nova.rb +1 -1
  23. data/lib/intelligence/adapters/together_ai.rb +1 -1
  24. data/lib/intelligence/chat_request.rb +57 -7
  25. data/lib/intelligence/chat_result.rb +4 -0
  26. data/lib/intelligence/chat_result_choice.rb +4 -2
  27. data/lib/intelligence/conversation.rb +37 -9
  28. data/lib/intelligence/message.rb +90 -23
  29. data/lib/intelligence/message_content/base.rb +10 -0
  30. data/lib/intelligence/message_content/binary.rb +6 -0
  31. data/lib/intelligence/message_content/file.rb +35 -0
  32. data/lib/intelligence/message_content/text.rb +5 -0
  33. data/lib/intelligence/message_content/tool_call.rb +8 -1
  34. data/lib/intelligence/message_content/tool_result.rb +8 -1
  35. data/lib/intelligence/message_content.rb +12 -3
  36. data/lib/intelligence/version.rb +1 -1
  37. data/lib/intelligence.rb +4 -3
  38. metadata +9 -5
  39. data/lib/intelligence/adapter/construction_methods.rb +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 694776aeafc7552879d8b9d5358907e62c90a6fc5a1219081ee1d1a4cb5ddeef
4
- data.tar.gz: 380f5cd13200750f65938fd4e92c21218af8c2b57af0614bfe16f714ba296e68
3
+ metadata.gz: 754f9f4fd414c8bee3fd90ea14aeae011e21f3312e483096f5a50b41698dfef5
4
+ data.tar.gz: 7ba7e4cb7cd172e152f8ab6c886255a13d0bbf26b6c6e0f674b71765ed03cf67
5
5
  SHA512:
6
- metadata.gz: f2ca65b2a4e3a202bc1b331f7965da9e76a8ccd2ec83f7c5c273c94137fb1e0131226ecf3bf2aed6d50e262933d26c0024b3a5052edda28c39768c766b508e29
7
- data.tar.gz: c6694ffa62ddd9eb766f42fb9cc073195a01e11e0d34cd346588b3923266c14f794d1711e1766888369874e27ce4ac696de0513055af8e27149a778611ce0cb2
6
+ metadata.gz: db69984a0028e297a30346406f351388caca294094fabe00d30c8598f038db8731acfbf1ded26c6fa8f653bf388826ec6fa07327c5868bec40ba6686a7f38182
7
+ data.tar.gz: f1a21376458d278c46828c564951a945bc7c896951946911e06fe73c04204dad2147ce91ba83434161d55e0c0c1a7132d1d9f7006ea9f2ef3fbe59bcf24bc87d
data/intelligence.gemspec CHANGED
@@ -37,7 +37,7 @@ Gem::Specification.new do | spec |
37
37
  spec.require_paths = [ "lib" ]
38
38
 
39
39
  spec.add_runtime_dependency 'faraday', '~> 2.7'
40
- spec.add_runtime_dependency 'adaptiveconfiguration', '~> 1.0.0.beta01'
40
+ spec.add_runtime_dependency 'adaptiveconfiguration', '~> 1.0.0.beta08'
41
41
  spec.add_runtime_dependency 'mime-types', '~> 3.6'
42
42
 
43
43
  spec.add_development_dependency 'rspec', '~> 3.4'
@@ -1,7 +1,20 @@
1
+ require_relative 'class_methods/construction'
2
+
1
3
  module Intelligence
2
4
  module Adapter
3
5
  class Base
4
6
  extend AdaptiveConfiguration::Configurable
5
- end
7
+ extend ClassMethods::Construction
8
+
9
+ def initialize( options = nil, configuration: nil )
10
+ @options = options ? self.class.configure( options ) : {}
11
+ @options = configuration.merge( @options ) if configuration
12
+ end
13
+
14
+ protected
15
+ attr_reader :options
16
+
17
+ end
6
18
  end
7
- end
19
+ end
20
+
@@ -0,0 +1,17 @@
1
+ module Intelligence
2
+ module Adapter
3
+ module ClassMethods
4
+ module Construction
5
+
6
+ def build( options = nil, &block )
7
+ self.new( configuration: self.configure( options, &block ) )
8
+ end
9
+
10
+ def build!( options = nil, &block )
11
+ self.new( configuration: self.configure!( options, &block ) )
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ module Intelligence
2
+ module Adapter
3
+ module ModuleMethods
4
+ module Construction
5
+
6
+ def []( adapter_type )
7
+
8
+ raise ArgumentError.new( "An adapter type is required but nil was given." ) \
9
+ if adapter_type.nil?
10
+
11
+ class_name = adapter_type.to_s.split( '_' ).map( &:capitalize ).join
12
+ class_name += "::Adapter"
13
+
14
+ adapter_class = Intelligence.const_get( class_name ) rescue nil
15
+ if adapter_class.nil?
16
+ adapter_file = File.expand_path( "../../../adapters/#{adapter_type}", __FILE__ )
17
+ unless require adapter_file
18
+ raise ArgumentError.new(
19
+ "The Intelligence adapter file #{adapter_file} is missing or does not define #{class_name}."
20
+ )
21
+ end
22
+ adapter_class = Intelligence.const_get( class_name ) rescue nil
23
+ end
24
+
25
+ raise ArgumentError.new( "An unknown Intelligence adapter #{adapter_type} was requested." ) \
26
+ if adapter_class.nil?
27
+
28
+ adapter_class
29
+
30
+ end
31
+
32
+ def build( adapter_type, attributes = nil, &block )
33
+ self.[]( adapter_type ).build( attributes, &block )
34
+ end
35
+
36
+ def build!( adapter_type, attributes = nil, &block )
37
+ self.[]( adapter_type ).build!( attributes, &block )
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,8 +1,8 @@
1
- require_relative 'adapter/construction_methods'
1
+ require_relative 'adapter/module_methods/construction'
2
2
  require_relative 'adapter/base'
3
3
 
4
4
  module Intelligence
5
5
  module Adapter
6
- extend ConstructionMethods
6
+ extend ModuleMethods::Construction
7
7
  end
8
8
  end
@@ -7,16 +7,16 @@ module Intelligence
7
7
  configuration do
8
8
 
9
9
  # normalized properties for all endpoints
10
- parameter :key, required: true
10
+ parameter :key
11
11
 
12
12
  # anthropic specific properties for all endpoints
13
- parameter :version, String, required: true, default: '2023-06-01'
13
+ parameter :version, String, default: '2023-06-01'
14
14
 
15
15
  group :chat_options do
16
16
 
17
17
  # normalized properties for anthropic generative text endpoint
18
- parameter :model, String, required: true
19
- parameter :max_tokens, Integer, required: true
18
+ parameter :model, String
19
+ parameter :max_tokens, Integer
20
20
  parameter :temperature, Float
21
21
  parameter :top_k, Integer
22
22
  parameter :top_p, Float
@@ -40,17 +40,6 @@ module Intelligence
40
40
 
41
41
  end
42
42
 
43
- attr_reader :key
44
- attr_reader :version
45
- attr_reader :chat_options
46
-
47
- def initialize( attributes = nil, &block )
48
- configuration = self.class.configure( attributes, &block )
49
- @key = configuration[ :key ]
50
- @version = configuration[ :version ]
51
- @chat_options = configuration[ :chat_options ] || {}
52
- end
53
-
54
43
  include ChatMethods
55
44
 
56
45
  end
@@ -1,5 +1,3 @@
1
- require 'base64'
2
-
3
1
  module Intelligence
4
2
  module Anthropic
5
3
  module ChatMethods
@@ -10,11 +8,13 @@ module Intelligence
10
8
  CHAT_REQUEST_URI
11
9
  end
12
10
 
13
- def chat_request_headers( options = {} )
11
+ def chat_request_headers( options = nil )
12
+ options = options ? self.class.configure( options ) : {}
13
+ options = @options.merge( options )
14
14
  result = {}
15
15
 
16
- key = options[ :key ] || self.key
17
- version = options[ :version ] || self.version || "2023-06-01"
16
+ key = options[ :key ]
17
+ version = options[ :version ] || "2023-06-01"
18
18
 
19
19
  raise ArgumentError.new(
20
20
  "An Anthropic key is required to build an Anthropic chat request."
@@ -27,9 +27,10 @@ module Intelligence
27
27
  result
28
28
  end
29
29
 
30
- def chat_request_body( conversation, options = {} )
31
- options = self.class.configure( options ) unless options.empty?
32
- result = self.chat_options.merge( options ).compact
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 || {}
33
34
 
34
35
  system_message = translate_system_message( conversation[ :system_message ] )
35
36
  result[ :system ] = system_message unless system_message.nil?
@@ -8,10 +8,10 @@ module Intelligence
8
8
  chat_request_uri "https://api.cerebras.ai/v1/chat/completions"
9
9
 
10
10
  configuration do
11
- parameter :key, required: true
11
+ parameter :key
12
12
  group :chat_options do
13
- parameter :model, String, required: true
14
- parameter :max_tokens, Integer, required: true
13
+ parameter :model, String
14
+ parameter :max_tokens, Integer
15
15
  parameter :response_format do
16
16
  parameter :type, String, default: 'json_schema'
17
17
  parameter :json_schema
@@ -4,18 +4,7 @@ require_relative 'chat_methods'
4
4
  module Intelligence
5
5
  module Generic
6
6
  class Adapter < Adapter::Base
7
-
8
- attr_reader :key
9
- attr_reader :chat_options
10
-
11
- def initialize( attributes = nil, &block )
12
- configuration = self.class.configure( attributes, &block )
13
- @key = configuration[ :key ]
14
- @chat_options = configuration[ :chat_options ] || {}
15
- end
16
-
17
7
  include ChatMethods
18
-
19
8
  end
20
9
  end
21
- end
10
+ end
@@ -20,10 +20,12 @@ module Intelligence
20
20
  self.class.chat_request_uri
21
21
  end
22
22
 
23
- def chat_request_headers( options = {} )
23
+ def chat_request_headers( options = nil )
24
+ options = options ? self.class.configure( options ) : {}
25
+ options = @options.merge( options )
24
26
  result = {}
25
27
 
26
- key = options[ :key ] || self.key
28
+ key = options[ :key ]
27
29
 
28
30
  raise ArgumentError.new( "An API key is required to build a chat request." ) \
29
31
  if key.nil?
@@ -34,8 +36,11 @@ module Intelligence
34
36
  result
35
37
  end
36
38
 
37
- def chat_request_body( conversation, options = {} )
38
- result = self.chat_options.merge( options ).compact
39
+ def chat_request_body( conversation, options = nil )
40
+ options = options ? self.class.configure( options ) : {}
41
+ options = @options.merge( options )
42
+
43
+ result = options[ :chat_options ]
39
44
  result[ :messages ] = []
40
45
 
41
46
  system_message = system_message_to_s( conversation[ :system_message ] )
@@ -79,6 +84,28 @@ module Intelligence
79
84
  'requires binary content to include content type and ( packed ) bytes'
80
85
  )
81
86
  end
87
+ when :file
88
+ content_type = content[ :content_type ]
89
+ uri = content[ :uri ]
90
+ if content_type && uri
91
+ mime_type = MIME::Types[ content_type ].first
92
+ if mime_type&.media_type == 'image'
93
+ result_message_content << {
94
+ type: 'image_url',
95
+ image_url: { url: uri }
96
+ }
97
+ else
98
+ raise UnsupportedContentError.new(
99
+ :generic,
100
+ 'only support content of type image/*'
101
+ )
102
+ end
103
+ else
104
+ raise UnsupportedContentError.new(
105
+ :generic,
106
+ 'requires binary content to include content type and ( packed ) bytes'
107
+ )
108
+ end
82
109
  end
83
110
  end
84
111
  result_message[ :content ] = result_message_content
@@ -124,7 +151,7 @@ module Intelligence
124
151
  end
125
152
 
126
153
  def chat_result_error_attributes( response )
127
-
154
+
128
155
  error_type, error_description = translate_error_response_status( response.status )
129
156
  result = {
130
157
  error_type: error_type.to_s,
@@ -178,7 +205,6 @@ module Intelligence
178
205
 
179
206
  next if line.end_with?( '[DONE]' )
180
207
  data = JSON.parse( line )
181
-
182
208
  if data.is_a?( Hash )
183
209
 
184
210
  data[ 'choices' ]&.each do | data_choice |
@@ -196,10 +222,8 @@ module Intelligence
196
222
  data_choice_content = data_choice_delta[ 'content' ] || ''
197
223
  if last_content.nil? || last_content[ :type ] == :tool_call
198
224
  contents.push( { type: :text, text: data_choice_content } )
199
- elsif last_content[ :type ].nil?
225
+ elsif last_content[ :type ] == :text || last_content[ :type ].nil?
200
226
  last_content[ :type ] = :text
201
- last_content[ :text ] = data_choice_content
202
- elsif last_content[ :type ] == :text
203
227
  last_content[ :text ] = ( last_content[ :text ] || '' ) + data_choice_content
204
228
  end
205
229
  elsif data_choice_delta.include?( 'function_call' )
@@ -223,7 +247,7 @@ module Intelligence
223
247
  end
224
248
  end
225
249
  choices[ data_choice_index ][ :message ][ :contents ] = contents
226
- choices[ data_choice_index ][ :end_reason ] =
250
+ choices[ data_choice_index ][ :end_reason ] ||=
227
251
  translate_end_result( data_choice_finish_reason )
228
252
  end
229
253
 
@@ -7,12 +7,12 @@ module Intelligence
7
7
  configuration do
8
8
 
9
9
  # normalized properties for all endpoints
10
- parameter :key, String, required: true
10
+ parameter :key, String
11
11
 
12
12
  group :chat_options, as: :generationConfig do
13
13
 
14
14
  # normalized properties for google generative text endpoint
15
- parameter :model, String, required: true
15
+ parameter :model, String
16
16
  parameter :max_tokens, Integer, as: :maxOutputTokens
17
17
  parameter :n, Integer, as: :candidateCount
18
18
  parameter :temperature, Float
@@ -38,19 +38,6 @@ module Intelligence
38
38
 
39
39
  end
40
40
 
41
- attr_reader :key
42
- attr_reader :model
43
- attr_reader :stream
44
- attr_reader :chat_options
45
-
46
- def initialize( attributes = nil, &block )
47
- configuration = self.class.configure( attributes, &block ).to_h
48
- @key = configuration.delete( :key )
49
- @model = configuration[ :generationConfig ]&.delete( :model )
50
- @stream = configuration[ :generationConfig ]&.delete( :stream ) || false
51
- @chat_options = configuration[ :generationConfig ] || {}
52
- end
53
-
54
41
  include ChatMethods
55
42
 
56
43
  end
@@ -6,18 +6,37 @@ module Intelligence
6
6
 
7
7
  GENERATIVE_LANGUAGE_URI = "https://generativelanguage.googleapis.com/v1beta/models/"
8
8
 
9
- def chat_request_uri( options )
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
+ ]
10
28
 
11
- options = options.nil? || options.empty? ? {} : self.class.configure( options )
29
+ def chat_request_uri( options )
30
+ options = options ? self.class.configure( options ) : {}
31
+ options = @options.merge( options )
12
32
 
13
- key = options[ :key ] || self.key
14
-
33
+ key = options[ :key ]
15
34
  gc = options[ :generationConfig ] || {}
16
- model = gc[ :model ] || self.model
17
- stream = gc.key?( :stream ) ? gc[ :stream ] : self.stream
35
+ model = gc[ :model ]
36
+ stream = gc.key?( :stream ) ? gc[ :stream ] : false
18
37
 
19
38
  raise ArgumentError.new( "A Google API key is required to build a Google chat request." ) \
20
- if self.key.nil?
39
+ if key.nil?
21
40
  raise ArgumentError.new( "A Google model is required to build a Google chat request." ) \
22
41
  if model.nil?
23
42
 
@@ -25,12 +44,11 @@ module Intelligence
25
44
  path = File.join( uri.path, model )
26
45
  path += stream ? ':streamGenerateContent' : ':generateContent'
27
46
  uri.path = path
28
- query = { key: self.key }
47
+ query = { key: key }
29
48
  query[ :alt ] = 'sse' if stream
30
49
  uri.query = URI.encode_www_form( query )
31
50
 
32
51
  uri.to_s
33
-
34
52
  end
35
53
 
36
54
  def chat_request_headers( options = {} )
@@ -38,16 +56,16 @@ module Intelligence
38
56
  end
39
57
 
40
58
  def chat_request_body( conversation, options = {} )
59
+ options = options ? self.class.configure( options ) : {}
60
+ options = @options.merge( options )
41
61
 
42
- result = {}
43
- result[ :generationConfig ] = self.chat_options
44
-
45
- options = options.nil? || options.empty? ? {} : self.class.configure( options )
46
- result = result.merge( options )
62
+ gc = options[ :generationConfig ]
63
+ # discard properties not part of the google generationConfig schema
64
+ gc.delete( :model )
65
+ gc.delete( :stream )
47
66
 
48
- # discard properties not part of the google endpoint schema
49
- result[ :generationConfig ].delete( :model )
50
- result[ :generationConfig ].delete( :stream )
67
+ result = {}
68
+ result[ :generationConfig ] = gc
51
69
 
52
70
  # construct the system prompt in the form of the google schema
53
71
  system_instructions = translate_system_message( conversation[ :system_message ] )
@@ -67,8 +85,9 @@ module Intelligence
67
85
  content_type = content[ :content_type ]
68
86
  bytes = content[ :bytes ]
69
87
  if content_type && bytes
70
- unless MIME::Types[ content_type ].empty?
71
- # TODO: verify the specific google supported MIME types
88
+ mime_type = MIME::Types[ content_type ].first
89
+ if SUPPORTED_BINARY_MEDIA_TYPES.include?( mime_type&.media_type ) ||
90
+ SUPPORTED_BINARY_CONTENT_TYPES.include?( content_type )
72
91
  result_message_parts << {
73
92
  inline_data: {
74
93
  mime_type: content_type,
@@ -78,7 +97,7 @@ module Intelligence
78
97
  else
79
98
  raise UnsupportedContentError.new(
80
99
  :google,
81
- 'only support recognized mime types'
100
+ "does not support #{content_type} content type"
82
101
  )
83
102
  end
84
103
  else
@@ -86,7 +105,35 @@ module Intelligence
86
105
  :google,
87
106
  'requires binary content to include content type and ( packed ) bytes'
88
107
  )
89
- end
108
+ end
109
+ when :file
110
+ content_type = content[ :content_type ]
111
+ uri = content[ :uri ]
112
+ if content_type && uri
113
+ mime_type = MIME::Types[ content_type ].first
114
+ if SUPPORTED_FILE_MEDIA_TYPES.include?( mime_type&.media_type ) ||
115
+ SUPPORTED_FILE_CONTENT_TYPES.include?( content_type )
116
+ result_message_parts << {
117
+ file_data: {
118
+ mime_type: content_type,
119
+ file_uri: uri
120
+ }
121
+ }
122
+ else
123
+ raise UnsupportedContentError.new(
124
+ :google,
125
+ "does not support #{content_type} content type"
126
+ )
127
+ end
128
+ else
129
+ raise UnsupportedContentError.new(
130
+ :google,
131
+ 'requires file content to include content type and uri'
132
+ )
133
+ end
134
+
135
+ else
136
+ raise InvalidContentError.new( :google )
90
137
  end
91
138
  end
92
139
 
@@ -8,7 +8,7 @@ module Intelligence
8
8
  chat_request_uri 'https://api.groq.com/openai/v1/chat/completions'
9
9
 
10
10
  configuration do
11
- parameter :key, String, required: true
11
+ parameter :key, String
12
12
  group :chat_options do
13
13
  parameter :frequency_penalty, Float
14
14
  parameter :logit_bias
@@ -45,6 +45,25 @@ module Intelligence
45
45
  end
46
46
  end
47
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
+ )
64
+ end
65
+ end
66
+
48
67
  end
49
68
 
50
69
  end
@@ -8,7 +8,7 @@ module Intelligence
8
8
  chat_request_uri "https://api.hyperbolic.xyz/v1/chat/completions"
9
9
 
10
10
  configuration do
11
- parameter :key, String, required: true
11
+ parameter :key, String
12
12
  group :chat_options do
13
13
  parameter :model, String
14
14
  parameter :temperature, Float
@@ -3,11 +3,9 @@ require_relative 'chat_methods'
3
3
 
4
4
  module Intelligence
5
5
  module Legacy
6
-
7
6
  class Adapter < Generic::Adapter
8
7
  include ChatMethods
9
8
  end
10
-
11
9
  end
12
10
  end
13
11
 
@@ -3,20 +3,37 @@ module Intelligence
3
3
  module ChatMethods
4
4
 
5
5
  def chat_request_body( conversation, options = {} )
6
- result = self.chat_options.merge( options ).compact
6
+ options = options ? self.class.configure( options ) : {}
7
+ options = @options.merge( options )
8
+
9
+ result = options[ :chat_options ]&.compact || {}
7
10
  result[ :messages ] = []
8
11
 
9
12
  system_message = system_message_to_s( conversation[ :system_message ] )
10
13
  result[ :messages ] << { role: 'system', content: system_message } if system_message
11
-
12
- conversation[ :messages ]&.each do | message |
13
- result[ :messages ] << chat_request_message_attributes( message )
14
+
15
+ # detect if the conversation has any non-text content; this handles the sittuation
16
+ # where non-vision models only support the legacy message schema while the vision
17
+ # models only support the modern message schema
18
+ has_non_text_content = conversation[ :messages ]&.find do | message |
19
+ message[ :contents ]&.find do | content |
20
+ content[ :type ] != nil && content[ :type ] != :text
21
+ end
22
+ end
23
+
24
+ if has_non_text_content
25
+ conversation[ :messages ]&.each do | message |
26
+ result[ :messages ] << chat_request_message_attributes( message )
27
+ end
28
+ else
29
+ conversation[ :messages ]&.each do | message |
30
+ result[ :messages ] << chat_request_legacy_message_attributes( message )
31
+ end
14
32
  end
15
-
16
33
  JSON.generate( result )
17
34
  end
18
35
 
19
- def chat_request_message_attributes( message )
36
+ def chat_request_legacy_message_attributes( message )
20
37
  result_message = { role: message[ :role ] }
21
38
  result_message_content = ""
22
39
 
@@ -0,0 +1,57 @@
1
+ require_relative 'legacy/adapter'
2
+
3
+ module Intelligence
4
+ module Mistral
5
+
6
+ class Adapter < Legacy::Adapter
7
+
8
+ chat_request_uri "https://api.mistral.ai/v1/chat/completions"
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 :max_tokens, Integer
17
+ parameter :min_tokens, Integer
18
+ parameter :seed, Integer, as: :random_seed
19
+ parameter :stop, String, array: true
20
+ parameter :stream, [ TrueClass, FalseClass ]
21
+
22
+ parameter :random_seed, Integer
23
+ group :response_format do
24
+ parameter :type, String
25
+ end
26
+ group :tool_choice do
27
+ parameter :type, String
28
+ group :function do
29
+ parameter :name, String
30
+ end
31
+ end
32
+ end
33
+ 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
+
54
+ end
55
+
56
+ end
57
+ end