From 1cb6498fdb13b64a8950ce44dca1515b0d76bb28 Mon Sep 17 00:00:00 2001 From: Ted Martinez Date: Thu, 25 May 2023 19:19:22 -0700 Subject: [PATCH] Tedma4/twilio tool (#5136) # Add twilio sms tool --------- Co-authored-by: Dev 2049 --- .../agents/tools/examples/twilio.ipynb | 107 ++++++++++++++++++ langchain/agents/load_tools.py | 10 ++ langchain/utilities/__init__.py | 22 ++-- langchain/utilities/twilio.py | 83 ++++++++++++++ .../utilities/test_twilio.py | 9 ++ 5 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 docs/modules/agents/tools/examples/twilio.ipynb create mode 100644 langchain/utilities/twilio.py create mode 100644 tests/integration_tests/utilities/test_twilio.py diff --git a/docs/modules/agents/tools/examples/twilio.ipynb b/docs/modules/agents/tools/examples/twilio.ipynb new file mode 100644 index 00000000..3b1bc71c --- /dev/null +++ b/docs/modules/agents/tools/examples/twilio.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dc23c48e", + "metadata": {}, + "source": [ + "# Twilio\n", + "\n", + "This notebook goes over how to use the [Twilio](https://www.twilio.com) API wrapper to send a text message." + ] + }, + { + "cell_type": "markdown", + "id": "c1a33b13", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "To use this tool you need to install the Python Twilio package `twilio`" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "98b544b9", + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install twilio" + ] + }, + { + "cell_type": "markdown", + "id": "f7e883ae", + "metadata": {}, + "source": [ + "You'll also need to set up a Twilio account and get your credentials. You'll need your Account String Identifier (SID) and your Auth Token. You'll also need a number to send messages from.\n", + "\n", + "You can either pass these in to the TwilioAPIWrapper as named parameters `account_sid`, `auth_token`, `from_number`, or you can set the environment variables `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_FROM_NUMBER`." + ] + }, + { + "cell_type": "markdown", + "id": "36c133be", + "metadata": {}, + "source": [ + "## Sending a message" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "54bf5afd", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.utilities.twilio import TwilioAPIWrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "31f8f382", + "metadata": {}, + "outputs": [], + "source": [ + "twilio = TwilioAPIWrapper(\n", + "# account_sid=\"foo\",\n", + "# auth_token=\"bar\",\n", + "# from_number=\"baz,\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5009d763", + "metadata": {}, + "outputs": [], + "source": [ + "twilio.run(\"hello world\", \"+16162904619\")" + ] + } + ], + "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.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain/agents/load_tools.py b/langchain/agents/load_tools.py index 13427d19..33e4f78a 100644 --- a/langchain/agents/load_tools.py +++ b/langchain/agents/load_tools.py @@ -46,6 +46,7 @@ from langchain.utilities.awslambda import LambdaWrapper from langchain.utilities.graphql import GraphQLAPIWrapper from langchain.utilities.searx_search import SearxSearchWrapper from langchain.utilities.serpapi import SerpAPIWrapper +from langchain.utilities.twilio import TwilioAPIWrapper from langchain.utilities.wikipedia import WikipediaAPIWrapper from langchain.utilities.wolfram_alpha import WolframAlphaAPIWrapper from langchain.utilities.openweathermap import OpenWeatherMapAPIWrapper @@ -218,6 +219,14 @@ def _get_serpapi(**kwargs: Any) -> BaseTool: ) +def _get_twilio(**kwargs: Any) -> BaseTool: + return Tool( + name="Text Message", + description="Useful for when you need to send a text message to a provided phone number.", + func=TwilioAPIWrapper(**kwargs).run, + ) + + def _get_searx_search(**kwargs: Any) -> BaseTool: return SearxSearchRun(wrapper=SearxSearchWrapper(**kwargs)) @@ -286,6 +295,7 @@ _EXTRA_OPTIONAL_TOOLS: Dict[str, Tuple[Callable[[KwArg(Any)], BaseTool], List[st ["serper_api_key", "aiosession"], ), "serpapi": (_get_serpapi, ["serpapi_api_key", "aiosession"]), + "twilio": (_get_twilio, ["account_sid", "auth_token", "from_number"]), "searx-search": (_get_searx_search, ["searx_host", "engines", "aiosession"]), "wikipedia": (_get_wikipedia, ["top_k_results", "lang"]), "arxiv": ( diff --git a/langchain/utilities/__init__.py b/langchain/utilities/__init__.py index bd45a8dc..58eff56b 100644 --- a/langchain/utilities/__init__.py +++ b/langchain/utilities/__init__.py @@ -17,6 +17,7 @@ from langchain.utilities.python import PythonREPL from langchain.utilities.searx_search import SearxSearchWrapper from langchain.utilities.serpapi import SerpAPIWrapper from langchain.utilities.spark_sql import SparkSQL +from langchain.utilities.twilio import TwilioAPIWrapper from langchain.utilities.wikipedia import WikipediaAPIWrapper from langchain.utilities.wolfram_alpha import WolframAlphaAPIWrapper @@ -24,21 +25,22 @@ __all__ = [ "ApifyWrapper", "ArxivAPIWrapper", "BashProcess", - "TextRequestsWrapper", + "BingSearchAPIWrapper", "DuckDuckGoSearchAPIWrapper", + "GooglePlacesAPIWrapper", "GoogleSearchAPIWrapper", "GoogleSerperAPIWrapper", - "GooglePlacesAPIWrapper", "GraphQLAPIWrapper", - "WolframAlphaAPIWrapper", - "SerpAPIWrapper", - "SearxSearchWrapper", - "BingSearchAPIWrapper", - "WikipediaAPIWrapper", - "OpenWeatherMapAPIWrapper", - "PythonREPL", "LambdaWrapper", + "MetaphorSearchAPIWrapper", + "OpenWeatherMapAPIWrapper", "PowerBIDataset", + "PythonREPL", + "SearxSearchWrapper", + "SerpAPIWrapper", "SparkSQL", - "MetaphorSearchAPIWrapper", + "TextRequestsWrapper", + "TwilioAPIWrapper", + "WikipediaAPIWrapper", + "WolframAlphaAPIWrapper", ] diff --git a/langchain/utilities/twilio.py b/langchain/utilities/twilio.py new file mode 100644 index 00000000..bf231b48 --- /dev/null +++ b/langchain/utilities/twilio.py @@ -0,0 +1,83 @@ +"""Util that calls Twilio.""" +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Extra, root_validator + +from langchain.utils import get_from_dict_or_env + + +class TwilioAPIWrapper(BaseModel): + """Sms Client using Twilio. + + To use, you should have the ``twilio`` python package installed, + and the environment variables ``TWILIO_ACCOUNT_SID``, ``TWILIO_AUTH_TOKEN``, and + ``TWILIO_FROM_NUMBER``, or pass `account_sid`, `auth_token`, and `from_number` as + named parameters to the constructor. + + Example: + .. code-block:: python + + from langchain.utilities.twilio import TwilioAPIWrapper + twilio = TwilioAPIWrapper( + account_sid="ACxxx", + auth_token="xxx", + from_number="+10123456789" + ) + twilio.run('test', '+12484345508') + """ + + client: Any #: :meta private: + account_sid: Optional[str] = None + """Twilio account string identifier.""" + auth_token: Optional[str] = None + """Twilio auth token.""" + from_number: Optional[str] = None + """A Twilio phone number in [E.164](https://www.twilio.com/docs/glossary/what-e164) + format, an + [alphanumeric sender ID](https://www.twilio.com/docs/sms/send-messages#use-an-alphanumeric-sender-id), + or a [Channel Endpoint address](https://www.twilio.com/docs/sms/channels#channel-addresses) + that is enabled for the type of message you want to send. Phone numbers or + [short codes](https://www.twilio.com/docs/sms/api/short-code) purchased from + Twilio also work here. You cannot, for example, spoof messages from a private + cell phone number. If you are using `messaging_service_sid`, this parameter + must be empty. + """ # noqa: E501 + + class Config: + """Configuration for this pydantic object.""" + + extra = Extra.forbid + arbitrary_types_allowed = False + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + """Validate that api key and python package exists in environment.""" + try: + from twilio.rest import Client + except ImportError: + raise ImportError( + "Could not import twilio python package. " + "Please install it with `pip install twilio`." + ) + account_sid = get_from_dict_or_env(values, "account_sid", "TWILIO_ACCOUNT_SID") + auth_token = get_from_dict_or_env(values, "auth_token", "TWILIO_AUTH_TOKEN") + values["from_number"] = get_from_dict_or_env( + values, "from_number", "TWILIO_FROM_NUMBER" + ) + values["client"] = Client(account_sid, auth_token) + return values + + def run(self, body: str, to: str) -> str: + """Run body through Twilio and respond with message sid. + + Args: + body: The text of the message you want to send. Can be up to 1,600 + characters in length. + to: The destination phone number in + [E.164](https://www.twilio.com/docs/glossary/what-e164) format for + SMS/MMS or + [Channel user address](https://www.twilio.com/docs/sms/channels#channel-addresses) + for other 3rd-party channels. + """ # noqa: E501 + message = self.client.messages.create(to, from_=self.from_number, body=body) + return message.sid diff --git a/tests/integration_tests/utilities/test_twilio.py b/tests/integration_tests/utilities/test_twilio.py new file mode 100644 index 00000000..c7b4de33 --- /dev/null +++ b/tests/integration_tests/utilities/test_twilio.py @@ -0,0 +1,9 @@ +"""Integration test for Sms.""" +from langchain.utilities.twilio import TwilioAPIWrapper + + +def test_call() -> None: + """Test that call runs.""" + twilio = TwilioAPIWrapper() + output = twilio.run("Message", "+16162904619") + assert output