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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/INTEGRATION_GUIDE.md +12 -1
- data/README.md +263 -14
- data/Rakefile +1 -1
- data/VECTOR_SEARCH_GUIDE.md +7 -3
- data/examples/README.md +2 -1
- data/examples/basic_usage.rb +1 -1
- data/examples/custom_contexts.rb +6 -21
- data/examples/vector_search.rb +69 -305
- data/exe/prescient +7 -0
- data/lib/prescient/cli.rb +479 -0
- data/lib/prescient/client.rb +16 -4
- data/lib/prescient/configuration_loader.rb +437 -0
- data/lib/prescient/provider/anthropic.rb +2 -2
- data/lib/prescient/provider/deepseek.rb +139 -0
- data/lib/prescient/provider/gemini.rb +173 -0
- data/lib/prescient/provider/huggingface.rb +8 -6
- data/lib/prescient/provider/mistral.rb +171 -0
- data/lib/prescient/provider/ollama.rb +3 -3
- data/lib/prescient/provider/openai.rb +10 -6
- data/lib/prescient/provider/xai.rb +139 -0
- data/lib/prescient/version.rb +1 -1
- data/lib/prescient.rb +117 -22
- data/schema/prescient.configuration.schema.json +153 -0
- data/sig/prescient.rbs +119 -2
- metadata +11 -2
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
|
|
7
|
+
# Command-line interface for common Prescient operations.
|
|
8
|
+
class Prescient::CLI
|
|
9
|
+
# Supported output formats.
|
|
10
|
+
# @return [Array<String>] Output format names
|
|
11
|
+
FORMATS = ['text', 'json'].freeze
|
|
12
|
+
|
|
13
|
+
# Schema URL and annotated starter configuration for `config example`.
|
|
14
|
+
CONFIGURATION_EXAMPLE = <<~YAML
|
|
15
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/kanutocd/prescient/refs/heads/main/schema/prescient.configuration.schema.json
|
|
16
|
+
#
|
|
17
|
+
# Prescient configuration example.
|
|
18
|
+
#
|
|
19
|
+
# Precedence, from lowest to highest:
|
|
20
|
+
# 1. Built-in defaults and provider environment variables.
|
|
21
|
+
# 2. Values in this YAML file.
|
|
22
|
+
# 3. Per-operation CLI overrides such as --provider and --chat-model.
|
|
23
|
+
#
|
|
24
|
+
# Use `prescient config validate` after editing this file.
|
|
25
|
+
# Keep credentials out of source control; use *_env references instead.
|
|
26
|
+
version: 1
|
|
27
|
+
|
|
28
|
+
# Global behavior.
|
|
29
|
+
default_provider: ollama
|
|
30
|
+
timeout: 30
|
|
31
|
+
retry_attempts: 3
|
|
32
|
+
retry_delay: 1.0
|
|
33
|
+
fallback_providers: []
|
|
34
|
+
sensitive_keys:
|
|
35
|
+
- api_key
|
|
36
|
+
- password
|
|
37
|
+
- token
|
|
38
|
+
- secret
|
|
39
|
+
|
|
40
|
+
providers:
|
|
41
|
+
# Local Ollama requires no API key.
|
|
42
|
+
ollama:
|
|
43
|
+
type: ollama
|
|
44
|
+
url: http://localhost:11434
|
|
45
|
+
embedding_model: nomic-embed-text
|
|
46
|
+
chat_model: llama3.2:3b
|
|
47
|
+
# prompt_templates:
|
|
48
|
+
# system_prompt: You are a concise assistant.
|
|
49
|
+
# no_context_template: "%<system_prompt>s\\n\\nUser: %<query>s"
|
|
50
|
+
# with_context_template: "%<system_prompt>s\\n\\nContext:\\n%<context>s\\n\\nUser: %<query>s"
|
|
51
|
+
|
|
52
|
+
# Uncomment a cloud provider and set its credential in the environment.
|
|
53
|
+
# openai:
|
|
54
|
+
# type: openai
|
|
55
|
+
# api_key_env: OPENAI_API_KEY
|
|
56
|
+
# embedding_model: text-embedding-3-small
|
|
57
|
+
# chat_model: gpt-4.1-mini
|
|
58
|
+
# prompt_templates:
|
|
59
|
+
# system_prompt: You are a concise assistant.
|
|
60
|
+
# no_context_template: "%<system_prompt>s\n\nUser: %<query>s"
|
|
61
|
+
|
|
62
|
+
# anthropic:
|
|
63
|
+
# type: anthropic
|
|
64
|
+
# api_key_env: ANTHROPIC_API_KEY
|
|
65
|
+
# model: claude-sonnet-4-20250514
|
|
66
|
+
|
|
67
|
+
# gemini:
|
|
68
|
+
# type: gemini
|
|
69
|
+
# api_key_env: GEMINI_API_KEY
|
|
70
|
+
# embedding_model: gemini-embedding-001
|
|
71
|
+
# chat_model: gemini-2.5-flash
|
|
72
|
+
|
|
73
|
+
# mistral:
|
|
74
|
+
# type: mistral
|
|
75
|
+
# api_key_env: MISTRAL_API_KEY
|
|
76
|
+
# embedding_model: mistral-embed
|
|
77
|
+
# chat_model: mistral-large-latest
|
|
78
|
+
|
|
79
|
+
# DeepSeek supports text generation, but not embeddings.
|
|
80
|
+
# deepseek:
|
|
81
|
+
# type: deepseek
|
|
82
|
+
# api_key_env: DEEPSEEK_API_KEY
|
|
83
|
+
# chat_model: deepseek-v4-flash
|
|
84
|
+
|
|
85
|
+
# xai:
|
|
86
|
+
# type: xai
|
|
87
|
+
# api_key_env: XAI_API_KEY
|
|
88
|
+
# chat_model: grok-4.5
|
|
89
|
+
|
|
90
|
+
# huggingface:
|
|
91
|
+
# type: huggingface
|
|
92
|
+
# api_key_env: HUGGINGFACE_API_KEY
|
|
93
|
+
# embedding_model: sentence-transformers/all-MiniLM-L6-v2
|
|
94
|
+
# chat_model: google/gemma-2-2b-it
|
|
95
|
+
YAML
|
|
96
|
+
|
|
97
|
+
# Raised when command-line arguments are invalid or incomplete.
|
|
98
|
+
class UsageError < StandardError; end
|
|
99
|
+
|
|
100
|
+
# Run the CLI and return a process exit status.
|
|
101
|
+
#
|
|
102
|
+
# @param arguments [Array<String>] Command-line arguments
|
|
103
|
+
# @param input [IO] Input stream used for stdin prompts
|
|
104
|
+
# @param output [IO] Output stream for command results
|
|
105
|
+
# @param errors [IO] Output stream for diagnostics
|
|
106
|
+
# @return [Integer] Process exit status
|
|
107
|
+
def self.run(arguments, input: $stdin, output: $stdout, errors: $stderr)
|
|
108
|
+
new(arguments, input:, output:, errors:).run
|
|
109
|
+
rescue UsageError, OptionParser::ParseError => e
|
|
110
|
+
errors.puts "prescient: #{e.message}"
|
|
111
|
+
2
|
|
112
|
+
rescue Prescient::Error => e
|
|
113
|
+
errors.puts "prescient: #{e.message}"
|
|
114
|
+
1
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Initialize a CLI runner with injectable streams.
|
|
118
|
+
#
|
|
119
|
+
# @param arguments [Array<String>] Command-line arguments
|
|
120
|
+
# @param input [IO] Input stream used for stdin prompts
|
|
121
|
+
# @param output [IO] Output stream for command results
|
|
122
|
+
# @param errors [IO] Output stream for diagnostics
|
|
123
|
+
def initialize(arguments, input:, output:, errors:)
|
|
124
|
+
@arguments = arguments.dup
|
|
125
|
+
@input = input
|
|
126
|
+
@output = output
|
|
127
|
+
@errors = errors
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Execute the CLI command and return its process status.
|
|
131
|
+
# @return [Integer] Process exit status
|
|
132
|
+
def run
|
|
133
|
+
config_path = extract_global_config_path
|
|
134
|
+
Prescient.load_configuration(config_path) if config_path || ENV['PRESCIENT_CONFIG']
|
|
135
|
+
|
|
136
|
+
command = @arguments.shift
|
|
137
|
+
return print_help(2) unless command
|
|
138
|
+
|
|
139
|
+
run_command(command)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Dispatch a parsed command to its handler.
|
|
143
|
+
# @param command [String] Command name
|
|
144
|
+
# @return [Integer] Process exit status
|
|
145
|
+
def run_command(command)
|
|
146
|
+
case command
|
|
147
|
+
when 'providers' then providers
|
|
148
|
+
when 'health' then health
|
|
149
|
+
when 'generate' then generate
|
|
150
|
+
when 'embed' then embed
|
|
151
|
+
when 'config' then config
|
|
152
|
+
when 'help', '--help', '-h' then print_help(0)
|
|
153
|
+
else
|
|
154
|
+
raise UsageError, "unknown command #{command.inspect}; run 'prescient help'"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
def providers
|
|
161
|
+
options = parse_options('List configured providers')
|
|
162
|
+
return options if options.is_a?(Integer)
|
|
163
|
+
|
|
164
|
+
provider_list = Prescient.configuration.providers.map { |name, registration|
|
|
165
|
+
{ name: name.to_s, class: registration[:class].name }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if options[:format] == 'json'
|
|
169
|
+
print_json(providers: provider_list)
|
|
170
|
+
else
|
|
171
|
+
provider_list.each { |provider| @output.puts "#{provider[:name]}\t#{provider[:class]}" }
|
|
172
|
+
end
|
|
173
|
+
0
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def health
|
|
177
|
+
options = parse_options('Check provider health')
|
|
178
|
+
return options if options.is_a?(Integer)
|
|
179
|
+
|
|
180
|
+
names = options[:provider] ? [options[:provider].to_sym] : Prescient.configuration.providers.keys
|
|
181
|
+
raise UsageError, 'no providers are configured' if names.empty?
|
|
182
|
+
|
|
183
|
+
results = names.to_h { |name| [name.to_s, Prescient.health_check(provider: name)] }
|
|
184
|
+
output_health(results, options[:format])
|
|
185
|
+
results.values.all? { |result| result[:reachable] != false } ? 0 : 1
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def generate
|
|
189
|
+
options = parse_options('Generate a text response', fallback: true)
|
|
190
|
+
return options if options.is_a?(Integer)
|
|
191
|
+
|
|
192
|
+
prompt = read_text(options[:arguments], 'prompt')
|
|
193
|
+
client = client_for(options)
|
|
194
|
+
response = client.generate_response(prompt, **model_options(options))
|
|
195
|
+
|
|
196
|
+
options[:format] == 'json' ? print_json(response) : @output.puts(response[:response])
|
|
197
|
+
0
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def embed
|
|
201
|
+
options = parse_options('Generate an embedding', fallback: true)
|
|
202
|
+
return options if options.is_a?(Integer)
|
|
203
|
+
|
|
204
|
+
text = read_text(options[:arguments], 'text')
|
|
205
|
+
client = client_for(options)
|
|
206
|
+
embedding = client.generate_embedding(text, **model_options(options))
|
|
207
|
+
|
|
208
|
+
if options[:format] == 'json'
|
|
209
|
+
print_json(embedding: embedding, dimensions: embedding.length, provider: client.provider_name.to_s)
|
|
210
|
+
else
|
|
211
|
+
@output.puts JSON.generate(embedding)
|
|
212
|
+
end
|
|
213
|
+
0
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def config
|
|
217
|
+
subcommand = @arguments.shift
|
|
218
|
+
case subcommand
|
|
219
|
+
when 'validate' then validate_config_command
|
|
220
|
+
when 'example' then configuration_example_command
|
|
221
|
+
else
|
|
222
|
+
raise UsageError, "unknown config command #{subcommand.inspect}"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def validate_config_command
|
|
227
|
+
options = parse_options('Validate the current configuration')
|
|
228
|
+
return options if options.is_a?(Integer)
|
|
229
|
+
|
|
230
|
+
validate_configuration
|
|
231
|
+
if options[:format] == 'json'
|
|
232
|
+
print_json(valid: true, providers: Prescient.configuration.providers.keys.map(&:to_s))
|
|
233
|
+
else
|
|
234
|
+
@output.puts 'configuration valid'
|
|
235
|
+
end
|
|
236
|
+
0
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def configuration_example_command
|
|
240
|
+
options = parse_options('Generate an annotated YAML configuration example')
|
|
241
|
+
return options if options.is_a?(Integer)
|
|
242
|
+
|
|
243
|
+
@output.write(CONFIGURATION_EXAMPLE)
|
|
244
|
+
0
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def validate_configuration
|
|
248
|
+
configuration = Prescient.configuration
|
|
249
|
+
unless configuration.provider(configuration.default_provider)
|
|
250
|
+
raise Prescient::Error, 'default provider is not configured'
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
configuration.providers.each_key { |name| configuration.provider(name) }
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def parse_options(description, fallback: false)
|
|
257
|
+
options = { format: 'text', fallback: fallback }
|
|
258
|
+
parser = OptionParser.new do |parser|
|
|
259
|
+
parser.banner = "Usage: prescient #{@arguments.first || 'command'} [options]"
|
|
260
|
+
parser.separator description
|
|
261
|
+
add_common_options(parser, options)
|
|
262
|
+
parser.on('--no-fallback', 'Disable provider fallback') { options[:fallback] = false } if fallback
|
|
263
|
+
parser.on('-h', '--help', 'Show command help') do
|
|
264
|
+
@output.puts parser
|
|
265
|
+
throw :help_shown, 0
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
result = catch(:help_shown) { parse_arguments(parser) }
|
|
270
|
+
return result unless result.nil?
|
|
271
|
+
|
|
272
|
+
options[:arguments] = @arguments
|
|
273
|
+
options
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def add_common_options(parser, options)
|
|
277
|
+
parser.on('--config PATH', 'Load configuration from a YAML file') do |value|
|
|
278
|
+
options[:config] = value
|
|
279
|
+
end
|
|
280
|
+
parser.on('--format FORMAT', FORMATS, "Output format (#{FORMATS.join(', ')})") do |value|
|
|
281
|
+
options[:format] = value
|
|
282
|
+
end
|
|
283
|
+
parser.on('--provider NAME', 'Use a specific provider') do |value|
|
|
284
|
+
options[:provider] = value
|
|
285
|
+
end
|
|
286
|
+
add_model_options(parser, options)
|
|
287
|
+
add_credential_options(parser, options)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Parse command arguments and return nil when parsing completes.
|
|
291
|
+
#
|
|
292
|
+
# @param parser [OptionParser] Configured command option parser
|
|
293
|
+
# @return [nil]
|
|
294
|
+
def parse_arguments(parser)
|
|
295
|
+
parser.parse!(@arguments)
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def model_options(options)
|
|
300
|
+
options[:model] ? { model: options[:model] } : {}
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def client_for(options)
|
|
304
|
+
validate_override_options(options)
|
|
305
|
+
Prescient.client(
|
|
306
|
+
options[:provider]&.to_sym,
|
|
307
|
+
enable_fallback: options[:fallback],
|
|
308
|
+
provider_options: provider_options(options),
|
|
309
|
+
)
|
|
310
|
+
rescue KeyError => e
|
|
311
|
+
raise UsageError, "environment variable not set: #{e.key}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def add_model_options(parser, options)
|
|
315
|
+
parser.on('--model NAME', 'Override the configured model') do |value|
|
|
316
|
+
options[:model] = value
|
|
317
|
+
end
|
|
318
|
+
parser.on('--embedding-model NAME', 'Override the embedding model') do |value|
|
|
319
|
+
options[:embedding_model] = value
|
|
320
|
+
end
|
|
321
|
+
parser.on('--chat-model NAME', 'Override the chat model') do |value|
|
|
322
|
+
options[:chat_model] = value
|
|
323
|
+
end
|
|
324
|
+
parser.on('--system-prompt TEXT', 'Override the system prompt') do |value|
|
|
325
|
+
options[:system_prompt] = value
|
|
326
|
+
end
|
|
327
|
+
parser.on('--no-context-template TEXT', 'Override the no-context prompt template') do |value|
|
|
328
|
+
options[:no_context_template] = value
|
|
329
|
+
end
|
|
330
|
+
parser.on('--with-context-template TEXT', 'Override the with-context prompt template') do |value|
|
|
331
|
+
options[:with_context_template] = value
|
|
332
|
+
end
|
|
333
|
+
parser.on('--prompt-templates-file PATH', 'Load prompt templates from a YAML file') do |value|
|
|
334
|
+
options[:prompt_templates_file] = value
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def add_credential_options(parser, options)
|
|
339
|
+
parser.on('--api-key KEY', 'Use an API key for this operation') do |value|
|
|
340
|
+
options[:api_key] = value
|
|
341
|
+
end
|
|
342
|
+
parser.on('--api-key-env NAME', 'Read the API key from this environment variable') do |value|
|
|
343
|
+
options[:api_key_env] = value
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def validate_override_options(options)
|
|
348
|
+
if options[:model] && (options[:embedding_model] || options[:chat_model])
|
|
349
|
+
raise UsageError, '--model cannot be combined with --embedding-model or --chat-model'
|
|
350
|
+
end
|
|
351
|
+
return unless options[:api_key] && options[:api_key_env]
|
|
352
|
+
|
|
353
|
+
raise UsageError, '--api-key cannot be combined with --api-key-env'
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def provider_options(options)
|
|
357
|
+
{
|
|
358
|
+
api_key: api_key_override(options),
|
|
359
|
+
embedding_model: options[:embedding_model],
|
|
360
|
+
chat_model: options[:chat_model],
|
|
361
|
+
prompt_templates: prompt_templates(options),
|
|
362
|
+
}.compact
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def prompt_templates(options)
|
|
366
|
+
templates = if options[:prompt_templates_file]
|
|
367
|
+
data = YAML.safe_load_file(
|
|
368
|
+
options[:prompt_templates_file],
|
|
369
|
+
permitted_classes: [],
|
|
370
|
+
permitted_symbols: [],
|
|
371
|
+
aliases: true,
|
|
372
|
+
)
|
|
373
|
+
raise UsageError, 'prompt templates file must contain a mapping' unless data.is_a?(Hash)
|
|
374
|
+
|
|
375
|
+
data.transform_keys(&:to_sym)
|
|
376
|
+
else
|
|
377
|
+
{}
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
[:system_prompt, :no_context_template, :with_context_template].each do |key|
|
|
381
|
+
templates[key] = options[key] if options[key]
|
|
382
|
+
end
|
|
383
|
+
templates.empty? ? nil : templates
|
|
384
|
+
rescue Errno::ENOENT
|
|
385
|
+
raise UsageError, "prompt templates file not found: #{options[:prompt_templates_file]}"
|
|
386
|
+
rescue Psych::SyntaxError => e
|
|
387
|
+
raise UsageError, "invalid prompt templates YAML: #{e.message}"
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def api_key_override(options)
|
|
391
|
+
return options[:api_key] if options[:api_key]
|
|
392
|
+
return ENV.fetch(options[:api_key_env]) if options[:api_key_env]
|
|
393
|
+
|
|
394
|
+
nil
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def output_health(results, format)
|
|
398
|
+
if format == 'json'
|
|
399
|
+
print_json(results)
|
|
400
|
+
else
|
|
401
|
+
results.each do |name, result|
|
|
402
|
+
@output.puts '%<name>-12s %<status>s' % { name: name, status: result[:status] || 'unknown' }
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def read_text(arguments, label)
|
|
408
|
+
return arguments.join(' ') unless arguments.empty?
|
|
409
|
+
return @input.read unless @input.tty?
|
|
410
|
+
|
|
411
|
+
raise UsageError, "missing #{label}; provide it as an argument or through stdin"
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def print_json(value)
|
|
415
|
+
@output.puts JSON.generate(value)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def print_help(status)
|
|
419
|
+
@output.puts <<~HELP
|
|
420
|
+
Usage: prescient COMMAND [options]
|
|
421
|
+
|
|
422
|
+
Commands:
|
|
423
|
+
providers List configured providers
|
|
424
|
+
health Check provider health
|
|
425
|
+
generate TEXT Generate a text response
|
|
426
|
+
embed TEXT Generate an embedding
|
|
427
|
+
config validate Validate the current configuration
|
|
428
|
+
config example Generate an annotated YAML configuration example
|
|
429
|
+
|
|
430
|
+
Options:
|
|
431
|
+
--config PATH Load configuration from a YAML file
|
|
432
|
+
--provider NAME Select a provider
|
|
433
|
+
--model NAME Override the selected operation's model
|
|
434
|
+
--chat-model NAME Override the chat model
|
|
435
|
+
--embedding-model NAME Override the embedding model
|
|
436
|
+
--system-prompt TEXT Override the system prompt
|
|
437
|
+
--no-context-template TEXT
|
|
438
|
+
Override the no-context prompt template
|
|
439
|
+
--with-context-template TEXT
|
|
440
|
+
Override the with-context prompt template
|
|
441
|
+
--prompt-templates-file PATH
|
|
442
|
+
Load prompt templates from a YAML file
|
|
443
|
+
--api-key KEY Use an API key for the operation
|
|
444
|
+
--api-key-env NAME Read the API key from an environment variable
|
|
445
|
+
--format FORMAT Use text or json output
|
|
446
|
+
HELP
|
|
447
|
+
status
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def extract_global_config_path
|
|
451
|
+
config_path = nil
|
|
452
|
+
filtered_arguments = []
|
|
453
|
+
index = 0
|
|
454
|
+
|
|
455
|
+
while index < @arguments.length
|
|
456
|
+
argument = @arguments[index]
|
|
457
|
+
if argument == '--config'
|
|
458
|
+
value = @arguments[index + 1]
|
|
459
|
+
raise UsageError, '--config requires a path' unless value
|
|
460
|
+
|
|
461
|
+
config_path = value
|
|
462
|
+
index += 2
|
|
463
|
+
next
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
if argument.start_with?('--config=')
|
|
467
|
+
config_path = argument.split('=', 2).last
|
|
468
|
+
index += 1
|
|
469
|
+
next
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
filtered_arguments << argument
|
|
473
|
+
index += 1
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
@arguments = filtered_arguments
|
|
477
|
+
config_path
|
|
478
|
+
end
|
|
479
|
+
end
|
data/lib/prescient/client.rb
CHANGED
|
@@ -27,10 +27,11 @@ module Prescient
|
|
|
27
27
|
#
|
|
28
28
|
# @param provider_name [Symbol, nil] Name of provider to use, or nil for default
|
|
29
29
|
# @param enable_fallback [Boolean] Whether to enable automatic fallback to other providers
|
|
30
|
+
# @param provider_options [Hash] Temporary options for the selected provider
|
|
30
31
|
# @raise [Prescient::Error] If the specified provider is not configured
|
|
31
|
-
def initialize(provider_name = nil, enable_fallback: true)
|
|
32
|
+
def initialize(provider_name = nil, enable_fallback: true, provider_options: {})
|
|
32
33
|
@provider_name = provider_name || Prescient.configuration.default_provider
|
|
33
|
-
@provider =
|
|
34
|
+
@provider = provider_with_options(@provider_name, provider_options)
|
|
34
35
|
@enable_fallback = enable_fallback
|
|
35
36
|
|
|
36
37
|
raise Prescient::Error, "Provider not configured: #{@provider_name}" unless @provider
|
|
@@ -131,6 +132,16 @@ module Prescient
|
|
|
131
132
|
end
|
|
132
133
|
end
|
|
133
134
|
|
|
135
|
+
def provider_with_options(provider_name, provider_options)
|
|
136
|
+
return Prescient.configuration.provider(provider_name) if provider_options.empty?
|
|
137
|
+
|
|
138
|
+
registration = Prescient.configuration.providers[provider_name.to_sym]
|
|
139
|
+
return unless registration
|
|
140
|
+
|
|
141
|
+
registration_options = registration[:options] # : Hash[Symbol, untyped]
|
|
142
|
+
registration[:class].new(**registration_options, **provider_options)
|
|
143
|
+
end
|
|
144
|
+
|
|
134
145
|
def with_error_handling
|
|
135
146
|
retries = 0
|
|
136
147
|
begin
|
|
@@ -208,9 +219,10 @@ module Prescient
|
|
|
208
219
|
#
|
|
209
220
|
# @param provider_name [Symbol, nil] Provider to use, or the configured default
|
|
210
221
|
# @param enable_fallback [Boolean] Whether provider fallback is enabled
|
|
222
|
+
# @param provider_options [Hash] Temporary options for the selected provider
|
|
211
223
|
# @return [Client] A configured client instance
|
|
212
|
-
def self.client(provider_name = nil, enable_fallback: true)
|
|
213
|
-
Client.new(provider_name, enable_fallback: enable_fallback)
|
|
224
|
+
def self.client(provider_name = nil, enable_fallback: true, provider_options: {})
|
|
225
|
+
Client.new(provider_name, enable_fallback: enable_fallback, provider_options: provider_options)
|
|
214
226
|
end
|
|
215
227
|
|
|
216
228
|
# Generate an embedding through a configured provider.
|