prescient 0.5.0 → 0.7.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/.dockerignore +18 -0
- data/CHANGELOG.md +42 -0
- data/Dockerfile +45 -0
- data/INTEGRATION_GUIDE.md +122 -11
- data/README.md +243 -24
- data/docker-compose.api.yml +21 -0
- data/docker-compose.yml +22 -0
- data/examples/README.md +51 -0
- data/examples/rest_api.ru +30 -0
- data/examples/web_search.rb +34 -0
- data/lib/prescient/api.rb +337 -0
- data/lib/prescient/cli.rb +136 -6
- data/lib/prescient/client.rb +26 -0
- data/lib/prescient/configuration_loader.rb +171 -0
- data/lib/prescient/errors.rb +12 -0
- data/lib/prescient/provider/openai.rb +0 -2
- data/lib/prescient/tool/search_api.rb +126 -0
- data/lib/prescient/tool/searxng.rb +124 -0
- data/lib/prescient/tool.rb +123 -0
- data/lib/prescient/version.rb +1 -1
- data/lib/prescient.rb +71 -1
- data/schema/prescient.configuration.schema.json +119 -0
- data/searxng/settings.yml +18 -0
- data/sig/prescient.rbs +62 -0
- metadata +16 -5
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'timeout'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
# SearXNG web-search tool adapter.
|
|
9
|
+
class Prescient::Tool::SearXNG < Prescient::Tool::Base
|
|
10
|
+
# Network failures that should be reported as connection errors.
|
|
11
|
+
NETWORK_ERRORS = [Net::OpenTimeout, Net::ReadTimeout, Timeout::Error, SocketError, Errno::ECONNREFUSED].freeze
|
|
12
|
+
|
|
13
|
+
# @param options [Hash] SearXNG configuration options
|
|
14
|
+
# @option options [String] :url SearXNG base URL
|
|
15
|
+
# @option options [String] :language Optional search language
|
|
16
|
+
# @option options [String, Array<String>] :categories Optional categories
|
|
17
|
+
def initialize(**options)
|
|
18
|
+
super
|
|
19
|
+
@base_url = normalized_base_url(options.fetch(:url))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Search SearXNG and return normalized result metadata.
|
|
23
|
+
# @param query [String] Search query
|
|
24
|
+
# @param limit [Integer, nil] Maximum number of results
|
|
25
|
+
# @param timeout [Numeric, nil] Request timeout in seconds
|
|
26
|
+
# @return [Hash] Normalized search result envelope
|
|
27
|
+
def search(query, limit: nil, timeout: nil)
|
|
28
|
+
normalized_query = validate_query(query)
|
|
29
|
+
requested_limit = result_limit(limit)
|
|
30
|
+
requested_timeout = request_timeout(timeout)
|
|
31
|
+
response = HTTParty.get(
|
|
32
|
+
search_url,
|
|
33
|
+
query: request_parameters(normalized_query),
|
|
34
|
+
timeout: requested_timeout,
|
|
35
|
+
)
|
|
36
|
+
validate_response!(response)
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
tool: 'web_search',
|
|
40
|
+
query: normalized_query,
|
|
41
|
+
source: 'searxng',
|
|
42
|
+
results: normalize_results(response.parsed_response, requested_limit),
|
|
43
|
+
}
|
|
44
|
+
rescue Prescient::Error
|
|
45
|
+
raise
|
|
46
|
+
rescue *NETWORK_ERRORS => e
|
|
47
|
+
raise Prescient::ToolConnectionError, "SearXNG request failed: #{e.class}"
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
raise Prescient::ToolError, "SearXNG request failed: #{e.message}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
protected
|
|
53
|
+
|
|
54
|
+
# Validate SearXNG-specific configuration.
|
|
55
|
+
# @return [void]
|
|
56
|
+
def validate_configuration!
|
|
57
|
+
normalized_base_url(@options.fetch(:url))
|
|
58
|
+
max_response_bytes
|
|
59
|
+
result_limit(nil)
|
|
60
|
+
request_timeout(nil)
|
|
61
|
+
rescue KeyError
|
|
62
|
+
raise Prescient::ToolConfigurationError, 'SearXNG requires a url'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def normalized_base_url(value)
|
|
68
|
+
uri = URI.parse(value.to_s)
|
|
69
|
+
valid_scheme = ['http', 'https'].include?(uri.scheme)
|
|
70
|
+
if !valid_scheme || uri.host.nil? || uri.userinfo
|
|
71
|
+
raise Prescient::ToolConfigurationError, 'SearXNG url must be an HTTP(S) URL without credentials'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
uri.to_s.delete_suffix('/')
|
|
75
|
+
rescue URI::InvalidURIError
|
|
76
|
+
raise Prescient::ToolConfigurationError, 'SearXNG url must be a valid HTTP(S) URL'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def search_url
|
|
80
|
+
"#{@base_url}/search"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def request_parameters(query)
|
|
84
|
+
parameters = { q: query, format: 'json' }
|
|
85
|
+
parameters[:language] = @options[:language] if @options[:language]
|
|
86
|
+
parameters[:categories] = Array(@options[:categories]).join(',') if @options[:categories]
|
|
87
|
+
parameters
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def validate_response!(response)
|
|
91
|
+
if response.respond_to?(:body) && response.body.to_s.bytesize > max_response_bytes
|
|
92
|
+
raise Prescient::ToolInvalidResponseError, 'SearXNG response exceeds configured size limit'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
return if response.success?
|
|
96
|
+
|
|
97
|
+
error_class = case response.code.to_i
|
|
98
|
+
when 401, 403 then Prescient::AuthenticationError
|
|
99
|
+
when 429 then Prescient::RateLimitError
|
|
100
|
+
when 500..599 then Prescient::ToolConnectionError
|
|
101
|
+
else Prescient::ToolError
|
|
102
|
+
end
|
|
103
|
+
raise error_class, "SearXNG returned HTTP #{response.code}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def normalize_results(payload, limit)
|
|
107
|
+
raw_results = payload.is_a?(Hash) ? payload['results'] : nil
|
|
108
|
+
unless raw_results.is_a?(Array)
|
|
109
|
+
raise Prescient::ToolInvalidResponseError, 'SearXNG response did not contain results'
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
raw_results.filter_map { |result|
|
|
113
|
+
next unless result.is_a?(Hash)
|
|
114
|
+
next unless result['url'].is_a?(String) && !result['url'].empty?
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
title: result['title'].to_s,
|
|
118
|
+
url: result['url'],
|
|
119
|
+
snippet: result['content'].to_s,
|
|
120
|
+
source: 'searxng',
|
|
121
|
+
}
|
|
122
|
+
}.first(limit)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Namespace for external capability adapters.
|
|
4
|
+
module Prescient::Tool
|
|
5
|
+
autoload :SearXNG, 'prescient/tool/searxng'
|
|
6
|
+
autoload :SearchApi, 'prescient/tool/search_api'
|
|
7
|
+
|
|
8
|
+
# Base contract for explicit external tool invocation.
|
|
9
|
+
class Base
|
|
10
|
+
# @return [Integer] Default request timeout in seconds
|
|
11
|
+
DEFAULT_TIMEOUT = 5
|
|
12
|
+
# @return [Integer] Default maximum number of returned results
|
|
13
|
+
DEFAULT_MAX_RESULTS = 5
|
|
14
|
+
# @return [Integer] Maximum accepted search query length
|
|
15
|
+
MAX_QUERY_LENGTH = 2_000
|
|
16
|
+
# @return [Integer] Maximum accepted tool response size in bytes
|
|
17
|
+
DEFAULT_MAX_RESPONSE_BYTES = 1_048_576
|
|
18
|
+
|
|
19
|
+
# @return [Hash<Symbol, untyped>] Tool configuration options
|
|
20
|
+
attr_reader :options
|
|
21
|
+
|
|
22
|
+
# @param options [Hash] Tool-specific configuration options
|
|
23
|
+
def initialize(**options)
|
|
24
|
+
@options = options
|
|
25
|
+
validate_configuration!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Execute a search using the tool.
|
|
29
|
+
# @param query [String] Search query
|
|
30
|
+
# @param options [Hash] Per-request options
|
|
31
|
+
# @return [Hash] Normalized tool result
|
|
32
|
+
# @raise [NotImplementedError] If the adapter does not support searching
|
|
33
|
+
def search(query, **options)
|
|
34
|
+
raise NotImplementedError, "#{self.class} must implement #search"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
# Validate adapter configuration.
|
|
40
|
+
# @return [void]
|
|
41
|
+
def validate_configuration!
|
|
42
|
+
# Override in subclasses.
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param query [String] Search query
|
|
46
|
+
# @return [String] Validated query
|
|
47
|
+
def validate_query(query)
|
|
48
|
+
unless query.is_a?(String) && !query.strip.empty?
|
|
49
|
+
raise Prescient::ToolConfigurationError, 'search query must be a non-empty string'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
cleaned_query = query.strip
|
|
53
|
+
if cleaned_query.length > MAX_QUERY_LENGTH
|
|
54
|
+
raise Prescient::ToolConfigurationError,
|
|
55
|
+
"search query cannot contain more than #{MAX_QUERY_LENGTH} characters"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
cleaned_query
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param value [Object] Requested result limit
|
|
62
|
+
# @return [Integer] Validated result limit
|
|
63
|
+
def result_limit(value)
|
|
64
|
+
limit = value.nil? ? @options.fetch(:max_results, DEFAULT_MAX_RESULTS) : value
|
|
65
|
+
unless limit.is_a?(Integer) && limit.positive?
|
|
66
|
+
raise Prescient::ToolConfigurationError, 'max_results must be a positive integer'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
[limit, 20].min
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param value [Object] Requested timeout
|
|
73
|
+
# @return [Numeric] Validated timeout
|
|
74
|
+
def request_timeout(value)
|
|
75
|
+
timeout = value.nil? ? @options.fetch(:timeout, DEFAULT_TIMEOUT) : value
|
|
76
|
+
unless timeout.is_a?(Numeric) && timeout.positive?
|
|
77
|
+
raise Prescient::ToolConfigurationError, 'timeout must be a positive number'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
timeout
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [Integer] Maximum accepted response size
|
|
84
|
+
def max_response_bytes
|
|
85
|
+
value = @options.fetch(:max_response_bytes, DEFAULT_MAX_RESPONSE_BYTES)
|
|
86
|
+
unless value.is_a?(Integer) && value.positive?
|
|
87
|
+
raise Prescient::ToolConfigurationError,
|
|
88
|
+
'max_response_bytes must be a positive integer'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
value
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Ordered implementations for one logical tool capability.
|
|
96
|
+
class Group < Base
|
|
97
|
+
# @return [Array<Base>] Ordered capability implementations
|
|
98
|
+
attr_reader :adapters
|
|
99
|
+
|
|
100
|
+
# @param adapters [Array<Base>] Ordered capability implementations
|
|
101
|
+
def initialize(adapters:)
|
|
102
|
+
super
|
|
103
|
+
@adapters = adapters
|
|
104
|
+
raise Prescient::ToolConfigurationError, 'tool group requires an adapter' if @adapters.empty?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Execute the first successful adapter, falling back only for transient
|
|
108
|
+
# connection and rate-limit failures.
|
|
109
|
+
# @param query [String] Search query
|
|
110
|
+
# @param options [Hash] Per-request options
|
|
111
|
+
# @return [Hash] Normalized tool result
|
|
112
|
+
def search(query, **options)
|
|
113
|
+
failures = []
|
|
114
|
+
@adapters.each do |adapter|
|
|
115
|
+
return adapter.search(query, **options)
|
|
116
|
+
rescue Prescient::ToolConnectionError, Prescient::RateLimitError => e
|
|
117
|
+
failures << e
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
raise failures.last
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/prescient/version.rb
CHANGED
data/lib/prescient.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'prescient/version'
|
|
4
4
|
require_relative 'prescient/errors'
|
|
5
|
+
Prescient.autoload :Tool, 'prescient/tool'
|
|
5
6
|
require_relative 'prescient/pgvector'
|
|
6
7
|
require_relative 'prescient/base'
|
|
7
8
|
require_relative 'prescient/provider/ollama'
|
|
@@ -14,7 +15,6 @@ require_relative 'prescient/provider/deepseek'
|
|
|
14
15
|
require_relative 'prescient/provider/xai'
|
|
15
16
|
require_relative 'prescient/configuration_loader'
|
|
16
17
|
require_relative 'prescient/client'
|
|
17
|
-
require_relative 'prescient/cli'
|
|
18
18
|
|
|
19
19
|
# Main Prescient module for AI provider abstraction
|
|
20
20
|
#
|
|
@@ -35,6 +35,10 @@ require_relative 'prescient/cli'
|
|
|
35
35
|
# embedding = client.generate_embedding("Some text to embed")
|
|
36
36
|
# puts embedding.length # => 1536 (for OpenAI text-embedding-3-small)
|
|
37
37
|
module Prescient
|
|
38
|
+
autoload :Tool, 'prescient/tool'
|
|
39
|
+
autoload :API, 'prescient/api'
|
|
40
|
+
autoload :CLI, 'prescient/cli'
|
|
41
|
+
|
|
38
42
|
# Configure Prescient with custom settings and providers
|
|
39
43
|
#
|
|
40
44
|
# @example Configure with custom provider
|
|
@@ -59,6 +63,13 @@ module Prescient
|
|
|
59
63
|
@_configuration ||= Configuration.new
|
|
60
64
|
end
|
|
61
65
|
|
|
66
|
+
# Look up a configured external tool.
|
|
67
|
+
# @param name [Symbol, String] Tool name
|
|
68
|
+
# @return [Prescient::Tool::Base, nil] Configured tool instance
|
|
69
|
+
def self.tool(name)
|
|
70
|
+
configuration.tool(name)
|
|
71
|
+
end
|
|
72
|
+
|
|
62
73
|
# Reset configuration to defaults
|
|
63
74
|
#
|
|
64
75
|
# @return [Configuration] New configuration instance
|
|
@@ -82,6 +93,7 @@ module Prescient
|
|
|
82
93
|
else
|
|
83
94
|
Configuration.new.tap do |config|
|
|
84
95
|
configure_default_providers(config, env)
|
|
96
|
+
configure_default_tools(config, env)
|
|
85
97
|
end
|
|
86
98
|
end
|
|
87
99
|
|
|
@@ -117,6 +129,9 @@ module Prescient
|
|
|
117
129
|
# @return [Hash] Registered providers configuration
|
|
118
130
|
attr_reader :providers
|
|
119
131
|
|
|
132
|
+
# @return [Hash] Registered tools configuration
|
|
133
|
+
attr_reader :tools
|
|
134
|
+
|
|
120
135
|
# Initialize configuration with default values
|
|
121
136
|
def initialize
|
|
122
137
|
@default_provider = :ollama
|
|
@@ -127,6 +142,8 @@ module Prescient
|
|
|
127
142
|
@sensitive_keys = []
|
|
128
143
|
@providers = {}
|
|
129
144
|
@provider_instances = {} # : Hash[Symbol, untyped]
|
|
145
|
+
@tools = {}
|
|
146
|
+
@tool_instances = {} # : Hash[Symbol, untyped]
|
|
130
147
|
end
|
|
131
148
|
|
|
132
149
|
# Configure additional keys to remove from provider information.
|
|
@@ -175,6 +192,52 @@ module Prescient
|
|
|
175
192
|
@provider_instances[provider_name] ||= provider_config[:class].new(**provider_options)
|
|
176
193
|
end
|
|
177
194
|
|
|
195
|
+
# Register an external tool.
|
|
196
|
+
# @param name [Symbol] Unique identifier for the tool
|
|
197
|
+
# @param tool_class [Class] Tool adapter class
|
|
198
|
+
# @param options [Hash] Tool-specific configuration options
|
|
199
|
+
# @return [void]
|
|
200
|
+
def add_tool(name, tool_class, **options)
|
|
201
|
+
tool_name = name.to_sym
|
|
202
|
+
@tools[tool_name] = {
|
|
203
|
+
class: tool_class,
|
|
204
|
+
options: options,
|
|
205
|
+
}
|
|
206
|
+
@tool_instances.delete(tool_name)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Register ordered implementations for one logical external tool.
|
|
210
|
+
# @param name [Symbol] Unique logical capability identifier
|
|
211
|
+
# @param adapters [Array<Hash>] Adapter class and option configurations
|
|
212
|
+
# @return [void]
|
|
213
|
+
def add_tool_group(name, adapters)
|
|
214
|
+
tool_name = name.to_sym
|
|
215
|
+
@tools[tool_name] = { adapters: adapters }
|
|
216
|
+
@tool_instances.delete(tool_name)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Instantiate a tool by name.
|
|
220
|
+
# @param name [Symbol, String] Tool name
|
|
221
|
+
# @return [Prescient::Tool::Base, nil] Tool instance or nil
|
|
222
|
+
def tool(name)
|
|
223
|
+
tool_name = name.to_sym
|
|
224
|
+
tool_config = @tools[tool_name]
|
|
225
|
+
return nil unless tool_config
|
|
226
|
+
|
|
227
|
+
if tool_config[:adapters]
|
|
228
|
+
@tool_instances[tool_name] ||= Prescient::Tool::Group.new(
|
|
229
|
+
adapters: tool_config[:adapters].map do |adapter|
|
|
230
|
+
adapter_options = adapter[:options] # : Hash[Symbol, untyped]
|
|
231
|
+
adapter[:class].new(**adapter_options)
|
|
232
|
+
end,
|
|
233
|
+
)
|
|
234
|
+
return @tool_instances[tool_name]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
tool_options = tool_config[:options] # : Hash[Symbol, untyped]
|
|
238
|
+
@tool_instances[tool_name] ||= tool_config[:class].new(**tool_options)
|
|
239
|
+
end
|
|
240
|
+
|
|
178
241
|
# Get list of providers that currently pass {Prescient::Base#available?}.
|
|
179
242
|
#
|
|
180
243
|
# Providers are included when their health check reports `reachable: true`,
|
|
@@ -204,6 +267,12 @@ module Prescient
|
|
|
204
267
|
configure_huggingface(config, env)
|
|
205
268
|
end
|
|
206
269
|
|
|
270
|
+
def configure_default_tools(config, env)
|
|
271
|
+
return unless env['SEARXNG_URL']
|
|
272
|
+
|
|
273
|
+
config.add_tool(:web_search, Prescient::Tool::SearXNG, url: env['SEARXNG_URL'])
|
|
274
|
+
end
|
|
275
|
+
|
|
207
276
|
def configure_ollama(config, env)
|
|
208
277
|
config.add_provider(
|
|
209
278
|
:ollama,
|
|
@@ -305,5 +374,6 @@ module Prescient
|
|
|
305
374
|
# Default configuration
|
|
306
375
|
configure do |config|
|
|
307
376
|
configure_default_providers(config, ENV)
|
|
377
|
+
configure_default_tools(config, ENV)
|
|
308
378
|
end
|
|
309
379
|
end
|
|
@@ -58,6 +58,12 @@
|
|
|
58
58
|
"additionalProperties": {
|
|
59
59
|
"$ref": "#/$defs/provider"
|
|
60
60
|
}
|
|
61
|
+
},
|
|
62
|
+
"tools": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": {
|
|
65
|
+
"$ref": "#/$defs/tool"
|
|
66
|
+
}
|
|
61
67
|
}
|
|
62
68
|
},
|
|
63
69
|
"$defs": {
|
|
@@ -148,6 +154,119 @@
|
|
|
148
154
|
"type": "string"
|
|
149
155
|
}
|
|
150
156
|
}
|
|
157
|
+
},
|
|
158
|
+
"tool": {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"additionalProperties": false,
|
|
161
|
+
"anyOf": [
|
|
162
|
+
{
|
|
163
|
+
"required": [
|
|
164
|
+
"type"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"required": [
|
|
169
|
+
"adapters"
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
"properties": {
|
|
174
|
+
"adapters": {
|
|
175
|
+
"type": "array",
|
|
176
|
+
"minItems": 1,
|
|
177
|
+
"items": {
|
|
178
|
+
"$ref": "#/$defs/tool"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"type": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"enum": [
|
|
184
|
+
"searxng",
|
|
185
|
+
"searchapi"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"api_key": {
|
|
189
|
+
"type": "string"
|
|
190
|
+
},
|
|
191
|
+
"api_key_env": {
|
|
192
|
+
"type": "string"
|
|
193
|
+
},
|
|
194
|
+
"engine": {
|
|
195
|
+
"type": "string"
|
|
196
|
+
},
|
|
197
|
+
"engine_env": {
|
|
198
|
+
"type": "string"
|
|
199
|
+
},
|
|
200
|
+
"location": {
|
|
201
|
+
"type": "string"
|
|
202
|
+
},
|
|
203
|
+
"location_env": {
|
|
204
|
+
"type": "string"
|
|
205
|
+
},
|
|
206
|
+
"hl": {
|
|
207
|
+
"type": "string"
|
|
208
|
+
},
|
|
209
|
+
"hl_env": {
|
|
210
|
+
"type": "string"
|
|
211
|
+
},
|
|
212
|
+
"gl": {
|
|
213
|
+
"type": "string"
|
|
214
|
+
},
|
|
215
|
+
"gl_env": {
|
|
216
|
+
"type": "string"
|
|
217
|
+
},
|
|
218
|
+
"url": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"format": "uri-reference"
|
|
221
|
+
},
|
|
222
|
+
"url_env": {
|
|
223
|
+
"type": "string"
|
|
224
|
+
},
|
|
225
|
+
"language": {
|
|
226
|
+
"type": "string"
|
|
227
|
+
},
|
|
228
|
+
"language_env": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"categories": {
|
|
232
|
+
"oneOf": [
|
|
233
|
+
{
|
|
234
|
+
"type": "string"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"type": "array",
|
|
238
|
+
"items": {
|
|
239
|
+
"type": "string"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
"categories_env": {
|
|
245
|
+
"type": "string"
|
|
246
|
+
},
|
|
247
|
+
"timeout": {
|
|
248
|
+
"type": "number",
|
|
249
|
+
"exclusiveMinimum": 0
|
|
250
|
+
},
|
|
251
|
+
"timeout_env": {
|
|
252
|
+
"type": "string"
|
|
253
|
+
},
|
|
254
|
+
"max_results": {
|
|
255
|
+
"type": "integer",
|
|
256
|
+
"minimum": 1,
|
|
257
|
+
"maximum": 20
|
|
258
|
+
},
|
|
259
|
+
"max_results_env": {
|
|
260
|
+
"type": "string"
|
|
261
|
+
},
|
|
262
|
+
"max_response_bytes": {
|
|
263
|
+
"type": "integer",
|
|
264
|
+
"minimum": 1
|
|
265
|
+
},
|
|
266
|
+
"max_response_bytes_env": {
|
|
267
|
+
"type": "string"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
151
270
|
}
|
|
152
271
|
}
|
|
153
272
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Development settings for the SearXNG service in docker-compose.yml.
|
|
2
|
+
use_default_settings: true
|
|
3
|
+
|
|
4
|
+
general:
|
|
5
|
+
debug: false
|
|
6
|
+
instance_name: Prescient SearXNG
|
|
7
|
+
|
|
8
|
+
search:
|
|
9
|
+
formats:
|
|
10
|
+
- html
|
|
11
|
+
- json
|
|
12
|
+
|
|
13
|
+
server:
|
|
14
|
+
secret_key: prescient-development-secret
|
|
15
|
+
bind_address: 0.0.0.0
|
|
16
|
+
base_url: http://localhost:8080/
|
|
17
|
+
limiter: false
|
|
18
|
+
image_proxy: false
|
data/sig/prescient.rbs
CHANGED
|
@@ -33,6 +33,49 @@ module Prescient
|
|
|
33
33
|
class ProviderError < Error
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
class ToolError < Error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class ToolConfigurationError < ToolError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class ToolConnectionError < ToolError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class ToolInvalidResponseError < ToolError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module Tool
|
|
49
|
+
class Base
|
|
50
|
+
DEFAULT_TIMEOUT: Integer
|
|
51
|
+
DEFAULT_MAX_RESULTS: Integer
|
|
52
|
+
MAX_QUERY_LENGTH: Integer
|
|
53
|
+
DEFAULT_MAX_RESPONSE_BYTES: Integer
|
|
54
|
+
|
|
55
|
+
attr_reader options: Hash[Symbol, untyped]
|
|
56
|
+
|
|
57
|
+
def initialize: (**untyped) -> void
|
|
58
|
+
def search: (String, **untyped) -> Hash[Symbol, untyped]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class Group < Base
|
|
62
|
+
attr_reader adapters: Array[Base]
|
|
63
|
+
def initialize: (adapters: Array[Base]) -> void
|
|
64
|
+
def search: (String, **untyped) -> Hash[Symbol, untyped]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class SearXNG < Base
|
|
68
|
+
def initialize: (**untyped) -> void
|
|
69
|
+
def search: (String, ?limit: Integer?, ?timeout: Numeric?) -> Hash[Symbol, untyped]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class SearchApi < Base
|
|
73
|
+
API_URL: String
|
|
74
|
+
def initialize: (**untyped) -> void
|
|
75
|
+
def search: (String, ?limit: Integer?, ?timeout: Numeric?) -> Hash[Symbol, untyped]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
36
79
|
class Base
|
|
37
80
|
attr_reader options: Hash[Symbol, untyped]
|
|
38
81
|
attr_reader provider_name: String
|
|
@@ -257,11 +300,15 @@ module Prescient
|
|
|
257
300
|
attr_accessor fallback_providers: Array[Symbol]
|
|
258
301
|
attr_reader sensitive_keys: Array[Symbol]
|
|
259
302
|
attr_reader providers: Hash[Symbol, Hash[Symbol, untyped]]
|
|
303
|
+
attr_reader tools: Hash[Symbol, Hash[Symbol, untyped]]
|
|
260
304
|
|
|
261
305
|
def initialize: () -> void
|
|
262
306
|
def sensitive_keys=: (Array[Symbol | String]) -> Array[Symbol]
|
|
263
307
|
def add_provider: (Symbol | String, Class, **untyped) -> void
|
|
264
308
|
def provider: (Symbol | String) -> untyped
|
|
309
|
+
def add_tool: (Symbol | String, Class, **untyped) -> void
|
|
310
|
+
def add_tool_group: (Symbol | String, Array[Hash[Symbol, untyped]]) -> void
|
|
311
|
+
def tool: (Symbol | String) -> untyped
|
|
265
312
|
def available_providers: () -> Array[Symbol]
|
|
266
313
|
end
|
|
267
314
|
|
|
@@ -298,11 +345,23 @@ module Prescient
|
|
|
298
345
|
def run: () -> Integer
|
|
299
346
|
end
|
|
300
347
|
|
|
348
|
+
class API
|
|
349
|
+
DEFAULT_MAX_BODY_BYTES: Integer
|
|
350
|
+
MAX_BATCH_SIZE: Integer
|
|
351
|
+
API_VERSION: String
|
|
352
|
+
ROUTES: Hash[Array[String], Symbol]
|
|
353
|
+
|
|
354
|
+
def initialize: (?authentication: untyped, ?max_body_bytes: Integer) -> void
|
|
355
|
+
def call: (Hash[String, untyped]) -> [Integer, Hash[String, String], Array[String]]
|
|
356
|
+
end
|
|
357
|
+
|
|
301
358
|
class ConfigurationLoader
|
|
302
359
|
CONFIGURATION_VERSION: Integer
|
|
303
360
|
TOP_LEVEL_KEYS: Array[String]
|
|
304
361
|
PROVIDER_TYPES: Hash[String, untyped]
|
|
305
362
|
COMMON_PROVIDER_KEYS: Array[String]
|
|
363
|
+
TOOL_TYPES: Hash[String, untyped]
|
|
364
|
+
COMMON_TOOL_KEYS: Array[String]
|
|
306
365
|
PROMPT_TEMPLATE_KEYS: Array[String]
|
|
307
366
|
ATTR_KEYS: Array[String]
|
|
308
367
|
|
|
@@ -325,11 +384,14 @@ module Prescient
|
|
|
325
384
|
def self.client: (?Symbol, ?enable_fallback: bool, ?provider_options: Hash[Symbol, untyped]) -> Client
|
|
326
385
|
def self.generate_embedding: (String, ?provider: Symbol, ?enable_fallback: bool, **untyped) -> Array[Float]
|
|
327
386
|
def self.generate_response: (String, ?Array[untyped], ?provider: Symbol, ?enable_fallback: bool, **untyped) -> Hash[Symbol, untyped]
|
|
387
|
+
def self.search_and_generate: (String, ?tool: Symbol | String, ?provider: Symbol, ?limit: Integer?, ?enable_fallback: bool, ?provider_options: Hash[Symbol, untyped], **untyped) -> Hash[Symbol, untyped]
|
|
328
388
|
def self.health_check: (?provider: Symbol) -> Hash[Symbol, untyped]
|
|
389
|
+
def self.tool: (Symbol | String) -> untyped
|
|
329
390
|
|
|
330
391
|
private
|
|
331
392
|
|
|
332
393
|
def self.configure_default_providers: (Configuration, untyped) -> void
|
|
394
|
+
def self.configure_default_tools: (Configuration, untyped) -> void
|
|
333
395
|
def self.configure_ollama: (Configuration, untyped) -> void
|
|
334
396
|
def self.configure_openai: (Configuration, untyped) -> void
|
|
335
397
|
def self.configure_anthropic: (Configuration, untyped) -> void
|