prescient 0.7.0 → 0.8.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/.rubocop.yml +21 -268
- data/CHANGELOG.md +37 -0
- data/INTEGRATION_GUIDE.md +7 -1
- data/README.md +210 -1
- data/Steepfile +12 -12
- data/db/migrate/001_create_prescient_tables.rb +15 -16
- data/examples/README.md +2 -1
- data/examples/custom_contexts.rb +4 -4
- data/exe/prescient +2 -2
- data/exe/prescient-mcp +7 -0
- data/lib/prescient/agent/audit_log.rb +37 -0
- data/lib/prescient/agent/cli_adapter.rb +29 -0
- data/lib/prescient/agent/configuration.rb +57 -0
- data/lib/prescient/agent/context.rb +56 -0
- data/lib/prescient/agent/error_serializer.rb +47 -0
- data/lib/prescient/agent/errors.rb +25 -0
- data/lib/prescient/agent/parser.rb +49 -0
- data/lib/prescient/agent/prompt_builder.rb +31 -0
- data/lib/prescient/agent/result.rb +36 -0
- data/lib/prescient/agent/runtime.rb +175 -0
- data/lib/prescient/agent/schema_validator.rb +215 -0
- data/lib/prescient/agent/tool_registry.rb +89 -0
- data/lib/prescient/agent.rb +22 -0
- data/lib/prescient/api.rb +337 -274
- data/lib/prescient/base.rb +370 -372
- data/lib/prescient/cli.rb +586 -526
- data/lib/prescient/client.rb +7 -6
- data/lib/prescient/configuration_loader.rb +492 -488
- data/lib/prescient/document_source.rb +114 -0
- data/lib/prescient/errors.rb +1 -3
- data/lib/prescient/mcp/authentication.rb +39 -0
- data/lib/prescient/mcp/configuration.rb +38 -0
- data/lib/prescient/mcp/rack.rb +243 -0
- data/lib/prescient/mcp/server.rb +202 -0
- data/lib/prescient/mcp/stdio.rb +42 -0
- data/lib/prescient/mcp.rb +8 -0
- data/lib/prescient/pgvector.rb +193 -189
- data/lib/prescient/provider/anthropic.rb +129 -125
- data/lib/prescient/provider/deepseek.rb +122 -118
- data/lib/prescient/provider/gemini.rb +153 -149
- data/lib/prescient/provider/huggingface.rb +191 -187
- data/lib/prescient/provider/mistral.rb +151 -147
- data/lib/prescient/provider/ollama.rb +168 -165
- data/lib/prescient/provider/openai.rb +174 -169
- data/lib/prescient/provider/xai.rb +122 -118
- data/lib/prescient/tool/search_api.rb +125 -121
- data/lib/prescient/tool/searxng.rb +123 -119
- data/lib/prescient/tool.rb +100 -98
- data/lib/prescient/version.rb +1 -1
- data/lib/prescient.rb +68 -62
- data/sig/prescient.rbs +176 -1
- metadata +23 -1
|
@@ -1,608 +1,612 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Prescient
|
|
7
|
+
# Load and validate Prescient configuration data from YAML.
|
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
|
9
|
+
class ConfigurationLoader
|
|
10
|
+
# Supported YAML configuration schema version.
|
|
11
|
+
CONFIGURATION_VERSION = 1
|
|
12
|
+
|
|
13
|
+
# Allowed top-level configuration keys.
|
|
14
|
+
TOP_LEVEL_KEYS = [
|
|
15
|
+
"$schema",
|
|
16
|
+
"default_provider",
|
|
17
|
+
"default_provider_env",
|
|
18
|
+
"fallback_providers",
|
|
19
|
+
"fallback_providers_env",
|
|
20
|
+
"providers",
|
|
21
|
+
"tools",
|
|
22
|
+
"retry_attempts",
|
|
23
|
+
"retry_attempts_env",
|
|
24
|
+
"retry_delay",
|
|
25
|
+
"retry_delay_env",
|
|
26
|
+
"sensitive_keys",
|
|
27
|
+
"sensitive_keys_env",
|
|
28
|
+
"timeout",
|
|
29
|
+
"timeout_env",
|
|
30
|
+
"version"
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
# Provider names mapped to their adapter classes.
|
|
34
|
+
PROVIDER_TYPES = {
|
|
35
|
+
"ollama" => Prescient::Provider::Ollama,
|
|
36
|
+
"anthropic" => Prescient::Provider::Anthropic,
|
|
37
|
+
"openai" => Prescient::Provider::OpenAI,
|
|
38
|
+
"huggingface" => Prescient::Provider::HuggingFace,
|
|
39
|
+
"gemini" => Prescient::Provider::Gemini,
|
|
40
|
+
"mistral" => Prescient::Provider::Mistral,
|
|
41
|
+
"deepseek" => Prescient::Provider::DeepSeek,
|
|
42
|
+
"xai" => Prescient::Provider::XAI
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
# Tool names mapped to lazily resolved adapter constants.
|
|
46
|
+
TOOL_TYPES = {
|
|
47
|
+
"searchapi" => :SearchApi,
|
|
48
|
+
"searxng" => :SearXNG
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
51
|
+
# Provider-specific keys shared by all supported adapters.
|
|
52
|
+
COMMON_PROVIDER_KEYS = %w[
|
|
53
|
+
api_key
|
|
54
|
+
api_key_env
|
|
55
|
+
chat_model
|
|
56
|
+
chat_model_env
|
|
57
|
+
context_configs
|
|
58
|
+
context_configs_env
|
|
59
|
+
embedding_dimensions
|
|
60
|
+
embedding_dimensions_env
|
|
61
|
+
embedding_model
|
|
62
|
+
embedding_model_env
|
|
63
|
+
model
|
|
64
|
+
model_env
|
|
65
|
+
prompt_templates
|
|
66
|
+
prompt_templates_env
|
|
67
|
+
timeout
|
|
68
|
+
timeout_env
|
|
69
|
+
url
|
|
70
|
+
url_env
|
|
71
|
+
].freeze
|
|
72
|
+
|
|
73
|
+
# Tool-specific keys accepted by all configured adapters.
|
|
74
|
+
COMMON_TOOL_KEYS = %w[
|
|
75
|
+
api_key
|
|
76
|
+
api_key_env
|
|
77
|
+
categories
|
|
78
|
+
categories_env
|
|
79
|
+
engine
|
|
80
|
+
engine_env
|
|
81
|
+
gl
|
|
82
|
+
gl_env
|
|
83
|
+
hl
|
|
84
|
+
hl_env
|
|
85
|
+
language
|
|
86
|
+
language_env
|
|
87
|
+
location
|
|
88
|
+
location_env
|
|
89
|
+
max_response_bytes
|
|
90
|
+
max_response_bytes_env
|
|
91
|
+
max_results
|
|
92
|
+
max_results_env
|
|
93
|
+
timeout
|
|
94
|
+
timeout_env
|
|
95
|
+
url
|
|
96
|
+
url_env
|
|
97
|
+
].freeze
|
|
98
|
+
|
|
99
|
+
# Prompt template keys supported by provider configuration.
|
|
100
|
+
PROMPT_TEMPLATE_KEYS = %w[system_prompt no_context_template with_context_template].freeze
|
|
101
|
+
|
|
102
|
+
# Configuration attributes supported by the loader.
|
|
103
|
+
ATTR_KEYS = %w[
|
|
104
|
+
default_provider
|
|
105
|
+
fallback_providers
|
|
106
|
+
retry_attempts
|
|
107
|
+
retry_delay
|
|
108
|
+
sensitive_keys
|
|
109
|
+
timeout
|
|
110
|
+
].freeze
|
|
111
|
+
|
|
112
|
+
class << self
|
|
113
|
+
# Load and validate configuration from a YAML file.
|
|
114
|
+
# @param path [String] Configuration file path
|
|
115
|
+
# @param env [Hash] Environment variables used during expansion
|
|
116
|
+
# @return [Prescient::Configuration] Loaded configuration
|
|
117
|
+
def load_file(path, env: ENV)
|
|
118
|
+
new(env).load_file(path)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Load and validate configuration from YAML content.
|
|
122
|
+
# @param content [String] YAML configuration content
|
|
123
|
+
# @param env [Hash] Environment variables used during expansion
|
|
124
|
+
# @return [Prescient::Configuration] Loaded configuration
|
|
125
|
+
def load_yaml(content, env: ENV)
|
|
126
|
+
new(env).load_yaml(content)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Load and validate configuration from a Ruby hash.
|
|
130
|
+
# @param data [Hash] Configuration data
|
|
131
|
+
# @param env [Hash] Environment variables used during expansion
|
|
132
|
+
# @return [Prescient::Configuration] Loaded configuration
|
|
133
|
+
def load_hash(data, env: ENV)
|
|
134
|
+
new(env).load_hash(data)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def initialize(env = ENV)
|
|
139
|
+
@env = env
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Load configuration from a YAML file.
|
|
112
143
|
# @param path [String] Configuration file path
|
|
113
|
-
# @param env [Hash] Environment variables used during expansion
|
|
114
144
|
# @return [Prescient::Configuration] Loaded configuration
|
|
115
|
-
def load_file(path
|
|
116
|
-
|
|
145
|
+
def load_file(path)
|
|
146
|
+
load_yaml(File.read(path), source: path)
|
|
147
|
+
rescue Errno::ENOENT
|
|
148
|
+
raise Prescient::Error, "Configuration file not found: #{path}"
|
|
117
149
|
end
|
|
118
150
|
|
|
119
|
-
# Load
|
|
151
|
+
# Load configuration from YAML content.
|
|
120
152
|
# @param content [String] YAML configuration content
|
|
121
|
-
# @param
|
|
153
|
+
# @param source [String, nil] Source label used in validation errors
|
|
122
154
|
# @return [Prescient::Configuration] Loaded configuration
|
|
123
|
-
def load_yaml(content,
|
|
124
|
-
|
|
155
|
+
def load_yaml(content, source: nil)
|
|
156
|
+
data = YAML.safe_load(content, permitted_classes: [], permitted_symbols: [], aliases: true)
|
|
157
|
+
load_hash(data || {}, source:)
|
|
158
|
+
rescue Psych::SyntaxError => e
|
|
159
|
+
raise Prescient::Error, "Invalid YAML configuration#{" in #{source}" if source}: #{e.message}"
|
|
125
160
|
end
|
|
126
161
|
|
|
127
|
-
# Load
|
|
162
|
+
# Load configuration from a Ruby hash.
|
|
128
163
|
# @param data [Hash] Configuration data
|
|
129
|
-
# @param
|
|
164
|
+
# @param source [String, nil] Source label used in validation errors
|
|
130
165
|
# @return [Prescient::Configuration] Loaded configuration
|
|
131
|
-
def load_hash(data,
|
|
132
|
-
new
|
|
166
|
+
def load_hash(data, source: nil)
|
|
167
|
+
configuration = Prescient::Configuration.new
|
|
168
|
+
Prescient.send(:configure_default_providers, configuration, @env)
|
|
169
|
+
Prescient.send(:configure_default_tools, configuration, @env)
|
|
170
|
+
apply!(configuration, data, source:)
|
|
171
|
+
configuration
|
|
133
172
|
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def initialize(env = ENV)
|
|
137
|
-
@env = env
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Load configuration from a YAML file.
|
|
141
|
-
# @param path [String] Configuration file path
|
|
142
|
-
# @return [Prescient::Configuration] Loaded configuration
|
|
143
|
-
def load_file(path)
|
|
144
|
-
load_yaml(File.read(path), source: path)
|
|
145
|
-
rescue Errno::ENOENT
|
|
146
|
-
raise Prescient::Error, "Configuration file not found: #{path}"
|
|
147
|
-
end
|
|
148
173
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
# Apply validated configuration data to an existing configuration object.
|
|
175
|
+
# @param configuration [Prescient::Configuration] Target configuration
|
|
176
|
+
# @param data [Hash] Configuration data
|
|
177
|
+
# @param source [String, nil] Source label used in validation errors
|
|
178
|
+
# @return [Prescient::Configuration] Updated configuration
|
|
179
|
+
def apply!(configuration, data, source: nil)
|
|
180
|
+
normalized = normalize_keys(data)
|
|
181
|
+
validate_root!(normalized, source:)
|
|
182
|
+
validate_version!(normalized, source:)
|
|
183
|
+
|
|
184
|
+
apply_scalar_settings(configuration, normalized, source:)
|
|
185
|
+
apply_provider_settings(configuration, normalized, source:)
|
|
186
|
+
apply_tool_settings(configuration, normalized, source:)
|
|
187
|
+
configuration
|
|
188
|
+
end
|
|
159
189
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
configuration = Prescient::Configuration.new
|
|
166
|
-
Prescient.send(:configure_default_providers, configuration, @env)
|
|
167
|
-
Prescient.send(:configure_default_tools, configuration, @env)
|
|
168
|
-
apply!(configuration, data, source:)
|
|
169
|
-
configuration
|
|
170
|
-
end
|
|
190
|
+
# Return the packaged JSON Schema path.
|
|
191
|
+
# @return [String] Absolute path to the configuration schema
|
|
192
|
+
def self.schema_path
|
|
193
|
+
File.expand_path("../../schema/prescient.configuration.schema.json", __dir__)
|
|
194
|
+
end
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
# @param configuration [Prescient::Configuration] Target configuration
|
|
174
|
-
# @param data [Hash] Configuration data
|
|
175
|
-
# @param source [String, nil] Source label used in validation errors
|
|
176
|
-
# @return [Prescient::Configuration] Updated configuration
|
|
177
|
-
def apply!(configuration, data, source: nil)
|
|
178
|
-
normalized = normalize_keys(data)
|
|
179
|
-
validate_root!(normalized, source:)
|
|
180
|
-
validate_version!(normalized, source:)
|
|
181
|
-
|
|
182
|
-
apply_scalar_settings(configuration, normalized, source:)
|
|
183
|
-
apply_provider_settings(configuration, normalized, source:)
|
|
184
|
-
apply_tool_settings(configuration, normalized, source:)
|
|
185
|
-
configuration
|
|
186
|
-
end
|
|
196
|
+
private
|
|
187
197
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
end
|
|
198
|
+
def validate_root!(data, source:)
|
|
199
|
+
unless data.is_a?(Hash)
|
|
200
|
+
raise Prescient::Error, "Configuration#{" in #{source}" if source} must be a mapping"
|
|
201
|
+
end
|
|
193
202
|
|
|
194
|
-
|
|
203
|
+
unknown_keys = data.keys.map(&:to_s) - TOP_LEVEL_KEYS
|
|
204
|
+
return if unknown_keys.empty?
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
raise Prescient::Error,
|
|
207
|
+
"Unknown configuration key#{"s" if unknown_keys.length > 1}: #{unknown_keys.join(", ")}" \
|
|
208
|
+
"#{" in #{source}" if source}"
|
|
199
209
|
end
|
|
200
210
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
"#{" in #{source}" if source}"
|
|
207
|
-
end
|
|
211
|
+
def apply_scalar_settings(configuration, data, source:)
|
|
212
|
+
apply_default_provider(configuration, data, source:)
|
|
213
|
+
apply_numeric_settings(configuration, data, source:)
|
|
214
|
+
apply_collection_settings(configuration, data, source:)
|
|
215
|
+
end
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
apply_numeric_settings(configuration, data, source:)
|
|
212
|
-
apply_collection_settings(configuration, data, source:)
|
|
213
|
-
end
|
|
217
|
+
def apply_default_provider(configuration, data, source:)
|
|
218
|
+
return unless scalar_present?(data, :default_provider)
|
|
214
219
|
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
default_provider = resolve_scalar(data, :default_provider, source:)
|
|
221
|
+
if default_provider.nil? || default_provider.to_s.empty?
|
|
222
|
+
raise Prescient::Error, "default_provider must be a non-empty string"
|
|
223
|
+
end
|
|
217
224
|
|
|
218
|
-
|
|
219
|
-
if default_provider.nil? || default_provider.to_s.empty?
|
|
220
|
-
raise Prescient::Error, 'default_provider must be a non-empty string'
|
|
225
|
+
configuration.default_provider = default_provider.to_sym
|
|
221
226
|
end
|
|
222
227
|
|
|
223
|
-
configuration
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
retry_attempts: [:coerce_integer, 'retry_attempts'],
|
|
230
|
-
retry_delay: [:coerce_float, 'retry_delay'],
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
numeric_settings.each do |key, (coercer, name)|
|
|
234
|
-
next unless scalar_present?(data, key)
|
|
228
|
+
def apply_numeric_settings(configuration, data, source:)
|
|
229
|
+
numeric_settings = {
|
|
230
|
+
timeout: [:coerce_integer, "timeout"],
|
|
231
|
+
retry_attempts: [:coerce_integer, "retry_attempts"],
|
|
232
|
+
retry_delay: [:coerce_float, "retry_delay"]
|
|
233
|
+
}
|
|
235
234
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
configuration.public_send(setter, send(coercer, value, name))
|
|
239
|
-
end
|
|
240
|
-
end
|
|
235
|
+
numeric_settings.each do |key, (coercer, name)|
|
|
236
|
+
next unless scalar_present?(data, key)
|
|
241
237
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
238
|
+
value = resolve_scalar(data, key, source:)
|
|
239
|
+
setter = "#{key}="
|
|
240
|
+
configuration.public_send(setter, send(coercer, value, name))
|
|
241
|
+
end
|
|
246
242
|
end
|
|
247
243
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
244
|
+
def apply_collection_settings(configuration, data, source:)
|
|
245
|
+
if scalar_present?(data, :fallback_providers)
|
|
246
|
+
value = resolve_scalar(data, :fallback_providers, source:)
|
|
247
|
+
configuration.fallback_providers = Array(value).map(&:to_sym)
|
|
248
|
+
end
|
|
252
249
|
|
|
253
|
-
|
|
254
|
-
return unless key_present?(data, :providers)
|
|
250
|
+
return unless scalar_present?(data, :sensitive_keys)
|
|
255
251
|
|
|
256
|
-
|
|
257
|
-
unless providers.is_a?(Hash)
|
|
258
|
-
raise Prescient::Error, "Configuration#{" in #{source}" if source} providers must be a mapping"
|
|
252
|
+
configuration.sensitive_keys = Array(resolve_scalar(data, :sensitive_keys, source:))
|
|
259
253
|
end
|
|
260
254
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
provider_settings = normalize_keys(provider_data)
|
|
264
|
-
validate_provider!(provider_name, provider_settings, source:)
|
|
255
|
+
def apply_provider_settings(configuration, data, source:)
|
|
256
|
+
return unless key_present?(data, :providers)
|
|
265
257
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
end
|
|
258
|
+
providers = data[:providers]
|
|
259
|
+
unless providers.is_a?(Hash)
|
|
260
|
+
raise Prescient::Error, "Configuration#{" in #{source}" if source} providers must be a mapping"
|
|
261
|
+
end
|
|
271
262
|
|
|
272
|
-
|
|
273
|
-
|
|
263
|
+
providers.each do |name, provider_data|
|
|
264
|
+
provider_name = name.to_sym
|
|
265
|
+
provider_settings = normalize_keys(provider_data)
|
|
266
|
+
validate_provider!(provider_name, provider_settings, source:)
|
|
274
267
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
268
|
+
provider_options = resolve_provider_options(provider_settings, source:)
|
|
269
|
+
configuration.add_provider(provider_name, PROVIDER_TYPES[provider_settings.fetch(:type).to_s],
|
|
270
|
+
**provider_options)
|
|
271
|
+
end
|
|
278
272
|
end
|
|
279
273
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
tool_settings = normalize_keys(tool_data)
|
|
283
|
-
validate_tool!(tool_name, tool_settings, source:)
|
|
274
|
+
def apply_tool_settings(configuration, data, source:)
|
|
275
|
+
return unless key_present?(data, :tools)
|
|
284
276
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
tool_options = resolve_tool_options(tool_settings, source:)
|
|
289
|
-
configuration.add_tool(tool_name, tool_class_for(tool_settings.fetch(:type).to_s), **tool_options)
|
|
277
|
+
tools = data[:tools]
|
|
278
|
+
unless tools.is_a?(Hash)
|
|
279
|
+
raise Prescient::Error, "Configuration#{" in #{source}" if source} tools must be a mapping"
|
|
290
280
|
end
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
281
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
validate_prompt_templates!(name, provider_data, source:)
|
|
299
|
-
end
|
|
282
|
+
tools.each do |name, tool_data|
|
|
283
|
+
tool_name = name.to_sym
|
|
284
|
+
tool_settings = normalize_keys(tool_data)
|
|
285
|
+
validate_tool!(tool_name, tool_settings, source:)
|
|
300
286
|
|
|
301
|
-
|
|
302
|
-
|
|
287
|
+
if tool_settings.key?(:adapters)
|
|
288
|
+
configuration.add_tool_group(tool_name, resolve_tool_adapters(tool_settings, source:))
|
|
289
|
+
else
|
|
290
|
+
tool_options = resolve_tool_options(tool_settings, source:)
|
|
291
|
+
configuration.add_tool(tool_name, tool_class_for(tool_settings.fetch(:type).to_s), **tool_options)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
303
295
|
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
def validate_provider!(name, provider_data, source:)
|
|
297
|
+
validate_provider_shape!(name, provider_data, source:)
|
|
298
|
+
validate_provider_type!(name, provider_data, source:)
|
|
299
|
+
validate_provider_keys!(name, provider_data, source:)
|
|
300
|
+
validate_prompt_templates!(name, provider_data, source:)
|
|
301
|
+
end
|
|
306
302
|
|
|
307
|
-
|
|
308
|
-
|
|
303
|
+
def validate_provider_shape!(name, provider_data, source:)
|
|
304
|
+
return if provider_data.is_a?(Hash)
|
|
309
305
|
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
raise Prescient::Error, "Provider #{name.inspect}#{" in #{source}" if source} must be a mapping"
|
|
307
|
+
end
|
|
312
308
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
return if unknown_keys.empty?
|
|
309
|
+
def validate_provider_type!(name, provider_data, source:)
|
|
310
|
+
return if provider_data.key?(:type)
|
|
316
311
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
"#{unknown_keys.join(', ')}" \
|
|
320
|
-
"#{" in #{source}" if source}"
|
|
321
|
-
end
|
|
312
|
+
raise Prescient::Error, "Provider #{name}#{" in #{source}" if source} must define type"
|
|
313
|
+
end
|
|
322
314
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
315
|
+
def validate_provider_keys!(name, provider_data, source:)
|
|
316
|
+
unknown_keys = provider_data.keys.map(&:to_s) - (["type"] + COMMON_PROVIDER_KEYS)
|
|
317
|
+
return if unknown_keys.empty?
|
|
326
318
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
319
|
+
raise Prescient::Error,
|
|
320
|
+
"Unknown provider configuration key#{"s" if unknown_keys.length > 1} for #{name}: " \
|
|
321
|
+
"#{unknown_keys.join(", ")}" \
|
|
322
|
+
"#{" in #{source}" if source}"
|
|
330
323
|
end
|
|
331
324
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
message = "Unknown prompt template key#{'s' if unknown_keys.length > 1} for #{name}: " \
|
|
336
|
-
"#{unknown_keys.join(', ')}#{source_suffix}"
|
|
337
|
-
raise Prescient::Error, message
|
|
338
|
-
end
|
|
325
|
+
def validate_prompt_templates!(name, provider_data, source:)
|
|
326
|
+
templates = provider_data[:prompt_templates]
|
|
327
|
+
return if templates.nil?
|
|
339
328
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
329
|
+
source_suffix = " in #{source}" if source
|
|
330
|
+
unless templates.is_a?(Hash)
|
|
331
|
+
raise Prescient::Error, "prompt_templates for #{name} must be a mapping#{source_suffix}"
|
|
332
|
+
end
|
|
343
333
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
end
|
|
334
|
+
unknown_keys = templates.keys.map(&:to_s) - PROMPT_TEMPLATE_KEYS
|
|
335
|
+
return if unknown_keys.empty?
|
|
347
336
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
source_suffix = " in #{source}" if source
|
|
352
|
-
raise Prescient::Error,
|
|
353
|
-
"Unknown tool group configuration key#{'s' if unknown_keys.length > 1} for #{name}: " \
|
|
354
|
-
"#{unknown_keys.join(', ')}#{source_suffix}"
|
|
337
|
+
message = "Unknown prompt template key#{"s" if unknown_keys.length > 1} for #{name}: " \
|
|
338
|
+
"#{unknown_keys.join(", ")}#{source_suffix}"
|
|
339
|
+
raise Prescient::Error, message
|
|
355
340
|
end
|
|
356
341
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
end
|
|
342
|
+
def validate_tool!(name, tool_data, source:)
|
|
343
|
+
validate_tool_shape!(name, tool_data, source:)
|
|
344
|
+
return validate_tool_group!(name, tool_data, source:) if tool_data.key?(:adapters)
|
|
361
345
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
validate_tool_shape!(adapter_name, adapter_data, source:)
|
|
365
|
-
validate_tool_type!(adapter_name, adapter_data, source:)
|
|
366
|
-
validate_tool_keys!(adapter_name, adapter_data, source:)
|
|
346
|
+
validate_tool_type!(name, tool_data, source:)
|
|
347
|
+
validate_tool_keys!(name, tool_data, source:)
|
|
367
348
|
end
|
|
368
|
-
end
|
|
369
349
|
|
|
370
|
-
|
|
371
|
-
|
|
350
|
+
def validate_tool_group!(name, tool_data, source:)
|
|
351
|
+
unknown_keys = tool_data.keys.map(&:to_s) - ["adapters"]
|
|
352
|
+
unless unknown_keys.empty?
|
|
353
|
+
source_suffix = " in #{source}" if source
|
|
354
|
+
raise Prescient::Error,
|
|
355
|
+
"Unknown tool group configuration key#{"s" if unknown_keys.length > 1} for #{name}: " \
|
|
356
|
+
"#{unknown_keys.join(", ")}#{source_suffix}"
|
|
357
|
+
end
|
|
372
358
|
|
|
373
|
-
|
|
374
|
-
|
|
359
|
+
adapters = tool_data[:adapters]
|
|
360
|
+
unless adapters.is_a?(Array) && adapters.any?
|
|
361
|
+
raise Prescient::Error, "Tool #{name} adapters must be a non-empty array"
|
|
362
|
+
end
|
|
375
363
|
|
|
376
|
-
|
|
377
|
-
|
|
364
|
+
adapters.each_with_index do |adapter_data, index|
|
|
365
|
+
adapter_name = "#{name} adapter #{index + 1}"
|
|
366
|
+
validate_tool_shape!(adapter_name, adapter_data, source:)
|
|
367
|
+
validate_tool_type!(adapter_name, adapter_data, source:)
|
|
368
|
+
validate_tool_keys!(adapter_name, adapter_data, source:)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
378
371
|
|
|
379
|
-
|
|
380
|
-
|
|
372
|
+
def validate_tool_shape!(name, tool_data, source:)
|
|
373
|
+
return if tool_data.is_a?(Hash)
|
|
381
374
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return if unknown_keys.empty?
|
|
375
|
+
raise Prescient::Error, "Tool #{name.inspect}#{" in #{source}" if source} must be a mapping"
|
|
376
|
+
end
|
|
385
377
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
"#{unknown_keys.join(', ')}" \
|
|
389
|
-
"#{" in #{source}" if source}"
|
|
390
|
-
end
|
|
378
|
+
def validate_tool_type!(name, tool_data, source:)
|
|
379
|
+
return if tool_data.key?(:type)
|
|
391
380
|
|
|
392
|
-
|
|
393
|
-
provider_type = provider_data[:type].to_s
|
|
394
|
-
provider_class = PROVIDER_TYPES[provider_type]
|
|
395
|
-
unless provider_class
|
|
396
|
-
raise Prescient::Error,
|
|
397
|
-
"Unknown provider type #{provider_type.inspect}#{" in #{source}" if source}"
|
|
381
|
+
raise Prescient::Error, "Tool #{name}#{" in #{source}" if source} must define type"
|
|
398
382
|
end
|
|
399
383
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
end
|
|
404
|
-
end
|
|
384
|
+
def validate_tool_keys!(name, tool_data, source:)
|
|
385
|
+
unknown_keys = tool_data.keys.map(&:to_s) - (["type"] + COMMON_TOOL_KEYS)
|
|
386
|
+
return if unknown_keys.empty?
|
|
405
387
|
|
|
406
|
-
def resolve_tool_options(tool_data, source:)
|
|
407
|
-
tool_type = tool_data[:type].to_s
|
|
408
|
-
tool_class = tool_class_for(tool_type)
|
|
409
|
-
unless tool_class
|
|
410
388
|
raise Prescient::Error,
|
|
411
|
-
"Unknown tool
|
|
389
|
+
"Unknown tool configuration key#{"s" if unknown_keys.length > 1} for #{name}: " \
|
|
390
|
+
"#{unknown_keys.join(", ")}" \
|
|
391
|
+
"#{" in #{source}" if source}"
|
|
412
392
|
end
|
|
413
393
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
394
|
+
def resolve_provider_options(provider_data, source:)
|
|
395
|
+
provider_type = provider_data[:type].to_s
|
|
396
|
+
provider_class = PROVIDER_TYPES[provider_type]
|
|
397
|
+
unless provider_class
|
|
398
|
+
raise Prescient::Error,
|
|
399
|
+
"Unknown provider type #{provider_type.inspect}#{" in #{source}" if source}"
|
|
400
|
+
end
|
|
419
401
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
class: tool_class_for(tool_type),
|
|
425
|
-
options: resolve_tool_options(adapter_data, source:),
|
|
426
|
-
}
|
|
402
|
+
provider_data.each_with_object({}) do |(key, value), options|
|
|
403
|
+
option = resolve_provider_option(provider_data, key, value, source:)
|
|
404
|
+
options[option.first] = option.last if option
|
|
405
|
+
end
|
|
427
406
|
end
|
|
428
|
-
end
|
|
429
407
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
Prescient::Tool.const_get(tool_name, false)
|
|
438
|
-
end
|
|
408
|
+
def resolve_tool_options(tool_data, source:)
|
|
409
|
+
tool_type = tool_data[:type].to_s
|
|
410
|
+
tool_class = tool_class_for(tool_type)
|
|
411
|
+
unless tool_class
|
|
412
|
+
raise Prescient::Error,
|
|
413
|
+
"Unknown tool type #{tool_type.inspect}#{" in #{source}" if source}"
|
|
414
|
+
end
|
|
439
415
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
416
|
+
tool_data.each_with_object({}) do |(key, value), options|
|
|
417
|
+
option = resolve_tool_option(tool_data, key, value, source:)
|
|
418
|
+
options[option.first] = option.last if option
|
|
419
|
+
end
|
|
420
|
+
end
|
|
443
421
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
422
|
+
def resolve_tool_adapters(tool_data, source:)
|
|
423
|
+
tool_data[:adapters].map do |adapter_data|
|
|
424
|
+
tool_type = adapter_data[:type].to_s
|
|
425
|
+
{
|
|
426
|
+
class: tool_class_for(tool_type),
|
|
427
|
+
options: resolve_tool_options(adapter_data, source:)
|
|
428
|
+
}
|
|
449
429
|
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Resolve a configured tool adapter only when configuration uses it.
|
|
433
|
+
# @param tool_type [String] Configured tool type
|
|
434
|
+
# @return [Class, nil] Tool adapter class
|
|
435
|
+
def tool_class_for(tool_type)
|
|
436
|
+
tool_name = TOOL_TYPES[tool_type]
|
|
437
|
+
return unless tool_name
|
|
450
438
|
|
|
451
|
-
|
|
452
|
-
else
|
|
453
|
-
[key, coerce_tool_option(key, resolve_value(value, source:))]
|
|
439
|
+
Prescient::Tool.const_get(tool_name, false)
|
|
454
440
|
end
|
|
455
|
-
end
|
|
456
441
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
442
|
+
def resolve_tool_option(tool_data, key, value, source:)
|
|
443
|
+
return if key == :type
|
|
444
|
+
return if key.to_s.end_with?("_env") && value.nil?
|
|
460
445
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
446
|
+
if key.to_s.end_with?("_env")
|
|
447
|
+
base_key = key.to_s.delete_suffix("_env").to_sym
|
|
448
|
+
if tool_data.key?(base_key)
|
|
449
|
+
raise Prescient::Error,
|
|
450
|
+
"Tool configuration cannot combine #{base_key} and #{key}"
|
|
451
|
+
end
|
|
467
452
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
453
|
+
[base_key, resolve_env_value(value, source:)]
|
|
454
|
+
else
|
|
455
|
+
[key, coerce_tool_option(key, resolve_value(value, source:))]
|
|
456
|
+
end
|
|
471
457
|
end
|
|
472
|
-
end
|
|
473
458
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
459
|
+
def resolve_provider_option(provider_data, key, value, source:)
|
|
460
|
+
return if key == :type
|
|
461
|
+
return if key.to_s.end_with?("_env") && value.nil?
|
|
462
|
+
|
|
463
|
+
if key.to_s.end_with?("_env")
|
|
464
|
+
base_key = key.to_s.delete_suffix("_env").to_sym
|
|
465
|
+
if provider_data.key?(base_key)
|
|
466
|
+
raise Prescient::Error,
|
|
467
|
+
"Provider configuration cannot combine #{base_key} and #{key}"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
[base_key, resolve_env_value(value, source:)]
|
|
471
|
+
else
|
|
472
|
+
[key, coerce_provider_option(key, resolve_value(value, source:))]
|
|
473
|
+
end
|
|
478
474
|
end
|
|
479
475
|
|
|
480
|
-
|
|
476
|
+
def resolve_scalar(data, key, source:)
|
|
477
|
+
env_key = :"#{key}_env"
|
|
478
|
+
if key_present?(data, key) && key_present?(data, env_key)
|
|
479
|
+
raise Prescient::Error, "Configuration cannot combine #{key} and #{key}_env"
|
|
480
|
+
end
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
return nil if value.nil?
|
|
482
|
+
return resolve_env_value(data[env_key], source:) if key_present?(data, env_key)
|
|
484
483
|
|
|
485
|
-
|
|
486
|
-
|
|
484
|
+
value = data[key]
|
|
485
|
+
return nil if value.nil?
|
|
487
486
|
|
|
488
|
-
|
|
489
|
-
case value
|
|
490
|
-
when Hash
|
|
491
|
-
resolve_hash_value(value, source:)
|
|
492
|
-
when Array
|
|
493
|
-
value.map { |item| resolve_value(item, source:) }
|
|
494
|
-
when String
|
|
495
|
-
interpolate_env(value, source:)
|
|
496
|
-
else
|
|
497
|
-
value
|
|
487
|
+
resolve_value(value, source:)
|
|
498
488
|
end
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
def resolve_hash_value(value, source:)
|
|
502
|
-
value.each_with_object({}) do |(key, nested_value), result|
|
|
503
|
-
key_name = key.to_s
|
|
504
|
-
if key_name.end_with?('_env')
|
|
505
|
-
base_key = key_name.delete_suffix('_env').to_sym
|
|
506
|
-
if value.key?(base_key) || value.key?(base_key.to_s)
|
|
507
|
-
raise Prescient::Error, "Configuration cannot combine #{base_key} and #{key_name}"
|
|
508
|
-
end
|
|
509
489
|
|
|
510
|
-
|
|
490
|
+
def resolve_value(value, source:)
|
|
491
|
+
case value
|
|
492
|
+
when Hash
|
|
493
|
+
resolve_hash_value(value, source:)
|
|
494
|
+
when Array
|
|
495
|
+
value.map { |item| resolve_value(item, source:) }
|
|
496
|
+
when String
|
|
497
|
+
interpolate_env(value, source:)
|
|
511
498
|
else
|
|
512
|
-
|
|
499
|
+
value
|
|
513
500
|
end
|
|
514
501
|
end
|
|
515
|
-
end
|
|
516
502
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
503
|
+
def resolve_hash_value(value, source:)
|
|
504
|
+
value.each_with_object({}) do |(key, nested_value), result|
|
|
505
|
+
key_name = key.to_s
|
|
506
|
+
if key_name.end_with?("_env")
|
|
507
|
+
base_key = key_name.delete_suffix("_env").to_sym
|
|
508
|
+
if value.key?(base_key) || value.key?(base_key.to_s)
|
|
509
|
+
raise Prescient::Error, "Configuration cannot combine #{base_key} and #{key_name}"
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
result[base_key] = resolve_env_value(nested_value, source:)
|
|
513
|
+
else
|
|
514
|
+
result[key.to_sym] = resolve_value(nested_value, source:)
|
|
515
|
+
end
|
|
516
|
+
end
|
|
521
517
|
end
|
|
522
518
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
end
|
|
519
|
+
def resolve_env_value(value, source:)
|
|
520
|
+
env_name = resolve_value(value, source:)
|
|
521
|
+
unless env_name.is_a?(String) && !env_name.empty?
|
|
522
|
+
raise Prescient::Error, "Environment variable name must be a non-empty string"
|
|
523
|
+
end
|
|
529
524
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
@env.fetch(env_name)
|
|
525
|
+
raw_value = @env.fetch(env_name)
|
|
526
|
+
parsed_value = YAML.safe_load(raw_value, permitted_classes: [], permitted_symbols: [], aliases: true)
|
|
527
|
+
parsed_value.nil? ? raw_value : parsed_value
|
|
534
528
|
rescue KeyError
|
|
535
529
|
raise Prescient::Error, "Environment variable not set: #{env_name}#{" in #{source}" if source}"
|
|
536
530
|
end
|
|
537
|
-
end
|
|
538
531
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
532
|
+
def interpolate_env(value, source:)
|
|
533
|
+
value.gsub(/\$\{([A-Z0-9_]+)\}/) do
|
|
534
|
+
env_name = Regexp.last_match(1)
|
|
535
|
+
@env.fetch(env_name)
|
|
536
|
+
rescue KeyError
|
|
537
|
+
raise Prescient::Error, "Environment variable not set: #{env_name}#{" in #{source}" if source}"
|
|
544
538
|
end
|
|
545
|
-
when Array
|
|
546
|
-
value.map { |item| normalize_keys(item) }
|
|
547
|
-
else
|
|
548
|
-
value
|
|
549
539
|
end
|
|
550
|
-
end
|
|
551
540
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
541
|
+
def normalize_keys(value)
|
|
542
|
+
case value
|
|
543
|
+
when Hash
|
|
544
|
+
value.each_with_object({}) do |(key, nested_value), result|
|
|
545
|
+
result[key.to_sym] = normalize_keys(nested_value)
|
|
546
|
+
end
|
|
547
|
+
when Array
|
|
548
|
+
value.map { |item| normalize_keys(item) }
|
|
549
|
+
else
|
|
550
|
+
value
|
|
551
|
+
end
|
|
552
|
+
end
|
|
555
553
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
554
|
+
def key_present?(data, key)
|
|
555
|
+
data.key?(key) || data.key?(key.to_s)
|
|
556
|
+
end
|
|
559
557
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
558
|
+
def scalar_present?(data, key)
|
|
559
|
+
key_present?(data, key) || key_present?(data, :"#{key}_env")
|
|
560
|
+
end
|
|
563
561
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
end
|
|
562
|
+
def coerce_integer(value, name)
|
|
563
|
+
return value if value.is_a?(Integer)
|
|
564
|
+
return value.to_i if value.is_a?(Numeric)
|
|
568
565
|
|
|
569
|
-
|
|
570
|
-
|
|
566
|
+
Integer(value)
|
|
567
|
+
rescue ArgumentError, TypeError
|
|
568
|
+
raise Prescient::Error, "#{name} must be an integer"
|
|
569
|
+
end
|
|
571
570
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
raise Prescient::Error, "#{name} must be a number"
|
|
575
|
-
end
|
|
571
|
+
def coerce_float(value, name)
|
|
572
|
+
return value.to_f if value.is_a?(Numeric)
|
|
576
573
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
coerce_integer(value, 'embedding_dimensions')
|
|
581
|
-
when :timeout
|
|
582
|
-
coerce_integer(value, 'timeout')
|
|
583
|
-
else
|
|
584
|
-
value
|
|
574
|
+
Float(value)
|
|
575
|
+
rescue ArgumentError, TypeError
|
|
576
|
+
raise Prescient::Error, "#{name} must be a number"
|
|
585
577
|
end
|
|
586
|
-
end
|
|
587
578
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
579
|
+
def coerce_provider_option(key, value)
|
|
580
|
+
case key.to_sym
|
|
581
|
+
when :embedding_dimensions
|
|
582
|
+
coerce_integer(value, "embedding_dimensions")
|
|
583
|
+
when :timeout
|
|
584
|
+
coerce_integer(value, "timeout")
|
|
585
|
+
else
|
|
586
|
+
value
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def coerce_tool_option(key, value)
|
|
591
|
+
case key.to_sym
|
|
592
|
+
when :max_results, :max_response_bytes
|
|
593
|
+
coerce_integer(value, key.to_s)
|
|
594
|
+
when :timeout
|
|
595
|
+
coerce_float(value, "timeout")
|
|
596
|
+
else
|
|
597
|
+
value
|
|
598
|
+
end
|
|
596
599
|
end
|
|
597
|
-
end
|
|
598
600
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
+
def validate_version!(data, source:)
|
|
602
|
+
return unless key_present?(data, :version)
|
|
601
603
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
+
version = resolve_value(data[:version], source:)
|
|
605
|
+
return if version == CONFIGURATION_VERSION
|
|
604
606
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
+
raise Prescient::Error,
|
|
608
|
+
"Unsupported configuration version #{version.inspect}#{" in #{source}" if source}"
|
|
609
|
+
end
|
|
607
610
|
end
|
|
611
|
+
# rubocop:enable Metrics/ClassLength
|
|
608
612
|
end
|