ask-web-fetch 0.7.0 → 0.7.2
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/browser.rb +17 -1
- data/lib/ask/web_fetch/backends/local.rb +14 -5
- data/lib/ask/web_fetch/tool.rb +44 -0
- data/lib/ask/web_fetch/version.rb +1 -1
- data/lib/ask/web_fetch.rb +13 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ad48d09aa8869b4193a703ad6a760753c2767c9e26a6919b1405a5fb186c7a4
|
|
4
|
+
data.tar.gz: 52960016a3d4740f4913d4879cbf80edacf95e4ffc07601e6dde0d80515ffe81
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa6d778abc6918bb7ae839d4f9c96dafe215014a8b2f4efc8027ab1e49d287e3ca57204f54f013531e38dff7393ac23f6da9619b3afd48efae8b6f7e42dfa0d9
|
|
7
|
+
data.tar.gz: 7d4e5fe0e70b28cc3349ec96d071d4e1cc7a46c2c21ca8f41c35968d8b15221854c2418fc72ae2a8e56e97c07930592b2ee5b79da921c4a4ccb4fe6990c7e938
|
|
@@ -96,6 +96,12 @@ module Ask
|
|
|
96
96
|
browser_mutex.synchronize { @browser ||= build_browser }
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
# Drops the shared browser so the next use builds a fresh
|
|
100
|
+
# session — called when the current one died mid-fetch.
|
|
101
|
+
def reset_browser
|
|
102
|
+
browser_mutex.synchronize { @browser = nil }
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
def content_filter
|
|
100
106
|
@content_filter ||= ContentFilter.default
|
|
101
107
|
end
|
|
@@ -162,7 +168,17 @@ module Ask
|
|
|
162
168
|
|
|
163
169
|
page = self.class.browser.create_page
|
|
164
170
|
fetch_attempt(page, url)
|
|
165
|
-
rescue Ferrum::
|
|
171
|
+
rescue Ferrum::DeadBrowserError => e
|
|
172
|
+
# The shared browser died mid-fetch (the browserless server
|
|
173
|
+
# killed the session's browser). A dead browser is transient —
|
|
174
|
+
# the next session starts a fresh one — so reconnect and retry
|
|
175
|
+
# ONCE instead of failing the page (observed 2026-08-14: the
|
|
176
|
+
# server's session limits killed browsers and pages were
|
|
177
|
+
# silently dropped).
|
|
178
|
+
self.class.reset_browser
|
|
179
|
+
page = self.class.browser.create_page
|
|
180
|
+
fetch_attempt(page, url)
|
|
181
|
+
rescue Ferrum::TimeoutError, Ferrum::ProcessTimeoutError => e
|
|
166
182
|
raise TimeoutError, "#{e.class}: #{e.message}"
|
|
167
183
|
rescue Ferrum::StatusError => e
|
|
168
184
|
raise FetchError, "browser could not load #{url}: #{e.message}"
|
|
@@ -63,7 +63,7 @@ module Ask
|
|
|
63
63
|
# and keeps a partial page from ever being stored as the real
|
|
64
64
|
# thing. Two detectors: known framework markers, or a large
|
|
65
65
|
# HTML page with almost no server-rendered text.
|
|
66
|
-
if js_app_shell?(body) && page[:content].length <
|
|
66
|
+
if js_app_shell?(body) && page[:content].length < SHELL_DEFER_THRESHOLD
|
|
67
67
|
raise EmptyContentError,
|
|
68
68
|
"JS-app shell at #{url} — server HTML renders only #{page[:content].length} chars; a rendering backend is required"
|
|
69
69
|
end
|
|
@@ -82,10 +82,19 @@ module Ask
|
|
|
82
82
|
# specific footprints of React/Vue/Next/Nuxt app shells.
|
|
83
83
|
JS_APP_SHELL_MARKERS = /id=["'](?:root|app|__next|site-content)["']|__NEXT_DATA__|window\.__NUXT__|ng-app|data-reactroot/
|
|
84
84
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# the
|
|
85
|
+
# A framework marker (React/Vue/Next) alone is NOT emptiness: a
|
|
86
|
+
# marked page can server-render real content (careers.abb job
|
|
87
|
+
# pages: 2,162 chars of job description) and must be stored as-is
|
|
88
|
+
# when the rendering backends cannot do better — dropping it lost
|
|
89
|
+
# real pages (2026-08-14). The shell deferral fires only when the
|
|
90
|
+
# extraction is genuinely little: below this, the page is a true
|
|
91
|
+
# shell (airbnb: 613KB HTML -> 143 chars). Tunable; the chain
|
|
92
|
+
# turns the signal into "prefer Browser for this URL".
|
|
93
|
+
SHELL_DEFER_THRESHOLD = 500
|
|
94
|
+
|
|
95
|
+
# The ratio detector's own bar, kept for compatibility with the
|
|
96
|
+
# comment below (large HTML + near-empty text is a shell whatever
|
|
97
|
+
# the framework).
|
|
89
98
|
SHELL_CONTENT_THRESHOLD = 4_000
|
|
90
99
|
|
|
91
100
|
# A page whose server HTML is large but yields almost no text is a
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ask-tools'
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
# The native agent tool for web fetch — the Ask::Tool framing of the
|
|
8
|
+
# library. Consumed WITHOUT MCP by agent frameworks (ask-agent's
|
|
9
|
+
# `tool: :web_fetch`, ask-app-server, llm-proxy) that resolve tools
|
|
10
|
+
# from the Ask::Tools registry. It is a pure adapter: the capability
|
|
11
|
+
# (chain, collapse, format) lives in Ask::WebFetch, and this file is
|
|
12
|
+
# loaded only when ask-tools is present (see lib/ask/web_fetch.rb), so
|
|
13
|
+
# the library itself never depends on it.
|
|
14
|
+
class WebFetch < Ask::Tool
|
|
15
|
+
# Chain configuration delegates to the library — swap or extend
|
|
16
|
+
# backends here or via Ask::WebFetch.backends.
|
|
17
|
+
def self.backends
|
|
18
|
+
Ask::WebFetch.backends
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.backends=(chain)
|
|
22
|
+
Ask::WebFetch.backends = chain
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
description 'Fetch a URL and return its content as clean markdown for LLM consumption. ' \
|
|
26
|
+
'Use this to read web pages, articles, and documentation.'
|
|
27
|
+
|
|
28
|
+
params(
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
url: { type: 'string', description: 'The URL to fetch' },
|
|
32
|
+
max_chars: { type: 'integer', description: 'Maximum number of characters to return (default 20000)' }
|
|
33
|
+
},
|
|
34
|
+
required: ['url']
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def execute(url:, max_chars: Ask::WebFetch::DEFAULT_MAX_CHARS)
|
|
38
|
+
Ask::Result.ok(data: Ask::WebFetch.fetch(url, max_chars: max_chars))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Ask::Tools.register(Ask::Tools::WebFetch)
|
data/lib/ask/web_fetch.rb
CHANGED
|
@@ -10,6 +10,19 @@ require_relative 'web_fetch/backends/crawl4ai'
|
|
|
10
10
|
require_relative 'web_fetch/backends/jina'
|
|
11
11
|
require_relative 'web_fetch/backends/browser'
|
|
12
12
|
|
|
13
|
+
# The native agent tool (Ask::Tools::WebFetch) is an OPTIONAL integration:
|
|
14
|
+
# it loads and registers only when ask-tools is present. The library works
|
|
15
|
+
# standalone — backend-only consumers (crawlers, pipelines) pay nothing —
|
|
16
|
+
# while agent frameworks (ask-agent, ask-app-server, llm-proxy) all ship
|
|
17
|
+
# ask-tools and get the registry tool with no extra step. Only the
|
|
18
|
+
# ask-tools miss is swallowed; any other LoadError is real.
|
|
19
|
+
begin
|
|
20
|
+
require 'ask-tools'
|
|
21
|
+
require_relative 'web_fetch/tool'
|
|
22
|
+
rescue LoadError => e
|
|
23
|
+
raise unless e.path == 'ask-tools'
|
|
24
|
+
end
|
|
25
|
+
|
|
13
26
|
module Ask
|
|
14
27
|
# Fetches a URL and returns its content as clean markdown for LLM
|
|
15
28
|
# consumption. The capability layer: a pluggable backend chain, a
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-web-fetch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -107,6 +107,20 @@ dependencies:
|
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: '3.26'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: ask-tools
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.1'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.1'
|
|
110
124
|
description: 'Fetches a URL and converts its content to clean markdown for LLM consumption.
|
|
111
125
|
A pluggable backend chain: pure Ruby httpx + Nokogiri + reverse_markdown by default,
|
|
112
126
|
a Jina Reader fallback for JS-rendered or blocked pages, and a real-Chrome fallback
|
|
@@ -133,6 +147,7 @@ files:
|
|
|
133
147
|
- lib/ask/web_fetch/http.rb
|
|
134
148
|
- lib/ask/web_fetch/markdown.rb
|
|
135
149
|
- lib/ask/web_fetch/noise_filter.rb
|
|
150
|
+
- lib/ask/web_fetch/tool.rb
|
|
136
151
|
- lib/ask/web_fetch/version.rb
|
|
137
152
|
homepage: https://github.com/ask-rb/ask-web-fetch
|
|
138
153
|
licenses:
|
|
@@ -156,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
171
|
- !ruby/object:Gem::Version
|
|
157
172
|
version: '0'
|
|
158
173
|
requirements: []
|
|
159
|
-
rubygems_version: 4.0.
|
|
174
|
+
rubygems_version: 4.0.18
|
|
160
175
|
specification_version: 4
|
|
161
176
|
summary: Web fetch library for the ask-rb ecosystem
|
|
162
177
|
test_files: []
|