diff --git a/docs/modules/agents/tools/examples/brave_search.ipynb b/docs/modules/agents/tools/examples/brave_search.ipynb new file mode 100644 index 00000000..322282a9 --- /dev/null +++ b/docs/modules/agents/tools/examples/brave_search.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "eda326e4", + "metadata": {}, + "source": [ + "# Brave Search\n", + "\n", + "This notebook goes over how to use the Brave Search tool." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a4c896e5", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.tools import BraveSearch" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6784d37c", + "metadata": {}, + "outputs": [], + "source": [ + "api_key = \"...\"" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5b14008a", + "metadata": {}, + "outputs": [], + "source": [ + "tool = BraveSearch.from_api_key(api_key=api_key, search_kwargs={\"count\": 3})" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f11937b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'[{\"title\": \"Barack Obama - Wikipedia\", \"link\": \"https://en.wikipedia.org/wiki/Barack_Obama\", \"snippet\": \"Outside of politics, Obama has published three bestselling books: Dreams from My Father (1995), The Audacity of Hope (2006) and A Promised Land (2020). Rankings by scholars and historians, in which he has been featured since 2010, place him in the middle to upper tier of American presidents.\"}, {\"title\": \"Obama\\'s Middle Name -- My Last Name -- is \\'Hussein.\\' So?\", \"link\": \"https://www.cair.com/cair_in_the_news/obamas-middle-name-my-last-name-is-hussein-so/\", \"snippet\": \"Many Americans understand that common names don\\\\u2019t only come in the form of a \\\\u201cSmith\\\\u201d or a \\\\u201cJohnson.\\\\u201d Perhaps, they have a neighbor, mechanic or teacher named Hussein. Or maybe they\\\\u2019ve seen fashion designer Hussein Chalayan in the pages of Vogue or recall King Hussein, our ally in the Middle East.\"}, {\"title\": \"What\\'s up with Obama\\'s middle name? - Quora\", \"link\": \"https://www.quora.com/Whats-up-with-Obamas-middle-name\", \"snippet\": \"Answer (1 of 15): A better question would be, \\\\u201cWhat\\\\u2019s up with Obama\\\\u2019s first name?\\\\u201d President Barack Hussein Obama\\\\u2019s father\\\\u2019s name was Barack Hussein Obama. He was named after his father. Hussein, Obama\\\\u2019s middle name, is a very common Arabic name, meaning "good," "handsome," or "beautiful."\"}]'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tool.run(\"obama middle name\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da9c63d5", + "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.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/modules/agents/tools/examples/google_search.ipynb b/docs/modules/agents/tools/examples/google_search.ipynb index 84c2a351..79827f33 100644 --- a/docs/modules/agents/tools/examples/google_search.ipynb +++ b/docs/modules/agents/tools/examples/google_search.ipynb @@ -184,7 +184,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.2" + "version": "3.9.1" }, "vscode": { "interpreter": { diff --git a/langchain/tools/__init__.py b/langchain/tools/__init__.py index 9f1d1de2..7e1cf7a7 100644 --- a/langchain/tools/__init__.py +++ b/langchain/tools/__init__.py @@ -8,6 +8,7 @@ from langchain.tools.azure_cognitive_services import ( ) from langchain.tools.base import BaseTool, StructuredTool, Tool, tool from langchain.tools.bing_search.tool import BingSearchResults, BingSearchRun +from langchain.tools.brave_search.tool import BraveSearch from langchain.tools.ddg_search.tool import DuckDuckGoSearchResults, DuckDuckGoSearchRun from langchain.tools.file_management.copy import CopyFileTool from langchain.tools.file_management.delete import DeleteFileTool @@ -118,4 +119,5 @@ __all__ = [ "ZapierNLARunAction", "tool", "YouTubeSearchTool", + "BraveSearch", ] diff --git a/langchain/tools/brave_search/__init__.py b/langchain/tools/brave_search/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/langchain/tools/brave_search/tool.py b/langchain/tools/brave_search/tool.py new file mode 100644 index 00000000..b8d41061 --- /dev/null +++ b/langchain/tools/brave_search/tool.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import Any, Optional + +from langchain.callbacks.manager import ( + AsyncCallbackManagerForToolRun, + CallbackManagerForToolRun, +) +from langchain.tools.base import BaseTool +from langchain.utilities.brave_search import BraveSearchWrapper + + +class BraveSearch(BaseTool): + name = "brave-search" + description = ( + "a search engine. " + "useful for when you need to answer questions about current events." + " input should be a search query." + ) + search_wrapper: BraveSearchWrapper + + @classmethod + def from_api_key( + cls, api_key: str, search_kwargs: Optional[dict] = None, **kwargs: Any + ) -> BraveSearch: + wrapper = BraveSearchWrapper(api_key=api_key, search_kwargs=search_kwargs or {}) + return cls(search_wrapper=wrapper, **kwargs) + + def _run( + self, + query: str, + run_manager: Optional[CallbackManagerForToolRun] = None, + ) -> str: + """Use the tool.""" + return self.search_wrapper.run(query) + + async def _arun( + self, + query: str, + run_manager: Optional[AsyncCallbackManagerForToolRun] = None, + ) -> str: + """Use the tool asynchronously.""" + raise NotImplementedError("BraveSearch does not support async") diff --git a/langchain/utilities/brave_search.py b/langchain/utilities/brave_search.py new file mode 100644 index 00000000..8210e408 --- /dev/null +++ b/langchain/utilities/brave_search.py @@ -0,0 +1,40 @@ +import json + +import requests +from pydantic import BaseModel, Field + + +class BraveSearchWrapper(BaseModel): + api_key: str + search_kwargs: dict = Field(default_factory=dict) + + def run(self, query: str) -> str: + headers = { + "X-Subscription-Token": self.api_key, + "Accept": "application/json", + } + base_url = "https://api.search.brave.com/res/v1/web/search" + req = requests.PreparedRequest() + params = {**self.search_kwargs, **{"q": query}} + req.prepare_url(base_url, params) + if req.url is None: + raise ValueError("prepared url is None, this should not happen") + + response = requests.get(req.url, headers=headers) + + if not response.ok: + raise Exception(f"HTTP error {response.status_code}") + + parsed_response = response.json() + web_search_results = parsed_response.get("web", {}).get("results", []) + final_results = [] + if isinstance(web_search_results, list): + for item in web_search_results: + final_results.append( + { + "title": item.get("title"), + "link": item.get("url"), + "snippet": item.get("description"), + } + ) + return json.dumps(final_results) diff --git a/tests/unit_tests/tools/test_public_api.py b/tests/unit_tests/tools/test_public_api.py index d73b6be7..ffefb8dd 100644 --- a/tests/unit_tests/tools/test_public_api.py +++ b/tests/unit_tests/tools/test_public_api.py @@ -60,6 +60,7 @@ _EXPECTED = [ "ZapierNLARunAction", "tool", "YouTubeSearchTool", + "BraveSearch", ]