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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56161d32f9534d056405cc0fd382fd6c07be300ae35084e0bea4a5017b1fe353
4
- data.tar.gz: cefb6a830bff2b3998174d2b9f9d75d4bcf42f00958766b55bc9d4679c2d8c17
3
+ metadata.gz: d8bd1c41d870cf5aa9f13332ba6b2d43fbcf6d6bbebb13e5390f2d9e8c0f6529
4
+ data.tar.gz: b1cc5035874cd122e9d4a8d1c4feec1bc1d146d12842714cbfe4672c43a4dba6
5
5
  SHA512:
6
- metadata.gz: 1d0b196f7b4be2b6f0da74c271aa2c2446c5d5d6ec45b16a0b4b9eac2a46abf37e8461895c1f679b3b78041f5d472c6ed6590a6a6347da1e2db636e422485a29
7
- data.tar.gz: fe6766f5f91bd11b82ea6d555c01248bbdbc93c152b657ce042c1011967d75994bfff13dce23730360bdde5eed3c14e28859749764f34c12fee81afaebf04fb2
6
+ metadata.gz: b80273643aa1f7e4a875832dcef9bfec8a2df3886af9be0f92651f55e8b6873364b59586a9bfa9e6ba5eb95394b686425ca7e7032ad69677ba1fab085fb42498
7
+ data.tar.gz: abae84930f02b3adbe06a136b676a79bb8e3ae2416aa7c94fd94da2d22862672ae35818b92434cb03a163e7a2770f63bfcfd7d10766a447a5f4de97fdf35a57d
@@ -1,5 +1,5 @@
1
1
  module Ask
2
2
  module WebSearch
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
@@ -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
- # ("No results found." when SearXNG found nothing). Raises on
41
- # connection/HTTP failures the caller decides how to surface them.
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
- format_results(search_results(query))
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
- parse_results(JSON.parse(res.body))
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
- def self.parse_results(data)
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
- results.uniq { |r| r[:url] }
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
 
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.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto