prescient 0.3.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.
data/lib/prescient.rb CHANGED
@@ -8,7 +8,13 @@ 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'
17
+ require_relative 'prescient/cli'
12
18
 
13
19
  # Main Prescient module for AI provider abstraction
14
20
  #
@@ -60,6 +66,28 @@ module Prescient
60
66
  @_configuration = Configuration.new
61
67
  end
62
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
+
63
91
  # Configuration class for managing Prescient settings and providers
64
92
  #
65
93
  # Handles global settings like timeouts and retry behavior, as well as
@@ -166,29 +194,62 @@ module Prescient
166
194
  private
167
195
 
168
196
  def configure_default_providers(config, env)
169
- config.add_provider(:ollama,
170
- Prescient::Provider::Ollama,
171
- url: env.fetch('OLLAMA_URL', 'http://localhost:11434'),
172
- embedding_model: env.fetch('OLLAMA_EMBEDDING_MODEL', 'nomic-embed-text'),
173
- chat_model: env.fetch('OLLAMA_CHAT_MODEL', 'llama3.2:3b'))
174
- if env['OPENAI_API_KEY']
175
- config.add_provider(
176
- :openai,
177
- Prescient::Provider::OpenAI,
178
- api_key: env['OPENAI_API_KEY'],
179
- embedding_model: env.fetch('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
180
- chat_model: env.fetch('OPENAI_CHAT_MODEL', 'gpt-4.1-mini'),
181
- )
182
- end
183
- if env['ANTHROPIC_API_KEY']
184
- config.add_provider(
185
- :anthropic,
186
- Prescient::Provider::Anthropic,
187
- api_key: env['ANTHROPIC_API_KEY'],
188
- model: env.fetch('ANTHROPIC_MODEL', 'claude-sonnet-4-20250514'),
189
- )
190
- 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
191
251
 
252
+ def configure_huggingface(config, env)
192
253
  return unless env['HUGGINGFACE_API_KEY']
193
254
 
194
255
  config.add_provider(
@@ -205,6 +266,40 @@ module Prescient
205
266
  ),
206
267
  )
207
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
208
303
  end
209
304
 
210
305
  # Default configuration
