prescient 0.4.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.
data/lib/prescient.rb CHANGED
@@ -8,8 +8,12 @@ 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
- require_relative 'prescient/cli'
13
17
 
14
18
  # Main Prescient module for AI provider abstraction
15
19
  #
@@ -30,6 +34,9 @@ require_relative 'prescient/cli'
30
34
  # embedding = client.generate_embedding("Some text to embed")
31
35
  # puts embedding.length # => 1536 (for OpenAI text-embedding-3-small)
32
36
  module Prescient
37
+ autoload :API, 'prescient/api'
38
+ autoload :CLI, 'prescient/cli'
39
+
33
40
  # Configure Prescient with custom settings and providers
34
41
  #
35
42
  # @example Configure with custom provider
@@ -61,6 +68,28 @@ module Prescient
61
68
  @_configuration = Configuration.new
62
69
  end
63
70
 
71
+ # Load configuration from a YAML file and replace the current configuration.
72
+ #
73
+ # The loaded configuration starts from the current environment defaults,
74
+ # then applies the YAML file, environment-variable references, and any
75
+ # optional overrides.
76
+ #
77
+ # @param path [String, nil] YAML configuration file path
78
+ # @param env [Hash] Environment variables used while loading configuration
79
+ # @return [Configuration] The loaded configuration
80
+ def self.load_configuration(path = nil, env: ENV)
81
+ effective_path = path || env['PRESCIENT_CONFIG']
82
+ configuration = if effective_path
83
+ ConfigurationLoader.load_file(effective_path, env:)
84
+ else
85
+ Configuration.new.tap do |config|
86
+ configure_default_providers(config, env)
87
+ end
88
+ end
89
+
90
+ @_configuration = configuration
91
+ end
92
+
64
93
  # Configuration class for managing Prescient settings and providers
65
94
  #
66
95
  # Handles global settings like timeouts and retry behavior, as well as
@@ -167,29 +196,62 @@ module Prescient
167
196
  private
168
197
 
169
198
  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
199
+ configure_ollama(config, env)
200
+ configure_openai(config, env)
201
+ configure_anthropic(config, env)
202
+ configure_gemini(config, env)
203
+ configure_mistral(config, env)
204
+ configure_deepseek(config, env)
205
+ configure_xai(config, env)
206
+ configure_huggingface(config, env)
207
+ end
208
+
209
+ def configure_ollama(config, env)
210
+ config.add_provider(
211
+ :ollama,
212
+ Prescient::Provider::Ollama,
213
+ url: env.fetch('OLLAMA_URL', 'http://localhost:11434'),
214
+ embedding_model: env.fetch('OLLAMA_EMBEDDING_MODEL', 'nomic-embed-text'),
215
+ chat_model: env.fetch('OLLAMA_CHAT_MODEL', 'llama3.2:3b'),
216
+ )
217
+ end
218
+
219
+ def configure_openai(config, env)
220
+ return unless env['OPENAI_API_KEY']
192
221
 
222
+ config.add_provider(
223
+ :openai,
224
+ Prescient::Provider::OpenAI,
225
+ api_key: env['OPENAI_API_KEY'],
226
+ embedding_model: env.fetch('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
227
+ chat_model: env.fetch('OPENAI_CHAT_MODEL', 'gpt-4.1-mini'),
228
+ )
229
+ end
230
+
231
+ def configure_anthropic(config, env)
232
+ return unless env['ANTHROPIC_API_KEY']
233
+
234
+ config.add_provider(
235
+ :anthropic,
236
+ Prescient::Provider::Anthropic,
237
+ api_key: env['ANTHROPIC_API_KEY'],
238
+ model: env.fetch('ANTHROPIC_MODEL', 'claude-sonnet-4-20250514'),
239
+ )
240
+ end
241
+
242
+ def configure_gemini(config, env)
243
+ return unless env['GEMINI_API_KEY']
244
+
245
+ config.add_provider(
246
+ :gemini,
247
+ Prescient::Provider::Gemini,
248
+ api_key: env['GEMINI_API_KEY'],
249
+ embedding_model: env.fetch('GEMINI_EMBEDDING_MODEL', 'gemini-embedding-001'),
250
+ chat_model: env.fetch('GEMINI_CHAT_MODEL', 'gemini-2.5-flash'),
251
+ )
252
+ end
253
+
254
+ def configure_huggingface(config, env)
193
255
  return unless env['HUGGINGFACE_API_KEY']
194
256
 
195
257
  config.add_provider(
@@ -206,6 +268,40 @@ module Prescient
206
268
  ),
207
269
  )
208
270
  end
271
+
272
+ def configure_deepseek(config, env)
273
+ return unless env['DEEPSEEK_API_KEY']
274
+
275
+ config.add_provider(
276
+ :deepseek,
277
+ Prescient::Provider::DeepSeek,
278
+ api_key: env['DEEPSEEK_API_KEY'],
279
+ chat_model: env.fetch('DEEPSEEK_CHAT_MODEL', 'deepseek-v4-flash'),
280
+ )
281
+ end
282
+
283
+ def configure_xai(config, env)
284
+ return unless env['XAI_API_KEY']
285
+
286
+ config.add_provider(
287
+ :xai,
288
+ Prescient::Provider::XAI,
289
+ api_key: env['XAI_API_KEY'],
290
+ chat_model: env.fetch('XAI_CHAT_MODEL', 'grok-4.5'),
291
+ )
292
+ end
293
+
294
+ def configure_mistral(config, env)
295
+ return unless env['MISTRAL_API_KEY']
296
+
297
+ config.add_provider(
298
+ :mistral,
299
+ Prescient::Provider::Mistral,
300
+ api_key: env['MISTRAL_API_KEY'],
301
+ embedding_model: env.fetch('MISTRAL_EMBEDDING_MODEL', 'mistral-embed'),
302
+ chat_model: env.fetch('MISTRAL_CHAT_MODEL', 'mistral-large-latest'),
303
+ )
304
+ end
209
305
  end
