ask-web-search 0.4.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/lib/ask/web_search/version.rb +1 -1
- data/lib/ask/web_search.rb +58 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8bd1c41d870cf5aa9f13332ba6b2d43fbcf6d6bbebb13e5390f2d9e8c0f6529
|
|
4
|
+
data.tar.gz: b1cc5035874cd122e9d4a8d1c4feec1bc1d146d12842714cbfe4672c43a4dba6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b80273643aa1f7e4a875832dcef9bfec8a2df3886af9be0f92651f55e8b6873364b59586a9bfa9e6ba5eb95394b686425ca7e7032ad69677ba1fab085fb42498
|
|
7
|
+
data.tar.gz: abae84930f02b3adbe06a136b676a79bb8e3ae2416aa7c94fd94da2d22862672ae35818b92434cb03a163e7a2770f63bfcfd7d10766a447a5f4de97fdf35a57d
|
data/lib/ask/web_search.rb
CHANGED
|
@@ -12,6 +12,16 @@ module Ask
|
|
|
12
12
|
# framing — name, parameter schema, result wrapping — lives with the
|
|
13
13
|
# consumers (the MCP server, the agents) that call this library.
|
|
14
14
|
module WebSearch
|
|
15
|
+
# Search errors that carry engine diagnostics — the agent needs to
|
|
16
|
+
# know *which* engines failed and *why* (CAPTCHA, timeout, suspended)
|
|
17
|
+
# instead of just "No results found."
|
|
18
|
+
class Error < StandardError; end
|
|
19
|
+
|
|
20
|
+
# SearXNG answered but every engine failed (CAPTCHA, timeout,
|
|
21
|
+
# suspended). The message lists which engines failed and why, plus
|
|
22
|
+
# what the agent can try next.
|
|
23
|
+
class AllEnginesFailedError < Error; end
|
|
24
|
+
|
|
15
25
|
# Local SearXNG endpoint. Defaults to the standard local instance;
|
|
16
26
|
# override with SEARXNG_URL or WebSearch.searxng_url=.
|
|
17
27
|
def self.searxng_url
|
|
@@ -36,11 +46,18 @@ module Ask
|
|
|
36
46
|
@max_retries = val
|
|
37
47
|
end
|
|
38
48
|
|
|
39
|
-
# Searches +query+ and returns the results as numbered markdown
|
|
40
|
-
#
|
|
41
|
-
#
|
|
49
|
+
# Searches +query+ and returns the results as numbered markdown.
|
|
50
|
+
# "No results found." only when SearXNG answered cleanly with zero
|
|
51
|
+
# results and no engine failures. When engines failed, raises
|
|
52
|
+
# AllEnginesFailedError with per-engine diagnostics (see
|
|
53
|
+
# #format_engine_error). Raises on connection/HTTP failures — the
|
|
54
|
+
# caller decides how to surface them.
|
|
42
55
|
def self.search(query)
|
|
43
|
-
|
|
56
|
+
response = search_raw(query)
|
|
57
|
+
raise AllEnginesFailedError, format_engine_error(query, response) if response[:results].empty? &&
|
|
58
|
+
response[:unresponsive].any?
|
|
59
|
+
|
|
60
|
+
format_results(response[:results])
|
|
44
61
|
end
|
|
45
62
|
|
|
46
63
|
# The raw result list: { url:, title:, content: } entries from the
|
|
@@ -48,6 +65,13 @@ module Ask
|
|
|
48
65
|
# max_retries times on connection/HTTP failures with exponential
|
|
49
66
|
# backoff. Set max_retries to 0 to disable.
|
|
50
67
|
def self.search_results(query)
|
|
68
|
+
search_raw(query)[:results]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The full SearXNG response: { results: [...], unresponsive: [[name,
|
|
72
|
+
# reason], ...] }. Same retry logic as #search_results, but preserves
|
|
73
|
+
# the engine diagnostics the caller needs to explain an empty result.
|
|
74
|
+
def self.search_raw(query)
|
|
51
75
|
uri = URI("#{searxng_url}/search?q=#{URI.encode_www_form_component(query)}&format=json")
|
|
52
76
|
retries = max_retries || 0
|
|
53
77
|
attempt = 0
|
|
@@ -61,7 +85,7 @@ module Ask
|
|
|
61
85
|
res = http.request(req)
|
|
62
86
|
raise "SearXNG returned #{res.code}: #{res.body}" unless res.code.start_with?("2")
|
|
63
87
|
|
|
64
|
-
|
|
88
|
+
parse_response(JSON.parse(res.body))
|
|
65
89
|
rescue StandardError
|
|
66
90
|
raise if attempt > retries
|
|
67
91
|
|
|
@@ -70,7 +94,26 @@ module Ask
|
|
|
70
94
|
end
|
|
71
95
|
end
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
# Formats the engine failure into an actionable message: which engines
|
|
98
|
+
# failed and why, plus what the agent can try next. Keeps the raw
|
|
99
|
+
# SearXNG reason strings (CAPTCHA, timeout, suspended) — the caller
|
|
100
|
+
# can see exactly what happened.
|
|
101
|
+
def self.format_engine_error(query, response)
|
|
102
|
+
lines = ["No search results for #{query.inspect} — all #{response[:unresponsive].size} engine(s) failed:"]
|
|
103
|
+
response[:unresponsive].each do |name, reason|
|
|
104
|
+
lines << "- #{name}: #{reason}"
|
|
105
|
+
end
|
|
106
|
+
lines << "Try: a simpler query, or ask-web-fetch for a known URL."
|
|
107
|
+
lines.join("\n")
|
|
108
|
+
end
|
|
109
|
+
private_class_method :format_engine_error
|
|
110
|
+
|
|
111
|
+
# Parses the full SearXNG JSON response into { results:, unresponsive: }.
|
|
112
|
+
# results are { url:, title:, content: } entries from results and
|
|
113
|
+
# infoboxes, deduplicated by url. unresponsive is the raw
|
|
114
|
+
# [[engine_name, reason], ...] list SearXNG returns for failed
|
|
115
|
+
# engines (e.g. [["duckduckgo", "CAPTCHA"], ["mojeek", "timeout"]]).
|
|
116
|
+
def self.parse_response(data)
|
|
74
117
|
results = []
|
|
75
118
|
data.fetch("results", []).each do |r|
|
|
76
119
|
results << { url: r["url"], title: r["title"], content: r["content"] }
|
|
@@ -78,7 +121,15 @@ module Ask
|
|
|
78
121
|
data.fetch("infoboxes", []).each do |ib|
|
|
79
122
|
results << { url: ib["id"], title: ib["infobox"], content: ib["content"] }
|
|
80
123
|
end
|
|
81
|
-
|
|
124
|
+
{
|
|
125
|
+
results: results.uniq { |r| r[:url] },
|
|
126
|
+
unresponsive: Array(data.fetch("unresponsive_engines", []))
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
private_class_method :parse_response
|
|
130
|
+
|
|
131
|
+
def self.parse_results(data)
|
|
132
|
+
parse_response(data)[:results]
|
|
82
133
|
end
|
|
83
134
|
private_class_method :parse_results
|
|
84
135
|
|