Class: Kward::WebSearch

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/tools/search/web.rb

Defined Under Namespace

Classes: NetHttpClient, Result, SearchResponse

Constant Summary collapse

DEFAULT_MAX_RESULTS =
5
MAX_MAX_RESULTS =
20
MAX_QUERIES =
4
MAX_OUTPUT_BYTES =
8 * 1024
MODEL_PROVIDER_MAX_TOKENS =
512
MAX_ANSWER_CHARS =
2_000
MAX_EXCERPT_CHARS =
300
HTTP_TIMEOUT_SECONDS =
10
DUCKDUCKGO_URL =
"https://html.duckduckgo.com/html/"
EXA_MCP_URL =
"https://mcp.exa.ai/mcp"
EXA_ANSWER_URL =
"https://api.exa.ai/answer"
EXA_SEARCH_URL =
"https://api.exa.ai/search"
PERPLEXITY_API_URL =
"https://api.perplexity.ai/chat/completions"
GEMINI_API_BASE =
"https://generativelanguage.googleapis.com/v1beta"
DEFAULT_GEMINI_MODEL =
"gemini-2.5-flash"
PUBLIC_SEARXNG_INSTANCES =
[
  "https://searx.be",
  "https://search.inetol.net",
  "https://searx.tiekoetter.com"
].freeze
PROVIDERS =
%w[auto exa perplexity gemini legacy duckduckgo].freeze

Instance Method Summary collapse

Constructor Details

#initialize(http_client: NetHttpClient.new, searxng_instances: PUBLIC_SEARXNG_INSTANCES, max_output_bytes: MAX_OUTPUT_BYTES, config: nil) ⇒ WebSearch

Returns a new instance of WebSearch.



35
36
37
38
39
40
# File 'lib/kward/tools/search/web.rb', line 35

def initialize(http_client: NetHttpClient.new, searxng_instances: PUBLIC_SEARXNG_INSTANCES, max_output_bytes: MAX_OUTPUT_BYTES, config: nil)
  @http_client = http_client
  @searxng_instances = searxng_instances
  @max_output_bytes = max_output_bytes
  @config = config
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


42
43
44
45
46
47
# File 'lib/kward/tools/search/web.rb', line 42

def available?
  enabled = boolean_config_value("enabled")
  return enabled unless enabled.nil?

  true
end

#search(args) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/kward/tools/search/web.rb', line 49

def search(args)
  queries = args_value(args, "queries")
  return "Error: queries must be an array with 1-#{MAX_QUERIES} strings" unless valid_queries?(queries)

  max_results = bounded_max_results(args_value(args, "max_results") || args_value(args, "num_results"))
  provider = normalize_provider(args_value(args, "provider") || config_value("provider") || "auto")
  return "Error: provider must be one of: #{PROVIDERS.join(", ")}" unless provider

  options = {
    max_results: max_results,
    recency_filter: normalize_recency(args_value(args, "recency_filter") || args_value(args, "recencyFilter")),
    domain_filter: normalize_domain_filter(args_value(args, "domain_filter") || args_value(args, "domainFilter")),
    provider: provider
  }

  sections = ["# Web search"]
  failures = []
  any_results = false

  queries.each do |query|
    response, error = search_query(query, options)
    any_results = true if successful_response?(response)
    failures << "#{query}: #{error}" if error && !successful_response?(response)
    sections << format_query_results(query, response, error)
  end

  unless any_results
    return "Error: web_search found no results\n#{failures.map { |failure| "- #{failure}" }.join("\n")}".strip
  end

  truncate_output(sections.join("\n\n"))
end