prescient 0.4.0 → 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.
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+
5
+ # Google Gemini API provider adapter.
6
+ class Prescient::Provider::Gemini < Prescient::Base
7
+ include HTTParty
8
+
9
+ base_uri 'https://generativelanguage.googleapis.com'
10
+
11
+ def initialize(**options)
12
+ super
13
+ @provider_name = 'Google Gemini'
14
+ self.class.default_timeout(@options[:timeout] || 60)
15
+ end
16
+
17
+ # Generate an embedding through Gemini's embedContent endpoint.
18
+ # @param text [String] Text to embed
19
+ # @return [Array<Float>] Embedding vector
20
+ def generate_embedding(text, **options)
21
+ handle_errors do
22
+ embedding_model = options[:model] || @options[:embedding_model]
23
+ response = self.class.post(
24
+ model_endpoint(embedding_model, 'embedContent'),
25
+ headers: api_headers,
26
+ body: {
27
+ content: { parts: [{ text: clean_text(text) }] },
28
+ }.to_json,
29
+ )
30
+
31
+ validate_response!(response, 'embedding generation')
32
+
33
+ embedding = response.parsed_response.dig('embedding', 'values')
34
+ raise Prescient::InvalidResponseError, 'No embedding returned' unless embedding.is_a?(Array)
35
+
36
+ expected_dimensions = @options[:embedding_dimensions]
37
+ expected_dimensions ? validate_embedding_dimensions(embedding, expected_dimensions) : embedding
38
+ end
39
+ end
40
+
41
+ # Generate a response through Gemini's generateContent endpoint.
42
+ # @param prompt [String] Prompt to send
43
+ # @param context_items [Array<Hash, String>] Optional context items
44
+ # @return [Hash] Normalized response data
45
+ def generate_response(prompt, context_items = [], **options)
46
+ handle_errors do
47
+ model = options[:model] || @options[:chat_model]
48
+ response = self.class.post(
49
+ model_endpoint(model, 'generateContent'),
50
+ headers: api_headers,
51
+ body: {
52
+ contents: [{ role: 'user', parts: [{ text: build_prompt(prompt, context_items) }] }],
53
+ generationConfig: {
54
+ maxOutputTokens: options[:max_tokens] || 2000,
55
+ temperature: options[:temperature] || 0.7,
56
+ topP: options[:top_p] || 0.9,
57
+ },
58
+ }.to_json,
59
+ )
60
+
61
+ validate_response!(response, 'text generation')
62
+
63
+ parsed_response = response.parsed_response
64
+ parts = parsed_response.dig('candidates', 0, 'content', 'parts')
65
+ content = Array(parts).filter_map { |part| part['text'] }.join
66
+ raise Prescient::InvalidResponseError, 'No response generated' if content.nil? || content.empty?
67
+
68
+ {
69
+ response: content.strip,
70
+ model: model,
71
+ provider: 'gemini',
72
+ processing_time: nil,
73
+ metadata: {
74
+ usage: parsed_response['usageMetadata'],
75
+ finish_reason: parsed_response.dig('candidates', 0, 'finishReason'),
76
+ },
77
+ }
78
+ end
79
+ end
80
+
81
+ # Check whether the configured Gemini models are available.
82
+ # @return [Hash] Provider health information
83
+ def health_check
84
+ handle_errors do
85
+ response = self.class.get('/v1beta/models', headers: api_headers)
86
+
87
+ if response.success?
88
+ models = response.parsed_response['models'] || []
89
+ embedding_model = find_model(models, @options[:embedding_model], 'embedContent')
90
+ chat_model = find_model(models, @options[:chat_model], 'generateContent')
91
+
92
+ {
93
+ status: 'healthy',
94
+ provider: 'gemini',
95
+ reachable: true,
96
+ models_available: models.map { |model| model['name'].to_s.delete_prefix('models/') },
97
+ embedding_model: { name: @options[:embedding_model], available: embedding_model },
98
+ chat_model: { name: @options[:chat_model], available: chat_model },
99
+ ready: embedding_model && chat_model,
100
+ }
101
+ else
102
+ {
103
+ status: 'unhealthy',
104
+ provider: 'gemini',
105
+ reachable: true,
106
+ error: "HTTP #{response.code}",
107
+ message: response.message,
108
+ ready: false,
109
+ }
110
+ end
111
+ end
112
+ rescue Prescient::Error => e
113
+ {
114
+ status: 'unavailable',
115
+ provider: 'gemini',
116
+ reachable: false,
117
+ error: e.class.name,
118
+ message: e.message,
119
+ ready: false,
120
+ }
121
+ end
122
+
123
+ # List models available to the configured Gemini API key.
124
+ # @return [Array<Hash>] Model descriptors
125
+ def list_models
126
+ handle_errors do
127
+ response = self.class.get('/v1beta/models', headers: api_headers)
128
+ validate_response!(response, 'model listing')
129
+
130
+ (response.parsed_response['models'] || []).map do |model|
131
+ {
132
+ name: model['name'].to_s.delete_prefix('models/'),
133
+ display_name: model['displayName'],
134
+ supported_generation_modes: model['supportedGenerationMethods'],
135
+ input_token_limit: model['inputTokenLimit'],
136
+ output_token_limit: model['outputTokenLimit'],
137
+ }.compact
138
+ end
139
+ end
140
+ end
141
+
142
+ protected
143
+
144
+ def validate_configuration!
145
+ required_options = [:api_key, :embedding_model, :chat_model]
146
+ missing_options = required_options.select { |option| @options[option].nil? }
147
+
148
+ return unless missing_options.any?
149
+
150
+ raise Prescient::Error, "Missing required options: #{missing_options.join(', ')}"
151
+ end
152
+
153
+ private
154
+
155
+ def api_headers
156
+ {
157
+ 'Content-Type' => 'application/json',
158
+ 'x-goog-api-key' => @options[:api_key],
159
+ }
160
+ end
161
+
162
+ def model_endpoint(model, operation)
163
+ model_name = model.to_s.delete_prefix('models/')
164
+ "/v1beta/models/#{model_name}:#{operation}"
165
+ end
166
+
167
+ def find_model(models, model_name, operation)
168
+ models.any? do |model|
169
+ model['name'].to_s.delete_prefix('models/') == model_name &&
170
+ model.fetch('supportedGenerationMethods', []).include?(operation)
171
+ end
172
+ end
173
+ end
@@ -29,6 +29,7 @@ class Prescient::Provider::HuggingFace < Prescient::Base
29
29
 
