Source code for langchain_community.utilities.searx_search

"""用于使用SearxNG元搜索API的实用程序。

SearxNG是一个隐私友好的免费元搜索引擎,它从多个搜索引擎和数据库中聚合结果,并支持OpenSearch规范。

有关安装说明的更多详细信息,请参阅此处。

有关搜索API,请参阅https://docs.searxng.org/dev/search_api.html

快速开始
-----------

为了使用此实用程序,您需要提供searx主机。这可以通过传递命名参数:attr:`searx_host <SearxSearchWrapper.searx_host>`或导出环境变量SEARX_HOST来完成。
注意:这是唯一必需的参数。

然后像这样创建一个searx搜索实例:

    .. code-block:: python

        from langchain_community.utilities import SearxSearchWrapper

        # 当主机以`http`开头时,SSL被禁用,连接被假定在私有网络上
        searx_host='http://self.hosted'

        search = SearxSearchWrapper(searx_host=searx_host)


现在您可以使用``search``实例来查询searx API。

搜索
---------

使用:meth:`run() <SearxSearchWrapper.run>`和:meth:`results() <SearxSearchWrapper.results>`方法来查询searx API。
其他方法可用于方便使用。

:class:`SearxResults`是对原始json结果的便捷包装。

使用``run``方法进行搜索的示例用法:

    .. code-block:: python

        s.run(query="what is the best search engine?")

引擎参数
-----------------

您可以将任何`接受的searx搜索API参数 <https://docs.searxng.org/dev/search_api.html>`_参数传递给:py:class:`SearxSearchWrapper`实例。

在以下示例中,我们使用了:attr:`engines <SearxSearchWrapper.engines>`和``language``参数:

    .. code-block:: python

        # 假设searx主机已设置如上或作为环境变量导出
        s = SearxSearchWrapper(engines=['google', 'bing'],
                            language='es')

搜索提示
-----------

Searx提供了一种特殊的`搜索语法 <https://docs.searxng.org/user/index.html#search-syntax>`_,也可以用来代替传递引擎参数。

例如,以下查询:

    .. code-block:: python

        s = SearxSearchWrapper("langchain library", engines=['github'])

        # 也可以写成:
        s = SearxSearchWrapper("langchain library !github")
        # 或者甚至:
        s = SearxSearchWrapper("langchain library !gh")


在某些情况下,您可能希望将额外的字符串传递给搜索查询。例如,当``run()``方法由代理调用时。搜索后缀也可以用作传递额外参数给searx或基础搜索引擎的一种方式。

    .. code-block:: python

        # 选择github引擎并传递搜索后缀
        s = SearchWrapper("langchain library", query_suffix="!gh")


        s = SearchWrapper("langchain library")
        # 选择github的传统google搜索语法
        s.run("large language models", query_suffix="site:github.com")


*注意*:搜索后缀可以在实例级别和方法级别上定义。生成的查询将是两者的连接,前者优先。

有关更多详细信息,请参阅`SearxNG配置引擎 <https://docs.searxng.org/admin/engines/configured_engines.html>`_和`SearxNG搜索语法 <https://docs.searxng.org/user/index.html#id1>`_。


-----
此包装器基于SearxNG分支https://github.com/searxng/searxng,该分支比原始的Searx项目维护得更好,并提供更多功能。

公共searxNG实例通常对API使用设置了速率限制器,因此您可能希望使用自托管实例并禁用速率限制器。

如果您正在自托管一个实例,您可以根据自己的网络自定义速率限制器,如下所述。

有关公共SearxNG实例列表,请参见https://searx.space/
"""

import json
from typing import Any, Dict, List, Optional

import aiohttp
import requests
from langchain_core.pydantic_v1 import (
    BaseModel,
    Extra,
    Field,
    PrivateAttr,
    root_validator,
    validator,
)
from langchain_core.utils import get_from_dict_or_env


def _get_default_params() -> dict:
    return {"language": "en", "format": "json"}


