diff --git a/llama_hub/tools/chatgpt_plugin/README.md b/llama_hub/tools/chatgpt_plugin/README.md new file mode 100644 index 0000000000..6b4b6b1406 --- /dev/null +++ b/llama_hub/tools/chatgpt_plugin/README.md @@ -0,0 +1,35 @@ +# ChatGPT Plugin Tool + +This tool allows Agents to load a plugin using a ChatGPT manifest file, and have the Agent interact with it. + +## Usage + +This tool has more extensive example usage documented in a Jupyter notebook [here](https://github.com/emptycrown/llama-hub/tree/main/llama_hub/tools/notebooks/chatgpt_plugin.ipynb) + +```python +# Load the manifest +import requests +import yaml +f = requests.get('https://raw.githubusercontent.com/sisbell/chatgpt-plugin-store/main/manifests/today-currency-converter.oiconma.repl.co.json').text +manifest = yaml.load(f, Loader=yaml.Loader) + +from llama_hub.tools.chatgpt_plugin.base import ChatGPTPluginToolSpec +from llama_index.agent import OpenAIAgent +from llama_hub.tools.requests.base import RequestsToolSpec + +requests_spec = RequestsToolSpec() +plugin_spec = ChatGPTPluginToolSpec(manifest) +# OR +plugin_spec = ChatGPTPluginToolSpec(manifest_url='https://raw.githubusercontent.com/sisbell/chatgpt-plugin-store/main/manifests/today-currency-converter.oiconma.repl.co.json') + +agent = OpenAIAgent.from_tools([*plugin_spec.to_tool_list(), *requests_spec.to_tool_list()], verbose=True) +print(agent.chat("Convert 100 euros to CAD")) +``` + +`describe_plugin`: Describe the plugin that has been loaded. +`load_openapi_spec`: Returns the parsed OpenAPI spec that the class was initialized with + +In addition to the above method, this tool makes all of the tools available from the OpenAPI Tool Spec and Requests Tool Spec available to the agent. The plugin OpenAPI defintion is loaded into the OpenAPI tool spec, and authentication headers are passed in to the Requests tool spec + + +This loader is designed to be used as a way to load data as a Tool in a Agent. See [here](https://github.com/emptycrown/llama-hub/tree/main) for examples. diff --git a/llama_hub/tools/chatgpt_plugin/__init__.py b/llama_hub/tools/chatgpt_plugin/__init__.py new file mode 100644 index 0000000000..b27244d45d --- /dev/null +++ b/llama_hub/tools/chatgpt_plugin/__init__.py @@ -0,0 +1 @@ +"""init.py""" \ No newline at end of file diff --git a/llama_hub/tools/chatgpt_plugin/base.py b/llama_hub/tools/chatgpt_plugin/base.py new file mode 100644 index 0000000000..50de7e382a --- /dev/null +++ b/llama_hub/tools/chatgpt_plugin/base.py @@ -0,0 +1,67 @@ +"""ChatGPT Plugiun Tool.""" + +from typing import List, Optional + +from llama_index.readers.schema.base import Document +from llama_index.tools.tool_spec.base import BaseToolSpec +from llama_hub.tools.openapi.base import OpenAPIToolSpec +import requests +import yaml + +class ChatGPTPluginToolSpec(BaseToolSpec): + """ChatGPT Plugin Tool + + This tool leverages the OpenAPI tool spec to automatically load ChatGPT + plugins from a manifest file. + You should also provide the Requests tool spec to allow the Agent to make calls to the OpenAPI endpoints + To use endpoints with authorization, use the Requests tool spec with the authorization headers + """ + + spec_functions = ['load_openapi_spec', 'describe_plugin'] + + def __init__(self, manifest: Optional[dict] = None, manifest_url: Optional[str] = None): + if manifest and manifest_url: + raise ValueError('You cannot provide both a manifest and a manifest_url') + elif manifest: + pass + elif manifest_url: + response = requests.get(manifest_url).text + manifest = yaml.load(response, Loader=yaml.Loader) + else: + raise ValueError('You must provide either a manifest or a manifest_url') + + if manifest['api']['type'] != 'openapi': + raise ValueError(f'API type must be "openapi", not "{manifest["api"]["type"]}"') + + if manifest['auth']['type'] != 'none': + raise ValueError(f'Authentication cannot be supported for ChatGPT plugins') + + self.openapi = OpenAPIToolSpec(url=manifest['api']['url']) + + self.plugin_description = f""" + 'human_description': {manifest['description_for_human']} + 'model_description': {manifest['description_for_model']} + """ + + def load_openapi_spec(self) -> List[Document]: + """ + You are an AI agent specifically designed to retrieve information by making web requests to an API based on an OpenAPI specification. + + Here's a step-by-step guide to assist you in answering questions: + + 1. Determine the base URL required for making the request + + 2. Identify the relevant paths necessary to address the question + + 3. Find the required parameters for making the request + + 4. Perform the necessary requests to obtain the answer + + Returns: + Document: A List of Document objects describing the OpenAPI spec + """ + return self.openapi.load_openapi_spec() + + def describe_plugin(self) -> List[Document]: + return self.plugin_description + diff --git a/llama_hub/tools/library.json b/llama_hub/tools/library.json index 7148447e9e..76460c15ed 100644 --- a/llama_hub/tools/library.json +++ b/llama_hub/tools/library.json @@ -1,4 +1,8 @@ { + "ChatGPTPluginToolSpec": { + "id": "tools/chatgpt_plugin", + "author": "ajhofmann" + }, "CodeInterpreterToolSpec": { "id": "tools/code_interpreter", "author": "ajhofmann" @@ -41,6 +45,10 @@ "id": "tools/slack", "author": "jerryjliu" }, + "TextToImageToolSpec": { + "id": "tools/text_to_image", + "author": "ajhofmann" + }, "VectorDBToolSpec": { "id": "tools/vector_db", "author": "jerryjliu" diff --git a/llama_hub/tools/notebooks/chatgpt_plugin.ipynb b/llama_hub/tools/notebooks/chatgpt_plugin.ipynb new file mode 100644 index 0000000000..12d0c8a821 --- /dev/null +++ b/llama_hub/tools/notebooks/chatgpt_plugin.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "5f976f46-9d51-407e-8eeb-e46cfdd22ae1", + "metadata": {}, + "outputs": [], + "source": [ + "import openai\n", + "openai.api_key = 'sk-your-key'\n", + "from llama_index.agent import OpenAIAgent" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "538e1519-2cfe-40f4-a861-65139156d2fe", + "metadata": {}, + "outputs": [], + "source": [ + "# Load the Plugin\n", + "import requests\n", + "import yaml\n", + "f = requests.get('https://raw.githubusercontent.com/sisbell/chatgpt-plugin-store/main/manifests/today-currency-converter.oiconma.repl.co.json').text\n", + "manifest = yaml.load(f, Loader=yaml.Loader)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b988f94e-6559-4404-9ba8-b41d4e4da770", + "metadata": {}, + "outputs": [], + "source": [ + "from llama_hub.tools.chatgpt_plugin.base import ChatGPTPluginToolSpec\n", + "from llama_hub.tools.requests.base import RequestsToolSpec\n", + "\n", + "requests_spec = RequestsToolSpec()\n", + "plugin_spec = ChatGPTPluginToolSpec(manifest)\n", + "# OR\n", + "plugin_spec = ChatGPTPluginToolSpec(manifest_url='https://raw.githubusercontent.com/sisbell/chatgpt-plugin-store/main/manifests/today-currency-converter.oiconma.repl.co.json')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "475cd741-e53e-4e9c-857b-6cb52641af0d", + "metadata": {}, + "outputs": [], + "source": [ + "agent = OpenAIAgent.from_tools([*plugin_spec.to_tool_list(), *requests_spec.to_tool_list()], verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "88175b8a-0345-44d2-8b78-983d7e7415bb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Calling Function ===\n", + "Calling function: describe_plugin with args: {}\n", + "Got output: \n", + " 'human_description': Converts currency values based on the latest exchange rates.\n", + " 'model_description': Converts currency values based on the latest exchange rates.\n", + " \n", + "========================\n", + "The OpenAPI plugin that was loaded is a plugin that converts currency values based on the latest exchange rates. It provides functionality to retrieve exchange rates and perform currency conversions.\n" + ] + } + ], + "source": [ + "print(agent.chat(\"Can you give me info on the OpenAPI plugin that was loaded\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "39e42a58-a6db-47f0-8bd7-a4c3728f0839", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Calling Function ===\n", + "Calling function: load_openapi_spec with args: {}\n", + "Got output: [Document(id_='bd92f5ee-3c31-4938-a75f-004d6e451181', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, hash='b43045dea83f8c9bbefe5516329054c5ec962524078a7c9f6055af71666dd4bb', text=\"{'servers': [{'url': 'https://today-currency-converter.oiconma.repl.co/'}], 'description': 'Allows users to convert currency values based on the latest exchange rates.', 'endpoints': [('GET /currency-converter', None, {'parameters': [{'name': 'from', 'in': 'query', 'description': 'The currency to convert from', 'required': True, 'schema': {'type': 'string'}}, {'name': 'to', 'in': 'query', 'description': 'The currency to convert to', 'required': True, 'schema': {'type': 'string'}}, {'name': 'amount', 'in': 'query', 'description': 'The amount to convert', 'required': True, 'schema': {'type': 'string'}}], 'responses': {'description': 'OK', 'content': {'application/json': {'schema': {'type': 'object', 'properties': {'result': {'type': 'object', 'properties': {'info': {'type': 'object', 'properties': {'rate': {'type': 'number', 'format': 'float', 'description': 'The conversion rate between the two currencies'}}}, 'date': {'type': 'string', 'description': 'The date of the conversion'}, 'result': {'type': 'number', 'format': 'float', 'description': 'The converted amount'}}}}}}}}})]}\", start_char_idx=None, end_char_idx=None, text_template='{metadata_str}\\n\\n{content}', metadata_template='{key}: {value}', metadata_seperator='\\n')]\n", + "========================\n", + "=== Calling Function ===\n", + "Calling function: get_request with args: {\n", + " \"url\": \"https://today-currency-converter.oiconma.repl.co/currency-converter?from=EUR&to=CAD&amount=100\"\n", + "}\n", + "Got output: {'date': '2023-07-24', 'historical': False, 'info': {'rate': 1.469716}, 'motd': {'msg': 'If you or your company use this project or like what we doing, please consider backing us so we can continue maintaining and evolving this project.', 'url': 'https://exchangerate.host/#/donate'}, 'query': {'amount': 100, 'from': 'EUR', 'to': 'CAD'}, 'result': 146.971621, 'success': True}\n", + "========================\n", + "The conversion of 100 euros to CAD is 146.971621 CAD.\n" + ] + } + ], + "source": [ + "print(agent.chat(\"Can you convert 100 euros to CAD\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b11e0621-b486-4f03-8c91-b16029a928c3", + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/llama_hub/tools/notebooks/openapi_and_requests.ipynb b/llama_hub/tools/notebooks/openapi_and_requests.ipynb index 49ec09eddb..ddc889c551 100644 --- a/llama_hub/tools/notebooks/openapi_and_requests.ipynb +++ b/llama_hub/tools/notebooks/openapi_and_requests.ipynb @@ -39,6 +39,9 @@ "from llama_index.tools.tool_spec.load_and_search.base import LoadAndSearchToolSpec\n", "\n", "open_spec = OpenAPIToolSpec(open_api_spec)\n", + "# OR \n", + "open_spec = OpenAPIToolSpec(url='https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/openai.com/1.2.0/openapi.yaml')\n", + "\n", "requests_spec = RequestsToolSpec({\n", " \"Authorization\": \"Bearer sk-your-key\",\n", " \"Content-Type\": \"application/json\",\n", @@ -220,7 +223,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.10" + "version": "3.9.12" } }, "nbformat": 4, diff --git a/llama_hub/tools/openapi/README.md b/llama_hub/tools/openapi/README.md index c034df749e..27c5910f40 100644 --- a/llama_hub/tools/openapi/README.md +++ b/llama_hub/tools/openapi/README.md @@ -15,6 +15,9 @@ from llama_index.agent import OpenAIAgent f = requests.get('https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/openai.com/1.2.0/openapi.yaml').text open_api_spec = yaml.load(f, Loader=yaml.Loader) +# OR +open_spec = OpenAPIToolSpec(url='https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/openai.com/1.2.0/openapi.yaml') + tool_spec = OpenAPIToolSpec(open_api_spec) diff --git a/llama_hub/tools/openapi/base.py b/llama_hub/tools/openapi/base.py index eec6d3b1f7..c93c6d7baa 100644 --- a/llama_hub/tools/openapi/base.py +++ b/llama_hub/tools/openapi/base.py @@ -1,10 +1,11 @@ """OpenAPI Tool.""" -from typing import List +from typing import List, Optional from llama_index.readers.schema.base import Document from llama_index.tools.tool_spec.base import BaseToolSpec - +import requests +import yaml class OpenAPIToolSpec(BaseToolSpec): """OpenAPI Tool @@ -15,8 +16,19 @@ class OpenAPIToolSpec(BaseToolSpec): spec_functions = ["load_openapi_spec"] - def __init__(self, spec: dict): - self.spec = Document(text=str(self.process_api_spec(spec))) + def __init__(self, spec: Optional[dict] = None, url: Optional[str] = None): + if spec and url: + raise ValueError("Only provide one of OpenAPI dict or url") + elif spec: + pass + elif url: + response = requests.get(url).text + spec = yaml.load(response, Loader=yaml.Loader) + else: + raise ValueError("You must provide a url or OpenAPI spec as a dict") + + parsed_spec = self.process_api_spec(spec) + self.spec = Document(text=str(parsed_spec)) def load_openapi_spec(self) -> List[Document]: """