20.2 serapi.py源码逐行剖析
我们可以看一下Google查询的例子,在LangChain中有多种实现的方式。
如图20-5所示,在utilities的serpapi.py代码文件中实现了SerpAPIWrapper。
图20- 5 utilities的serpapi.py的SerpAPIWrapper
在langchain目录的serpapi.py代码中,从langchain.utilities.serpapi中导入SerpAPIWrapper。
- """用于向后兼容性"""
- from langchain.utilities.serpapi import SerpAPIWrapper
- __all__ = ["SerpAPIWrapper"]
utilities目录的serpapi.py的SerpAPIWrapper代码实现:
Gavin大咖微信:NLP_Matrix_Space
- class SerpAPIWrapper(BaseModel):
- """ SerpAPI封装。
- 要使用,应该安装google-search-results的python包,并使用API键设置环境变量SERPAPI_API_KEY,或者将SERPAPI_API_KEY作为命名参数传递给构造函数。
- 示例:
- .. code-block:: python
- from langchain.utilities import SerpAPIWrapper
- serpapi = SerpAPIWrapper()
- """
- ...
- def run(self, query: str, **kwargs: Any) -> str:
- """通过SerpAPI运行查询并解析结果."""
- return self._process_response(self.results(query))
- ...
以下是SerpAPIWrapper的一个使用示例,展示了使用LangChain 库中的代理和工具来实现自问自答,并进行搜索的示例。其中,定义了一个工具列表 tools,包含一个“Intermediate Answer”(“中间答案”)的工具。该工具的 func 属性指向了 search.run 方法,用于执行搜索操作,而search即是SerpAPIWrapper类的一个实例。
- from langchain import OpenAI, SerpAPIWrapper
- from langchain.agents import initialize_agent, Tool
- from langchain.agents import AgentType
- llm = OpenAI(temperature=0)
- search = SerpAPIWrapper()
- tools = [
- Tool(
- name="Intermediate Answer",
- func=search.run,
- description="useful for when you need to ask with search",
- )
- ]
- self_ask_with_search = initialize_agent(
- tools, llm, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True
- )
- self_ask_with_search.run(
- "What is the hometown of the reigning men's U.S. Open champion?"
- )
这是utilities目录中serpapi.py部分的代码。接下来,让我们转到tools目录里面的google_search目录,看一个使用GoogleSearchRun、GoogleSearchResults工具进行Goolge查询的方式,如图20-6所示。Gavin大咖微信:NLP_Matrix_Space
图20- 6 tools代码目录
google_search的__init__.py的代码实现:
- """谷歌搜索API工具包"""
- from langchain.tools.google_search.tool import GoogleSearchResults, GoogleSearchRun
- __all__ = ["GoogleSearchRun", "GoogleSearchResults"]
google_search的tool.py的GoogleSearchResults代码实现:
- class GoogleSearchResults(BaseTool):
- """查询Google Search API并获取json的工具."""
- name = "Google Search Results JSON"
- description = (
- "A wrapper around Google Search. "
- "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"
- )
- num_results: int = 4
- api_wrapper: GoogleSearchAPIWrapper
- def _run(
- self,
- query: str,
- run_manager: Optional[CallbackManagerForToolRun] = None,
- ) -> str:
- """使用工具."""
- return str(self.api_wrapper.results(query, self.num_results))
- async def _arun(
- self,
- query: str,
- run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
- ) -> str:
- """异步使用该工具."""
- raise NotImplementedError("GoogleSearchRun does not support async")
GoogleSearchResults 是一个用于与 Google 搜索 API 进行交互的工具类。通过使用该工具类,可以方便地进行搜索查询并获取返回的 JSON 格式的结果。
GoogleSearchResults类的属性:
- name:工具的名称,设定为 "Google Search Results JSON"。这个名称描述了工具的主要功能,即获取 Google 搜索结果的 JSON 数据。
- description:工具的描述提供了更详细的说明。该工具在处理当前事件相关的问题时非常有用。用户需要提供一个搜索查询作为输入,并且工具会返回一个包含查询结果的 JSON 数组。
- num_results:定义了需要获取的搜索结果数量,默认为 4。
- api_wrapper:是一个 GoogleSearchAPIWrapper 类的实例,用于封装与 Google 搜索 API 的交互。
GoogleSearchResults类的方法:
- _arun 方法用于异步使用该工具。尽管该方法在代码中被定义,但它抛出了 NotImplementedError,表示该方法尚未实现。这意味着当前版本的GoogleSearchResults工具类不支持异步运行。
- _run 方法用于同步使用该工具。用户可以传入一个查询字符串 query 作为参数,以及一个可选的 run_manager 参数,用于管理工具运行过程的回调函数。在方法内部,工具类调用 api_wrapper 对象的 results 方法,传递查询字符串和结果数量,并将结果转换为字符串类型返回。Gavin大咖微信:NLP_Matrix_Space
上述代码中第19行,调用了api_wrapper.results方法,api_wrapper是一个GoogleSearchAPIWrapper类,使用Google进行查询的时候,它们的原理机制都是一样的。
utilities的google_search.py的GoogleSearchAPIWrapper代码实现:
- class GoogleSearchAPIWrapper(BaseModel):
- """谷歌搜索API封装器
- ...
- """
- def results(self, query: str, num_results: int) -> List[Dict]:
- """通过GoogleSearch运行查询并返回元数据
- 参数:
- query: 要搜索的查询
- num_results:要返回的结果数
- 返回:
- 具有以下关键字的词典列表:
- snippet-结果的描述。
- title-结果的标题。
- link-指向结果的链接。
- """
- metadata_results = []
- results = self._google_search_results(query, num=num_results)
- if len(results) == 0:
- return [{"Result": "No good Google Search Result was found"}]
- for result in results:
- metadata_result = {
- "title": result["title"],
- "link": result["link"],
- }
- if "snippet" in result:
- metadata_result["snippet"] = result["snippet"]
- metadata_results.append(metadata_result)
- return metadata_results
GoogleSearchAPIWrapper 是一个封装了谷歌搜索 API 的类,包含一个 results方法,用于执行搜索查询并返回结果的元数据,这是Google API的调用。results方法接受两个参数:query是要搜索的查询字符串。num_results是要返回的结果数量。首先,创建一个空的 metadata_results 列表,用于存储结果的元数据。然后,调用 _google_search_results方法执行查询,并将结果存储在results变量中。如果结果数量为零,返回一个包含文本“No good Google Search Result was found”的字典。接着,遍历结果列表,并为每个结果创建一个 metadata_result 字典。该字典包含以下关键字,“title”是结果的标题,“link”是指向结果的链接,“snippet”是结果的描述。然后,将每个metadata_result添加到metadata_results列表中,返回metadata_results列表。Gavin大咖微信:NLP_Matrix_Space
接下来,我们看另外一种使用Google进行查询的方式,使用GoogleSerperRun、GoogleSerperResults进行查询。如图20-7所示。
图20- 7 Google Serper的查询方式
google_serper的__init__.py的代码实现:
- from langchain.tools.google_serper.tool import GoogleSerperResults, GoogleSerperRun
- """Google Serper API工具包."""
- """Serer.dev谷歌搜索API工具."""
- __all__ = ["GoogleSerperRun", "GoogleSerperResults"]
google_serper的tool.py的代码实现:
- class GoogleSerperResults(BaseTool):
- """ 查询Serper.dev谷歌搜索API并获取json的工具."""
- name = "google_serrper_results_json"
- description = (
- "A low-cost Google Search API."
- "Useful for when you need to answer questions about current events."
- "Input should be a search query. Output is a JSON object of the query results"
- )
- api_wrapper: GoogleSerperAPIWrapper = Field(default_factory=GoogleSerperAPIWrapper)
- def _run(
- self,
- query: str,
- run_manager: Optional[CallbackManagerForToolRun] = None,
- ) -> str:
- """使用工具."""
- return str(self.api_wrapper.results(query))
- async def _arun(
- self,
- query: str,
- run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
- ) -> str:
- """异步使用该工具."""
- return (await self.api_wrapper.aresults(query)).__str__()
GoogleSerperResults类继承自 BaseTool 类,用于通过调用 Serper.dev 谷歌搜索 API 并获取 JSON 格式的查询结果。
GoogleSerperResults类的属性:
- name:工具的名称为 "google_serrper_results_json"。
- description:工具的描述表明它是一个低成本的谷歌搜索 API,适用于回答与当前事件相关的问题。输入是一个搜索查询,输出是查询结果的 JSON 对象。
- api_wrapper:是一个 GoogleSerperAPIWrapper 类的实例。
GoogleSerperResults类的方法:Gavin大咖微信:NLP_Matrix_Space
- _run 方法:该方法用于执行工具的功能。它接受一个查询字符串作为参数,并返回一个字符串,表示查询结果的 JSON。这跟Google Search的方式是一致的。上述代码第18行,调用了api_wrapper.results方法。
- _arun方法:异步使用该工具。
utilities的google_serper.py的results代码实现:
- class GoogleSerperAPIWrapper(BaseModel):
- """Serper.dev谷歌搜索API的封装
- ....
- """
- def results(self, query: str, **kwargs: Any) -> Dict:
- """通过GoogleSearch运行查询"""
- return self._google_serper_api_results(
- query,
- gl=self.gl,
- hl=self.hl,
- num=self.k,
- tbs=self.tbs,
- search_type=self.type,
- **kwargs,
- )
以上代码中第7行,调用的是内部的方法_google_serper_api_results。
utilities的google_serper.py的_google_serper_api_results代码实现:
- class GoogleSerperAPIWrapper(BaseModel):
- """Serper.dev谷歌搜索API的封装
- ...
- """
- ...
- def _google_serper_api_results(
- self, search_term: str, search_type: str = "search", **kwargs: Any
- ) -> dict:
- headers = {
- "X-API-KEY": self.serper_api_key or "",
- "Content-Type": "application/json",
- }
- params = {
- "q": search_term,
- **{key: value for key, value in kwargs.items() if value is not None},
- }
- response = requests.post(
- f"https://google.serper.dev/{search_type}", headers=headers, params=params
- )
- response.raise_for_status()
- search_results = response.json()
- return search_results
- async def _async_google_serper_search_results(
- self, search_term: str, search_type: str = "search", **kwargs: Any
- ) -> dict:
- headers = {
- "X-API-KEY": self.serper_api_key or "",
- "Content-Type": "application/json",
- }
- url = f"https://google.serper.dev/{search_type}"
- params = {
- "q": search_term,
- **{key: value for key, value in kwargs.items() if value is not None},
- }
- if not self.aiosession:
- async with aiohttp.ClientSession() as session:
- async with session.post(
- url, params=params, headers=headers, raise_for_status=False
- ) as response:
- search_results = await response.json()
- else:
- async with self.aiosession.post(
- url, params=params, headers=headers, raise_for_status=True
- ) as response:
- search_results = await response.json()
- return search_results
_google_serper_api_results函数,通过调用 Serper.dev 的谷歌搜索 API ,获取搜索结果。在进行Google网络搜索的时候,涉及到一些传输协议的内容。对于有基本开发经验的人来说,这应该是常识。从底层实现的角度,当请求是一个 HTTP 时,我们使用 POST 方法,然后,会得到一个响应 (response),通常大家会以 JSON 的方式获取并处理结果。该函数接收一个搜索词 search_term 和一个可选的搜索类型 search_type(默认为 "search")作为参数,创建一个包含请求头信息的 headers 字典,包括 API 密钥和内容类型。接着,创建了一个包含查询参数的params字典,其中包括搜索词和其他关键字参数,发起一个 POST 请求到google.serper.dev,并传入请求头和查询参数,然后检查响应的状态码,如果状态码表示请求成功,将响应的 JSON 数据解析为字典,最终返回搜索结果的字典。
当然,你也可以使用async的方式进行操作,使用_async_google_serper_search_results函数,它会用异步非阻塞的方式,其过程是一致的。使用async会涉及到一些概念,其底层的原理机制,实际上是基于协程 (Coroutine),在这里类似于Future,在将来的某个时刻你会完成操作,并在那时接收结果。对于具备编程经验的人来说,基本上都会理解这种直观的运行机制,因为你想要非阻塞,但又无法预知将来何时完成操作,因此使用了“as response”,因为你不确定何时会获得结果,所以使用了“await”,这样当你获取到结果时,再进行赋值。从理解的角度来看,大家不会遇到太多难点。我们关注的重点是LangChain 框架的执行方式,这里只是简单地展示一下异步的运行原理,它本身并不会带来太多困难。