From 0072686aab8b5d85bb6584f1e2f5c1224aaa8965 Mon Sep 17 00:00:00 2001 From: Harrison Chase Date: Fri, 30 Dec 2022 08:06:57 -0500 Subject: [PATCH] Harrison/new search engine (#477) Co-authored-by: Nicolas --- docs/examples/agents/search_tools.ipynb | 199 ++++++++++++++++ docs/examples/chains/google_search.ipynb | 100 ++++++++ docs/reference/integrations.md | 3 + langchain/__init__.py | 2 + langchain/agents/load_tools.py | 10 + langchain/utilities/__init__.py | 2 + langchain/utilities/google_search.py | 100 ++++++++ poetry.lock | 225 +++++++++++++++++- pyproject.toml | 4 +- .../test_googlesearch_api.py | 9 + 10 files changed, 651 insertions(+), 3 deletions(-) create mode 100644 docs/examples/agents/search_tools.ipynb create mode 100644 docs/examples/chains/google_search.ipynb create mode 100644 langchain/utilities/google_search.py create mode 100644 tests/integration_tests/test_googlesearch_api.py diff --git a/docs/examples/agents/search_tools.ipynb b/docs/examples/agents/search_tools.ipynb new file mode 100644 index 00000000..b6fe214e --- /dev/null +++ b/docs/examples/agents/search_tools.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6510f51c", + "metadata": {}, + "source": [ + "# Search Tools\n", + "\n", + "This notebook shows off usage of various search tools." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e6860c2d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import load_tools\n", + "from langchain.agents import initialize_agent\n", + "from langchain.llms import OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dadbcfcd", + "metadata": {}, + "outputs": [], + "source": [ + "llm = OpenAI(temperature=0)" + ] + }, + { + "cell_type": "markdown", + "id": "a09ca013", + "metadata": {}, + "source": [ + "## SerpAPI\n", + "\n", + "First, let's use the SerpAPI tool." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "dd4ce6d9", + "metadata": {}, + "outputs": [], + "source": [ + "tools = load_tools([\"serpapi\"], llm=llm)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ef63bb84", + "metadata": {}, + "outputs": [], + "source": [ + "agent = initialize_agent(tools, llm, agent=\"zero-shot-react-description\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "53e24f5d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m I need to find out what the current weather is in Pomfret.\n", + "Action: Search\n", + "Action Input: \"weather in Pomfret\"\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m10 Day Weather-Pomfret, CT ; Thu 29. 48° · 32°. 6% · SW 9 mph ; Fri 30. 53° · 39°. 8% · SW 8 mph ; Sat 31. 51° · 47°. 33% · SW 8 mph.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I now know the current weather in Pomfret.\n", + "Final Answer: The current weather in Pomfret is 10 Day Weather-Pomfret, CT ; Thu 29. 48° · 32°. 6% · SW 9 mph ; Fri 30. 53° · 39°. 8% · SW 8 mph ; Sat 31. 51° · 47°. 33% · SW 8 mph.\u001b[0m\n", + "\u001b[1m> Finished AgentExecutor chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "'The current weather in Pomfret is 10 Day Weather-Pomfret, CT ; Thu 29. 48° · 32°. 6% · SW 9 mph ; Fri 30. 53° · 39°. 8% · SW 8 mph ; Sat 31. 51° · 47°. 33% · SW 8 mph.'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent.run(\"What is the weather in Pomfret?\")" + ] + }, + { + "cell_type": "markdown", + "id": "8ef49137", + "metadata": {}, + "source": [ + "## GoogleSearchAPIWrapper\n", + "\n", + "Now, let's use the official Google Search API Wrapper." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3e9c7c20", + "metadata": {}, + "outputs": [], + "source": [ + "tools = load_tools([\"google-search\"], llm=llm)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "b83624dc", + "metadata": {}, + "outputs": [], + "source": [ + "agent = initialize_agent(tools, llm, agent=\"zero-shot-react-description\", verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9d5835e2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m I should look up the current weather conditions.\n", + "Action: Google Search\n", + "Action Input: \"weather in Pomfret\"\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mBe prepared with the most accurate 10-day forecast for Pomfret, CT with highs, lows, chance of precipitation from The Weather Channel and Weather.com. Pomfret, CT Weather Forecast, with current conditions, wind, air quality, and what to expect for the next 3 days. Hourly Weather-Pomfret, CT. As of 6:05 am EST. Thursday, December 29. 6:00 am. Pomfret CT. Today. Today: Partly sunny, with a high near 40. ... National Digital Forecast Database Maximum Temperature Forecast. High Temperature. Hourly Weather-Pomfret, CT. As of 4:54 pm EST. Tuesday, December 27. 5:00 pm. Pomfret Center Weather Forecasts. Weather Underground provides local & long-range weather forecasts, weatherreports, maps & tropical weather conditions for ... South Pomfret VT ... Hazardous Weather Outlook · Regional Weather Conditions ... National Digital Forecast Database Maximum Temperature Forecast. North Pomfret Weather Forecasts. Weather Underground provides local & long-range weather forecasts, weatherreports, maps & tropical weather conditions for ... Pomfret, CT 12 hour by hour weather forecast includes precipitation, temperatures, sky conditions, rain chance, dew-point, relative humidity, wind direction ... Hazardous Weather Conditions ... Pomfret Center CT ... Temperature rising to near 53 by 8am, then falling to around 38 during the remainder of the day.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I now know the current weather conditions in Pomfret.\n", + "Final Answer: The current weather in Pomfret is partly sunny with a high near 40. There is a chance of precipitation and temperatures will rise to near 53 by 8am, then fall to around 38 during the remainder of the day.\u001b[0m\n", + "\u001b[1m> Finished AgentExecutor chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "'The current weather in Pomfret is partly sunny with a high near 40. There is a chance of precipitation and temperatures will rise to near 53 by 8am, then fall to around 38 during the remainder of the day.'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent.run(\"What is the weather in Pomfret?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68a05cfd", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/chains/google_search.ipynb b/docs/examples/chains/google_search.ipynb new file mode 100644 index 00000000..ce8d59d4 --- /dev/null +++ b/docs/examples/chains/google_search.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "245a954a", + "metadata": {}, + "source": [ + "# Google Search\n", + "\n", + "This notebook goes over how to use the google search component.\n", + "\n", + "First, you need to set up the proper API keys and environment variables. To set it up, follow the instructions found [here](https://stackoverflow.com/questions/37083058/programmatically-searching-google-in-python-using-custom-search).\n", + "\n", + "Then we will need to set some environment variables." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "34bb5968", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"GOOGLE_CSE_ID\"] = \n", + "os.environ[\"GOOGLE_API_KEY\"] = " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ac4910f8", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain import GoogleSearchAPIWrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "84b8f773", + "metadata": {}, + "outputs": [], + "source": [ + "search = GoogleSearchAPIWrapper()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "068991a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'STATE OF HAWAII. 1 Child\\'s First Name. (Type or print). 2. Sex. BARACK. 3. This Birth. CERTIFICATE OF LIVE BIRTH. FILE. NUMBER 151 le. lb. Middle Name. Barack Hussein Obama II is an American politician who served as the 44th president of the United States from 2009 to 2017. A member of the Democratic Party,\\xa0... First Lady Michelle LaVaughn Robinson Obama is a lawyer, writer, and the wife of the 44th President, Barack Obama. She is the first African-American First\\xa0... Barack Obama, in full Barack Hussein Obama II, (born August 4, 1961, Honolulu, Hawaii, U.S.), 44th president of the United States (2009–17) and the first\\xa0... Aug 18, 2017 ... It took him several seconds and multiple clues to remember former President Barack Obama\\'s first name. Miller knew that every answer had to\\xa0... Feb 9, 2015 ... Michael Jordan misspelled Barack Obama\\'s first name on 50th-birthday gift ... Knowing Obama is a Chicagoan and huge basketball fan,\\xa0... His full name is Barack Hussein Obama II. Since the “II” is simply because he was named for his father, his last name is Obama. Jan 16, 2007 ... 4, 1961, in Honolulu. His first name means \"one who is blessed\" in Swahili. While Obama\\'s father, Barack Hussein Obama Sr., was from Kenya, his\\xa0... Jan 19, 2017 ... Hopeful parents named their sons for the first Black president, whose name is a variation of the Hebrew name Baruch, which means “blessed”\\xa0... Feb 27, 2020 ... President Barack Obama was born Barack Hussein Obama, II, as shown here on his birth certificate here . As reported by Reuters here , his\\xa0...'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.run(\"Obama's first name?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "028f4cba", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/reference/integrations.md b/docs/reference/integrations.md index 14dcdcd8..bf4799b7 100644 --- a/docs/reference/integrations.md +++ b/docs/reference/integrations.md @@ -18,6 +18,9 @@ The following use cases require specific installs and api keys: - _SerpAPI_: - Install requirements with `pip install google-search-results` - Get a SerpAPI api key and either set it as an environment variable (`SERPAPI_API_KEY`) or pass it to the LLM constructor as `serpapi_api_key`. +- _GoogleSearchAPI_: + - Install requirements with `pip install google-api-python-client` + - Get a Google api key and either set it as an environment variable (`GOOGLE_API_KEY`) or pass it to the LLM constructor as `google_api_key`. You will also need to set the `GOOGLE_CSE_ID` environment variable to your custom search engine id. You can pass it to the LLM constructor as `google_cse_id` as well. - _NatBot_: - Install requirements with `pip install playwright` - _Wikipedia_: diff --git a/langchain/__init__.py b/langchain/__init__.py index c100d0e3..537d209e 100644 --- a/langchain/__init__.py +++ b/langchain/__init__.py @@ -28,6 +28,7 @@ from langchain.prompts import ( ) from langchain.serpapi import SerpAPIChain, SerpAPIWrapper from langchain.sql_database import SQLDatabase +from langchain.utilities.google_search import GoogleSearchAPIWrapper from langchain.vectorstores import FAISS, ElasticVectorSearch logger: BaseLogger = StdOutLogger() @@ -42,6 +43,7 @@ __all__ = [ "SelfAskWithSearchChain", "SerpAPIWrapper", "SerpAPIChain", + "GoogleSearchAPIWrapper", "Cohere", "OpenAI", "BasePromptTemplate", diff --git a/langchain/agents/load_tools.py b/langchain/agents/load_tools.py index 2e41b3b9..57ee5516 100644 --- a/langchain/agents/load_tools.py +++ b/langchain/agents/load_tools.py @@ -12,6 +12,7 @@ from langchain.python import PythonREPL from langchain.requests import RequestsWrapper from langchain.serpapi import SerpAPIWrapper from langchain.utilities.bash import BashProcess +from langchain.utilities.google_search import GoogleSearchAPIWrapper def _get_python_repl() -> Tool: @@ -30,6 +31,14 @@ def _get_serpapi() -> Tool: ) +def _get_google_search() -> Tool: + return Tool( + "Google Search", + GoogleSearchAPIWrapper().run, + "A wrapper around Google Search. Useful for when you need to answer questions about current events. Input should be a search query.", + ) + + def _get_requests() -> Tool: return Tool( "Requests", @@ -51,6 +60,7 @@ _BASE_TOOLS = { "serpapi": _get_serpapi, "requests": _get_requests, "terminal": _get_terminal, + "google-search": _get_google_search, } diff --git a/langchain/utilities/__init__.py b/langchain/utilities/__init__.py index da09cd68..34488334 100644 --- a/langchain/utilities/__init__.py +++ b/langchain/utilities/__init__.py @@ -1,6 +1,8 @@ """General utilities.""" from langchain.utilities.bash import BashProcess +from langchain.utilities.google_search import GoogleSearchAPIWrapper __all__ = [ "BashProcess", + "GoogleSearchAPIWrapper", ] diff --git a/langchain/utilities/google_search.py b/langchain/utilities/google_search.py new file mode 100644 index 00000000..bd732f0e --- /dev/null +++ b/langchain/utilities/google_search.py @@ -0,0 +1,100 @@ +"""Util that calls Google Search.""" +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, Extra, root_validator + +from langchain.utils import get_from_dict_or_env + + +class GoogleSearchAPIWrapper(BaseModel): + """Wrapper for Google Search API. + + Adapted from: Instructions adapted from https://stackoverflow.com/questions/ + 37083058/ + programmatically-searching-google-in-python-using-custom-search + + TODO: DOCS for using it + 1. Install google-api-python-client + - If you don't already have a Google account, sign up. + - If you have never created a Google APIs Console project, + read the Managing Projects page and create a project in the Google API Console. + - Install the library using pip install google-api-python-client + The current version of the library is 2.70.0 at this time + + 2. To create an API key: + - Navigate to the APIs & Services→Credentials panel in Cloud Console. + - Select Create credentials, then select API key from the drop-down menu. + - The API key created dialog box displays your newly created key. + - You now have an API_KEY + + 3. Setup Custom Search Engine so you can search the entire web + - Create a custom search engine in this link. + - In Sites to search, add any valid URL (i.e. www.stackoverflow.com). + - That’s all you have to fill up, the rest doesn’t matter. + In the left-side menu, click Edit search engine → {your search engine name} + → Setup Set Search the entire web to ON. Remove the URL you added from + the list of Sites to search. + - Under Search engine ID you’ll find the search-engine-ID. + + 4. Enable the Custom Search API + - Navigate to the APIs & Services→Dashboard panel in Cloud Console. + - Click Enable APIs and Services. + - Search for Custom Search API and click on it. + - Click Enable. + URL for it: https://console.cloud.google.com/apis/library/customsearch.googleapis + .com + """ + + search_engine: Any #: :meta private: + google_api_key: Optional[str] = None + google_cse_id: Optional[str] = None + + class Config: + """Configuration for this pydantic object.""" + + extra = Extra.forbid + + def _google_search_results(self, search_term: str, **kwargs: Any) -> List[dict]: + res = ( + self.search_engine.cse() + .list(q=search_term, cx=self.google_cse_id, **kwargs) + .execute() + ) + return res["items"] + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + """Validate that api key and python package exists in environment.""" + google_api_key = get_from_dict_or_env( + values, "google_api_key", "GOOGLE_API_KEY" + ) + values["google_api_key"] = google_api_key + + google_cse_id = get_from_dict_or_env(values, "google_cse_id", "GOOGLE_CSE_ID") + values["google_cse_id"] = google_cse_id + + try: + from googleapiclient.discovery import build + + except ImportError: + raise ImportError( + "google-api-python-client is not installed. " + "Please install it with `pip install google-api-python-client`" + ) + + service = build("customsearch", "v1", developerKey=google_api_key) + values["search_engine"] = service + + # TODO: Add error handling if keys are missing + return values + + def run(self, query: str) -> str: + """Run query through GoogleSearch and parse result.""" + snippets = [] + results = self._google_search_results(query, num=10) + if len(results) == 0: + return "No good Google Search Result was found" + for result in results: + snippets.append(result["snippet"]) + + return " ".join(snippets) diff --git a/poetry.lock b/poetry.lock index 73b3479b..15853759 100644 --- a/poetry.lock +++ b/poetry.lock @@ -315,6 +315,18 @@ lxml = ">=4.9,<5.0" pycryptodomex = ">=3.8,<4.0" urllib3 = ">=1.25,<2.0" +[[package]] +name = "cachetools" +version = "5.2.0" +description = "Extensible memoizing collections and decorators" +category = "main" +optional = true +python-versions = "~=3.7" +files = [ + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, +] + [[package]] name = "catalogue" version = "2.0.8" @@ -999,6 +1011,107 @@ files = [ {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, ] +[[package]] +name = "google-api-core" +version = "2.11.0" +description = "Google API client core library" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.11.0.tar.gz", hash = "sha256:4b9bb5d5a380a0befa0573b302651b8a9a89262c1730e37bf423cec511804c22"}, + {file = "google_api_core-2.11.0-py3-none-any.whl", hash = "sha256:ce222e27b0de0d7bc63eb043b956996d6dccab14cc3b690aaea91c9cc99dc16e"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0dev" +googleapis-common-protos = ">=1.56.2,<2.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +requests = ">=2.18.0,<3.0.0dev" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)", "grpcio-status (>=1.49.1,<2.0dev)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] + +[[package]] +name = "google-api-python-client" +version = "2.70.0" +description = "Google API Client Library for Python" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "google-api-python-client-2.70.0.tar.gz", hash = "sha256:262de094d5a30d337f59e66581019fed45b698c078397ac48dd323c0968236e7"}, + {file = "google_api_python_client-2.70.0-py2.py3-none-any.whl", hash = "sha256:67da78956f2bf4b763305cd791aeab250878c1f88f1422aaba4682a608b8e5a4"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.19.0,<3.0.0dev" +google-auth-httplib2 = ">=0.1.0" +httplib2 = ">=0.15.0,<1dev" +uritemplate = ">=3.0.1,<5" + +[[package]] +name = "google-auth" +version = "2.15.0" +description = "Google Authentication Library" +category = "main" +optional = true +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +files = [ + {file = "google-auth-2.15.0.tar.gz", hash = "sha256:72f12a6cfc968d754d7bdab369c5c5c16032106e52d32c6dfd8484e4c01a6d1f"}, + {file = "google_auth-2.15.0-py2.py3-none-any.whl", hash = "sha256:6897b93556d8d807ad70701bb89f000183aea366ca7ed94680828b37437a4994"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} +six = ">=1.9.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] + +[[package]] +name = "google-auth-httplib2" +version = "0.1.0" +description = "Google Authentication Library: httplib2 transport" +category = "main" +optional = true +python-versions = "*" +files = [ + {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, + {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, +] + +[package.dependencies] +google-auth = "*" +httplib2 = ">=0.15.0" +six = "*" + +[[package]] +name = "googleapis-common-protos" +version = "1.57.0" +description = "Common protobufs used in Google APIs" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.57.0.tar.gz", hash = "sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46"}, + {file = "googleapis_common_protos-1.57.0-py2.py3-none-any.whl", hash = "sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] + [[package]] name = "greenlet" version = "2.0.1" @@ -1073,6 +1186,21 @@ files = [ docs = ["Sphinx", "docutils (<0.18)"] test = ["faulthandler", "objgraph", "psutil"] +[[package]] +name = "httplib2" +version = "0.21.0" +description = "A comprehensive HTTP client library." +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "httplib2-0.21.0-py3-none-any.whl", hash = "sha256:987c8bb3eb82d3fa60c68699510a692aa2ad9c4bd4f123e51dfb1488c14cdd01"}, + {file = "httplib2-0.21.0.tar.gz", hash = "sha256:fc144f091c7286b82bec71bdbd9b27323ba709cc612568d3000893bfd9cb4b34"}, +] + +[package.dependencies] +pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} + [[package]] name = "huggingface-hub" version = "0.11.1" @@ -2504,6 +2632,30 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "protobuf" +version = "4.21.12" +description = "" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.21.12-cp310-abi3-win32.whl", hash = "sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1"}, + {file = "protobuf-4.21.12-cp310-abi3-win_amd64.whl", hash = "sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2"}, + {file = "protobuf-4.21.12-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791"}, + {file = "protobuf-4.21.12-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97"}, + {file = "protobuf-4.21.12-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7"}, + {file = "protobuf-4.21.12-cp37-cp37m-win32.whl", hash = "sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717"}, + {file = "protobuf-4.21.12-cp37-cp37m-win_amd64.whl", hash = "sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"}, + {file = "protobuf-4.21.12-cp38-cp38-win32.whl", hash = "sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec"}, + {file = "protobuf-4.21.12-cp38-cp38-win_amd64.whl", hash = "sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30"}, + {file = "protobuf-4.21.12-cp39-cp39-win32.whl", hash = "sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc"}, + {file = "protobuf-4.21.12-cp39-cp39-win_amd64.whl", hash = "sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b"}, + {file = "protobuf-4.21.12-py2.py3-none-any.whl", hash = "sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5"}, + {file = "protobuf-4.21.12-py3-none-any.whl", hash = "sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462"}, + {file = "protobuf-4.21.12.tar.gz", hash = "sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab"}, +] + [[package]] name = "psutil" version = "5.9.4" @@ -2570,6 +2722,33 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +[[package]] +name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" +optional = true +python-versions = "*" +files = [ + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.2.8" +description = "A collection of ASN.1-based protocols modules." +category = "main" +optional = true +python-versions = "*" +files = [ + {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, + {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.5.0" + [[package]] name = "pycodestyle" version = "2.10.0" @@ -2743,6 +2922,21 @@ files = [ [package.extras] plugins = ["importlib-metadata"] +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = true +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pyrsistent" version = "0.19.2" @@ -3279,6 +3473,21 @@ files = [ {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, ] +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +category = "main" +optional = true +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "send2trash" version = "1.8.0" @@ -4126,6 +4335,18 @@ files = [ [package.extras] dev = ["flake8 (<4.0.0)", "flake8-annotations", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-noqa", "flake8-requirements", "flake8-type-annotations", "flake8-use-fstring", "mypy", "pep8-naming"] +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +category = "main" +optional = true +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + [[package]] name = "urllib3" version = "1.26.13" @@ -4358,10 +4579,10 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] -all = ["manifest-ml", "elasticsearch", "faiss-cpu", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "weaviate-client", "redis"] +all = ["manifest-ml", "elasticsearch", "faiss-cpu", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "weaviate-client", "redis", "google-api-python-client"] llms = ["manifest-ml", "torch", "transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "42285e79e2456ec07fc1b1baff1ed28c344f9be95ffda46bbea5b48441050078" +content-hash = "e74acf12a1ee1db03d9ab506880937a0843f057f0478e06c54562379ce651088" diff --git a/pyproject.toml b/pyproject.toml index 78cf4709..e19a4f84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ jinja2 = {version = "^3", optional = true} tiktoken = {version = "^0", optional = true, python="^3.9"} pinecone-client = {version = "^2", optional = true} weaviate-client = {version = "^3", optional = true} +google-api-python-client = {version = "2.70.0", optional = true} + [tool.poetry.group.test.dependencies] pytest = "^7.2.0" @@ -58,7 +60,7 @@ playwright = "^1.28.0" [tool.poetry.extras] llms = ["cohere", "openai", "nlpcloud", "huggingface_hub", "manifest-ml", "torch", "transformers"] -all = ["cohere", "openai", "nlpcloud", "huggingface_hub", "manifest-ml", "elasticsearch", "google-search-results", "faiss-cpu", "sentence_transformers", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "weaviate-client", "redis"] +all = ["cohere", "openai", "nlpcloud", "huggingface_hub", "manifest-ml", "elasticsearch", "google-search-results", "faiss-cpu", "sentence_transformers", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "weaviate-client", "redis", "google-api-python-client"] [tool.isort] profile = "black" diff --git a/tests/integration_tests/test_googlesearch_api.py b/tests/integration_tests/test_googlesearch_api.py new file mode 100644 index 00000000..7bbef2cb --- /dev/null +++ b/tests/integration_tests/test_googlesearch_api.py @@ -0,0 +1,9 @@ +"""Integration test for Google Search API Wrapper.""" +from langchain.utilities.google_search import GoogleSearchAPIWrapper + + +def test_call() -> None: + """Test that call gives the correct answer.""" + search = GoogleSearchAPIWrapper() + output = search.run("What was Obama's first name?") + assert "Barack Hussein Obama II" in output