@@ -0,0 +1,153 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/kanutocd/prescient/schema/prescient.configuration.schema.json",
4
+ "title": "Prescient Configuration",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string"
10
+ },
11
+ "version": {
12
+ "const": 1
13
+ },
14
+ "default_provider": {
15
+ "type": "string"
16
+ },
17
+ "default_provider_env": {
18
+ "type": "string"
19
+ },
20
+ "timeout": {
21
+ "type": "number"
22
+ },
23
+ "timeout_env": {
24
+ "type": "string"
25
+ },
26
+ "retry_attempts": {
27
+ "type": "integer"
28
+ },
29
+ "retry_attempts_env": {
30
+ "type": "string"
31
+ },
32
+ "retry_delay": {
33
+ "type": "number"
34
+ },
35
+ "retry_delay_env": {
36
+ "type": "string"
37
+ },
38
+ "fallback_providers": {
39
+ "type": "array",
40
+ "items": {
41
+ "type": "string"
42
+ }
43
+ },
44
+ "fallback_providers_env": {
45
+ "type": "string"
46
+ },
47
+ "sensitive_keys": {
48
+ "type": "array",
49
+ "items": {
50
+ "type": "string"
51
+ }
52
+ },
53
+ "sensitive_keys_env": {
54
+ "type": "string"
55
+ },
56
+ "providers": {
57
+ "type": "object",
58
+ "additionalProperties": {
59
+ "$ref": "#/$defs/provider"
60
+ }
61
+ }
62
+ },
63
+ "$defs": {
64
+ "provider": {
65
+ "type": "object",
66
+ "additionalProperties": false,
67
+ "required": [
68
+ "type"
69
+ ],
70
+ "properties": {
71
+ "type": {
72
+ "type": "string",
73
+ "enum": [
74
+ "ollama",
75
+ "anthropic",
76
+ "openai",
77
+ "huggingface",
78
+ "gemini",
79
+ "mistral",
80
+ "deepseek",
81
+ "xai"
82
+ ]
83
+ },
84
+ "api_key": {
85
+ "type": "string"
86
+ },
87
+ "api_key_env": {
88
+ "type": "string"
89
+ },
90
+ "url": {
91
+ "type": "string"
92
+ },
93
+ "url_env": {
94
+ "type": "string"
95
+ },
96
+ "model": {
97
+ "type": "string"
98
+ },
99
+ "model_env": {
100
+ "type": "string"
101
+ },
102
+ "chat_model": {
103
+ "type": "string"
104
+ },
105
+ "chat_model_env": {
106
+ "type": "string"
107
+ },
108
+ "embedding_model": {
109
+ "type": "string"
110
+ },
111
+ "embedding_model_env": {
112
+ "type": "string"
113
+ },
114
+ "embedding_dimensions": {
115
+ "type": "integer"
116
+ },
117
+ "embedding_dimensions_env": {
118
+ "type": "string"
119
+ },
120
+ "timeout": {
121
+ "type": "number"
122
+ },
123
+ "timeout_env": {
124
+ "type": "string"
125
+ },
126
+ "context_configs": {
127
+ "type": "object"
128
+ },
129
+ "context_configs_env": {
130
+ "type": "string"
131
+ },
132
+ "prompt_templates": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "properties": {
136
+ "system_prompt": {
137
+ "type": "string"
138
+ },
139
+ "no_context_template": {
140
+ "type": "string"
141
+ },
142
+ "with_context_template": {
143
+ "type": "string"
144
+ }
145
+ }
146
+ },
147
+ "prompt_templates_env": {
148
+ "type": "string"
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
data/sig/prescient.rbs CHANGED
@@ -154,6 +154,82 @@ module Prescient
154
154
  def validate_configuration!: () -> void
155
155
  def validate_response!: (untyped, String) -> void
156
156
  end
157
+
158
+ class Gemini < Base
159
+ def self.base_uri: (String) -> untyped
160
+ def self.default_timeout: (Integer) -> untyped
161
+ def self.get: (String, **untyped) -> untyped
162
+ def self.post: (String, **untyped) -> untyped
163
+
164
+ def initialize: (**untyped) -> void
165
+ def generate_embedding: (String, **untyped) -> Array[Float]
166
+ def generate_response: (String, ?Array[untyped], **untyped) -> Hash[Symbol, untyped]
167
+ def health_check: () -> Hash[Symbol, untyped]
168
+ def list_models: () -> Array[Hash[Symbol, untyped]]
169
+
170
+ private
171
+
172
+ def validate_configuration!: () -> void
173
+ def api_headers: () -> Hash[String, String]
174
+ def model_endpoint: (untyped, String) -> String
175
+ def find_model: (Array[untyped], untyped, String) -> bool
176
+ end
177
+
178
+ class Mistral < Base
179
+ def self.base_uri: (String) -> untyped
180
+ def self.default_timeout: (Integer) -> untyped
181
+ def self.get: (String, **untyped) -> untyped
182
+ def self.post: (String, **untyped) -> untyped
183
+
184
+ def initialize: (**untyped) -> void
185
+ def generate_embedding: (String, **untyped) -> Array[Float]
186
+ def generate_response: (String, ?Array[untyped], **untyped) -> Hash[Symbol, untyped]
187
+ def health_check: () -> Hash[Symbol, untyped]
188
+ def list_models: () -> Array[Hash[Symbol, untyped]]
189
+
190
+ private
191
+
192
+ def validate_configuration!: () -> void
193
+ def api_headers: () -> Hash[String, String]
194
+ def model_available?: (Array[untyped], untyped) -> bool
195
+ def normalize_content: (untyped) -> String?
196
+ end
197
+
198
+ class DeepSeek < Base
199
+ def self.base_uri: (String) -> untyped
200
+ def self.default_timeout: (Integer) -> untyped
201
+ def self.get: (String, **untyped) -> untyped
202
+ def self.post: (String, **untyped) -> untyped
203
+
204
+ def initialize: (**untyped) -> void
205
+ def generate_embedding: (String, **untyped) -> untyped
206
+ def generate_response: (String, ?Array[untyped], **untyped) -> Hash[Symbol, untyped]
207
+ def health_check: () -> Hash[Symbol, untyped]
208
+ def list_models: () -> Array[Hash[Symbol, untyped]]
209
+
210
+ private
211
+
212
+ def validate_configuration!: () -> void
213
+ def api_headers: () -> Hash[String, String]
214
+ end
215
+
216
+ class XAI < Base
217
+ def self.base_uri: (String) -> untyped
218
+ def self.default_timeout: (Integer) -> untyped
219
+ def self.get: (String, **untyped) -> untyped
220
+ def self.post: (String, **untyped) -> untyped
221
+
222
+ def initialize: (**untyped) -> void
223
+ def generate_embedding: (String, **untyped) -> untyped
224
+ def generate_response: (String, ?Array[untyped], **untyped) -> Hash[Symbol, untyped]
225
+ def health_check: () -> Hash[Symbol, untyped]
226
+ def list_models: () -> Array[Hash[Symbol, untyped]]
227
+
228
+ private
229
+
230
+ def validate_configuration!: () -> void
231
+ def api_headers: () -> Hash[String, String]
232
+ end
157
233
  end
158
234
 
159
235
  module Pgvector
@@ -193,7 +269,7 @@ module Prescient
193
269
  attr_reader provider_name: Symbol
194
270
  attr_reader provider: untyped
195
271
 
196
- def initialize: (?Symbol, ?enable_fallback: bool) -> void
272
+ def initialize: (?Symbol, ?enable_fallback: bool, ?provider_options: Hash[Symbol, untyped]) -> void
197
273
  def generate_embedding: (String, **untyped) -> Array[Float]
198
274
  def generate_response: (String, ?Array[untyped], **untyped) -> Hash[Symbol, untyped]
199
275
  def health_check: () -> Hash[Symbol, untyped]
@@ -203,6 +279,7 @@ module Prescient
203
279
  private
204
280
 
205
281
  def sanitize_options: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
282
+ def provider_with_options: (Symbol, Hash[Symbol, untyped]) -> untyped
206
283
  def with_error_handling: () { () -> untyped } -> untyped
207
284
  def with_fallback_handling: (Symbol, *untyped, **untyped) -> untyped
208
285
  def provider_for: (Symbol, Integer) -> untyped
@@ -210,10 +287,42 @@ module Prescient
210
287
  def fallback_eligible?: (Error) -> bool
211
288
  end
212
289
 
290
+ class CLI
291
+ FORMATS: Array[String]
292
+
293
+ class UsageError < StandardError
294
+ end
295
+
296
+ def self.run: (Array[String], ?input: IO, ?output: IO, ?errors: IO) -> Integer
297
+ def initialize: (Array[String], input: IO, output: IO, errors: IO) -> void
298
+ def run: () -> Integer
299
+ end
300
+
301
+ class ConfigurationLoader
302
+ CONFIGURATION_VERSION: Integer
303
+ TOP_LEVEL_KEYS: Array[String]
304
+ PROVIDER_TYPES: Hash[String, untyped]
305
+ COMMON_PROVIDER_KEYS: Array[String]
306
+ PROMPT_TEMPLATE_KEYS: Array[String]
307
+ ATTR_KEYS: Array[String]
308
+
309
+ def self.load_file: (String, ?env: untyped) -> Configuration
310
+ def self.load_yaml: (String, ?env: untyped) -> Configuration
311
+ def self.load_hash: (Hash[untyped, untyped], ?env: untyped) -> Configuration
312
+ def self.schema_path: () -> String
313
+
314
+ def initialize: (?untyped) -> void
315
+ def load_file: (String) -> Configuration
316
+ def load_yaml: (String, ?source: String?) -> Configuration
317
+ def load_hash: (untyped, ?source: String?) -> Configuration
318
+ def apply!: (Configuration, untyped, ?source: String?) -> Configuration
319
+ end
320
+
213
321
  def self.configure: () { (Configuration) -> void } -> void
214
322
  def self.configuration: () -> Configuration
215
323
  def self.reset_configuration!: () -> Configuration
216
- def self.client: (?Symbol, ?enable_fallback: bool) -> Client
324
+ def self.load_configuration: (?String?, ?env: untyped) -> Configuration
325
+ def self.client: (?Symbol, ?enable_fallback: bool, ?provider_options: Hash[Symbol, untyped]) -> Client
217
326
  def self.generate_embedding: (String, ?provider: Symbol, ?enable_fallback: bool, **untyped) -> Array[Float]
218
327
  def self.generate_response: (String, ?Array[untyped], ?provider: Symbol, ?enable_fallback: bool, **untyped) -> Hash[Symbol, untyped]
219
328
  def self.health_check: (?provider: Symbol) -> Hash[Symbol, untyped]
@@ -221,4 +330,12 @@ module Prescient
221
330
  private
222
331
 
223
332
  def self.configure_default_providers: (Configuration, untyped) -> void
333
+ def self.configure_ollama: (Configuration, untyped) -> void
334
+ def self.configure_openai: (Configuration, untyped) -> void
335
+ def self.configure_anthropic: (Configuration, untyped) -> void
336
+ def self.configure_gemini: (Configuration, untyped) -> void
337
+ def self.configure_mistral: (Configuration, untyped) -> void
338
+ def self.configure_deepseek: (Configuration, untyped) -> void
339
+ def self.configure_xai: (Configuration, untyped) -> void
340
+ def self.configure_huggingface: (Configuration, untyped) -> void
224
341
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prescient
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa
@@ -28,7 +28,8 @@ description: "Prescient provides a consistent interface for AI providers includi
28
28
  health checks, and automatic fallback.\n"
29
29
  email:
30
30
  - kenneth.c.demanawa@gmail.com
31
- executables: []
31
+ executables:
32
+ - prescient
32
33
  extensions: []
33
34
  extra_rdoc_files: []
34
35
  files:
@@ -55,16 +56,24 @@ files:
55
56
  - examples/custom_contexts.rb
56
57
  - examples/custom_prompts.rb
57
58
  - examples/vector_search.rb
59
+ - exe/prescient
58
60
  - lib/prescient.rb
59
61
  - lib/prescient/base.rb
62
+ - lib/prescient/cli.rb
60
63
  - lib/prescient/client.rb
64
+ - lib/prescient/configuration_loader.rb
61
65
  - lib/prescient/errors.rb
62
66
  - lib/prescient/pgvector.rb
63
67
  - lib/prescient/provider/anthropic.rb
68
+ - lib/prescient/provider/deepseek.rb
69
+ - lib/prescient/provider/gemini.rb
64
70
  - lib/prescient/provider/huggingface.rb
71
+ - lib/prescient/provider/mistral.rb
65
72
  - lib/prescient/provider/ollama.rb
66
73
  - lib/prescient/provider/openai.rb
74
+ - lib/prescient/provider/xai.rb
67
75
  - lib/prescient/version.rb
76
+ - schema/prescient.configuration.schema.json
68
77
  - scripts/setup-ollama-models.sh
69
78
  - sig/prescient.rbs
70
79
  homepage: https://kanutocd.github.io/prescient