intelligence 0.5.0 → 0.6.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.
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