Skip to content

Commit

Permalink
Bugfix/Searxng tool not working (#3263)
Browse files Browse the repository at this point in the history
fix searxng tool not working
  • Loading branch information
HenryHengZJ authored Sep 26, 2024
1 parent bc76886 commit 8690c43
Showing 1 changed file with 197 additions and 16 deletions.
213 changes: 197 additions & 16 deletions packages/components/nodes/tools/Searxng/Searxng.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
import { SearxngSearch } from '@langchain/community/tools/searxng_search'
import { Tool } from '@langchain/core/tools'
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'

interface SearxngResults {
query: string
number_of_results: number
results: Array<{
url: string
title: string
content: string
img_src: string
engine: string
parsed_url: Array<string>
template: string
engines: Array<string>
positions: Array<number>
score: number
category: string
pretty_url: string
open_group?: boolean
close_group?: boolean
}>
answers: Array<string>
corrections: Array<string>
infoboxes: Array<{
infobox: string
content: string
engine: string
engines: Array<string>
}>
suggestions: Array<string>
unresponsive_engines: Array<string>
}

interface SearxngCustomHeaders {
[key: string]: string
}

interface SearxngSearchParams {
numResults?: number
categories?: string
engines?: string
language?: string
pageNumber?: number
timeRange?: number
format?: string
resultsOnNewTab?: 0 | 1
imageProxy?: boolean
autocomplete?: string
safesearch?: 0 | 1 | 2
}

class Searxng_Tools implements INode {
label: string
name: string
Expand All @@ -16,7 +65,7 @@ class Searxng_Tools implements INode {
constructor() {
this.label = 'SearXNG'
this.name = 'searXNG'
this.version = 1.0
this.version = 2.0
this.type = 'SearXNG'
this.icon = 'SearXNG.svg'
this.category = 'Tools'
Expand All @@ -28,6 +77,33 @@ class Searxng_Tools implements INode {
type: 'string',
default: 'http://searxng:8080'
},
{
label: 'Headers',
name: 'headers',
type: 'json',
description: 'Custom headers for the request',
optional: true,
additionalParams: true
},
{
label: 'Format',
name: 'format',
type: 'options',
options: [
{
label: 'JSON',
name: 'json'
},
{
label: 'HTML',
name: 'html'
}
],
default: 'json',
description:
'Format of the response. You need to enable search formats in settings.yml. Refer to <a target="_blank" href="https://docs.flowiseai.com/integrations/langchain/tools/searchapi">SearXNG Setup Guide</a> for more details.',
additionalParams: true
},
{
label: 'Categories',
name: 'categories',
Expand Down Expand Up @@ -86,34 +162,139 @@ class Searxng_Tools implements INode {

async init(nodeData: INodeData, _: string): Promise<any> {
const apiBase = nodeData.inputs?.apiBase as string
const headers = nodeData.inputs?.headers as string
const categories = nodeData.inputs?.categories as string
const engines = nodeData.inputs?.engines as string
const language = nodeData.inputs?.language as string
const pageno = nodeData.inputs?.pageno as number
const pageno = nodeData.inputs?.pageno as string
const time_range = nodeData.inputs?.time_range as string
const safesearch = nodeData.inputs?.safesearch as 0 | 1 | 2 | undefined
const format = 'json' as 'json'

const params = {
format,
categories,
engines,
language,
pageno,
time_range,
safesearch
}
const format = nodeData.inputs?.format as string

const headers = {}
const params: SearxngSearchParams = {}

if (categories) params.categories = categories
if (engines) params.engines = engines
if (language) params.language = language
if (pageno) params.pageNumber = parseFloat(pageno)
if (time_range) params.timeRange = parseFloat(time_range)
if (safesearch) params.safesearch = safesearch
if (format) params.format = format

let customHeaders = undefined
if (headers) {
customHeaders = typeof headers === 'string' ? JSON.parse(headers) : headers
}

const tool = new SearxngSearch({
apiBase,
params,
headers
headers: customHeaders
})

return tool
}
}

class SearxngSearch extends Tool {
static lc_name() {
return 'SearxngSearch'
}

name = 'searxng-search'

description =
'A meta search engine. Useful for when you need to answer questions about current events. Input should be a search query. Output is a JSON array of the query results'

protected apiBase?: string

protected params?: SearxngSearchParams = {
numResults: 10,
pageNumber: 1,
imageProxy: true,
safesearch: 0
}

protected headers?: SearxngCustomHeaders

get lc_secrets(): { [key: string]: string } | undefined {
return {
apiBase: 'SEARXNG_API_BASE'
}
}

constructor({ apiBase, params, headers }: { apiBase?: string; params?: SearxngSearchParams; headers?: SearxngCustomHeaders }) {
super(...arguments)

this.apiBase = apiBase
this.headers = { 'content-type': 'application/json', ...headers }

if (!this.apiBase) {
throw new Error(`SEARXNG_API_BASE not set. You can set it as "SEARXNG_API_BASE" in your environment variables.`)
}

if (params) {
this.params = { ...this.params, ...params }
}
}

protected buildUrl<P extends SearxngSearchParams>(path: string, parameters: P, baseUrl: string): string {
const nonUndefinedParams: [string, string][] = Object.entries(parameters)
.filter(([_, value]) => value !== undefined)
.map(([key, value]) => [key, value.toString()]) // Avoid string conversion
const searchParams = new URLSearchParams(nonUndefinedParams)
return `${baseUrl}/${path}?${searchParams}`
}

async _call(input: string): Promise<string> {
const queryParams = {
q: input,
...this.params
}
const url = this.buildUrl('search', queryParams, this.apiBase as string)

const resp = await fetch(url, {
method: 'POST',
headers: this.headers,
signal: AbortSignal.timeout(5 * 1000) // 5 seconds
})

if (!resp.ok) {
throw new Error(resp.statusText)
}

const res: SearxngResults = await resp.json()

if (!res.results.length && !res.answers.length && !res.infoboxes.length && !res.suggestions.length) {
return 'No good results found.'
} else if (res.results.length) {
const response: string[] = []

res.results.forEach((r) => {
response.push(
JSON.stringify({
title: r.title || '',
link: r.url || '',
snippet: r.content || ''
})
)
})

return response.slice(0, this.params?.numResults).toString()
} else if (res.answers.length) {
return res.answers[0]
} else if (res.infoboxes.length) {
return res.infoboxes[0]?.content.replaceAll(/<[^>]+>/gi, '')
} else if (res.suggestions.length) {
let suggestions = 'Suggestions: '
res.suggestions.forEach((s) => {
suggestions += `${s}, `
})
return suggestions
} else {
return 'No good results found.'
}
}
}

module.exports = { nodeClass: Searxng_Tools }

0 comments on commit 8690c43

Please sign in to comment.