{"version":3,"file":"searxng_search.cjs","names":["Tool"],"sources":["../../src/tools/searxng_search.ts"],"sourcesContent":["import { getEnvironmentVariable } from \"@langchain/core/utils/env\";\nimport { Tool } from \"@langchain/core/tools\";\n\n/**\n * Interface for the results returned by the Searxng search.\n */\ninterface SearxngResults {\n  query: string;\n  number_of_results: number;\n  results: Array<{\n    url: string;\n    title: string;\n    content: string;\n    img_src: string;\n    engine: string;\n    parsed_url: Array<string>;\n    template: string;\n    engines: Array<string>;\n    positions: Array<number>;\n    score: number;\n    category: string;\n    pretty_url: string;\n    open_group?: boolean;\n    close_group?: boolean;\n  }>;\n  answers: Array<string>;\n  corrections: Array<string>;\n  infoboxes: Array<{\n    infobox: string;\n    content: string;\n    engine: string;\n    engines: Array<string>;\n  }>;\n  suggestions: Array<string>;\n  unresponsive_engines: Array<string>;\n}\n\n/**\n * Interface for custom headers used in the Searxng search.\n */\ninterface SearxngCustomHeaders {\n  [key: string]: string;\n}\n\ninterface SearxngSearchParams {\n  /**\n   * @default 10\n   * Number of results included in results\n   */\n  numResults?: number;\n  /** Comma separated list, specifies the active search categories\n   * https://docs.searxng.org/user/configured_engines.html#configured-engines\n   */\n  categories?: string;\n\n  /** Comma separated list, specifies the active search engines\n   * https://docs.searxng.org/user/configured_engines.html#configured-engines\n   */\n  engines?: string;\n\n  /** Code of the language. */\n  language?: string;\n  /** Search page number. */\n  pageNumber?: number;\n  /**\n   * day / month / year\n   *\n   * Time range of search for engines which support it. See if an engine supports time range search in the preferences page of an instance.\n   */\n  timeRange?: number;\n\n  /**\n   * Throws Error if format is set anything other than \"json\"\n   * Output format of results. Format needs to be activated in search:\n   */\n  format?: \"json\";\n  /** Open search results on new tab. */\n  resultsOnNewTab?: 0 | 1;\n  /** Proxy image results through SearXNG. */\n  imageProxy?: boolean;\n  autocomplete?: string;\n  /**\n   * Filter search results of engines which support safe search. See if an engine supports safe search in the preferences page of an instance.\n   */\n  safesearch?: 0 | 1 | 2;\n}\n\n/**\n * SearxngSearch class represents a meta search engine tool.\n * Use this class when you need to answer questions about current events.\n * The input should be a search query, and the output is a JSON array of the query results.\n *\n * note: works best with *agentType*: `structured-chat-zero-shot-react-description`\n * https://github.com/searxng/searxng\n * @example\n * ```typescript\n * const executor = AgentExecutor.fromAgentAndTools({\n *   agent,\n *   tools: [\n *     new SearxngSearch({\n *       params: {\n *         format: \"json\",\n *         engines: \"google\",\n *       },\n *       headers: {},\n *     }),\n *   ],\n * });\n * const result = await executor.invoke({\n *   input: `What is Langchain? Describe in 50 words`,\n * });\n * ```\n */\nexport class SearxngSearch extends Tool {\n  static lc_name() {\n    return \"SearxngSearch\";\n  }\n\n  name = \"searxng-search\";\n\n  description =\n    \"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\";\n\n  protected apiBase?: string;\n\n  protected params?: SearxngSearchParams = {\n    numResults: 10,\n    pageNumber: 1,\n    format: \"json\",\n    imageProxy: true,\n    safesearch: 0,\n  };\n\n  protected headers?: SearxngCustomHeaders;\n\n  get lc_secrets(): { [key: string]: string } | undefined {\n    return {\n      apiBase: \"SEARXNG_API_BASE\",\n    };\n  }\n\n  /**\n   * Constructor for the SearxngSearch class\n   * @param apiBase Base URL of the Searxng instance\n   * @param params SearxNG parameters\n   * @param headers Custom headers\n   */\n  constructor({\n    apiBase,\n    params,\n    headers,\n  }: {\n    /** Base URL of Searxng instance */\n    apiBase?: string;\n\n    /** SearxNG Paramerters\n     *\n     *  https://docs.searxng.org/dev/search_api.html check here for more details\n     */\n    params?: SearxngSearchParams;\n\n    /**\n     * Custom headers\n     * Set custom headers if you're using a api from RapidAPI (https://rapidapi.com/iamrony777/api/searxng)\n     * No headers needed for a locally self-hosted instance\n     */\n    headers?: SearxngCustomHeaders;\n  }) {\n    super(...arguments);\n\n    this.apiBase = getEnvironmentVariable(\"SEARXNG_API_BASE\") || apiBase;\n    this.headers = { \"content-type\": \"application/json\", ...headers };\n\n    if (!this.apiBase) {\n      throw new Error(\n        `SEARXNG_API_BASE not set. You can set it as \"SEARXNG_API_BASE\" in your environment variables.`\n      );\n    }\n\n    if (params) {\n      this.params = { ...this.params, ...params };\n    }\n  }\n\n  /**\n   * Builds the URL for the Searxng search.\n   * @param path The path for the URL.\n   * @param parameters The parameters for the URL.\n   * @param baseUrl The base URL.\n   * @returns The complete URL as a string.\n   */\n  protected buildUrl<P extends SearxngSearchParams>(\n    path: string,\n    parameters: P,\n    baseUrl: string\n  ): string {\n    const nonUndefinedParams: [string, string][] = Object.entries(parameters)\n      .filter(([_, value]) => value !== undefined)\n      .map(([key, value]) => [key, value.toString()]); // Avoid string conversion\n    const searchParams = new URLSearchParams(nonUndefinedParams);\n    return `${baseUrl}/${path}?${searchParams}`;\n  }\n\n  async _call(input: string): Promise<string> {\n    const queryParams = {\n      q: input,\n      ...this.params,\n    };\n    const url = this.buildUrl(\"search\", queryParams, this.apiBase as string);\n\n    const resp = await fetch(url, {\n      method: \"POST\",\n      headers: this.headers,\n      signal: AbortSignal.timeout(5 * 1000), // 5 seconds\n    });\n\n    if (!resp.ok) {\n      throw new Error(resp.statusText);\n    }\n\n    const res: SearxngResults = await resp.json();\n\n    if (\n      !res.results.length &&\n      !res.answers.length &&\n      !res.infoboxes.length &&\n      !res.suggestions.length\n    ) {\n      return \"No good results found.\";\n    } else if (res.results.length) {\n      const response: string[] = [];\n\n      res.results.forEach((r) => {\n        response.push(\n          JSON.stringify({\n            title: r.title || \"\",\n            link: r.url || \"\",\n            snippet: r.content || \"\",\n          })\n        );\n      });\n\n      return response.slice(0, this.params?.numResults).toString();\n    } else if (res.answers.length) {\n      return res.answers[0];\n    } else if (res.infoboxes.length) {\n      // Apply HTML tag stripping repeatedly until no more replacements occur\n      // to prevent incomplete sanitization from crafted inputs like \"<scr<script>ipt>\"\n      let content = res.infoboxes[0]?.content ?? \"\";\n      let previous: string;\n      do {\n        previous = content;\n        content = content.replace(/<[^>]+>/gi, \"\");\n      } while (content !== previous);\n      return content;\n    } else if (res.suggestions.length) {\n      let suggestions = \"Suggestions: \";\n      res.suggestions.forEach((s) => {\n        suggestions += `${s}, `;\n      });\n      return suggestions;\n    } else {\n      return \"No good results found.\";\n    }\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiHA,IAAa,gBAAb,cAAmCA,sBAAAA,KAAK;CACtC,OAAO,UAAU;AACf,SAAO;;CAGT,OAAO;CAEP,cACE;CAEF;CAEA,SAAyC;EACvC,YAAY;EACZ,YAAY;EACZ,QAAQ;EACR,YAAY;EACZ,YAAY;EACb;CAED;CAEA,IAAI,aAAoD;AACtD,SAAO,EACL,SAAS,oBACV;;;;;;;;CASH,YAAY,EACV,SACA,QACA,WAiBC;AACD,QAAM,GAAG,UAAU;AAEnB,OAAK,WAAA,GAAA,0BAAA,wBAAiC,mBAAmB,IAAI;AAC7D,OAAK,UAAU;GAAE,gBAAgB;GAAoB,GAAG;GAAS;AAEjE,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MACR,gGACD;AAGH,MAAI,OACF,MAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAQ;;;;;;;;;CAW/C,SACE,MACA,YACA,SACQ;EACR,MAAM,qBAAyC,OAAO,QAAQ,WAAW,CACtE,QAAQ,CAAC,GAAG,WAAW,UAAU,KAAA,EAAU,CAC3C,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,MAAM,UAAU,CAAC,CAAC;AAEjD,SAAO,GAAG,QAAQ,GAAG,KAAK,GADL,IAAI,gBAAgB,mBAAmB;;CAI9D,MAAM,MAAM,OAAgC;EAC1C,MAAM,cAAc;GAClB,GAAG;GACH,GAAG,KAAK;GACT;EACD,MAAM,MAAM,KAAK,SAAS,UAAU,aAAa,KAAK,QAAkB;EAExE,MAAM,OAAO,MAAM,MAAM,KAAK;GAC5B,QAAQ;GACR,SAAS,KAAK;GACd,QAAQ,YAAY,QAAQ,IAAI,IAAK;GACtC,CAAC;AAEF,MAAI,CAAC,KAAK,GACR,OAAM,IAAI,MAAM,KAAK,WAAW;EAGlC,MAAM,MAAsB,MAAM,KAAK,MAAM;AAE7C,MACE,CAAC,IAAI,QAAQ,UACb,CAAC,IAAI,QAAQ,UACb,CAAC,IAAI,UAAU,UACf,CAAC,IAAI,YAAY,OAEjB,QAAO;WACE,IAAI,QAAQ,QAAQ;GAC7B,MAAM,WAAqB,EAAE;AAE7B,OAAI,QAAQ,SAAS,MAAM;AACzB,aAAS,KACP,KAAK,UAAU;KACb,OAAO,EAAE,SAAS;KAClB,MAAM,EAAE,OAAO;KACf,SAAS,EAAE,WAAW;KACvB,CAAC,CACH;KACD;AAEF,UAAO,SAAS,MAAM,GAAG,KAAK,QAAQ,WAAW,CAAC,UAAU;aACnD,IAAI,QAAQ,OACrB,QAAO,IAAI,QAAQ;WACV,IAAI,UAAU,QAAQ;GAG/B,IAAI,UAAU,IAAI,UAAU,IAAI,WAAW;GAC3C,IAAI;AACJ,MAAG;AACD,eAAW;AACX,cAAU,QAAQ,QAAQ,aAAa,GAAG;YACnC,YAAY;AACrB,UAAO;aACE,IAAI,YAAY,QAAQ;GACjC,IAAI,cAAc;AAClB,OAAI,YAAY,SAAS,MAAM;AAC7B,mBAAe,GAAG,EAAE;KACpB;AACF,UAAO;QAEP,QAAO"}