30
30
  def initialize(**options)
31
31
  super
32
+ @provider_name = 'Hugging Face'
32
33
  self.class.default_timeout(@options[:timeout] || 60)
33
34
  end
34
35
 
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+
5
+ # Mistral AI API provider adapter.
6
+ class Prescient::Provider::Mistral < Prescient::Base
7
+ include HTTParty
8
+
9
+ base_uri 'https://api.mistral.ai'
10
+
11
+ def initialize(**options)
12
+ super
13
+ self.class.default_timeout(@options[:timeout] || 60)
14
+ end
15
+
16
+ # Generate an embedding through Mistral's embeddings API.
17
+ # @param text [String] Text to embed
18
+ # @return [Array<Float>] Embedding vector
19
+ def generate_embedding(text, **options)
20
+ handle_errors do
21
+ embedding_model = options[:model] || @options[:embedding_model]
22
+ response = self.class.post(
23
+ '/v1/embeddings',
24
+ headers: api_headers,
25
+ body: {
26
+ model: embedding_model,
27
+ input: clean_text(text),
28
+ }.to_json,
29
+ )
30
+
31
+ validate_response!(response, 'embedding generation')
32
+
33
+ embedding = response.parsed_response.dig('data', 0, 'embedding')
34
+ raise Prescient::InvalidResponseError, 'No embedding returned' unless embedding.is_a?(Array)
35
+
36
+ expected_dimensions = @options[:embedding_dimensions]
37
+ expected_dimensions ? validate_embedding_dimensions(embedding, expected_dimensions) : embedding
38
+ end
39
+ end
40
+
41
+ # Generate a response through Mistral's chat completions API.
42
+ # @param prompt [String] Prompt to send
43
+ # @param context_items [Array<Hash, String>] Optional context items
44
+ # @return [Hash] Normalized response data
45
+ def generate_response(prompt, context_items = [], **options)
46
+ handle_errors do
47
+ model = options[:model] || @options[:chat_model]
48
+ response = self.class.post(
49
+ '/v1/chat/completions',
50
+ headers: api_headers,
51
+ body: {
52
+ model: model,
53
+ messages: [{ role: 'user', content: build_prompt(prompt, context_items) }],
54
+ max_tokens: options[:max_tokens] || 2000,
55
+ temperature: options[:temperature] || 0.7,
56
+ top_p: options[:top_p] || 0.9,
57
+ }.to_json,
58
+ )
59
+
60
+ validate_response!(response, 'text generation')
61
+
62
+ parsed_response = response.parsed_response
63
+ content = normalize_content(parsed_response.dig('choices', 0, 'message', 'content'))
64
+ raise Prescient::InvalidResponseError, 'No response generated' if content.nil? || content.empty?
65
+
66
+ {
67
+ response: content.strip,
68
+ model: model,
69
+ provider: 'mistral',
70
+ processing_time: nil,
71
+ metadata: {
72
+ usage: parsed_response['usage'],
73
+ finish_reason: parsed_response.dig('choices', 0, 'finish_reason'),
74
+ },
75
+ }
76
+ end
77
+ end
78
+
79
+ # Check whether the configured Mistral models are available.
80
+ # @return [Hash] Provider health information
81
+ def health_check
82
+ handle_errors do
83
+ response = self.class.get('/v1/models', headers: api_headers)
84
+
85
+ if response.success?
86
+ models = response.parsed_response['data'] || []
87
+ embedding_available = model_available?(models, @options[:embedding_model])
88
+ chat_available = model_available?(models, @options[:chat_model])
89
+
90
+ {
91
+ status: 'healthy',
92
+ provider: 'mistral',
93
+ reachable: true,
94
+ models_available: models.map { |model| model['id'] },
95
+ embedding_model: { name: @options[:embedding_model], available: embedding_available },
96
+ chat_model: { name: @options[:chat_model], available: chat_available },
97
+ ready: embedding_available && chat_available,
98
+ }
99
+ else
100
+ {
101
+ status: 'unhealthy',
102
+ provider: 'mistral',
103
+ reachable: true,
104
+ error: "HTTP #{response.code}",
105
+ message: response.message,
106
+ ready: false,
107
+ }
108
+ end
109
+ end
110
+ rescue Prescient::Error => e
111
+ {
112
+ status: 'unavailable',
113
+ provider: 'mistral',
114
+ reachable: false,
115
+ error: e.class.name,
116
+ message: e.message,
117
+ ready: false,
118
+ }
119
+ end
120
+
121
+ # List models available to the configured Mistral API key.
122
+ # @return [Array<Hash>] Model descriptors
123
+ def list_models
124
+ handle_errors do
125
+ response = self.class.get('/v1/models', headers: api_headers)
126
+ validate_response!(response, 'model listing')
127
+
128
+ (response.parsed_response['data'] || []).map do |model|
129
+ {
130
+ name: model['id'],
131
+ object: model['object'],
132
+ created: model['created'],
133
+ owned_by: model['owned_by'],
134
+ capabilities: model['capabilities'],
135
+ max_context_length: model['max_context_length'],
136
+ }.compact
137
+ end
138
+ end
139
+ end
140
+
141
+ protected
142
+
143
+ def validate_configuration!
144
+ required_options = [:api_key, :embedding_model, :chat_model]
145
+ missing_options = required_options.select { |option| @options[option].nil? }
146
+
147
+ return unless missing_options.any?
148
+
149
+ raise Prescient::Error, "Missing required options: #{missing_options.join(', ')}"
150
+ end
151
+
152
+ private
153
+
154
+ def api_headers
155
+ {
156
+ 'Content-Type' => 'application/json',
157
+ 'Authorization' => "Bearer #{@options[:api_key]}",
158
+ }
159
+ end
160
+
161
+ def model_available?(models, model_name)
162
+ models.any? { |model| model['id'] == model_name }
163
+ end
164
+
165
+ def normalize_content(content)
166
+ return content if content.is_a?(String)
167
+ return unless content.is_a?(Array)
168
+
169
+ content.filter_map { |part| part['text'] if part.is_a?(Hash) }.join
170
+ end
171
+ end
@@ -17,6 +17,7 @@ class Prescient::Provider::OpenAI < Prescient::Base
17
17
 