210
306
 
211
307
  # 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
@@ -222,9 +298,40 @@ module Prescient
222
298
  def run: () -> Integer
223
299
  end
224
300
 
301
+ class API
302
+ DEFAULT_MAX_BODY_BYTES: Integer
303
+ MAX_BATCH_SIZE: Integer
304
+ API_VERSION: String
305
+ ROUTES: Hash[Array[String], Symbol]
306
+
307
+ def initialize: (?authentication: untyped, ?max_body_bytes: Integer) -> void
308
+ def call: (Hash[String, untyped]) -> [Integer, Hash[String, String], Array[String]]
309
+ end
310
+
311
+ class ConfigurationLoader
312
+ CONFIGURATION_VERSION: Integer
313
+ TOP_LEVEL_KEYS: Array[String]
314
+ PROVIDER_TYPES: Hash[String, untyped]
315
+ COMMON_PROVIDER_KEYS: Array[String]
316
+ PROMPT_TEMPLATE_KEYS: Array[String]
317
+ ATTR_KEYS: Array[String]
318
+
319
+ def self.load_file: (String, ?env: untyped) -> Configuration
320
+ def self.load_yaml: (String, ?env: untyped) -> Configuration
321
+ def self.load_hash: (Hash[untyped, untyped], ?env: untyped) -> Configuration
322
+ def self.schema_path: () -> String
323
+
324
+ def initialize: (?untyped) -> void
325
+ def load_file: (String) -> Configuration
326
+ def load_yaml: (String, ?source: String?) -> Configuration
327
+ def load_hash: (untyped, ?source: String?) -> Configuration
328
+ def apply!: (Configuration, untyped, ?source: String?) -> Configuration
329
+ end
330
+
225
331
  def self.configure: () { (Configuration) -> void } -> void
226
332
  def self.configuration: () -> Configuration
227
333
  def self.reset_configuration!: () -> Configuration
334
+ def self.load_configuration: (?String?, ?env: untyped) -> Configuration
228
335
  def self.client: (?Symbol, ?enable_fallback: bool, ?provider_options: Hash[Symbol, untyped]) -> Client
229
336
  def self.generate_embedding: (String, ?provider: Symbol, ?enable_fallback: bool, **untyped) -> Array[Float]
230
337
  def self.generate_response: (String, ?Array[untyped], ?provider: Symbol, ?enable_fallback: bool, **untyped) -> Hash[Symbol, untyped]
@@ -233,4 +340,12 @@ module Prescient
233
340
  private
234
341
 
235
342
  def self.configure_default_providers: (Configuration, untyped) -> void
343
+ def self.configure_ollama: (Configuration, untyped) -> void
344
+ def self.configure_openai: (Configuration, untyped) -> void
345
+ def self.configure_anthropic: (Configuration, untyped) -> void
346
+ def self.configure_gemini: (Configuration, untyped) -> void
347
+ def self.configure_mistral: (Configuration, untyped) -> void
348
+ def self.configure_deepseek: (Configuration, untyped) -> void
349
+ def self.configure_xai: (Configuration, untyped) -> void
350
+ def self.configure_huggingface: (Configuration, untyped) -> void
236
351
  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.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa
@@ -33,11 +33,13 @@ executables:
33
33
  extensions: []
34
34
  extra_rdoc_files: []
35
35
  files:
36
+ - ".dockerignore"
36
37
  - ".env.example"
37
38
  - ".rubocop.yml"
38
39
  - ".yardopts"
39
40
  - CHANGELOG.md
40
41
  - CODE_OF_CONDUCT.md
42
+ - Dockerfile
41
43
  - Dockerfile.example
42
44
  - INTEGRATION_GUIDE.md
43
45
  - LICENSE.txt
@@ -50,24 +52,33 @@ files:
50
52
  - db/init/03_create_indexes.sql
51
53
  - db/init/04_insert_sample_data.sql
52
54
  - db/migrate/001_create_prescient_tables.rb
55
+ - docker-compose.api.yml
53
56
  - docker-compose.yml
54
57
  - examples/README.md
55
58
  - examples/basic_usage.rb
56
59
  - examples/custom_contexts.rb
57
60
  - examples/custom_prompts.rb
61
+ - examples/rest_api.ru
58
62
  - examples/vector_search.rb
59
63
  - exe/prescient
60
64
  - lib/prescient.rb
65
+ - lib/prescient/api.rb
61
66
  - lib/prescient/base.rb
62
67
  - lib/prescient/cli.rb
63
68
  - lib/prescient/client.rb
69
+ - lib/prescient/configuration_loader.rb
64
70
  - lib/prescient/errors.rb
65
71
  - lib/prescient/pgvector.rb
66
72
  - lib/prescient/provider/anthropic.rb
73
+ - lib/prescient/provider/deepseek.rb
74
+ - lib/prescient/provider/gemini.rb
67
75
  - lib/prescient/provider/huggingface.rb
76
+ - lib/prescient/provider/mistral.rb
68
77
  - lib/prescient/provider/ollama.rb
69
78
  - lib/prescient/provider/openai.rb
79
+ - lib/prescient/provider/xai.rb
70
80
  - lib/prescient/version.rb
81
+ - schema/prescient.configuration.schema.json
71
82
  - scripts/setup-ollama-models.sh
72
83
  - sig/prescient.rbs
73
84
  homepage: https://kanutocd.github.io/prescient