ask-web-search 0.7.1 → 0.9.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/README.md +29 -22
- data/lib/ask/web_search/version.rb +1 -1
- data/lib/ask/web_search.rb +97 -26
- metadata +21 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1b6fbfe4321cb25a1da21bfcdb45c3a4817c50c27edae5adc429b6383950a2d
|
|
4
|
+
data.tar.gz: fb594f9d216c1a474248f5c2ac2209404fae1566d14c8863bb2e1202c3dcfa5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2517586f4020e29f66c2d0130cb315395db80209896ef1203af37f2c92c2b6b59b8b9380bf4646c3217677eb70b4bec22a30ceb92460dc072aba8f8c2f12bee
|
|
7
|
+
data.tar.gz: e273bea7c63d08c06957e9b7cf8562b951a563ad8dc0c6d42258389995f2d057a58743db01c6fc53082fc9f568551ab614e737fb744c5b4ea4a84c6c61466fb7
|
data/README.md
CHANGED
|
@@ -9,21 +9,9 @@ depends only on ask-tools.
|
|
|
9
9
|
|
|
10
10
|
## Backends
|
|
11
11
|
|
|
12
|
-
Two interchangeable backends
|
|
12
|
+
Two interchangeable backends:
|
|
13
13
|
|
|
14
|
-
1. **
|
|
15
|
-
[agent.tinyfish.ai](https://agent.tinyfish.ai/api-keys) and export it:
|
|
16
|
-
|
|
17
|
-
```sh
|
|
18
|
-
export TINYFISH_API_KEY=...
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
That's the whole setup — no Docker, no SearXNG instance. Live
|
|
22
|
-
browser-rendered results, freshness windows, and the news /
|
|
23
|
-
research-paper verticals all work out of the box.
|
|
24
|
-
|
|
25
|
-
2. **SearXNG (self-hosted).** For users who want queries to stay local,
|
|
26
|
-
and as the automatic fallback when TinyFish fails: a running
|
|
14
|
+
1. **SearXNG (the default — for everyone).** Queries go to a local
|
|
27
15
|
[SearXNG](https://docs.searxng.org/) instance, default
|
|
28
16
|
`http://localhost:8888`. Start one with Docker:
|
|
29
17
|
|
|
@@ -34,14 +22,32 @@ Two interchangeable backends, chosen automatically at call time:
|
|
|
34
22
|
Or use the provided `docker-compose.yml` in the `searxng` directory of
|
|
35
23
|
this repository.
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
2. **TinyFish (opt-in — for people who'd rather hold an API key than set
|
|
26
|
+
up SearXNG).** Two steps: select the backend, and provide a key.
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
export SEARCH_BACKEND=tinyfish
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Get a free key (no credit card) at
|
|
33
|
+
[agent.tinyfish.ai](https://agent.tinyfish.ai/api-keys) and store it —
|
|
34
|
+
either in `~/.ask/credentials.yml` via ask-auth (one line:
|
|
35
|
+
`tinyfish_api_key: <key>`, file is 0600) or `export
|
|
36
|
+
TINYFISH_API_KEY=...`. Selecting tinyfish without a key raises an
|
|
37
|
+
onboarding error that says exactly that; holding a key without
|
|
38
|
+
selecting does nothing — SearXNG stays the default.
|
|
39
|
+
|
|
40
|
+
Selection precedence (first match wins):
|
|
41
|
+
|
|
42
|
+
1. `TINYFISH_SEARCH=0` — a hard-off that forces SearXNG
|
|
43
|
+
2. `Ask::WebSearch.backend = :tinyfish | :searxng` (in code)
|
|
44
|
+
3. `SEARCH_BACKEND=tinyfish | searxng` (env)
|
|
45
|
+
4. default → SearXNG
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- `TINYFISH_SEARCH=0` disables TinyFish outright (SearXNG only, even
|
|
44
|
-
with a key).
|
|
47
|
+
When the tinyfish backend fails (network, rate limit) and a SearXNG
|
|
48
|
+
endpoint is explicitly configured, the call falls back to SearXNG
|
|
49
|
+
automatically — combined errors surface both failures. Invalid backend
|
|
50
|
+
names raise `ArgumentError` listing the valid ones.
|
|
45
51
|
|
|
46
52
|
## Installation
|
|
47
53
|
|
|
@@ -52,7 +58,8 @@ gem "ask-web-search"
|
|
|
52
58
|
## Configuration
|
|
53
59
|
|
|
54
60
|
```sh
|
|
55
|
-
export
|
|
61
|
+
export SEARCH_BACKEND=tinyfish # opt in to TinyFish (default: searxng)
|
|
62
|
+
export TINYFISH_API_KEY=... # TinyFish key (or tinyfish_api_key: in ~/.ask/credentials.yml)
|
|
56
63
|
export SEARXNG_URL=http://localhost:8888 # SearXNG endpoint (default shown)
|
|
57
64
|
```
|
|
58
65
|
|
data/lib/ask/web_search.rb
CHANGED
|
@@ -7,13 +7,15 @@ require_relative "web_search/version"
|
|
|
7
7
|
|
|
8
8
|
module Ask
|
|
9
9
|
# Searches the web and returns the results as clean, numbered markdown
|
|
10
|
-
# for LLM consumption. Two interchangeable backends:
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# fails
|
|
15
|
-
#
|
|
16
|
-
#
|
|
10
|
+
# for LLM consumption. Two interchangeable backends: a local SearXNG
|
|
11
|
+
# instance — the default path for everyone — and TinyFish's hosted
|
|
12
|
+
# search API, strictly opt-in (SEARCH_BACKEND=tinyfish + a free API
|
|
13
|
+
# key) for users who'd rather hold a key than run SearXNG. When the
|
|
14
|
+
# tinyfish backend fails, an explicitly configured SearXNG endpoint is
|
|
15
|
+
# still the automatic fallback. The capability layer: one entry point
|
|
16
|
+
# (WebSearch.search). Tool framing — name, parameter schema, result
|
|
17
|
+
# wrapping — lives with the consumers (the MCP server, the agents)
|
|
18
|
+
# that call this library.
|
|
17
19
|
module WebSearch
|
|
18
20
|
# Search errors that carry engine diagnostics — the agent needs to
|
|
19
21
|
# know *which* engines failed and *why* (CAPTCHA, timeout, suspended)
|
|
@@ -46,12 +48,15 @@ module Ask
|
|
|
46
48
|
# unfiltered.
|
|
47
49
|
TIME_RANGES = %w[day week month year].freeze
|
|
48
50
|
|
|
49
|
-
# TinyFish's hosted search endpoint —
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
51
|
+
# TinyFish's hosted search endpoint — available when the tinyfish
|
|
52
|
+
# backend is selected (SEARCH_BACKEND=tinyfish). The bare host root
|
|
53
|
+
# per the official docs curl example; the /client.search.query path
|
|
54
|
+
# seen on the marketing pages 404s.
|
|
53
55
|
TINYFISH_SEARCH_URL = "https://api.search.tinyfish.ai"
|
|
54
56
|
|
|
57
|
+
# The selectable backends (see .backend for how one is chosen).
|
|
58
|
+
BACKENDS = %w[searxng tinyfish].freeze
|
|
59
|
+
|
|
55
60
|
# Our time_range → TinyFish's recency_minutes (SearXNG's month/year
|
|
56
61
|
# are approximate engine filters anyway; these are the equivalents).
|
|
57
62
|
TIME_RANGE_TO_MINUTES = {
|
|
@@ -72,13 +77,58 @@ module Ask
|
|
|
72
77
|
"research_paper" => "research_paper"
|
|
73
78
|
}.freeze
|
|
74
79
|
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
# The search backend — :searxng (the default path for everyone) or
|
|
81
|
+
# :tinyfish (opt-in for users who'd rather hold an API key than run
|
|
82
|
+
# SearXNG). Selection precedence:
|
|
83
|
+
#
|
|
84
|
+
# 1. TINYFISH_SEARCH=0 — a hard-off that forces :searxng
|
|
85
|
+
# 2. backend= setter (Ask::WebSearch.backend = :tinyfish)
|
|
86
|
+
# 3. SEARCH_BACKEND env ("searxng" | "tinyfish")
|
|
87
|
+
# 4. default :searxng
|
|
88
|
+
def self.backend
|
|
89
|
+
return :searxng if ENV["TINYFISH_SEARCH"] == "0"
|
|
90
|
+
return @backend if defined?(@backend) && @backend
|
|
91
|
+
|
|
92
|
+
raw = ENV["SEARCH_BACKEND"]
|
|
93
|
+
return :searxng if raw.to_s.empty?
|
|
94
|
+
|
|
95
|
+
normalize_backend(raw)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Selects the backend in code (:searxng / :tinyfish, or nil to fall
|
|
99
|
+
# back to env/default). Raises ArgumentError for anything else —
|
|
100
|
+
# config-time failure instead of a silently ignored value.
|
|
101
|
+
def self.backend=(name)
|
|
102
|
+
@backend = name.nil? ? nil : normalize_backend(name)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.normalize_backend(name)
|
|
106
|
+
value = name.to_s.strip.downcase
|
|
107
|
+
unless BACKENDS.include?(value)
|
|
108
|
+
raise ArgumentError, "invalid backend #{name.inspect} — use one of: #{BACKENDS.join(', ')}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
value.to_sym
|
|
112
|
+
end
|
|
113
|
+
private_class_method :normalize_backend
|
|
80
114
|
|
|
81
|
-
|
|
115
|
+
# The TinyFish API key: ask-auth's chain when that gem is present
|
|
116
|
+
# (env override → ~/.ask/credentials.yml → …), raw ENV otherwise.
|
|
117
|
+
# nil when nothing resolves.
|
|
118
|
+
def self.tinyfish_api_key
|
|
119
|
+
return ENV["TINYFISH_API_KEY"] unless defined?(Ask::Auth)
|
|
120
|
+
|
|
121
|
+
Ask::Auth.resolve(:tinyfish_api_key)
|
|
122
|
+
rescue Ask::Auth::MissingCredential
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# True when searches should go to TinyFish — i.e. the backend was
|
|
127
|
+
# explicitly selected. Read fresh on every call so tests and config
|
|
128
|
+
# reloads don't need memo resets. Holding a key alone does NOT
|
|
129
|
+
# opt in: SearXNG stays the default path.
|
|
130
|
+
def self.use_tinyfish?
|
|
131
|
+
backend == :tinyfish
|
|
82
132
|
end
|
|
83
133
|
|
|
84
134
|
# True when a SearXNG endpoint was configured explicitly (SEARXNG_URL
|
|
@@ -139,14 +189,15 @@ module Ask
|
|
|
139
189
|
end
|
|
140
190
|
|
|
141
191
|
# The full response: { results: [...], unresponsive: [[name,
|
|
142
|
-
# reason], ...] }. Routes to TinyFish when #use_tinyfish
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
# raw or
|
|
192
|
+
# reason], ...] }. Routes to TinyFish when #use_tinyfish? (the
|
|
193
|
+
# opt-in tinyfish backend — a keyless selection raises its
|
|
194
|
+
# onboarding error; a transport failure falls back to SearXNG when
|
|
195
|
+
# explicitly configured), otherwise to SearXNG — the default path
|
|
196
|
+
# for everyone. Same retry logic either way, but preserves the
|
|
197
|
+
# engine diagnostics the caller needs to explain an empty result.
|
|
198
|
+
# time_range / categories are normalized by the backends that build
|
|
199
|
+
# requests — idempotent, so callers may pass raw or
|
|
200
|
+
# already-normalized values.
|
|
150
201
|
def self.search_raw(query, time_range: nil, categories: nil)
|
|
151
202
|
return searxng_search_raw(query, time_range: time_range, categories: categories) unless use_tinyfish?
|
|
152
203
|
|
|
@@ -195,6 +246,14 @@ module Ask
|
|
|
195
246
|
# catches). time_range maps to recency_minutes; the FIRST
|
|
196
247
|
# recognized categories value maps to domain_type.
|
|
197
248
|
def self.tinyfish_search_raw(query, time_range: nil, categories: nil)
|
|
249
|
+
key = tinyfish_api_key
|
|
250
|
+
if key.to_s.empty?
|
|
251
|
+
raise Error, "TinyFish backend selected (SEARCH_BACKEND=tinyfish) but no API key resolves. " \
|
|
252
|
+
"Get a free key at https://agent.tinyfish.ai/api-keys, then add " \
|
|
253
|
+
"`tinyfish_api_key: <key>` to ~/.ask/credentials.yml or export " \
|
|
254
|
+
"TINYFISH_API_KEY — or drop SEARCH_BACKEND to stay on the default SearXNG path."
|
|
255
|
+
end
|
|
256
|
+
|
|
198
257
|
params = { query: query }
|
|
199
258
|
normalized_range = normalize_time_range(time_range)
|
|
200
259
|
params[:recency_minutes] = TIME_RANGE_TO_MINUTES[normalized_range] if normalized_range
|
|
@@ -212,7 +271,7 @@ module Ask
|
|
|
212
271
|
http.open_timeout = 5
|
|
213
272
|
http.read_timeout = 10
|
|
214
273
|
req = Net::HTTP::Get.new(uri)
|
|
215
|
-
req["X-API-Key"] =
|
|
274
|
+
req["X-API-Key"] = key
|
|
216
275
|
req["User-Agent"] = "ask-web-search/#{Ask::WebSearch::VERSION}"
|
|
217
276
|
res = http.request(req)
|
|
218
277
|
raise Error, "TinyFish returned #{res.code}: #{res.body[0, 200]}" unless res.code.start_with?("2")
|
|
@@ -332,3 +391,15 @@ begin
|
|
|
332
391
|
rescue LoadError => e
|
|
333
392
|
raise unless e.path == "ask-tools"
|
|
334
393
|
end
|
|
394
|
+
|
|
395
|
+
# ask-auth is an OPTIONAL credential source for the TinyFish key: when
|
|
396
|
+
# present, #tinyfish_api_key resolves through Ask::Auth (env override →
|
|
397
|
+
# ~/.ask/credentials.yml → …) instead of raw ENV alone, so the key can
|
|
398
|
+
# live in one canonical 0600 file instead of every config that spawns a
|
|
399
|
+
# server. Only the ask-auth miss is swallowed; any other LoadError is
|
|
400
|
+
# real.
|
|
401
|
+
begin
|
|
402
|
+
require "ask-auth"
|
|
403
|
+
rescue LoadError => e
|
|
404
|
+
raise unless e.path == "ask-auth"
|
|
405
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-web-search
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ask-auth
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.3'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.3'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: vcr
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,11 +93,12 @@ dependencies:
|
|
|
79
93
|
- - "~>"
|
|
80
94
|
- !ruby/object:Gem::Version
|
|
81
95
|
version: '13.0'
|
|
82
|
-
description: 'Web search returning results as clean numbered markdown. Two
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(WebSearch.search); the native
|
|
86
|
-
that registers when
|
|
96
|
+
description: 'Web search returning results as clean numbered markdown. Two selectable
|
|
97
|
+
backends: a local SearXNG instance (SEARXNG_URL, the default path for everyone)
|
|
98
|
+
and TinyFish''s hosted API (opt-in: SEARCH_BACKEND=tinyfish plus a TINYFISH_API_KEY
|
|
99
|
+
or ask-auth credentials key). The capability layer (WebSearch.search); the native
|
|
100
|
+
Ask::Tools::WebSearch agent tool is an optional integration that registers when
|
|
101
|
+
ask-tools is present.'
|
|
87
102
|
email:
|
|
88
103
|
- kaka@myrrlabs.com
|
|
89
104
|
executables: []
|