ask-web-fetch 0.5.9 → 0.6.1
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_fetch/backends/crawl4ai.rb +6 -0
- data/lib/ask/web_fetch/backends/jina.rb +6 -1
- data/lib/ask/web_fetch/tool.rb +36 -5
- data/lib/ask/web_fetch/version.rb +1 -1
- 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: 15d4465ae7ca7805003043af5b97e96ddc8c2ba19618daab313377f4a529bd54
|
|
4
|
+
data.tar.gz: 9f83f8c2258dc973457158e7d7e2288afc43403221453545a4d19c97440b85ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b34dfd5f3d45b827cf8882d3c9c3b47e05ea8c16622849b2a41fc51b8c5f005f0b149ef83a87db57f4167470e85911ec58f72830afded9a84ab707ea08dcbc68
|
|
7
|
+
data.tar.gz: c8245c574d480179cd3b8ad67f1049b096fdf298f0886a790cb557f698c11e8dbaed9bb4336de4ccf84882e03eb8cf716830db069988f310c35cf4202cf01d60
|
|
@@ -55,6 +55,12 @@ module Ask
|
|
|
55
55
|
raise FetchError, "challenge page at #{url}" if challenge_page?(body)
|
|
56
56
|
|
|
57
57
|
page = to_page(body, url)
|
|
58
|
+
# A registrar parking page renders fine in headless Chrome too —
|
|
59
|
+
# the shared detector (0.5.7) catches the text markers that
|
|
60
|
+
# survive conversion, so the ad is rejected, not returned as
|
|
61
|
+
# the site's content. Crawl4AI leads the default chain, so this
|
|
62
|
+
# check is what keeps parked domains out of every result.
|
|
63
|
+
raise ParkedDomainError, "parked domain at #{url} — registrar parking page, not site content" if parked_domain?(page[:content])
|
|
58
64
|
raise EmptyContentError, "no readable content at #{url}" unless usable_content?(page[:content])
|
|
59
65
|
|
|
60
66
|
page
|
|
@@ -39,8 +39,13 @@ module Ask
|
|
|
39
39
|
# links, resolved against the requested URL. Content runs
|
|
40
40
|
# through the same Markdown.clean as the converting backends,
|
|
41
41
|
# so decorative symbol noise is stripped here too; a page
|
|
42
|
-
# whose only "content" was noise falls through as empty.
|
|
42
|
+
# whose only "content" was noise falls through as empty. A
|
|
43
|
+
# registrar parking page renders fine through Jina — the
|
|
44
|
+
# shared detector (0.5.7) catches the text markers that
|
|
45
|
+
# survive conversion, so the ad is rejected, not returned as
|
|
46
|
+
# the site's content.
|
|
43
47
|
content = Markdown.clean(body)
|
|
48
|
+
raise ParkedDomainError, "parked domain at #{url} — registrar parking page, not site content" if parked_domain?(content)
|
|
44
49
|
raise EmptyContentError, 'empty response from Jina' unless usable_content?(content)
|
|
45
50
|
|
|
46
51
|
{ title: nil, description: nil, content: content, outlinks: markdown_outlinks(body, url) }
|
data/lib/ask/web_fetch/tool.rb
CHANGED
|
@@ -21,6 +21,13 @@ module Ask
|
|
|
21
21
|
class WebFetch < Ask::Tool
|
|
22
22
|
DEFAULT_MAX_CHARS = 20_000
|
|
23
23
|
|
|
24
|
+
# Errors that mean "the URL is dead" — no amount of retrying changes
|
|
25
|
+
# the answer. When EVERY backend failed this way, the aggregate
|
|
26
|
+
# re-raises as FetchError so callers can fail fast; any transient
|
|
27
|
+
# failure in the mix (timeout, 5xx, empty render) keeps the base
|
|
28
|
+
# Error, which recovers on retry.
|
|
29
|
+
DETERMINISTIC = [Ask::WebFetch::FetchError, Ask::WebFetch::EmptyContentError].freeze
|
|
30
|
+
|
|
24
31
|
# Backend chain, tried in order. Crawl4AI leads when configured
|
|
25
32
|
# (CRAWL4AI_URL), so a present self-hosted renderer is preferred;
|
|
26
33
|
# otherwise Local, with Jina as the last resort, and Browser appended
|
|
@@ -37,9 +44,34 @@ module Ask
|
|
|
37
44
|
end
|
|
38
45
|
end
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
class << self
|
|
48
|
+
attr_writer :backends
|
|
49
|
+
|
|
50
|
+
# Collapses every backend's failure into ONE error whose class
|
|
51
|
+
# carries the best explanation. Precedence, most definitive first: a
|
|
52
|
+
# parked domain beats an empty shell (the shell IS the parking ad's
|
|
53
|
+
# shell — Local sees the JS redirect stub, Browser the lander),
|
|
54
|
+
# empty beats a dead 4xx (the page existed, it just had no content),
|
|
55
|
+
# and any deterministic explanation beats a transient one (transient
|
|
56
|
+
# keeps the retryable base Error). Clients read the class:
|
|
57
|
+
# ParkedDomainError / EmptyContentError / FetchError are terminal —
|
|
58
|
+
# retrying never changes the answer; Error may recover on retry.
|
|
59
|
+
def collapse(failures, url)
|
|
60
|
+
detail = failures.map { |backend, e| "#{backend.backend_name}: #{e.message}" }.join('; ')
|
|
61
|
+
message = "all web fetch backends failed for #{url} (#{detail})"
|
|
62
|
+
|
|
63
|
+
classes = failures.map { |_, e| e.class }
|
|
64
|
+
if classes.any? { |k| k <= Ask::WebFetch::ParkedDomainError }
|
|
65
|
+
raise Ask::WebFetch::ParkedDomainError, message
|
|
66
|
+
end
|
|
67
|
+
if classes.any? { |k| k <= Ask::WebFetch::EmptyContentError }
|
|
68
|
+
raise Ask::WebFetch::EmptyContentError, message
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
deterministic = failures.all? { |_, e| DETERMINISTIC.any? { |klass| e.is_a?(klass) } }
|
|
72
|
+
raise(deterministic ? Ask::WebFetch::FetchError : Ask::WebFetch::Error, message)
|
|
42
73
|
end
|
|
74
|
+
end
|
|
43
75
|
|
|
44
76
|
description 'Fetch a URL and return its content as clean markdown for LLM consumption. ' \
|
|
45
77
|
'Use this to read web pages, articles, and documentation.'
|
|
@@ -68,10 +100,9 @@ module Ask
|
|
|
68
100
|
self.class.backends.each do |backend_class|
|
|
69
101
|
return backend_class.new.fetch(url)
|
|
70
102
|
rescue Ask::WebFetch::Error => e
|
|
71
|
-
failures <<
|
|
103
|
+
failures << [backend_class, e]
|
|
72
104
|
end
|
|
73
|
-
|
|
74
|
-
"all web fetch backends failed for #{url} (#{failures.join('; ')})"
|
|
105
|
+
self.class.collapse(failures, url)
|
|
75
106
|
end
|
|
76
107
|
|
|
77
108
|
def format(page, url)
|