18
18
  def initialize(**options)
19
19
  super
20
+ @provider_name = 'OpenAI'
20
21
  self.class.default_timeout(@options[:timeout] || 60)
21
22
  end
22
23
 
@@ -82,6 +83,8 @@ class Prescient::Provider::OpenAI < Prescient::Base
82
83
 
83
84
  validate_response!(response, 'text generation')
84
85
 
86
+ puts "response.parsed_response: #{response.parsed_response.inspect}"
87
+
85
88
  content = response.parsed_response.dig('choices', 0, 'message', 'content')
86
89
  raise Prescient::InvalidResponseError, 'No response generated' unless content
87
90
 
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+
5
+ # xAI API provider adapter.
6
+ class Prescient::Provider::XAI < Prescient::Base
7
+ include HTTParty
8
+
9
+ base_uri 'https://api.x.ai'
10
+
11
+ def initialize(**options)
12
+ super
13
+ @provider_name = 'xAI'
14
+ self.class.default_timeout(@options[:timeout] || 60)
15
+ end
16
+
17
+ # xAI does not expose a standard embeddings API for this adapter.
18
+ # @raise [Prescient::Error] Always, because embeddings are unsupported
19
+ def generate_embedding(_text, **_options)
20
+ raise Prescient::Error, 'xAI provider does not support embeddings.'
21
+ end
22
+
23
+ # Generate a response through xAI's OpenAI-compatible chat API.
24
+ # @param prompt [String] Prompt to send
25
+ # @param context_items [Array<Hash, String>] Optional context items
26
+ # @return [Hash] Normalized response data
27
+ def generate_response(prompt, context_items = [], **options)
28
+ handle_errors do
29
+ model = options[:model] || @options[:chat_model]
30
+ response = self.class.post(
31
+ '/v1/chat/completions',
32
+ headers: api_headers,
33
+ body: {
34
+ model: model,
35
+ messages: [{ role: 'user', content: build_prompt(prompt, context_items) }],
36
+ max_tokens: options[:max_tokens] || 2000,
37
+ temperature: options[:temperature] || 0.7,
38
+ top_p: options[:top_p] || 0.9,
39
+ }.to_json,
40
+ )
41
+
42
+ validate_response!(response, 'text generation')
43
+
44
+ parsed_response = response.parsed_response
45
+ content = parsed_response.dig('choices', 0, 'message', 'content')
46
+ raise Prescient::InvalidResponseError, 'No response generated' unless content.is_a?(String) && !content.empty?
47
+
48
+ {
49
+ response: content.strip,
50
+ model: model,
51
+ provider: 'xai',
52
+ processing_time: nil,
53
+ metadata: {
54
+ usage: parsed_response['usage'],
55
+ finish_reason: parsed_response.dig('choices', 0, 'finish_reason'),
56
+ },
57
+ }
58
+ end
59
+ end
60
+
61
+ # Check whether the configured xAI model is available.
62
+ # @return [Hash] Provider health information
63
+ def health_check
64
+ handle_errors do
65
+ response = self.class.get('/v1/models', headers: api_headers)
66
+
67
+ if response.success?
68
+ models = response.parsed_response['data'] || []
69
+ model_available = models.any? { |model| model['id'] == @options[:chat_model] }
70
+
71
+ {
72
+ status: 'healthy',
73
+ provider: 'xai',
74
+ reachable: true,
75
+ models_available: models.map { |model| model['id'] },
76
+ chat_model: { name: @options[:chat_model], available: model_available },
77
+ ready: model_available,
78
+ }
79
+ else
80
+ {
81
+ status: 'unhealthy',
82
+ provider: 'xai',
83
+ reachable: true,
84
+ error: "HTTP #{response.code}",
85
+ message: response.message,
86
+ ready: false,
87
+ }
88
+ end
89
+ end
90
+ rescue Prescient::Error => e
91
+ {
92
+ status: 'unavailable',
93
+ provider: 'xai',
94
+ reachable: false,
95
+ error: e.class.name,
96
+ message: e.message,
97
+ ready: false,
98
+ }
99
+ end
100
+
101
+ # List models available to the configured xAI API key.
102
+ # @return [Array<Hash>] Model descriptors
103
+ def list_models
104
+ handle_errors do
105
+ response = self.class.get('/v1/models', headers: api_headers)
106
+ validate_response!(response, 'model listing')
107
+
108
+ (response.parsed_response['data'] || []).map do |model|
109
+ {
110
+ name: model['id'],
111
+ object: model['object'],
112
+ created: model['created'],
113
+ owned_by: model['owned_by'],
114
+ context_length: model['context_length'],
115
+ }.compact
116
+ end
117
+ end
118
+ end
119
+
120
+ protected
121
+
122
+ def validate_configuration!
123
+ required_options = [:api_key, :chat_model]
124
+ missing_options = required_options.select { |option| @options[option].nil? }
125
+
126
+ return unless missing_options.any?
127
+
128
+ raise Prescient::Error, "Missing required options: #{missing_options.join(', ')}"
129
+ end
130
+
131
+ private
132
+
133
+ def api_headers
134
+ {
135
+ 'Content-Type' => 'application/json',
136
+ 'Authorization' => "Bearer #{@options[:api_key]}",
137
+ }
138
+ end
139
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Prescient
4
4
  # Current Prescient gem version.
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
data/lib/prescient.rb CHANGED
@@ -8,6 +8,11 @@ require_relative 'prescient/provider/ollama'
8
8
  require_relative 'prescient/provider/anthropic'
