ask-web-search 0.2.3 → 0.3.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 +19 -43
- data/lib/ask/web_search/tool.rb +12 -46
- data/lib/ask/web_search/version.rb +1 -1
- data/lib/ask/web_search.rb +83 -1
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe356e9cdc2a7ee736d51ad93d36dfcaaef72168b0a4b3d98bc19945b919dd4a
|
|
4
|
+
data.tar.gz: e334c6903ab48de0a78fee36c07933487dcab49648cc31bbe3adfd46653de01a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1fc95c583e7fc95a15ef32b7e1253302c6e2ba1edee41781445c912a1d8631cba839dac044fb94a1cc20d51d219960102cccec5d75eecbf1cef92de0f92e559
|
|
7
|
+
data.tar.gz: f56f4d8df11e3de5de981e0f5d182d8491cb12840f12dc4e546986fd9c4f6b82668313cd4270468215f460e5a29ece4ecb0e28bc4c301c88dfd39dc1656ae7e6
|
data/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# ask-web-search
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[](https://badge.fury.io/rb/ask-web-search)
|
|
4
|
+
|
|
5
|
+
A web search tool for the ask-rb ecosystem. It provides
|
|
6
|
+
`Ask::Tools::WebSearch`, which searches the web via a local
|
|
7
|
+
[SearXNG](https://docs.searxng.org/) instance and returns numbered markdown
|
|
8
|
+
results for LLM consumption. It has no Rails dependencies; it depends only on
|
|
9
|
+
ask-tools.
|
|
6
10
|
|
|
7
11
|
## Prerequisites
|
|
8
12
|
|
|
@@ -14,21 +18,11 @@ Start one with Docker:
|
|
|
14
18
|
docker run -d --name searxng -p 8888:8080 searxng/searxng
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
Or use the provided `docker-compose.yml
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
cd searxng
|
|
21
|
-
docker compose up -d
|
|
22
|
-
```
|
|
21
|
+
Or use the provided `docker-compose.yml` in the `searxng` directory of this
|
|
22
|
+
repository:
|
|
23
23
|
|
|
24
24
|
## Installation
|
|
25
25
|
|
|
26
|
-
```sh
|
|
27
|
-
gem install ask-web-search
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Or in your Gemfile:
|
|
31
|
-
|
|
32
26
|
```ruby
|
|
33
27
|
gem "ask-web-search"
|
|
34
28
|
```
|
|
@@ -43,7 +37,7 @@ export SEARXNG_URL=http://localhost:8888
|
|
|
43
37
|
|
|
44
38
|
Defaults to `http://localhost:8888`.
|
|
45
39
|
|
|
46
|
-
##
|
|
40
|
+
## Quick Start
|
|
47
41
|
|
|
48
42
|
```ruby
|
|
49
43
|
require "ask/web_search"
|
|
@@ -51,35 +45,12 @@ require "ask/web_search"
|
|
|
51
45
|
tool = Ask::Tools::WebSearch.new
|
|
52
46
|
result = tool.execute(query: "ruby programming language")
|
|
53
47
|
puts result
|
|
54
|
-
# => 1. Ruby — A Programmer's Best Friend
|
|
55
|
-
# https://www.ruby-lang.org
|
|
56
|
-
# Ruby is a dynamic, open-source programming language...
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### With ask-rb Chat
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
chat = Ask::Agent::Chat.new(
|
|
63
|
-
model: "deepseek-v4-flash",
|
|
64
|
-
tools: [Ask::Tools::WebSearch.new]
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
chat.ask("What's the latest on AI? Search the web.")
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### With ask-rb Agent
|
|
71
|
-
|
|
72
|
-
```ruby
|
|
73
|
-
agent = Ask::Agent.new(tools: [Ask::Tools::WebSearch])
|
|
74
|
-
agent.run("Find recent news about SpaceX")
|
|
75
48
|
```
|
|
76
49
|
|
|
77
|
-
## Output Format
|
|
78
|
-
|
|
79
50
|
Results are returned as a numbered markdown-like string:
|
|
80
51
|
|
|
81
52
|
```
|
|
82
|
-
1. Ruby
|
|
53
|
+
1. Ruby - A Programmer's Best Friend
|
|
83
54
|
https://www.ruby-lang.org
|
|
84
55
|
Ruby is a dynamic, open-source programming language...
|
|
85
56
|
|
|
@@ -90,11 +61,16 @@ Results are returned as a numbered markdown-like string:
|
|
|
90
61
|
|
|
91
62
|
If no results are found, returns `"No results found."`.
|
|
92
63
|
|
|
64
|
+
## Full documentation
|
|
65
|
+
|
|
66
|
+
The full ask-rb documentation lives at https://ask-rb.github.io/ask-docs.
|
|
67
|
+
[Core: Web Search](https://ask-rb.github.io/ask-docs/core/web-search) covers
|
|
68
|
+
ask-web-search in depth, including the ask-agent integration and the MCP
|
|
69
|
+
server. API reference: https://ask-rb.github.io/ask-docs/reference/api.
|
|
70
|
+
|
|
93
71
|
## Development
|
|
94
72
|
|
|
95
|
-
```
|
|
96
|
-
git clone https://github.com/ask-rb/ask-web-search
|
|
97
|
-
cd ask-web-search
|
|
73
|
+
```
|
|
98
74
|
bundle install
|
|
99
75
|
bundle exec rake test
|
|
100
76
|
```
|
data/lib/ask/web_search/tool.rb
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "ask-tools"
|
|
2
|
-
require "net/http"
|
|
3
|
-
require "uri"
|
|
4
|
-
require "json"
|
|
5
4
|
|
|
6
5
|
module Ask
|
|
7
6
|
module Tools
|
|
7
|
+
# The native agent tool for web search — the Ask::Tool framing of the
|
|
8
|
+
# library. Consumed WITHOUT MCP by agent frameworks (ask-agent's
|
|
9
|
+
# `tool: :web_search`, ask-app-server, llm-proxy) that resolve tools
|
|
10
|
+
# from the Ask::Tools registry. A pure adapter: the capability
|
|
11
|
+
# (search, parsing, formatting) lives in Ask::WebSearch, and this file
|
|
12
|
+
# loads only when ask-tools is present (see lib/ask/web_search.rb).
|
|
8
13
|
class WebSearch < Ask::Tool
|
|
9
14
|
def self.searxng_url
|
|
10
|
-
|
|
15
|
+
Ask::WebSearch.searxng_url
|
|
11
16
|
end
|
|
12
17
|
|
|
13
18
|
def self.searxng_url=(url)
|
|
14
|
-
|
|
19
|
+
Ask::WebSearch.searxng_url = url
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
description "Search the web for current information. Use this to get up-to-date results, recent events, or facts that may have changed."
|
|
@@ -25,49 +30,10 @@ module Ask
|
|
|
25
30
|
)
|
|
26
31
|
|
|
27
32
|
def execute(query:)
|
|
28
|
-
|
|
29
|
-
Ask::Result.ok(data: format_results(results))
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def search(query)
|
|
35
|
-
uri = URI("#{self.class.searxng_url}/search?q=#{URI.encode_www_form_component(query)}&format=json")
|
|
36
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
37
|
-
http.open_timeout = 5
|
|
38
|
-
http.read_timeout = 10
|
|
39
|
-
req = Net::HTTP::Get.new(uri)
|
|
40
|
-
req["User-Agent"] = "ask-web-search/1.0"
|
|
41
|
-
res = http.request(req)
|
|
42
|
-
raise "SearXNG returned #{res.code}: #{res.body}" unless res.code.start_with?("2")
|
|
43
|
-
|
|
44
|
-
data = JSON.parse(res.body)
|
|
45
|
-
parse_results(data)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def parse_results(data)
|
|
49
|
-
results = []
|
|
50
|
-
data.fetch("results", []).each do |r|
|
|
51
|
-
results << { url: r["url"], title: r["title"], content: r["content"] }
|
|
52
|
-
end
|
|
53
|
-
data.fetch("infoboxes", []).each do |ib|
|
|
54
|
-
results << { url: ib["id"], title: ib["infobox"], content: ib["content"] }
|
|
55
|
-
end
|
|
56
|
-
results.uniq { |r| r[:url] }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def format_results(results)
|
|
60
|
-
return "No results found." if results.empty?
|
|
61
|
-
|
|
62
|
-
results.each_with_index.map do |r, i|
|
|
63
|
-
line = "#{i + 1}. #{r[:title]}"
|
|
64
|
-
line += "\n #{r[:url]}"
|
|
65
|
-
line += "\n #{r[:content]}" if r[:content] && !r[:content].empty?
|
|
66
|
-
line
|
|
67
|
-
end.join("\n\n")
|
|
33
|
+
Ask::Result.ok(data: Ask::WebSearch.search(query))
|
|
68
34
|
end
|
|
69
35
|
end
|
|
70
36
|
end
|
|
71
37
|
end
|
|
72
38
|
|
|
73
|
-
Ask::Tools.register(Ask::Tools::WebSearch)
|
|
39
|
+
Ask::Tools.register(Ask::Tools::WebSearch)
|
data/lib/ask/web_search.rb
CHANGED
|
@@ -1,2 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
1
6
|
require_relative "web_search/version"
|
|
2
|
-
|
|
7
|
+
|
|
8
|
+
module Ask
|
|
9
|
+
# Searches the web via a local SearXNG instance and returns the results
|
|
10
|
+
# as clean, numbered markdown for LLM consumption. The capability layer:
|
|
11
|
+
# one entry point (WebSearch.search) and a configurable endpoint. Tool
|
|
12
|
+
# framing — name, parameter schema, result wrapping — lives with the
|
|
13
|
+
# consumers (the MCP server, the agents) that call this library.
|
|
14
|
+
module WebSearch
|
|
15
|
+
# Local SearXNG endpoint. Defaults to the standard local instance;
|
|
16
|
+
# override with SEARXNG_URL or WebSearch.searxng_url=.
|
|
17
|
+
def self.searxng_url
|
|
18
|
+
@searxng_url || ENV.fetch("SEARXNG_URL", "http://localhost:8888")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.searxng_url=(url)
|
|
22
|
+
@searxng_url = url
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Searches +query+ and returns the results as numbered markdown
|
|
26
|
+
# ("No results found." when SearXNG found nothing). Raises on
|
|
27
|
+
# connection/HTTP failures — the caller decides how to surface them.
|
|
28
|
+
def self.search(query)
|
|
29
|
+
format_results(search_results(query))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The raw result list: { url:, title:, content: } entries from the
|
|
33
|
+
# results and infoboxes, deduplicated by url.
|
|
34
|
+
def self.search_results(query)
|
|
35
|
+
uri = URI("#{searxng_url}/search?q=#{URI.encode_www_form_component(query)}&format=json")
|
|
36
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
37
|
+
http.open_timeout = 5
|
|
38
|
+
http.read_timeout = 10
|
|
39
|
+
req = Net::HTTP::Get.new(uri)
|
|
40
|
+
req["User-Agent"] = "ask-web-search/#{Ask::WebSearch::VERSION}"
|
|
41
|
+
res = http.request(req)
|
|
42
|
+
raise "SearXNG returned #{res.code}: #{res.body}" unless res.code.start_with?("2")
|
|
43
|
+
|
|
44
|
+
parse_results(JSON.parse(res.body))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.parse_results(data)
|
|
48
|
+
results = []
|
|
49
|
+
data.fetch("results", []).each do |r|
|
|
50
|
+
results << { url: r["url"], title: r["title"], content: r["content"] }
|
|
51
|
+
end
|
|
52
|
+
data.fetch("infoboxes", []).each do |ib|
|
|
53
|
+
results << { url: ib["id"], title: ib["infobox"], content: ib["content"] }
|
|
54
|
+
end
|
|
55
|
+
results.uniq { |r| r[:url] }
|
|
56
|
+
end
|
|
57
|
+
private_class_method :parse_results
|
|
58
|
+
|
|
59
|
+
def self.format_results(results)
|
|
60
|
+
return "No results found." if results.empty?
|
|
61
|
+
|
|
62
|
+
results.each_with_index.map do |r, i|
|
|
63
|
+
line = "#{i + 1}. #{r[:title]}"
|
|
64
|
+
line += "\n #{r[:url]}"
|
|
65
|
+
line += "\n #{r[:content]}" if r[:content] && !r[:content].empty?
|
|
66
|
+
line
|
|
67
|
+
end.join("\n\n")
|
|
68
|
+
end
|
|
69
|
+
private_class_method :format_results
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The native agent tool (Ask::Tools::WebSearch) is an OPTIONAL integration:
|
|
74
|
+
# it loads and registers only when ask-tools is present. The library works
|
|
75
|
+
# standalone — consumers that only call WebSearch.search pay nothing —
|
|
76
|
+
# while agent frameworks (ask-agent, ask-app-server, llm-proxy) all ship
|
|
77
|
+
# ask-tools and get the registry tool with no extra step. Only the
|
|
78
|
+
# ask-tools miss is swallowed; any other LoadError is real.
|
|
79
|
+
begin
|
|
80
|
+
require "ask-tools"
|
|
81
|
+
require_relative "web_search/tool"
|
|
82
|
+
rescue LoadError => e
|
|
83
|
+
raise unless e.path == "ask-tools"
|
|
84
|
+
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -16,7 +16,7 @@ dependencies:
|
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '0.1'
|
|
19
|
-
type: :
|
|
19
|
+
type: :development
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
@@ -79,9 +79,10 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '13.0'
|
|
82
|
-
description:
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
description: Searches the web via a local SearXNG instance and returns the results
|
|
83
|
+
as clean numbered markdown. The capability layer (WebSearch.search); the native
|
|
84
|
+
Ask::Tools::WebSearch agent tool is an optional integration that registers when
|
|
85
|
+
ask-tools is present. Configure endpoint via SEARXNG_URL env var.
|
|
85
86
|
email:
|
|
86
87
|
- kaka@myrrlabs.com
|
|
87
88
|
executables: []
|
|
@@ -117,5 +118,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
117
118
|
requirements: []
|
|
118
119
|
rubygems_version: 4.0.3
|
|
119
120
|
specification_version: 4
|
|
120
|
-
summary: Web search
|
|
121
|
+
summary: Web search library for the ask-rb ecosystem
|
|
121
122
|
test_files: []
|