[docs]class SearxResults(dict): """在搜索API结果周围的类似字典的包装器。""" _data: str = ""
[docs] def __init__(self, data: str): """从Searx获取原始结果,并将其转换为类似字典的对象。""" json_data = json.loads(data) super().__init__(json_data) self.__dict__ = self
def __str__(self) -> str: """searx结果的文本表示。""" return self._data @property def results(self) -> Any: """静音mypy以访问此字段。 :meta private: """ return self.get("results") @property def answers(self) -> Any: """JSON结果的辅助访问器。""" return self.get("answers")
[docs]class SearxSearchWrapper(BaseModel): """封装了Searx API。 要使用,需要通过传递命名参数“searx_host”或导出环境变量“SEARX_HOST”来提供searx主机。 在某些情况下,您可能希望禁用SSL验证,例如在本地运行searx时。您可以通过传递命名参数“unsecure”来实现此目的。您还可以将主机URL方案传递为“http”以禁用SSL。 示例: .. code-block:: python from langchain_community.utilities import SearxSearchWrapper searx = SearxSearchWrapper(searx_host="http://localhost:8888") 禁用SSL的示例: .. code-block:: python from langchain_community.utilities import SearxSearchWrapper # 请注意,如果将url方案传递为http,则不需要unsecure参数 searx = SearxSearchWrapper(searx_host="http://localhost:8888", unsecure=True)""" _result: SearxResults = PrivateAttr() searx_host: str = "" unsecure: bool = False params: dict = Field(default_factory=_get_default_params) headers: Optional[dict] = None engines: Optional[List[str]] = [] categories: Optional[List[str]] = [] query_suffix: Optional[str] = "" k: int = 10 aiosession: Optional[Any] = None @validator("unsecure") def disable_ssl_warnings(cls, v: bool) -> bool: """禁用SSL警告。""" if v: # requests.urllib3.disable_warnings() try: import urllib3 urllib3.disable_warnings() except ImportError as e: print(e) # noqa: T201 return v @root_validator() def validate_params(cls, values: Dict) -> Dict: """验证自定义searx参数是否与默认参数合并。""" user_params = values["params"] default = _get_default_params() values["params"] = {**default, **user_params} engines = values.get("engines") if engines: values["params"]["engines"] = ",".join(engines) categories = values.get("categories") if categories: values["params"]["categories"] = ",".join(categories) searx_host = get_from_dict_or_env(values, "searx_host", "SEARX_HOST") if not searx_host.startswith("http"): print( # noqa: T201 f"Warning: missing the url scheme on host \ ! assuming secure https://{searx_host} " ) searx_host = "https://" + searx_host elif searx_host.startswith("http://"): values["unsecure"] = True cls.disable_ssl_warnings(True) values["searx_host"] = searx_host return values class Config: """此pydantic对象的配置。""" extra = Extra.forbid def _searx_api_query(self, params: dict) -> SearxResults: """实际请求到searx API。""" raw_result = requests.get( self.searx_host, headers=self.headers, params=params, verify=not self.unsecure, ) # test if http result is ok if not raw_result.ok: raise ValueError("Searx API returned an error: ", raw_result.text) res = SearxResults(raw_result.text) self._result = res return res async def _asearx_api_query(self, params: dict) -> SearxResults: if not self.aiosession: async with aiohttp.ClientSession() as session: async with session.get( self.searx_host, headers=self.headers, params=params, ssl=(lambda: False if self.unsecure else None)(), ) as response: if not response.ok: raise ValueError("Searx API returned an error: ", response.text) result = SearxResults(await response.text()) self._result = result else: async with self.aiosession.get( self.searx_host, headers=self.headers, params=params, verify=not self.unsecure, ) as response: if not response.ok: raise ValueError("Searx API returned an error: ", response.text) result = SearxResults(await response.text()) self._result = result return result
[docs] def run( self, query: str, engines: Optional[List[str]] = None, categories: Optional[List[str]] = None, query_suffix: Optional[str] = "", **kwargs: Any, ) -> str: """通过Searx API运行查询并解析结果。 您可以将任何其他参数传递给searx查询API。 参数: query: 要搜索的查询。 query_suffix: 附加到查询的额外后缀。 engines: 用于查询的引擎列表。 categories: 用于查询的类别列表。 **kwargs: 传递给searx API的额外参数。 返回: str: 查询的结果。 引发: ValueError: 如果查询出现错误。 示例: 这将向qwant引擎发出查询: .. code-block:: python from langchain_community.utilities import SearxSearchWrapper searx = SearxSearchWrapper(searx_host="http://my.searx.host") searx.run("what is the weather in France ?", engine="qwant") # 可以使用`query_suffix`使用searx的`!`语法选择引擎来实现相同的结果 searx.run("what is the weather in France ?", query_suffix="!qwant") """ _params = { "q": query, } params = {**self.params, **_params, **kwargs} if self.query_suffix and len(self.query_suffix) > 0: params["q"] += " " + self.query_suffix if isinstance(query_suffix, str) and len(query_suffix) > 0: params["q"] += " " + query_suffix if isinstance(engines, list) and len(engines) > 0: params["engines"] = ",".join(engines) if isinstance(categories, list) and len(categories) > 0: params["categories"] = ",".join(categories) res = self._searx_api_query(params) if len(res.answers) > 0: toret = res.answers[0] # only return the content of the results list elif len(res.results) > 0: toret = "\n\n".join([r.get("content", "") for r in res.results[: self.k]]) else: toret = "No good search result found" return toret
[docs] async def arun( self, query: str, engines: Optional[List[str]] = None, query_suffix: Optional[str] = "", **kwargs: Any, ) -> str: """`run`的异步版本。""" _params = { "q": query, } params = {**self.params, **_params, **kwargs} if self.query_suffix and len(self.query_suffix) > 0: params["q"] += " " + self.query_suffix if isinstance(query_suffix, str) and len(query_suffix) > 0: params["q"] += " " + query_suffix if isinstance(engines, list) and len(engines) > 0: params["engines"] = ",".join(engines) res = await self._asearx_api_query(params) if len(res.answers) > 0: toret = res.answers[0] # only return the content of the results list elif len(res.results) > 0: toret = "\n\n".join([r.get("content", "") for r in res.results[: self.k]]) else: toret = "No good search result found" return toret
[docs] def results( self, query: str, num_results: int, engines: Optional[List[str]] = None, categories: Optional[List[str]] = None, query_suffix: Optional[str] = "", **kwargs: Any, ) -> List[Dict]: """通过Searx API运行查询并返回带有元数据的结果。 参数: query: 要搜索的查询。 query_suffix: 附加到查询的额外后缀。 num_results: 限制要返回的结果数量。 engines: 用于查询的引擎列表。 categories: 用于查询的类别列表。 **kwargs: 传递给searx API的额外参数。 返回: 具有以下键的字典: { snippet: 结果的描述。 title: 结果的标题。 link: 结果的链接。 engines: 用于结果的引擎。 category: 结果的Searx类别。 } """ _params = { "q": query, } params = {**self.params, **_params, **kwargs} if self.query_suffix and len(self.query_suffix) > 0: params["q"] += " " + self.query_suffix if isinstance(query_suffix, str) and len(query_suffix) > 0: params["q"] += " " + query_suffix if isinstance(engines, list) and len(engines) > 0: params["engines"] = ",".join(engines) if isinstance(categories, list) and len(categories) > 0: params["categories"] = ",".join(categories) results = self._searx_api_query(params).results[:num_results] if len(results) == 0: return [{"Result": "No good Search Result was found"}] return [ { "snippet": result.get("content", ""), "title": result["title"], "link": result["url"], "engines": result["engines"], "category": result["category"], } for result in results ]
[docs] async def aresults( self, query: str, num_results: int, engines: Optional[List[str]] = None, query_suffix: Optional[str] = "", **kwargs: Any, ) -> List[Dict]: """使用JSON结果进行异步查询。 使用aiohttp。有关更多信息,请参阅`results`。 """ _params = { "q": query, } params = {**self.params, **_params, **kwargs} if self.query_suffix and len(self.query_suffix) > 0: params["q"] += " " + self.query_suffix if isinstance(query_suffix, str) and len(query_suffix) > 0: params["q"] += " " + query_suffix if isinstance(engines, list) and len(engines) > 0: params["engines"] = ",".join(engines) results = (await self._asearx_api_query(params)).results[:num_results] if len(results) == 0: return [{"Result": "No good Search Result was found"}] return [ { "snippet": result.get("content", ""), "title": result["title"], "link": result["url"], "engines": result["engines"], "category": result["category"], } for result in results ]