9
9
  require_relative 'prescient/provider/openai'
10
10
  require_relative 'prescient/provider/huggingface'
11
+ require_relative 'prescient/provider/gemini'
12
+ require_relative 'prescient/provider/mistral'
13
+ require_relative 'prescient/provider/deepseek'
14
+ require_relative 'prescient/provider/xai'
15
+ require_relative 'prescient/configuration_loader'
11
16
  require_relative 'prescient/client'
12
17
  require_relative 'prescient/cli'
13
18
 
@@ -61,6 +66,28 @@ module Prescient
61
66
  @_configuration = Configuration.new
62
67
  end
63
68
 
69
+ # Load configuration from a YAML file and replace the current configuration.
70
+ #
71
+ # The loaded configuration starts from the current environment defaults,
72
+ # then applies the YAML file, environment-variable references, and any
73
+ # optional overrides.
74
+ #
75
+ # @param path [String, nil] YAML configuration file path
76
+ # @param env [Hash] Environment variables used while loading configuration
77
+ # @return [Configuration] The loaded configuration
78
+ def self.load_configuration(path = nil, env: ENV)
79
+ effective_path = path || env['PRESCIENT_CONFIG']
80
+ configuration = if effective_path
81
+ ConfigurationLoader.load_file(effective_path, env:)
82
+ else
83
+ Configuration.new.tap do |config|
84
+ configure_default_providers(config, env)
85
+ end
86
+ end
87
+
88
+ @_configuration = configuration
89
+ end
90
+
64
91
  # Configuration class for managing Prescient settings and providers
65
92
  #
66
93
  # Handles global settings like timeouts and retry behavior, as well as
@@ -167,29 +194,62 @@ module Prescient
167
194
  private
168
195
 
169
196
  def configure_default_providers(config, env)
170
- config.add_provider(:ollama,
171
- Prescient::Provider::Ollama,
172
- url: env.fetch('OLLAMA_URL', 'http://localhost:11434'),
173
- embedding_model: env.fetch('OLLAMA_EMBEDDING_MODEL', 'nomic-embed-text'),
174
- chat_model: env.fetch('OLLAMA_CHAT_MODEL', 'llama3.2:3b'))
175
- if env['OPENAI_API_KEY']
176
- config.add_provider(
177
- :openai,
178
- Prescient::Provider::OpenAI,
179
- api_key: env['OPENAI_API_KEY'],
180
- embedding_model: env.fetch('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
181
- chat_model: env.fetch('OPENAI_CHAT_MODEL', 'gpt-4.1-mini'),
182
- )
183
- end
184
- if env['ANTHROPIC_API_KEY']
185
- config.add_provider(
186
- :anthropic,
187
- Prescient::Provider::Anthropic,
188
- api_key: env['ANTHROPIC_API_KEY'],
189
- model: env.fetch('ANTHROPIC_MODEL', 'claude-sonnet-4-20250514'),
190
- )
191
- end
197
+ configure_ollama(config, env)
198
+ configure_openai(config, env)
199
+ configure_anthropic(config, env)
200
+ configure_gemini(config, env)
201
+ configure_mistral(config, env)
202
+ configure_deepseek(config, env)
203
+ configure_xai(config, env)
204
+ configure_huggingface(config, env)
205
+ end
206
+
207
+ def configure_ollama(config, env)
208
+ config.add_provider(
209
+ :ollama,
210
+ Prescient::Provider::Ollama,
211
+ url: env.fetch('OLLAMA_URL', 'http://localhost:11434'),
212
+ embedding_model: env.fetch('OLLAMA_EMBEDDING_MODEL', 'nomic-embed-text'),
213
+ chat_model: env.fetch('OLLAMA_CHAT_MODEL', 'llama3.2:3b'),
214
+ )
215
+ end
216
+
217
+ def configure_openai(config, env)
218
+ return unless env['OPENAI_API_KEY']
219
+
220
+ config.add_provider(
221
+ :openai,
222
+ Prescient::Provider::OpenAI,
223
+ api_key: env['OPENAI_API_KEY'],
224
+ embedding_model: env.fetch('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
225
+ chat_model: env.fetch('OPENAI_CHAT_MODEL', 'gpt-4.1-mini'),
226
+ )
227
+ end
228
+
229
+ def configure_anthropic(config, env)
230
+ return unless env['ANTHROPIC_API_KEY']
231
+
232
+ config.add_provider(
233
+ :anthropic,
234
+ Prescient::Provider::Anthropic,
235
+ api_key: env['ANTHROPIC_API_KEY'],
236
+ model: env.fetch('ANTHROPIC_MODEL', 'claude-sonnet-4-20250514'),
237
+ )
238
+ end
239
+
240
+ def configure_gemini(config, env)
241
+ return unless env['GEMINI_API_KEY']
242
+
243
+ config.add_provider(
244
+ :gemini,
245
+ Prescient::Provider::Gemini,
246
+ api_key: env['GEMINI_API_KEY'],
247
+ embedding_model: env.fetch('GEMINI_EMBEDDING_MODEL', 'gemini-embedding-001'),
248
+ chat_model: env.fetch('GEMINI_CHAT_MODEL', 'gemini-2.5-flash'),
249
+ )
250
+ end
192
251
 
252
+ def configure_huggingface(config, env)
193
253
  return unless env['HUGGINGFACE_API_KEY']
194
254
 
195
255
  config.add_provider(
@@ -206,6 +266,40 @@ module Prescient
206
266
  ),
207
267
  )
208
268
  end
269
+
270
+ def configure_deepseek(config, env)
271
+ return unless env['DEEPSEEK_API_KEY']
272
+
273
+ config.add_provider(
274
+ :deepseek,
275
+ Prescient::Provider::DeepSeek,
276
+ api_key: env['DEEPSEEK_API_KEY'],
277
+ chat_model: env.fetch('DEEPSEEK_CHAT_MODEL', 'deepseek-v4-flash'),
278
+ )
279
+ end
280
+
281
+ def configure_xai(config, env)
282
+ return unless env['XAI_API_KEY']
283
+
284
+ config.add_provider(
285
+ :xai,
286
+ Prescient::Provider::XAI,
287
+ api_key: env['XAI_API_KEY'],
288
+ chat_model: env.fetch('XAI_CHAT_MODEL', 'grok-4.5'),
289
+ )
290
+ end
291
+
292
+ def configure_mistral(config, env)
293
+ return unless env['MISTRAL_API_KEY']
294
+
295
+ config.add_provider(
296
+ :mistral,
297
+ Prescient::Provider::Mistral,
298
+ api_key: env['MISTRAL_API_KEY'],
299
+ embedding_model: env.fetch('MISTRAL_EMBEDDING_MODEL', 'mistral-embed'),
300
+ chat_model: env.fetch('MISTRAL_CHAT_MODEL', 'mistral-large-latest'),
301
+ )
302
+ end
209
303
  end
210
304
 
211
305
  # Default configuration