├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── docs ├── .readthedocs.yaml ├── Makefile ├── README.md ├── conf.py ├── examples │ ├── apply_plugins_three_steps.ipynb │ ├── create_prompt_plugins.ipynb │ ├── get-list-of-plugins.md │ ├── plugin_retriever_with_langchain_agent.ipynb │ ├── plugin_retriever_with_langchain_agent_clean_version.ipynb │ └── plugins_step_by_step.ipynb ├── get-started │ └── quickstart.md ├── index.rst ├── make.bat ├── modules │ ├── embeddings.md │ ├── plugins.md │ └── utility-functions.md ├── reference │ ├── embeddings.rst │ └── plugins.rst └── requirements.txt ├── examples ├── apply_plugins_three_steps.ipynb ├── create_prompt_plugins.ipynb ├── langchain_chatgpt_example.ipynb ├── oauth_example │ ├── oauth_server.py │ └── run_plugin_with_auth.py ├── old_gpt4_plugins_step_by_step.ipynb ├── plugin_retriever_with_langchain_agent.ipynb ├── plugins_step_by_step.ipynb ├── retrieve_plugins_api.ipynb └── retrieve_plugins_for_langchain_agent.ipynb ├── plugnplai ├── __init__.py ├── api │ └── retrieve.py ├── api_call.py ├── embeddings.py ├── plugins.py ├── prompt_templates.py ├── utils.py └── verify_plugins │ ├── __init__.py │ ├── verify_for_plugnplai.csv │ ├── verify_langchain.py │ └── verify_plugnplai.py ├── poetry.lock ├── pyproject.toml └── tests └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | debug.py 54 | debug.ipynb 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | dev_jupyter.ipynb 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | s.py -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to plugnplai 2 | 3 | We welcome contributions from the community. Here are the guidelines for contributing to this project: 4 | 5 | ## Reporting Issues 6 | 7 | If you encounter any bugs or issues, please file an issue on the [issue tracker](https://github.com/edreisMD/plugnplai/issues). When filing an issue, please provide: 8 | 9 | - A clear and descriptive title 10 | - A summary of the issue 11 | - Steps to reproduce the issue 12 | - The expected behavior 13 | - Screenshots if applicable 14 | - Your system information (OS, Python version, etc.) 15 | 16 | ## Contributing Code 17 | 18 | We welcome pull requests to fix bugs, add new features, and improve the codebase. Here are the steps to contribute code: 19 | 20 | 1. Fork the repository and clone your fork 21 | 2. Create a branch for your changes: `git checkout -b my-feature-branch` 22 | 3. Make your changes and commit them: `git commit -am 'Add new feature'` 23 | 4. Push the branch to your fork: `git push origin my-feature-branch` 24 | 5. Create a pull request from your branch against the `main` branch 25 | 6. Your PR will be reviewed by the maintainers, who may ask you to make some changes before merging 26 | 27 | Please make sure to follow the code style and conventions used in the project. We use [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) and [Google's Python Style Guide](https://google.github.io/styleguide/pyguide.html). 28 | 29 | ## Testing 30 | 31 | We welcome contributions that add or improve tests. Please ensure that any code changes are accompanied by tests. 32 | 33 | To run the tests, install the test dependencies and run: 34 | 35 | ```bash 36 | pip install -r requirements-dev.txt 37 | pytest 38 | ``` 39 | 40 | ## Style Guide 41 | 42 | We follow [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) and [Google's Python Style Guide](https://google.github.io/styleguide/pyguide.html). Please ensure your contributions follow these style guides. 43 | 44 | To run plugnplai locally with Poetry: 45 | 46 | 1. Install Poetry if you haven't already: 47 | 48 | ```bash 49 | pip install poetry 50 | ``` 51 | 52 | 2. Clone the repository: 53 | 54 | ```bash 55 | git clone https://github.com/edreisMD/plugnplai.git 56 | cd plugnplai 57 | ``` 58 | 59 | 3. Install dependencies: 60 | 61 | ```bash 62 | poetry install 63 | ``` 64 | 65 | 4. Activate the Poetry shell: 66 | 67 | ```bash 68 | poetry shell 69 | ``` 70 | 71 | 5. Make your changes and run the tests: 72 | 73 | ```bash 74 | pytest 75 | ``` 76 | 77 | 6. Commit your changes and make a pull request! 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Eduardo Reis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | autoformat: 2 | set -e 3 | isort . 4 | black --config pyproject.toml . 5 | flake8 6 | 7 | lint: 8 | set -e 9 | isort -c . 10 | black --check --config pyproject.toml . 11 | flake8 12 | 13 | dev-lint: 14 | pip install --upgrade black==22.8.0 coverage isort flake8 flake8-bugbear flake8-comprehensions pre-commit pooch 15 | 16 | build-docs: 17 | set -e 18 | mkdir -p docs/source/_static 19 | rm -rf docs/build 20 | rm -rf docs/source/generated 21 | cd docs && make html 22 | 23 | all: autoformat build-docs 24 | # Docs 25 | watch-docs: ## Build and watch documentation 26 | sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/plugnplai -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎸 Plug and Plai 2 | 3 | Plug and Plai is an open source library aiming to simplify the integration of AI plugins into open-source language models (LLMs). 4 | 5 | It provides utility functions to get a list of active plugins from [plugnplai.com](https://plugnplai.com/) directory, get plugin manifests, and extract OpenAPI specifications and load plugins. 6 | 7 | ## Installation 8 | 9 | You can install Plug and Plai using pip: 10 | 11 | ```python 12 | pip install plugnplai 13 | ``` 14 | 15 | ## Quick Start Example 16 | 17 | **Plugins Retrieval API - https://www.plugnplai.com/_functions/retrieve?text={user_message_here}:** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/retrieve_plugins_api.ipynb) 18 | 19 | **Use an Specific Plugin - Step by Step:** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/plugins_step_by_step.ipynb) 20 | 21 | 22 | ## More Examples 23 | 24 | **Generate Prompt with Plugins Description:** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/create_prompt_plugins.ipynb) 25 | 26 | **Plugins Retrieval:** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/plugin_retriever_with_langchain_agent.ipynb) 27 | 28 | **OAuth Plugins** Example for setting up plugins with OAuth: [examples/oauth_example](https://github.com/edreisMD/plugnplai/tree/master/examples/oauth_example) 29 | 30 | ## Usage 31 | 32 | ### Get a list of plugins 33 | 34 | - `urls = get_plugins()`: Get a list of available plugins from a [plugins repository](https://www.plugplai.com/). 35 | 36 | - `urls = get_plugins(filter = 'ChatGPT', category='dev')`: Use 'filter' or 'category' variables to query specific plugins 37 | 38 | Example: 39 | 40 | ```python 41 | import plugnplai 42 | 43 | # Get all plugins from plugnplai.com 44 | urls = plugnplai.get_plugins() 45 | 46 | # Get ChatGPT plugins - only ChatGPT verified plugins 47 | urls = plugnplai.get_plugins(filter = 'ChatGPT') 48 | 49 | # Get working plugins - only tested plugins (in progress) 50 | urls = plugnplai.get_plugins(filter = 'working') 51 | 52 | # Get plugins by category - only tested plugins (in progress) 53 | urls = plugnplai.get_plugins(category = 'travel') 54 | 55 | # Get the names list of categories 56 | urls = plugnplai.get_category_names() 57 | ``` 58 | 59 | ### Utility Functions 60 | 61 | Help to load the plugins manifest and OpenAPI specification 62 | 63 | - `manifest = get_plugin_manifest(url)`: Get the AI plugin manifest from the specified plugin URL. 64 | - `specUrl = get_openapi_url(url, manifest)`: Get the OpenAPI URL from the plugin manifest. 65 | - `spec = get_openapi_spec(openapi_url)`: Get the OpenAPI specification from the specified OpenAPI URL. 66 | - `manifest, spec = spec_from_url(url)`: Returns the Manifest and OpenAPI specification from the plugin URL. 67 | 68 | Example: 69 | 70 | ```python 71 | import plugnplai 72 | 73 | # Get the Manifest and the OpenAPI specification from the plugin URL 74 | manifest, openapi_spec = plugnplai.spec_from_url(urls[0]) 75 | ``` 76 | 77 | ### Load Plugins 78 | **Example:** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/plugins_step_by_step.ipynb) 79 | 80 | ```python 81 | from plugnplai import Plugins 82 | 83 | ###### ACTIVATE A MAX OF 3 PLUGINS ###### 84 | # Context length limits the number of plugins you can activate, 85 | # you need to make sure the prompt fits in your context lenght, 86 | # still leaving space for the user message 87 | 88 | # Initialize 'Plugins' by passing a list of urls, this function will 89 | # load the plugins and build a default description to be used as prefix prompt 90 | plugins = Plugins.install_and_activate(urls) 91 | 92 | # Print the deafult prompt for the activated plugins 93 | print(plugins.prompt) 94 | 95 | # Print the number of tokens of the prefix prompt 96 | print(plugins.tokens) 97 | ``` 98 | 99 | Example on installing (loading) all plugins, and activating a few later: 100 | 101 | ```python 102 | from plugnplai import Plugins 103 | 104 | # If you just want to load the plugins, but activate only 105 | # some of them later use Plugins(urls) instead 106 | plugins = Plugins(urls) 107 | 108 | # Print the names of installed plugins 109 | print(plugins.list_installed) 110 | 111 | # Activate the plugins you want 112 | plugins.activate(name1) 113 | plugins.activate(name2) 114 | plugins.activate(name3) 115 | 116 | # Deactivate the last plugin 117 | plugins.deactivate(name3) 118 | ``` 119 | 120 | ### Prompt and Tokens Counting 121 | 122 | The `plugins.prompt` attribute contains a prompt with descriptions of the active plugins. 123 | The `plugins.tokens` attribute contains the number of tokens in the prompt. 124 | 125 | For example: 126 | ```python 127 | plugins = Plugins.install_and_activate(urls) 128 | print(plugins.prompt) 129 | print(plugins.tokens) 130 | ``` 131 | This will print the prompt with plugin descriptions and the number of tokens. 132 | 133 | 134 | ### Parse LLM Response for API Tag 135 | 136 | The `parse_llm_response()` function parses an LLM response searching for API calls. It looks for the `` pattern defined in the `plugins.prompt` and extracts the plugin name, operation ID, and parameters. 137 | 138 | 139 | ### Call API 140 | 141 | The `call_api()` function calls an operation in an active plugin. It takes the plugin name, operation ID, and parameters extracted by `parse_llm_response()` and makes a request to the plugin API. 142 | 143 | **Plugin Authentication:** Plugins with Oauth, user or service level authentication pass api_key(string) as parameter. For oauth, use the access_token as the api_key parameter. For more detail about oauth authentication please see [oauth_example/run_plugin_with_oauth.py](https://github.com/edreisMD/plugnplai/tree/master/examples/oauth_example/run_plugin_with_auth.py) and [oauth_example/oauth_server.py](https://github.com/edreisMD/plugnplai/tree/master/examples/oauth_example/oauth_server.py) files. 144 | 145 | 146 | ### Apply Plugins 147 | 148 | The `@plugins.apply_plugins` decorator can be used to easily apply active plugins to an LLM function. 149 | 150 | This method is suboptimal for GPT chat models because it doesn't make use of the different available roles on the chat api (system, user, or assistant roles). But it may be useful for other LLM providers or open-source models. 151 | 152 | 1. Import the Plugins class and decorator: 153 | 154 | ```python 155 | from plugnplai import Plugins, plugins.apply_plugins 156 | ``` 157 | 158 | 2. Define your LLM function, that necessarily takes a string (the user input) as the first argument and returns a string (the response): 159 | 160 | ```python 161 | @plugins.apply_plugins 162 | def call_llm(user_input): 163 | ... 164 | return response 165 | ``` 166 | 167 | 3. The decorator will handle the following: 168 | 169 | - Prepending the prompt (with plugin descriptions) to the user input 170 | - Checking the LLM response for API calls (the ... pattern) 171 | - Calling the specified plugins 172 | - Summarizing the API calls in the LLM response 173 | - Calling the LLM function again with the summary to get a final response 174 | 175 | 4. If no API calls are detected, the original LLM response is returned. 176 | 177 | To more details on the implementation of these steps, see example "Step by Step": [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/plugins_step_by_step.ipynb) 178 | 179 | ### Plugins Retrieval 180 | 181 | **PlugnPlai Retrieval API - https://www.plugnplai.com/_functions/retrieve?text={user_message_here} :** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/retrieve_plugins_api.ipynb) 182 | 183 | **Build our own Plugins Vector Database:** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/plugin_retriever_with_langchain_agent.ipynb) 184 | 185 | 186 | ```python 187 | from plugnplai import PluginRetriever 188 | 189 | # Initialize the plugins retriever vector database and index the plugins descriptions. 190 | # Loading the plugins from plugnplai.com directory 191 | plugin_retriever = PluginRetriever.from_directory() 192 | 193 | # Retrieve the names of the plugins given a user's message 194 | plugin_retriever.retrieve_names("what shirts can i buy?") 195 | ``` 196 | 197 | 198 | ## Contributing 199 | 200 | Plug and Plai is an open source library, and we welcome contributions from the entire community. If you're interested in contributing to the project, please feel free to fork, submit pull requests, report issues, or suggest new features. 201 | 202 | #### To dos 203 | - [x] [Load] Define a default object to read plugins - use LangChain standard? (for now using only manifest and specs jsons) 204 | - [ ] [Load] Fix breaking on reading certain plugins specs 205 | - [x] [Load] Accept different specs methods and versions (param, query, body) 206 | - [x] [Prompt] Build a utility function to return a default prompts for a plugin 207 | - [x] [Prompt] Fix prompt building for body (e.g. "Speak") 208 | - [x] [Prompt] Build a utility function to return a default prompts for a set of plugins 209 | - [x] [Prompt] Build a utility function to count tokens of the plugins prompt 210 | - [ ] [Prompt] Use the prompt tokens number to short or expand a plugin prompt, use LLM to summarize the prefix prompt 211 | - [x] [CallAPI] Build a function to call API given a dictionary of parameters 212 | - [x] [CallAPI] Add example for calling API 213 | - [ ] [Embeddings] Add filter option (e.g. "working", "ChatGPT") to "PluginRetriever.from_directory()" 214 | - [x] [Docs] Add Sphynx docs 215 | - [ ] [Verification] Build automated tests to verify new plugins 216 | - [x] [Verification] Build automated monitoring for working plugins for plugnplai 217 | - [ ] [Verification] Build automated monitoring for working plugins for langchain 218 | - [ ] [Website] Build an open-source website 219 | - [x] [Authentication] Add support for OAuth, user and service level authentication 220 | - [x] [Functions] Automaticly create OpenAI function dictionary for plugins (plugins.functions) 221 | - [ ] [Model] Create a dataset for finetuning open-source models to call Plugins / APIs. Use the descriptions on plugins.prompt (eventually plugins.functions) as input 222 | - [ ] [Model] Finetune an open-source model to call Plugins / APIs 223 | 224 | #### Project Roadmap 225 | 1. Build auxiliary functions that helps everyone to use plugins as defined by [OpenAI](https://platform.openai.com/docs/plugins/introduction) 226 | 2. Build in compatibility with different open-source formats (e.g. LangChain, BabyAGI, etc) 227 | 3. Find a best prompt format for plugins, optimizing for token number and description completness 228 | 4. Help with authentication 229 | 5. Build a dataset to finetune open-source models to call plugins 230 | 6. Finetune an open-source model to call plugins 231 | 7. Etc. 232 | 233 | ## Links 234 | - Plugins directory: [https://plugnplai.com/](https://plugnplai.com/) 235 | - API reference: [https://plugnplai.github.io/](https://plugnplai.github.io/) 236 | -------------------------------------------------------------------------------- /docs/.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.9" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the docs/ directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # If using Sphinx, optionally build your docs in additional formats such as PDF 23 | # formats: 24 | # - pdf 25 | 26 | # Optionally declare the Python requirements required to build your docs 27 | python: 28 | install: 29 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | livehtml: 17 | sphinx-autobuild -a -b html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(0) --watch ../sqlite_utils 18 | # Catch-all target: route all unknown targets to Sphinx using the new 19 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 20 | %: Makefile 21 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Plugnplai Documentation 2 | 3 | ## A guide for docs contributors 4 | 5 | The `docs` directory contains the sphinx source text for Plugnplai's documentation. 6 | 7 | This guide is made for anyone who's interested in running the docs locally, or 8 | contributing to the docs. 9 | 10 | ## Build Docs 11 | 12 | If you haven't already, clone the Plugnplai Github repo to a local directory: 13 | 14 | 15 | Install all dependencies required for building docs (mainly `sphinx` and its extension): 16 | 17 | ```bash 18 | pip install -r docs/requirements.txt 19 | ``` 20 | 21 | Build the sphinx docs: 22 | 23 | ```bash 24 | cd docs 25 | make html 26 | ``` 27 | 28 | The docs HTML files are now generated under `docs/_build/html` directory, you can preview 29 | it locally with the following command: 30 | 31 | ```bash 32 | python -m http.server 8000 -d _build/html 33 | ``` 34 | 35 | And open your browser at http://0.0.0.0:8000/ to view the generated docs. 36 | 37 | 38 | ##### Watch Docs 39 | 40 | During development, it is advisable to utilize sphinx-autobuild, which offers a live-reloading server. This server automatically rebuilds the documentation and refreshes any open pages whenever changes are saved. This feature facilitates a shorter feedback loop, thereby enhancing productivity when writing documentation. 41 | 42 | Simply run the following command from Plugnplai project's root directory: 43 | ```bash 44 | make watch-docs 45 | ``` -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | import os 9 | import sys 10 | sys.path.insert(0, os.path.abspath('../')) 11 | print(os.path.abspath('.')) 12 | project = '🎸 plugnplai' 13 | copyright = '2023, Eduardo Reis' 14 | author = 'Eduardo Reis' 15 | release = '0.0.1' 16 | 17 | # -- General configuration --------------------------------------------------- 18 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 19 | 20 | extensions = [ 21 | 'sphinx.ext.autodoc', 22 | "sphinx.ext.coverage", 23 | "sphinx.ext.autodoc.typehints", 24 | "sphinx.ext.autosummary", 25 | "sphinx.ext.napoleon", 26 | "sphinx_rtd_theme", 27 | "sphinx.ext.mathjax", 28 | "myst_nb", 29 | ] 30 | 31 | myst_heading_anchors = 4 32 | # TODO: Fix the non-consecutive header level in our docs, until then 33 | # disable the sphinx/myst warnings 34 | suppress_warnings = ["myst.header"] 35 | 36 | templates_path = ["_templates"] 37 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 38 | 39 | 40 | # -- Options for HTML output ------------------------------------------------- 41 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 42 | 43 | html_theme = "furo" 44 | html_title = project + " " + "0.0.1" 45 | # html_static_path = ["_static"] 46 | 47 | html_css_files = [ 48 | "css/custom.css", 49 | ] 50 | nb_execution_mode = "off" -------------------------------------------------------------------------------- /docs/examples/apply_plugins_three_steps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "xXnd1cCu1TkJ" 7 | }, 8 | "source": [ 9 | "# Add Plugins in Three Steps\n", 10 | "\n", 11 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/apply_plugins_three_steps.ipynb)\n", 12 | "\n", 13 | "1. Load the plugins from a plugins directory (E.g. [plugnplai.com](https://plugnplai.com))\n", 14 | "2. Install and activate the plugins with `plugins = Plugins.install_and_activate(urls)`\n", 15 | "3. Add the plugins to your LLM function (you can use the decorator `@plugins.apply_plugins`)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": { 21 | "id": "OZWQil8RrAKV" 22 | }, 23 | "source": [ 24 | "## Install and import necessary packages" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": { 31 | "colab": { 32 | "base_uri": "https://localhost:8080/" 33 | }, 34 | "id": "7RUcr3tLEK7M", 35 | "outputId": "2aa31e20-4106-4048-c34c-b468f10fff56" 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "pip install plugnplai -q" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 8, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "# You will need to first define your API key\n", 49 | "import os\n", 50 | "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_OPENAI_KEY\"" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 13, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "import plugnplai as pl\n", 60 | "from langchain.chat_models import ChatOpenAI\n", 61 | "from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate\n", 62 | "from langchain.schema import AIMessage, HumanMessage, SystemMessage\n", 63 | "from IPython.display import display, Markdown" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": { 69 | "id": "7fYY9i4Z3sBO" 70 | }, 71 | "source": [ 72 | "## 1. Load plugins from [plugnplai.com](https://plugnplai.com)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": { 78 | "id": "nlC-9wyp2vtF" 79 | }, 80 | "source": [ 81 | "Lets find one plugin for each category, using PlugnPlai categories (see [API reference](https://plugnplai.github.io/))\n", 82 | "- travel\n", 83 | "- shopping \n", 84 | "- weather" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "metadata": { 91 | "colab": { 92 | "base_uri": "https://localhost:8080/" 93 | }, 94 | "id": "MmqqHyg5Eee0", 95 | "outputId": "33e028f9-003f-41c1-f9d0-acfcc4fd1fdd" 96 | }, 97 | "outputs": [ 98 | { 99 | "name": "stdout", 100 | "output_type": "stream", 101 | "text": [ 102 | "Our chosen Plugins: ['https://trip.com', 'https://klarna.com', 'https://api.speak.com']\n" 103 | ] 104 | } 105 | ], 106 | "source": [ 107 | "# Get working plugins - only tested plugins (in progress)\n", 108 | "urlsTravel = pl.get_plugins()\n", 109 | "\n", 110 | "# Lets pick Trip, Klarna and Speak\n", 111 | "urls = [plugin for plugin in urlsTravel if any(word in plugin for word in ('trip.com', 'klarna', 'speak'))]\n", 112 | "\n", 113 | "print(f'Our chosen Plugins: {urls}')" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": { 119 | "id": "hj5MtCaPyk9p" 120 | }, 121 | "source": [ 122 | "## 2. Install and activate the plugins" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 6, 128 | "metadata": { 129 | "id": "JMHi1UTpy8LV" 130 | }, 131 | "outputs": [], 132 | "source": [ 133 | "from plugnplai import Plugins\n", 134 | "\n", 135 | "plugins = Plugins.install_and_activate(urls)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": { 141 | "id": "2-alHvKpyk9q" 142 | }, 143 | "source": [ 144 | "## 3. Apply the plugins \n", 145 | "\n", 146 | "Use `@plugins.apply_plugins` decorator to easily apply plugins to your LLM function\n", 147 | "\n", 148 | "The LLM function MUST get a string as the user input as first variable and output the string with the response" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 9, 154 | "metadata": { 155 | "colab": { 156 | "base_uri": "https://localhost:8080/" 157 | }, 158 | "id": "g770AOGZ0vwH", 159 | "outputId": "739138b3-e6f7-460d-9d5a-0837fc2f3594" 160 | }, 161 | "outputs": [], 162 | "source": [ 163 | "chat = ChatOpenAI(temperature=0, model=\"gpt-4\")\n", 164 | "\n", 165 | "@plugins.apply_plugins\n", 166 | "def call_llm(user_message):\n", 167 | " messages = [\n", 168 | " SystemMessage(content=\"\"),\n", 169 | " HumanMessage(content=user_message)\n", 170 | " ]\n", 171 | "\n", 172 | " res = chat(messages)\n", 173 | "\n", 174 | " llm_first_response = res.content\n", 175 | "\n", 176 | " return llm_first_response" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "## Enjoy - Let's try it\n", 184 | "\n", 185 | "Now your function should call a plugin when the LLM decides to use a plugin \n", 186 | "or just returns the normal message if not." 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 16, 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "name": "stdout", 196 | "output_type": "stream", 197 | "text": [ 198 | "Using KlarnaProducts\n" 199 | ] 200 | }, 201 | { 202 | "data": { 203 | "text/markdown": [ 204 | "I found a few t-shirts for you:\n", 205 | "\n", 206 | "1. [T-shirt](https://www.klarna.com/us/shopping/pl/cl10001/3203506327/Clothing/T-shirt/?utm_source=openai&ref-site=openai_plugin) - $20.99\n", 207 | " - Material: Cotton\n", 208 | " - Target Group: Man\n", 209 | " - Color: Gray, White, Blue, Black, Orange\n", 210 | "\n", 211 | "2. [Polo Ralph Lauren Slim Fit Cotton T-shirt 3-pack](https://www.klarna.com/us/shopping/pl/cl10001/3201838628/Clothing/Polo-Ralph-Lauren-Slim-Fit-Cotton-T-shirt-3-pack/?utm_source=openai&ref-site=openai_plugin) - $42.50\n", 212 | " - Material: Cotton\n", 213 | " - Target Group: Man\n", 214 | " - Color: Gray, White, Blue, Multicolor, Black\n", 215 | "\n", 216 | "3. [Psycho Bunny Mens Copa Gradient Logo Graphic Tee](https://www.klarna.com/us/shopping/pl/cl10001/3203663222/Clothing/Psycho-Bunny-Mens-Copa-Gradient-Logo-Graphic-Tee/?utm_source=openai&ref-site=openai_plugin) - $49.00\n", 217 | " - Material: Cotton\n", 218 | " - Target Group: Man\n", 219 | " - Color: White, Blue, Black, Orange\n", 220 | "\n", 221 | "You can click on the links to view more details and make a purchase." 222 | ], 223 | "text/plain": [ 224 | "" 225 | ] 226 | }, 227 | "metadata": {}, 228 | "output_type": "display_data" 229 | } 230 | ], 231 | "source": [ 232 | "# Message that triggers the use of plugin 1\n", 233 | "response = call_llm(\"Buy a tshirt\")\n", 234 | "# Display in markdown format\n", 235 | "display(Markdown(response))" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 17, 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "name": "stdout", 245 | "output_type": "stream", 246 | "text": [ 247 | "Using Trip\n" 248 | ] 249 | }, 250 | { 251 | "data": { 252 | "text/markdown": [ 253 | "I have found the top 5 hotels in Paris for your stay from December 3rd to December 8th, 2023. Here are the details:\n", 254 | "\n", 255 | "1. [Le Tsuba Hotel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=6597288&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 256 | " - Address: 45 Rue des Acacias\n", 257 | " - Price: $295 USD per night\n", 258 | " - Star rating: 4 stars\n", 259 | " - Score: 4.6/5.0 (36 reviews)\n", 260 | " - Features: Sauna, gym\n", 261 | "\n", 262 | "2. [Pullman Paris Centre - Bercy](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2107175&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 263 | " - Address: 1 Rue de Libourne\n", 264 | " - Price: $236 USD per night\n", 265 | " - Star rating: 4 stars\n", 266 | " - Score: 4.5/5.0 (42 reviews)\n", 267 | " - Features: Swimming pool, children's playground\n", 268 | "\n", 269 | "3. [Pullman Paris Tour Eiffel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2081163&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 270 | " - Address: 18 Avenue De Suffren, 22 Rue Jean Rey Entrée Au\n", 271 | " - Price: $298 USD per night\n", 272 | " - Star rating: 4 stars\n", 273 | " - Score: 4.2/5.0 (112 reviews)\n", 274 | " - Features: Gym, multi-function hall\n", 275 | "\n", 276 | "4. [Hotel de Castiglione Paris](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2157992&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 277 | " - Address: 38-40 Rue du Faubourg Saint-Honoré\n", 278 | " - Price: $221 USD per night\n", 279 | " - Star rating: 4 stars\n", 280 | " - Score: 3.9/5.0 (49 reviews)\n", 281 | " - Features: Tea room, conference hall\n", 282 | "\n", 283 | "5. [Hotel de Crillon A Rosewood Hotel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=1619850&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 284 | " - Address: 10 Pl. de la Concorde\n", 285 | " - Price: $1673 USD per night\n", 286 | " - Star rating: 5 stars\n", 287 | " - Score: 4.7/5.0 (7 reviews)\n", 288 | " - Features: Sunbathing area, sauna\n", 289 | "\n", 290 | "Please note that prices and availability are subject to change. Make sure to book your preferred hotel as soon as possible to secure your reservation." 291 | ], 292 | "text/plain": [ 293 | "" 294 | ] 295 | }, 296 | "metadata": {}, 297 | "output_type": "display_data" 298 | } 299 | ], 300 | "source": [ 301 | "# Message that triggers the use of plugin 2\n", 302 | "response = call_llm(\"Book a hotel in paris for Dec.3-8\")\n", 303 | "display(Markdown(response))" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 18, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "name": "stdout", 313 | "output_type": "stream", 314 | "text": [ 315 | "Using speak\n" 316 | ] 317 | }, 318 | { 319 | "data": { 320 | "text/markdown": [ 321 | "In Portuguese, you can say \"I love you\" as:\n", 322 | "\n", 323 | "**Eu te amo**\n", 324 | "\n", 325 | "Here are some alternative ways to express love in Portuguese:\n", 326 | "\n", 327 | "1. \"Amo-te\" (More formal, less commonly used, but still appropriate in romantic settings)\n", 328 | "2. \"Adoro-te\" (Similar to \"I adore you\", commonly used in romantic relationships)\n", 329 | "3. \"Te quero\" (Less intense than \"I love you\", also appropriate to use with friends, family, or significant others)\n", 330 | "\n", 331 | "Here's an example conversation in Portuguese:\n", 332 | "\n", 333 | "*Context: Maria and João are a young couple who are deeply in love and are on a date at the beach at sunset.*\n", 334 | "\n", 335 | "* João: \"Maria, eu te amo tanto, não consigo imaginar minha vida sem você.\"\n", 336 | "* Maria: \"Eu também te amo, João. Você é o melhor namorado que eu poderia pedir.\"\n", 337 | "\n", 338 | "[Report an issue or leave feedback](https://speak.com/chatgpt?rid=zgoen4wd4xgoc65ndmbzx4og)" 339 | ], 340 | "text/plain": [ 341 | "" 342 | ] 343 | }, 344 | "metadata": {}, 345 | "output_type": "display_data" 346 | } 347 | ], 348 | "source": [ 349 | "# Message that triggers the use of plugin 3\n", 350 | "response = call_llm(\"How to say I love you in Portuguese?\")\n", 351 | "display(Markdown(response))" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "### Making a question that doesn't trigger the use of a plugin" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 15, 364 | "metadata": {}, 365 | "outputs": [ 366 | { 367 | "data": { 368 | "text/plain": [ 369 | "'🤖 The AI revolution is transforming the world! 🌍 From enhancing productivity to personalizing experiences, artificial intelligence is unlocking new possibilities and reshaping industries. Embrace the change and join the #AIRevolution! 💡 #TechTrends #Innovation #FutureIsNow'" 370 | ] 371 | }, 372 | "execution_count": 15, 373 | "metadata": {}, 374 | "output_type": "execute_result" 375 | } 376 | ], 377 | "source": [ 378 | "call_llm(\"Write tweet about the AI revolution.\")" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": null, 384 | "metadata": {}, 385 | "outputs": [], 386 | "source": [] 387 | } 388 | ], 389 | "metadata": { 390 | "colab": { 391 | "provenance": [] 392 | }, 393 | "kernelspec": { 394 | "display_name": "Python 3.10.8 ('env': venv)", 395 | "language": "python", 396 | "name": "python3" 397 | }, 398 | "language_info": { 399 | "codemirror_mode": { 400 | "name": "ipython", 401 | "version": 3 402 | }, 403 | "file_extension": ".py", 404 | "mimetype": "text/x-python", 405 | "name": "python", 406 | "nbconvert_exporter": "python", 407 | "pygments_lexer": "ipython3", 408 | "version": "3.10.8" 409 | }, 410 | "vscode": { 411 | "interpreter": { 412 | "hash": "db088be0df81f10a0d149836483f30eb6911268c99c8cd2461b5be70fec9cf57" 413 | } 414 | } 415 | }, 416 | "nbformat": 4, 417 | "nbformat_minor": 0 418 | } 419 | -------------------------------------------------------------------------------- /docs/examples/create_prompt_plugins.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "xXnd1cCu1TkJ" 7 | }, 8 | "source": [ 9 | "# Create Prompt with Plugins Descriptions\n", 10 | "\n", 11 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/create_prompt_plugins.ipynb)\n", 12 | "\n", 13 | "The goal of this example is to load plugins specifications\n", 14 | "and create a prefix prompt describing the API to the LLM" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": { 20 | "id": "OZWQil8RrAKV" 21 | }, 22 | "source": [ 23 | "## Install" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": { 30 | "colab": { 31 | "base_uri": "https://localhost:8080/" 32 | }, 33 | "id": "7RUcr3tLEK7M", 34 | "outputId": "595c3751-91c9-4bb6-8326-9fbcca19fcff" 35 | }, 36 | "outputs": [ 37 | { 38 | "name": "stdout", 39 | "output_type": "stream", 40 | "text": [ 41 | "Note: you may need to restart the kernel to use updated packages.\n" 42 | ] 43 | } 44 | ], 45 | "source": [ 46 | "pip install plugnplai -q" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": { 52 | "id": "7fYY9i4Z3sBO" 53 | }, 54 | "source": [ 55 | "## Get the plugins from the directory" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": { 61 | "id": "nlC-9wyp2vtF" 62 | }, 63 | "source": [ 64 | "We want to install three plugins at maximum to fit the description on the context length\n", 65 | "\n", 66 | "Lets find one plugin for each category:\n", 67 | "1. travel\n", 68 | "2. shopping \n", 69 | "3. weather\n", 70 | "\n", 71 | "We can use PlugnPlai categories (see [API reference](https://plugnplai.github.io/))" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 2, 77 | "metadata": { 78 | "colab": { 79 | "base_uri": "https://localhost:8080/" 80 | }, 81 | "id": "MmqqHyg5Eee0", 82 | "outputId": "2e002204-ddfb-4bb4-8a29-bf0a1bed2a25" 83 | }, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "Travel plugins: ['https://gogaffl.com', 'https://trip.com', 'https://api.yelp.com', 'https://gps-telecom.com']\n", 90 | "Shopping plugins: ['https://pricerunner.com', 'https://server.shop.app', 'https://klarna.com']\n", 91 | "Weather plugins: ['https://api.tomorrow.io']\n" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "import plugnplai as pl\n", 97 | "\n", 98 | "# Get working plugins - only tested plugins (in progress)\n", 99 | "urlsTravel = pl.get_plugins(category='travel')\n", 100 | "print(f'Travel plugins: {urlsTravel}')\n", 101 | "\n", 102 | "urlsShopping = pl.get_plugins(category='shopping')\n", 103 | "print(f'Shopping plugins: {urlsShopping}')\n", 104 | "\n", 105 | "urlsWeather = pl.get_plugins(category='weather')\n", 106 | "print(f'Weather plugins: {urlsWeather}')" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 3, 112 | "metadata": { 113 | "colab": { 114 | "base_uri": "https://localhost:8080/" 115 | }, 116 | "id": "81MK5G3aJ6pA", 117 | "outputId": "4d4e72fa-ab73-45e3-d1f4-b478d0b5aa1e" 118 | }, 119 | "outputs": [ 120 | { 121 | "name": "stdout", 122 | "output_type": "stream", 123 | "text": [ 124 | "Our chosen Plugins: ['https://trip.com', 'https://klarna.com', 'https://api.tomorrow.io']\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "# Lets pick one of each list and add to our url list\n", 130 | "\n", 131 | "urls = []\n", 132 | "\n", 133 | "# Trip (list index 1)\n", 134 | "urls.append([plugin for plugin in urlsTravel if 'trip' in plugin][0])\n", 135 | "\n", 136 | "# Klarna (list index 2)\n", 137 | "urls.append([plugin for plugin in urlsShopping if 'klarna' in plugin][0])\n", 138 | "\n", 139 | "# Speak (list index 0)\n", 140 | "urls.append([plugin for plugin in urlsWeather if 'tomorrow' in plugin][0])\n", 141 | "\n", 142 | "print(f'Our chosen Plugins: {urls}')" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "## Load and activate the plugins" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 4, 155 | "metadata": { 156 | "id": "JMHi1UTpy8LV" 157 | }, 158 | "outputs": [], 159 | "source": [ 160 | "from plugnplai import Plugins\n", 161 | "\n", 162 | "plugins = Plugins.install_and_activate(urls)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Print the default prompt for the active plugins" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 5, 175 | "metadata": { 176 | "colab": { 177 | "base_uri": "https://localhost:8080/" 178 | }, 179 | "id": "g770AOGZ0vwH", 180 | "outputId": "e83aeca5-d65f-4367-a653-2a36523045a5" 181 | }, 182 | "outputs": [ 183 | { 184 | "name": "stdout", 185 | "output_type": "stream", 186 | "text": [ 187 | "\n", 188 | "# SYSTEM MESSAGE\n", 189 | "You are a large language model trained to assist humans.\n", 190 | "Knowledge Cutoff: 2021-09\n", 191 | "Current date: 2023-05-12\n", 192 | "Below is a list of available APIs that you can utilize to fulfill user requests. \n", 193 | "When using an API, please follow the specified format to make the API call. \n", 194 | "If possible, avoid asking follow-up questions and aim to complete the task with the information provided by the user.\n", 195 | "\n", 196 | "To make an API call, use the following format:\n", 197 | "\n", 198 | "[API]namespace.operationId[/API]\n", 199 | "[PARAMS]{ \n", 200 | " \"parameter_name\": \"parameter_value\",\n", 201 | " ...\n", 202 | "}[/PARAMS]\n", 203 | "\n", 204 | "For example, to call an API operation with the operation ID \"productsUsingGET\" in the \"KlarnaProducts\" namespace, \n", 205 | "and provide the required parameters \"q\" and \"size\", the format would be as follows:\n", 206 | "\n", 207 | "[API]KlarnaProducts.productsUsingGET[/API]\n", 208 | "[PARAMS]{\n", 209 | " \"q\": \"t-shirt\", \n", 210 | " \"size\": 3\n", 211 | "}[/PARAMS]\n", 212 | "\n", 213 | "Please ensure that you use the correct namespace and operation ID, and provide the necessary parameters for each API call. \n", 214 | "After requesting the API, refrain from writing anything else and wait for the API response, which will be delivered in a new message.\n", 215 | "\n", 216 | "## Plugins description\n", 217 | "\n", 218 | "### Plugin 1\n", 219 | "// Plugin for users to effortlessly get customized travel product recommendation and itinerary planning including hotels and flights using chatGPT.\n", 220 | "namespace Trip {\n", 221 | "\n", 222 | "operationId search_flight_ticket = (_: {'originCityCode'*: 'str', 'destinationCityCode'*: 'str', 'departureDate'*: 'str', 'returnDate'*: 'str', 'locale'*: 'str', 'oneWayOrRoundTrip'*: 'str'}) => any\n", 223 | "\n", 224 | "operationId search_hotel = (_: {'cityName'*: 'str', 'topHotel'*: 'int', 'locale'*: 'str', 'checkIn'*: 'any', 'checkOut'*: 'any'}) => any}\n", 225 | "\n", 226 | "### Plugin 2\n", 227 | "// Assistant uses the Klarna plugin to get relevant product suggestions for any shopping or product discovery purpose. Assistant will reply with the following 3 paragraphs 1) Search Results 2) Product Comparison of the Search Results 3) Followup Questions. The first paragraph contains a list of the products with their attributes listed clearly and concisely as bullet points under the product, together with a link to the product and an explanation. Links will always be returned and should be shown to the user. The second paragraph compares the results returned in a summary sentence starting with \"In summary\". Assistant comparisons consider only the most important features of the products that will help them fit the users request, and each product mention is brief, short and concise. In the third paragraph assistant always asks helpful follow-up questions and end with a question mark. When assistant is asking a follow-up question, it uses it's product expertise to provide information pertaining to the subject of the user's request that may guide them in their search for the right product.\n", 228 | "namespace KlarnaProducts {\n", 229 | "\n", 230 | "operationId productsUsingGET = (_: {'q'*: 'str', 'size': 'int', 'min_price': 'int', 'max_price': 'int'}) => any}\n", 231 | "\n", 232 | "### Plugin 3\n", 233 | "// Plugin that answers questions about the weather to help users predict, plan, and adapt their day to day to the weather forecast via contextualized chat-based insights.\n", 234 | "namespace weather {\n", 235 | "\n", 236 | "operationId handleWeatherQuestion = (_: {'question'*: 'str'}) => any}\n", 237 | "\n", 238 | "\n", 239 | "# USER MESSAGE\n", 240 | "\n" 241 | ] 242 | } 243 | ], 244 | "source": [ 245 | "print(plugins.prompt)" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "## Lets look at the length of the prompt\n", 253 | "\n", 254 | "Get the number of tokens of the prompt by just calling 'plugins.tokens' " 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 6, 260 | "metadata": { 261 | "colab": { 262 | "base_uri": "https://localhost:8080/" 263 | }, 264 | "id": "jnFld3xszdrx", 265 | "outputId": "fd6efdf5-6aca-45dc-bdac-f95c5a9c8d96" 266 | }, 267 | "outputs": [ 268 | { 269 | "name": "stdout", 270 | "output_type": "stream", 271 | "text": [ 272 | "731\n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "print(plugins.tokens)" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "metadata": {}, 283 | "source": [ 284 | "## Customize the prompt template\n", 285 | "\n", 286 | "You can customize the template by just passing 'template' as a variable\n", 287 | "\n", 288 | "Lets adapt this awesome and funny template from this [LangChain example](https://python.langchain.com/en/latest/use_cases/agents/custom_agent_with_plugin_retrieval.html)\n", 289 | "\n", 290 | "The template must have a {{plugins}} variable, to be filled with the descriptions. " 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": 7, 296 | "metadata": {}, 297 | "outputs": [], 298 | "source": [ 299 | "custom_template = '''\n", 300 | "Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following APIs:\n", 301 | "\n", 302 | "{{plugins}}\n", 303 | "To call any api write the name, operation Id and parameters in the following format:\n", 304 | "[API]namespace.operationId[/API]\n", 305 | "[PARAMS]{ \"parameter_name\": \"parameter_value\", ...}[/PARAMS]\n", 306 | "\n", 307 | "Like in this example to shop a t-shirt: \n", 308 | "[API]KlarnaProducts.productsUsingGET[/API]\n", 309 | "[PARAMS]{\"q\": \"t-shirt\", \"size\": 3}[/PARAMS]\n", 310 | "\n", 311 | "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s\n", 312 | "\n", 313 | "Question:\n", 314 | "'''" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 8, 320 | "metadata": {}, 321 | "outputs": [ 322 | { 323 | "name": "stdout", 324 | "output_type": "stream", 325 | "text": [ 326 | "\n", 327 | "Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following APIs:\n", 328 | "\n", 329 | "### Plugin 1\n", 330 | "// Plugin for users to effortlessly get customized travel product recommendation and itinerary planning including hotels and flights using chatGPT.\n", 331 | "namespace Trip {\n", 332 | "\n", 333 | "operationId search_flight_ticket = (_: {'originCityCode'*: 'str', 'destinationCityCode'*: 'str', 'departureDate'*: 'str', 'returnDate'*: 'str', 'locale'*: 'str', 'oneWayOrRoundTrip'*: 'str'}) => any\n", 334 | "\n", 335 | "operationId search_hotel = (_: {'cityName'*: 'str', 'topHotel'*: 'int', 'locale'*: 'str', 'checkIn'*: 'any', 'checkOut'*: 'any'}) => any}\n", 336 | "\n", 337 | "### Plugin 2\n", 338 | "// Assistant uses the Klarna plugin to get relevant product suggestions for any shopping or product discovery purpose. Assistant will reply with the following 3 paragraphs 1) Search Results 2) Product Comparison of the Search Results 3) Followup Questions. The first paragraph contains a list of the products with their attributes listed clearly and concisely as bullet points under the product, together with a link to the product and an explanation. Links will always be returned and should be shown to the user. The second paragraph compares the results returned in a summary sentence starting with \"In summary\". Assistant comparisons consider only the most important features of the products that will help them fit the users request, and each product mention is brief, short and concise. In the third paragraph assistant always asks helpful follow-up questions and end with a question mark. When assistant is asking a follow-up question, it uses it's product expertise to provide information pertaining to the subject of the user's request that may guide them in their search for the right product.\n", 339 | "namespace KlarnaProducts {\n", 340 | "\n", 341 | "operationId productsUsingGET = (_: {'q'*: 'str', 'size': 'int', 'min_price': 'int', 'max_price': 'int'}) => any}\n", 342 | "\n", 343 | "### Plugin 3\n", 344 | "// Plugin that answers questions about the weather to help users predict, plan, and adapt their day to day to the weather forecast via contextualized chat-based insights.\n", 345 | "namespace weather {\n", 346 | "\n", 347 | "operationId handleWeatherQuestion = (_: {'question'*: 'str'}) => any}\n", 348 | "\n", 349 | "\n", 350 | "To call any api write the name, operation Id and parameters in the following format:\n", 351 | "[API]namespace.operationId[/API]\n", 352 | "[PARAMS]{ \"parameter_name\": \"parameter_value\", ...}[/PARAMS]\n", 353 | "\n", 354 | "Like in this example to shop a t-shirt: \n", 355 | "[API]KlarnaProducts.productsUsingGET[/API]\n", 356 | "[PARAMS]{\"q\": \"t-shirt\", \"size\": 3}[/PARAMS]\n", 357 | "\n", 358 | "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s\n", 359 | "\n", 360 | "Question:\n", 361 | "\n", 362 | "Number of tokens: 592\n" 363 | ] 364 | } 365 | ], 366 | "source": [ 367 | "plugins = Plugins.install_and_activate(urls, template = custom_template)\n", 368 | "print(plugins.prompt)\n", 369 | "print(f'Number of tokens: {plugins.tokens}')" 370 | ] 371 | } 372 | ], 373 | "metadata": { 374 | "colab": { 375 | "provenance": [] 376 | }, 377 | "kernelspec": { 378 | "display_name": "Python 3.8.15 ('envv': venv)", 379 | "language": "python", 380 | "name": "python3" 381 | }, 382 | "language_info": { 383 | "codemirror_mode": { 384 | "name": "ipython", 385 | "version": 3 386 | }, 387 | "file_extension": ".py", 388 | "mimetype": "text/x-python", 389 | "name": "python", 390 | "nbconvert_exporter": "python", 391 | "pygments_lexer": "ipython3", 392 | "version": "3.8.15" 393 | }, 394 | "vscode": { 395 | "interpreter": { 396 | "hash": "7eed2ceb37f2e6de236eac810ddd55539a1a94fcc3b18d3286d083ea6ee73195" 397 | } 398 | } 399 | }, 400 | "nbformat": 4, 401 | "nbformat_minor": 0 402 | } 403 | -------------------------------------------------------------------------------- /docs/examples/get-list-of-plugins.md: -------------------------------------------------------------------------------- 1 | # Get list of plugins 2 | 3 | Here is an example of how to get a list of plugins from the [plugnplai.com](https://plugnplai.com) API: 4 | 5 | ```python 6 | import plugnplai 7 | 8 | # Get all plugins from plugnplai.com 9 | urls = plugnplai.get_plugins() 10 | 11 | # Get ChatGPT plugins - only ChatGPT verified plugins 12 | urls = plugnplai.get_plugins(filter = 'ChatGPT') 13 | 14 | # Get working plugins - only tested plugins (in progress) 15 | urls = plugnplai.get_plugins(filter = 'working') 16 | 17 | 18 | # Get the Manifest and the OpenAPI specification from the plugin URL 19 | manifest, openapi_spec = plugnplai.spec_from_url(urls[0]) 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/examples/plugin_retriever_with_langchain_agent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "ba5f8741", 6 | "metadata": { 7 | "id": "ba5f8741" 8 | }, 9 | "source": [ 10 | "# Plugin Retriever - Plug and Plai\n", 11 | "### Retrieve the optimal plugin to use with LLMs based on the user's message\n", 12 | "\n", 13 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/docs/examples/plugin_retriever_with_langchain_agent.ipynb)\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "id": "rPtygjemCQHE", 19 | "metadata": { 20 | "id": "rPtygjemCQHE" 21 | }, 22 | "source": [ 23 | "This notebook builds upon the idea of LangChain [tool retrieval](custom_agent_with_plugin_retrieval.html), but pulls all tools from `plugnplai` - a directory of AI Plugins. And abstracts the the instantiation of the vector database (plugin_retriever)." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "id": "fea4812c", 29 | "metadata": { 30 | "id": "fea4812c" 31 | }, 32 | "source": [ 33 | "## Set up environment" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "aca08be8", 39 | "metadata": { 40 | "id": "aca08be8" 41 | }, 42 | "source": [ 43 | "Install plugnplai lib to get a list of active plugins from https://plugplai.com directory" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 22, 49 | "id": "OKr4jRvDv4IJ", 50 | "metadata": { 51 | "id": "OKr4jRvDv4IJ" 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "!pip install plugnplai -q" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 23, 61 | "id": "EiqnqramMcth", 62 | "metadata": { 63 | "id": "EiqnqramMcth" 64 | }, 65 | "outputs": [], 66 | "source": [ 67 | "import os\n", 68 | "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_OPENAI_KEY\" # Copy your OpenAI Key from https://platform.openai.com/account/api-keys" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "id": "6df0253f", 74 | "metadata": { 75 | "id": "6df0253f" 76 | }, 77 | "source": [ 78 | "## Set up plugins\n", 79 | "\n", 80 | "Load and index plugins" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 24, 86 | "id": "9e0f7882", 87 | "metadata": { 88 | "id": "9e0f7882" 89 | }, 90 | "outputs": [], 91 | "source": [ 92 | "import plugnplai \n", 93 | "\n", 94 | "# Get all plugins from plugnplai.com\n", 95 | "urls = plugnplai.get_plugins()\n", 96 | "\n", 97 | "# Or get ChatGPT plugins - only ChatGPT verified plugins\n", 98 | "urls = plugnplai.get_plugins(filter = 'ChatGPT')\n", 99 | "\n", 100 | "# Or get working plugins - only tested plugins (in progress)\n", 101 | "urls = plugnplai.get_plugins(filter = 'working')" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "id": "17362717", 107 | "metadata": { 108 | "id": "17362717" 109 | }, 110 | "source": [ 111 | "## Plugin Retriever\n", 112 | "\n", 113 | "We will use a vectorstore to create embeddings for each tool description. Then, for an incoming query we can create embeddings for that query and do a similarity search for relevant tools." 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 25, 119 | "id": "EqHhHrPbrMMj", 120 | "metadata": { 121 | "id": "EqHhHrPbrMMj" 122 | }, 123 | "outputs": [], 124 | "source": [ 125 | "'''\n", 126 | "Initialize the PluginRetriever - Index the manifests in the vector database\n", 127 | "'''\n", 128 | "\n", 129 | "# Import PluginRetriever from plugnplai lib\n", 130 | "from plugnplai import PluginRetriever\n", 131 | "\n", 132 | "\n", 133 | "# Initialize directly from the provider website (https://plugnplai.com)\n", 134 | "# In this case it wouldn't be necessary to use any previous functions\n", 135 | "plugin_retriever = PluginRetriever.from_directory()\n", 136 | "\n", 137 | "# Or initialize from the manifests\n", 138 | "manifests = [plugnplai.spec_from_url(url)[0] for url in urls]\n", 139 | "plugin_retriever = PluginRetriever(manifests)\n", 140 | "\n", 141 | "# Or initialize directly from the ulrs, lets use this option to be consistent with the next steps\n", 142 | "plugin_retriever = PluginRetriever.from_urls(urls)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "id": "FAA45e2Z6JZI", 148 | "metadata": { 149 | "id": "FAA45e2Z6JZI" 150 | }, 151 | "source": [ 152 | "## Define LangChain Agent with PluginsRetriever" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "id": "V0SK-Pk4y-w2", 158 | "metadata": { 159 | "id": "V0SK-Pk4y-w2" 160 | }, 161 | "source": [ 162 | "Add plugin_retriever to LangChain Tools to call the custom retrieved APIs" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 26, 168 | "id": "5yVgfYRETCPG", 169 | "metadata": { 170 | "colab": { 171 | "base_uri": "https://localhost:8080/" 172 | }, 173 | "id": "5yVgfYRETCPG", 174 | "outputId": "e34f54b2-a64c-4d1a-d288-dab3c35b8e11" 175 | }, 176 | "outputs": [ 177 | { 178 | "name": "stderr", 179 | "output_type": "stream", 180 | "text": [ 181 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load a Swagger 2.0 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 182 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 183 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 184 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.2 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 185 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 186 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 187 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 188 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.2 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 189 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 190 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n", 191 | "WARNING:langchain.tools.openapi.utils.openapi_utils:Attempting to load an OpenAPI 3.0.1 spec. This may result in degraded performance. Convert your OpenAPI spec to 3.1.* spec for better support.\n" 192 | ] 193 | } 194 | ], 195 | "source": [ 196 | "from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n", 197 | "from langchain.prompts import StringPromptTemplate\n", 198 | "from langchain import OpenAI, SerpAPIWrapper, LLMChain\n", 199 | "from typing import List, Union\n", 200 | "from langchain.schema import AgentAction, AgentFinish\n", 201 | "from langchain.agents.agent_toolkits import NLAToolkit\n", 202 | "from langchain.tools.plugin import AIPlugin\n", 203 | "import re\n", 204 | "\n", 205 | "# Load LangChain AIPlugin Objects \n", 206 | "AI_PLUGINS = [AIPlugin.from_url(url + \"/.well-known/ai-plugin.json\") for url in urls]\n", 207 | "\n", 208 | "# Define llm\n", 209 | "llm = OpenAI(temperature=0)\n", 210 | "\n", 211 | "toolkits_dict = {plugin.name_for_model: \n", 212 | " NLAToolkit.from_llm_and_ai_plugin(llm, plugin)\n", 213 | " for plugin in AI_PLUGINS}\n", 214 | "\n", 215 | "def get_tools(query):\n", 216 | " # Get documents, which contain the Plugins to use\n", 217 | " docs = plugin_retriever.retrieve_names(query)\n", 218 | " # Get the toolkits, one for each plugin\n", 219 | " tool_kits = [toolkits_dict[d] for d in docs]\n", 220 | " # Get the tools: a separate NLAChain for each endpoint\n", 221 | " tools = []\n", 222 | " for tk in tool_kits:\n", 223 | " tools.extend(tk.nla_tools)\n", 224 | " return tools" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "7699afd7", 230 | "metadata": { 231 | "id": "7699afd7" 232 | }, 233 | "source": [ 234 | "\n", 235 | "We can now test this retriever to see if it seems to work." 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 27, 241 | "id": "425f2886", 242 | "metadata": { 243 | "colab": { 244 | "base_uri": "https://localhost:8080/" 245 | }, 246 | "id": "425f2886", 247 | "outputId": "3cb5fc3c-4eb8-4669-c086-ad1310ea02f4" 248 | }, 249 | "outputs": [ 250 | { 251 | "data": { 252 | "text/plain": [ 253 | "['Milo.askMilo',\n", 254 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.search_all_actions',\n", 255 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.preview_a_zap',\n", 256 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.get_configuration_link',\n", 257 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.list_exposed_actions',\n", 258 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.get_execution_log_endpoint',\n", 259 | " 'SchoolDigger_API_V2.0.Autocomplete_GetSchools',\n", 260 | " 'SchoolDigger_API_V2.0.Districts_GetAllDistricts2',\n", 261 | " 'SchoolDigger_API_V2.0.Districts_GetDistrict2',\n", 262 | " 'SchoolDigger_API_V2.0.Rankings_GetSchoolRank2',\n", 263 | " 'SchoolDigger_API_V2.0.Rankings_GetRank_District',\n", 264 | " 'SchoolDigger_API_V2.0.Schools_GetAllSchools20',\n", 265 | " 'SchoolDigger_API_V2.0.Schools_GetSchool20',\n", 266 | " 'Open_AI_Klarna_product_Api.productsUsingGET']" 267 | ] 268 | }, 269 | "execution_count": 27, 270 | "metadata": {}, 271 | "output_type": "execute_result" 272 | } 273 | ], 274 | "source": [ 275 | "tools = get_tools(\"what could I do today with my kiddo\")\n", 276 | "[t.name for t in tools]" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 28, 282 | "id": "3aa88768", 283 | "metadata": { 284 | "colab": { 285 | "base_uri": "https://localhost:8080/" 286 | }, 287 | "id": "3aa88768", 288 | "outputId": "e4e69afd-1fdc-4421-a10a-eca22255a26c" 289 | }, 290 | "outputs": [ 291 | { 292 | "data": { 293 | "text/plain": [ 294 | "['Open_AI_Klarna_product_Api.productsUsingGET',\n", 295 | " 'Milo.askMilo',\n", 296 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.search_all_actions',\n", 297 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.preview_a_zap',\n", 298 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.get_configuration_link',\n", 299 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.list_exposed_actions',\n", 300 | " 'Zapier_Natural_Language_Actions_(NLA)_API_(Dynamic)_-_Beta.get_execution_log_endpoint',\n", 301 | " 'SchoolDigger_API_V2.0.Autocomplete_GetSchools',\n", 302 | " 'SchoolDigger_API_V2.0.Districts_GetAllDistricts2',\n", 303 | " 'SchoolDigger_API_V2.0.Districts_GetDistrict2',\n", 304 | " 'SchoolDigger_API_V2.0.Rankings_GetSchoolRank2',\n", 305 | " 'SchoolDigger_API_V2.0.Rankings_GetRank_District',\n", 306 | " 'SchoolDigger_API_V2.0.Schools_GetAllSchools20',\n", 307 | " 'SchoolDigger_API_V2.0.Schools_GetSchool20']" 308 | ] 309 | }, 310 | "execution_count": 28, 311 | "metadata": {}, 312 | "output_type": "execute_result" 313 | } 314 | ], 315 | "source": [ 316 | "tools = get_tools(\"what shirts can i buy?\")\n", 317 | "[t.name for t in tools]" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "id": "2e7a075c", 323 | "metadata": { 324 | "id": "2e7a075c" 325 | }, 326 | "source": [ 327 | "## Prompt Template\n", 328 | "\n", 329 | "The prompt template is pretty standard, because we're not actually changing that much logic in the actual prompt template, but rather we are just changing how retrieval is done." 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": 29, 335 | "id": "339b1bb8", 336 | "metadata": { 337 | "id": "339b1bb8" 338 | }, 339 | "outputs": [], 340 | "source": [ 341 | "# Set up the base template\n", 342 | "template = \"\"\"Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:\n", 343 | "\n", 344 | "{tools}\n", 345 | "\n", 346 | "Use the following format:\n", 347 | "\n", 348 | "Question: the input question you must answer\n", 349 | "Thought: you should always think about what to do\n", 350 | "Action: the action to take, should be one of [{tool_names}]\n", 351 | "Action Input: the input to the action\n", 352 | "Observation: the result of the action\n", 353 | "... (this Thought/Action/Action Input/Observation can repeat N times)\n", 354 | "Thought: I now know the final answer\n", 355 | "Final Answer: the final answer to the original input question\n", 356 | "\n", 357 | "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s\n", 358 | "\n", 359 | "Question: {input}\n", 360 | "{agent_scratchpad}\"\"\"" 361 | ] 362 | }, 363 | { 364 | "cell_type": "markdown", 365 | "id": "1583acdc", 366 | "metadata": { 367 | "id": "1583acdc" 368 | }, 369 | "source": [ 370 | "The custom prompt template now has the concept of a tools_getter, which we call on the input to select the tools to use" 371 | ] 372 | }, 373 | { 374 | "cell_type": "code", 375 | "execution_count": 30, 376 | "id": "fd969d31", 377 | "metadata": { 378 | "id": "fd969d31" 379 | }, 380 | "outputs": [], 381 | "source": [ 382 | "from typing import Callable\n", 383 | "# Set up a prompt template\n", 384 | "class CustomPromptTemplate(StringPromptTemplate):\n", 385 | " # The template to use\n", 386 | " template: str\n", 387 | " ############## NEW ######################\n", 388 | " # The list of tools available\n", 389 | " tools_getter: Callable\n", 390 | " \n", 391 | " def format(self, **kwargs) -> str:\n", 392 | " # Get the intermediate steps (AgentAction, Observation tuples)\n", 393 | " # Format them in a particular way\n", 394 | " intermediate_steps = kwargs.pop(\"intermediate_steps\")\n", 395 | " thoughts = \"\"\n", 396 | " for action, observation in intermediate_steps:\n", 397 | " thoughts += action.log\n", 398 | " thoughts += f\"\\nObservation: {observation}\\nThought: \"\n", 399 | " # Set the agent_scratchpad variable to that value\n", 400 | " kwargs[\"agent_scratchpad\"] = thoughts\n", 401 | " ############## NEW ######################\n", 402 | " tools = self.tools_getter(kwargs[\"input\"])\n", 403 | " # Create a tools variable from the list of tools provided\n", 404 | " kwargs[\"tools\"] = \"\\n\".join([f\"{tool.name}: {tool.description}\" for tool in tools])\n", 405 | " # Create a list of tool names for the tools provided\n", 406 | " kwargs[\"tool_names\"] = \", \".join([tool.name for tool in tools])\n", 407 | " return self.template.format(**kwargs)" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 31, 413 | "id": "798ef9fb", 414 | "metadata": { 415 | "id": "798ef9fb" 416 | }, 417 | "outputs": [], 418 | "source": [ 419 | "prompt = CustomPromptTemplate(\n", 420 | " template=template,\n", 421 | " tools_getter=get_tools,\n", 422 | " # This omits the `agent_scratchpad`, `tools`, and `tool_names` variables because those are generated dynamically\n", 423 | " # This includes the `intermediate_steps` variable because that is needed\n", 424 | " input_variables=[\"input\", \"intermediate_steps\"]\n", 425 | ")" 426 | ] 427 | }, 428 | { 429 | "cell_type": "markdown", 430 | "id": "ef3a1af3", 431 | "metadata": { 432 | "id": "ef3a1af3" 433 | }, 434 | "source": [ 435 | "## Output Parser\n", 436 | "\n", 437 | "The output parser is unchanged from the previous notebook, since we are not changing anything about the output format." 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": 32, 443 | "id": "7c6fe0d3", 444 | "metadata": { 445 | "id": "7c6fe0d3" 446 | }, 447 | "outputs": [], 448 | "source": [ 449 | "class CustomOutputParser(AgentOutputParser):\n", 450 | " \n", 451 | " def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:\n", 452 | " # Check if agent should finish\n", 453 | " if \"Final Answer:\" in llm_output:\n", 454 | " return AgentFinish(\n", 455 | " # Return values is generally always a dictionary with a single `output` key\n", 456 | " # It is not recommended to try anything else at the moment :)\n", 457 | " return_values={\"output\": llm_output.split(\"Final Answer:\")[-1].strip()},\n", 458 | " log=llm_output,\n", 459 | " )\n", 460 | " # Parse out the action and action input\n", 461 | " regex = r\"Action\\s*\\d*\\s*:(.*?)\\nAction\\s*\\d*\\s*Input\\s*\\d*\\s*:[\\s]*(.*)\"\n", 462 | " match = re.search(regex, llm_output, re.DOTALL)\n", 463 | " if not match:\n", 464 | " raise ValueError(f\"Could not parse LLM output: `{llm_output}`\")\n", 465 | " action = match.group(1).strip()\n", 466 | " action_input = match.group(2)\n", 467 | " # Return the action and action input\n", 468 | " return AgentAction(tool=action, tool_input=action_input.strip(\" \").strip('\"'), log=llm_output)" 469 | ] 470 | }, 471 | { 472 | "cell_type": "code", 473 | "execution_count": 33, 474 | "id": "d278706a", 475 | "metadata": { 476 | "id": "d278706a" 477 | }, 478 | "outputs": [], 479 | "source": [ 480 | "output_parser = CustomOutputParser()" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "id": "170587b1", 486 | "metadata": { 487 | "id": "170587b1" 488 | }, 489 | "source": [ 490 | "## Set up LLM, stop sequence, and the agent\n", 491 | "\n", 492 | "Also the same as the previous notebook" 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": 34, 498 | "id": "f9d4c374", 499 | "metadata": { 500 | "id": "f9d4c374" 501 | }, 502 | "outputs": [], 503 | "source": [ 504 | "llm = OpenAI(temperature=0)" 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": 35, 510 | "id": "9b1cc2a2", 511 | "metadata": { 512 | "id": "9b1cc2a2" 513 | }, 514 | "outputs": [], 515 | "source": [ 516 | "# LLM chain consisting of the LLM and a prompt\n", 517 | "llm_chain = LLMChain(llm=llm, prompt=prompt)" 518 | ] 519 | }, 520 | { 521 | "cell_type": "code", 522 | "execution_count": 36, 523 | "id": "e4f5092f", 524 | "metadata": { 525 | "id": "e4f5092f" 526 | }, 527 | "outputs": [], 528 | "source": [ 529 | "tool_names = [tool.name for tool in tools]\n", 530 | "agent = LLMSingleActionAgent(\n", 531 | " llm_chain=llm_chain, \n", 532 | " output_parser=output_parser,\n", 533 | " stop=[\"\\nObservation:\"], \n", 534 | " allowed_tools=tool_names\n", 535 | ")" 536 | ] 537 | }, 538 | { 539 | "cell_type": "markdown", 540 | "id": "aa8a5326", 541 | "metadata": { 542 | "id": "aa8a5326" 543 | }, 544 | "source": [ 545 | "## Use the Agent\n", 546 | "\n", 547 | "Now we can use it!" 548 | ] 549 | }, 550 | { 551 | "cell_type": "code", 552 | "execution_count": 37, 553 | "id": "490604e9", 554 | "metadata": { 555 | "id": "490604e9" 556 | }, 557 | "outputs": [], 558 | "source": [ 559 | "agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)" 560 | ] 561 | }, 562 | { 563 | "cell_type": "code", 564 | "execution_count": 38, 565 | "id": "653b1617", 566 | "metadata": { 567 | "colab": { 568 | "base_uri": "https://localhost:8080/", 569 | "height": 288 570 | }, 571 | "id": "653b1617", 572 | "outputId": "3d870dbd-9fba-465b-a2f3-e507735a182f" 573 | }, 574 | "outputs": [ 575 | { 576 | "name": "stdout", 577 | "output_type": "stream", 578 | "text": [ 579 | "\n", 580 | "\n", 581 | "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", 582 | "\u001b[32;1m\u001b[1;3mThought: I need to find a way to get product information\n", 583 | "Action: Open_AI_Klarna_product_Api.productsUsingGET\n", 584 | "Action Input: shirts\u001b[0m\n", 585 | "\n", 586 | "Observation:\u001b[36;1m\u001b[1;3mI found 10 shirts from the API response. They range in price from $13.94 to $491.00 and come in a variety of materials, sizes, colors, and styles.\u001b[0m\n", 587 | "\u001b[32;1m\u001b[1;3m I now know the final answer\n", 588 | "Final Answer: Arg, ye can buy any of these 10 shirts from the API response. They range in price from $13.94 to $491.00 and come in a variety of materials, sizes, colors, and styles.\u001b[0m\n", 589 | "\n", 590 | "\u001b[1m> Finished chain.\u001b[0m\n" 591 | ] 592 | }, 593 | { 594 | "data": { 595 | "application/vnd.google.colaboratory.intrinsic+json": { 596 | "type": "string" 597 | }, 598 | "text/plain": [ 599 | "'Arg, ye can buy any of these 10 shirts from the API response. They range in price from $13.94 to $491.00 and come in a variety of materials, sizes, colors, and styles.'" 600 | ] 601 | }, 602 | "execution_count": 38, 603 | "metadata": {}, 604 | "output_type": "execute_result" 605 | } 606 | ], 607 | "source": [ 608 | "agent_executor.run(\"what shirts can i buy?\")" 609 | ] 610 | }, 611 | { 612 | "cell_type": "code", 613 | "execution_count": null, 614 | "id": "dvcVS5dLJQYn", 615 | "metadata": { 616 | "id": "dvcVS5dLJQYn" 617 | }, 618 | "outputs": [], 619 | "source": [] 620 | } 621 | ], 622 | "metadata": { 623 | "colab": { 624 | "provenance": [] 625 | }, 626 | "kernelspec": { 627 | "display_name": "Python 3.10.6 64-bit", 628 | "language": "python", 629 | "name": "python3" 630 | }, 631 | "language_info": { 632 | "codemirror_mode": { 633 | "name": "ipython", 634 | "version": 3 635 | }, 636 | "file_extension": ".py", 637 | "mimetype": "text/x-python", 638 | "name": "python", 639 | "nbconvert_exporter": "python", 640 | "pygments_lexer": "ipython3", 641 | "version": "3.10.6" 642 | }, 643 | "vscode": { 644 | "interpreter": { 645 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 646 | } 647 | } 648 | }, 649 | "nbformat": 4, 650 | "nbformat_minor": 5 651 | } 652 | -------------------------------------------------------------------------------- /docs/get-started/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | ## Installation 4 | 5 | You can install Plug and PlAI using pip: 6 | 7 | ```python 8 | pip install plugnplai 9 | ``` 10 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to plugnplai 2 | ===================================== 3 | 4 | Overview 5 | ^^^^^^^^ 6 | 7 | Plug and Plai is an open source library aiming to simplify the integration of AI plugins into open-source language models (LLMs). 8 | 9 | It provides utility functions to get a list of active plugins from https://plugnplai.com/ directory, get plugin manifests, and extract OpenAPI specifications and load plugins. 10 | 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :caption: Getting Started 15 | :hidden: 16 | 17 | get-started/quickstart.md 18 | 19 | 20 | 21 | 22 | .. toctree:: 23 | :caption: Examples 24 | :hidden: 25 | 26 | examples/get-list-of-plugins.md 27 | examples/apply_plugins_three_steps.ipynb 28 | examples/plugins_step_by_step.ipynb 29 | examples/create_prompt_plugins.ipynb 30 | examples/plugin_retriever_with_langchain_agent_clean_version.ipynb 31 | examples/plugin_retriever_with_langchain_agent.ipynb 32 | 33 | 34 | 35 | .. toctree:: 36 | :maxdepth: 2 37 | :caption: Modules 38 | :hidden: 39 | 40 | modules/utility-functions.md 41 | modules/plugins.md 42 | modules/embeddings.md 43 | 44 | 45 | .. toctree:: 46 | :maxdepth: 2 47 | :caption: Reference 48 | :hidden: 49 | 50 | 51 | reference/plugins.rst 52 | reference/embeddings.rst 53 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/modules/embeddings.md: -------------------------------------------------------------------------------- 1 | # PluginRetriever 2 | 3 | This module provides a `PluginRetriever` class that retrieves plugin information based on queries using embeddings and vector stores. 4 | 5 | ## Classes 6 | 7 | ### PluginRetriever 8 | 9 | `PluginRetriever` retrieves plugin information based on queries using embeddings and vector stores. 10 | 11 | #### Parameters 12 | 13 | - `manifests` (list): List of manifest objects. 14 | - `returnList` (list, optional): List of objects to be returned. Can be a list of URLs or a list of objects like `LangChain` `AIPlugin` object. Defaults to `None`. 15 | 16 | #### Methods 17 | 18 | - `__init__(manifests, returnList=None)`: Initializes the `PluginRetriever`. 19 | - `from_urls(urls)`: Creates a `PluginRetriever` object from a list of URLs. 20 | - `from_directory(provider='plugnplai')`: Creates a `PluginRetriever` object from a directory. 21 | - `retrieve_names(query)`: Retrieves plugin names based on a query. 22 | - `retrieve_urls(query)`: Retrieves plugin URLs based on a query. 23 | -------------------------------------------------------------------------------- /docs/modules/plugins.md: -------------------------------------------------------------------------------- 1 | ## count_tokens(text: str, model_name: str = "gpt-4") -> int 2 | 3 | Count the number of tokens in a text. 4 | 5 | ### Parameters 6 | - `text` (str): The input text. 7 | - `model_name` (str): The name of the GPT model. Defaults to "gpt-4". 8 | 9 | ### Returns 10 | - `int`: The number of tokens in the text. 11 | 12 | 13 | ## build_request_body(schema: Dict[str, Any], parameters: Dict[str, Any]) -> Any 14 | 15 | Build the request body for an API call. 16 | 17 | ### Parameters 18 | - `schema` (Dict[str, Any]): The schema for the request body. 19 | - `parameters` (Dict[str, Any]): The parameters to pass to the API call. 20 | 21 | ### Returns 22 | - `Any`: The request body. 23 | 24 | 25 | ## PluginObject 26 | 27 | Represents an AI plugin object. 28 | 29 | ### Attributes 30 | - `openapi` (dict): The OpenAPI specification for the plugin. 31 | - `info` (dict): The info object from the OpenAPI spec. 32 | - `paths` (dict): The paths object from the OpenAPI spec. 33 | - `servers` (list): The servers list from the OpenAPI spec. 34 | - `manifest` (dict): The plugin manifest. 35 | - `url` (str): The plugin URL. 36 | - `name_for_model` (str): The plugin name. 37 | - `description_for_model` (str): The plugin description. 38 | - `operation_details_dict` (dict): A dictionary containing details for each operation in the plugin. 39 | - `description_prompt` (str): A prompt describing the plugin operations. 40 | - `tokens` (int): The number of tokens in the description_prompt. 41 | 42 | ### Methods 43 | - `__init__(self, url: str, spec: Dict[str, Any], manifest: Dict[str, Any])`: Initialize the PluginObject. 44 | - `get_operation_details(self)`: Get the details for each operation in the plugin. 45 | - `call_operation(self, operation_id: str, parameters: Dict[str, Any])`: Call an operation in the plugin. 46 | - `describe_api(self)`: Generate a prompt describing the plugin operations. 47 | 48 | 49 | ## call_operation(operation_id: str, parameters: Dict[str, Any]) -> Optional[requests.Response] 50 | 51 | Call an operation in the plugin. 52 | 53 | ### Parameters 54 | - `operation_id` (str): The ID of the operation to call. 55 | - `parameters` (dict): The parameters to pass to the operation. 56 | 57 | ### Returns 58 | - `requests.Response` or `None`: The response from the API call, or `None` if unsuccessful. 59 | 60 | 61 | ## describe_api() -> str 62 | 63 | Generate a prompt describing the plugin operations. 64 | 65 | ### Returns 66 | - `str`: The generated prompt. 67 | 68 | 69 | ## Plugins 70 | 71 | Manages installed and active plugins. 72 | 73 | ### Attributes 74 | - `installed_plugins` (dict): A dictionary of installed PluginObject instances, keyed by plugin name. 75 | - `active_plugins` (dict): A dictionary of active PluginObject instances, keyed by plugin name. 76 | - `template` (str): The prompt template to use. 77 | - `prompt` (str): The generated prompt with descriptions of active plugins. 78 | - `tokens` (int): The number of tokens in the prompt. 79 | - `max_plugins` (int): The maximum number of plugins that can be active at once. 80 | 81 | ### Methods 82 | - `__init__(self, urls: List[str], template: str = None)`: Initialize the Plugins class. 83 | - `install_and_activate(cls, urls: Union[str, List[str]], template: Optional[str] = None)`: Install plugins from URLs and activate them. 84 | - `list_installed(self) -> List[str]`: Get a list of installed plugin names. 85 | - `list_active(self) -> List[str]`: Get a list of active plugin names. (Max 3 active plugins) 86 | -------------------------------------------------------------------------------- /docs/modules/utility-functions.md: -------------------------------------------------------------------------------- 1 | # Utility Functions 2 | 3 | ### Utility Functions 4 | 5 | The following utility functions are available in the library: 6 | 7 | * `get_plugins(endpoint)`: Get a list of available plugins from a [plugins repository](https://www.plugplai.com/). 8 | * `get_plugin_manifest(url)`: Get the AI plugin manifest from the specified plugin URL. 9 | * `get_openapi_url(url, manifest)`: Get the OpenAPI URL from the plugin manifest. 10 | * `get_openapi_spec(openapi_url)`: Get the OpenAPI specification from the specified OpenAPI URL. 11 | * `spec_from_url(url)`: Returns the Manifest and OpenAPI specification from the plugin URL. 12 | -------------------------------------------------------------------------------- /docs/reference/embeddings.rst: -------------------------------------------------------------------------------- 1 | Embeddings 2 | ============================== 3 | 4 | .. automodule:: plugnplai.embeddings 5 | :members: 6 | :inherited-members: 7 | :undoc-members: 8 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/plugins.rst: -------------------------------------------------------------------------------- 1 | Plugins 2 | ============================== 3 | 4 | .. autoclass:: plugnplai.plugins.Plugins 5 | :members: 6 | :inherited-members: 7 | :undoc-members: 8 | :show-inheritance: 9 | 10 | PluginObject 11 | ------------ 12 | 13 | .. autoclass:: plugnplai.plugins.PluginObject 14 | :members: 15 | :inherited-members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | count_tokens 20 | ------------ 21 | .. autofunction:: plugnplai.plugins.count_tokens 22 | 23 | build_request_body 24 | ------------ 25 | .. autofunction:: plugnplai.plugins.build_request_body 26 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=4.3.0 2 | furo>=2023.3.27 3 | docutils<0.17 4 | myst-parser 5 | myst-nb 6 | sphinx-autobuild 7 | sphinx_rtd_theme 8 | nbsphinx 9 | jsonref 10 | langchain 11 | tiktoken -------------------------------------------------------------------------------- /examples/apply_plugins_three_steps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "xXnd1cCu1TkJ" 7 | }, 8 | "source": [ 9 | "# Add Plugins in Three Steps\n", 10 | "\n", 11 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/apply_plugins_three_steps.ipynb)\n", 12 | "\n", 13 | "1. Load the plugins from a plugins directory (E.g. [plugnplai.com](https://plugnplai.com))\n", 14 | "2. Install and activate the plugins with `plugins = Plugins.install_and_activate(urls)`\n", 15 | "3. Add the plugins to your LLM function (you can use the decorator `@plugins.apply_plugins`)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": { 21 | "id": "OZWQil8RrAKV" 22 | }, 23 | "source": [ 24 | "# Install and import necessary packages" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": { 31 | "colab": { 32 | "base_uri": "https://localhost:8080/" 33 | }, 34 | "id": "7RUcr3tLEK7M", 35 | "outputId": "2aa31e20-4106-4048-c34c-b468f10fff56" 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "pip install plugnplai -q" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 8, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "# You will need to first define your API key\n", 49 | "import os\n", 50 | "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_OPENAI_KEY\"" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 13, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "import plugnplai as pl\n", 60 | "from langchain.chat_models import ChatOpenAI\n", 61 | "from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate\n", 62 | "from langchain.schema import AIMessage, HumanMessage, SystemMessage\n", 63 | "from IPython.display import display, Markdown" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": { 69 | "id": "7fYY9i4Z3sBO" 70 | }, 71 | "source": [ 72 | "# 1. Load plugins from [plugnplai.com](https://plugnplai.com)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": { 78 | "id": "nlC-9wyp2vtF" 79 | }, 80 | "source": [ 81 | "Lets find one plugin for each category, using PlugnPlai categories (see [API reference](https://plugnplai.github.io/))\n", 82 | "- travel\n", 83 | "- shopping \n", 84 | "- weather" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "metadata": { 91 | "colab": { 92 | "base_uri": "https://localhost:8080/" 93 | }, 94 | "id": "MmqqHyg5Eee0", 95 | "outputId": "33e028f9-003f-41c1-f9d0-acfcc4fd1fdd" 96 | }, 97 | "outputs": [ 98 | { 99 | "name": "stdout", 100 | "output_type": "stream", 101 | "text": [ 102 | "Our chosen Plugins: ['https://trip.com', 'https://klarna.com', 'https://api.speak.com']\n" 103 | ] 104 | } 105 | ], 106 | "source": [ 107 | "# Get working plugins - only tested plugins (in progress)\n", 108 | "urlsTravel = pl.get_plugins()\n", 109 | "\n", 110 | "# Lets pick Trip, Klarna and Speak\n", 111 | "urls = [plugin for plugin in urlsTravel if any(word in plugin for word in ('trip.com', 'klarna', 'speak'))]\n", 112 | "\n", 113 | "print(f'Our chosen Plugins: {urls}')" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": { 119 | "id": "hj5MtCaPyk9p" 120 | }, 121 | "source": [ 122 | "# 2. Install and activate the plugins" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 6, 128 | "metadata": { 129 | "id": "JMHi1UTpy8LV" 130 | }, 131 | "outputs": [], 132 | "source": [ 133 | "from plugnplai import Plugins\n", 134 | "\n", 135 | "plugins = Plugins.install_and_activate(urls)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": { 141 | "id": "2-alHvKpyk9q" 142 | }, 143 | "source": [ 144 | "# 3. Apply the plugins \n", 145 | "\n", 146 | "Use `@plugins.apply_plugins` decorator to easily apply plugins to your LLM function\n", 147 | "\n", 148 | "The LLM function MUST get a string as the user input as first variable and output the string with the response" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 9, 154 | "metadata": { 155 | "colab": { 156 | "base_uri": "https://localhost:8080/" 157 | }, 158 | "id": "g770AOGZ0vwH", 159 | "outputId": "739138b3-e6f7-460d-9d5a-0837fc2f3594" 160 | }, 161 | "outputs": [], 162 | "source": [ 163 | "chat = ChatOpenAI(temperature=0, model=\"gpt-4\")\n", 164 | "\n", 165 | "@plugins.apply_plugins\n", 166 | "def call_llm(user_message):\n", 167 | " messages = [\n", 168 | " SystemMessage(content=\"\"),\n", 169 | " HumanMessage(content=user_message)\n", 170 | " ]\n", 171 | "\n", 172 | " res = chat(messages)\n", 173 | "\n", 174 | " llm_first_response = res.content\n", 175 | "\n", 176 | " return llm_first_response" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "# Enjoy - Let's try it\n", 184 | "\n", 185 | "Now your function should call a plugin when the LLM decides to use a plugin \n", 186 | "or just returns the normal message if not." 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 16, 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "name": "stdout", 196 | "output_type": "stream", 197 | "text": [ 198 | "Using KlarnaProducts\n" 199 | ] 200 | }, 201 | { 202 | "data": { 203 | "text/markdown": [ 204 | "I found a few t-shirts for you:\n", 205 | "\n", 206 | "1. [T-shirt](https://www.klarna.com/us/shopping/pl/cl10001/3203506327/Clothing/T-shirt/?utm_source=openai&ref-site=openai_plugin) - $20.99\n", 207 | " - Material: Cotton\n", 208 | " - Target Group: Man\n", 209 | " - Color: Gray, White, Blue, Black, Orange\n", 210 | "\n", 211 | "2. [Polo Ralph Lauren Slim Fit Cotton T-shirt 3-pack](https://www.klarna.com/us/shopping/pl/cl10001/3201838628/Clothing/Polo-Ralph-Lauren-Slim-Fit-Cotton-T-shirt-3-pack/?utm_source=openai&ref-site=openai_plugin) - $42.50\n", 212 | " - Material: Cotton\n", 213 | " - Target Group: Man\n", 214 | " - Color: Gray, White, Blue, Multicolor, Black\n", 215 | "\n", 216 | "3. [Psycho Bunny Mens Copa Gradient Logo Graphic Tee](https://www.klarna.com/us/shopping/pl/cl10001/3203663222/Clothing/Psycho-Bunny-Mens-Copa-Gradient-Logo-Graphic-Tee/?utm_source=openai&ref-site=openai_plugin) - $49.00\n", 217 | " - Material: Cotton\n", 218 | " - Target Group: Man\n", 219 | " - Color: White, Blue, Black, Orange\n", 220 | "\n", 221 | "You can click on the links to view more details and make a purchase." 222 | ], 223 | "text/plain": [ 224 | "" 225 | ] 226 | }, 227 | "metadata": {}, 228 | "output_type": "display_data" 229 | } 230 | ], 231 | "source": [ 232 | "# Message that triggers the use of plugin 1\n", 233 | "response = call_llm(\"Buy a tshirt\")\n", 234 | "# Display in markdown format\n", 235 | "display(Markdown(response))" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 17, 241 | "metadata": {}, 242 | "outputs": [ 243 | { 244 | "name": "stdout", 245 | "output_type": "stream", 246 | "text": [ 247 | "Using Trip\n" 248 | ] 249 | }, 250 | { 251 | "data": { 252 | "text/markdown": [ 253 | "I have found the top 5 hotels in Paris for your stay from December 3rd to December 8th, 2023. Here are the details:\n", 254 | "\n", 255 | "1. [Le Tsuba Hotel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=6597288&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 256 | " - Address: 45 Rue des Acacias\n", 257 | " - Price: $295 USD per night\n", 258 | " - Star rating: 4 stars\n", 259 | " - Score: 4.6/5.0 (36 reviews)\n", 260 | " - Features: Sauna, gym\n", 261 | "\n", 262 | "2. [Pullman Paris Centre - Bercy](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2107175&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 263 | " - Address: 1 Rue de Libourne\n", 264 | " - Price: $236 USD per night\n", 265 | " - Star rating: 4 stars\n", 266 | " - Score: 4.5/5.0 (42 reviews)\n", 267 | " - Features: Swimming pool, children's playground\n", 268 | "\n", 269 | "3. [Pullman Paris Tour Eiffel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2081163&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 270 | " - Address: 18 Avenue De Suffren, 22 Rue Jean Rey Entrée Au\n", 271 | " - Price: $298 USD per night\n", 272 | " - Star rating: 4 stars\n", 273 | " - Score: 4.2/5.0 (112 reviews)\n", 274 | " - Features: Gym, multi-function hall\n", 275 | "\n", 276 | "4. [Hotel de Castiglione Paris](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2157992&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 277 | " - Address: 38-40 Rue du Faubourg Saint-Honoré\n", 278 | " - Price: $221 USD per night\n", 279 | " - Star rating: 4 stars\n", 280 | " - Score: 3.9/5.0 (49 reviews)\n", 281 | " - Features: Tea room, conference hall\n", 282 | "\n", 283 | "5. [Hotel de Crillon A Rosewood Hotel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=1619850&checkin=2023-12-03&checkout=2023-12-08&curr=USD)\n", 284 | " - Address: 10 Pl. de la Concorde\n", 285 | " - Price: $1673 USD per night\n", 286 | " - Star rating: 5 stars\n", 287 | " - Score: 4.7/5.0 (7 reviews)\n", 288 | " - Features: Sunbathing area, sauna\n", 289 | "\n", 290 | "Please note that prices and availability are subject to change. Make sure to book your preferred hotel as soon as possible to secure your reservation." 291 | ], 292 | "text/plain": [ 293 | "" 294 | ] 295 | }, 296 | "metadata": {}, 297 | "output_type": "display_data" 298 | } 299 | ], 300 | "source": [ 301 | "# Message that triggers the use of plugin 2\n", 302 | "response = call_llm(\"Book a hotel in paris for Dec.3-8\")\n", 303 | "display(Markdown(response))" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 18, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "name": "stdout", 313 | "output_type": "stream", 314 | "text": [ 315 | "Using speak\n" 316 | ] 317 | }, 318 | { 319 | "data": { 320 | "text/markdown": [ 321 | "In Portuguese, you can say \"I love you\" as:\n", 322 | "\n", 323 | "**Eu te amo**\n", 324 | "\n", 325 | "Here are some alternative ways to express love in Portuguese:\n", 326 | "\n", 327 | "1. \"Amo-te\" (More formal, less commonly used, but still appropriate in romantic settings)\n", 328 | "2. \"Adoro-te\" (Similar to \"I adore you\", commonly used in romantic relationships)\n", 329 | "3. \"Te quero\" (Less intense than \"I love you\", also appropriate to use with friends, family, or significant others)\n", 330 | "\n", 331 | "Here's an example conversation in Portuguese:\n", 332 | "\n", 333 | "*Context: Maria and João are a young couple who are deeply in love and are on a date at the beach at sunset.*\n", 334 | "\n", 335 | "* João: \"Maria, eu te amo tanto, não consigo imaginar minha vida sem você.\"\n", 336 | "* Maria: \"Eu também te amo, João. Você é o melhor namorado que eu poderia pedir.\"\n", 337 | "\n", 338 | "[Report an issue or leave feedback](https://speak.com/chatgpt?rid=zgoen4wd4xgoc65ndmbzx4og)" 339 | ], 340 | "text/plain": [ 341 | "" 342 | ] 343 | }, 344 | "metadata": {}, 345 | "output_type": "display_data" 346 | } 347 | ], 348 | "source": [ 349 | "# Message that triggers the use of plugin 3\n", 350 | "response = call_llm(\"How to say I love you in Portuguese?\")\n", 351 | "display(Markdown(response))" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "### Making a question that doesn't trigger the use of a plugin" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 15, 364 | "metadata": {}, 365 | "outputs": [ 366 | { 367 | "data": { 368 | "text/plain": [ 369 | "'🤖 The AI revolution is transforming the world! 🌍 From enhancing productivity to personalizing experiences, artificial intelligence is unlocking new possibilities and reshaping industries. Embrace the change and join the #AIRevolution! 💡 #TechTrends #Innovation #FutureIsNow'" 370 | ] 371 | }, 372 | "execution_count": 15, 373 | "metadata": {}, 374 | "output_type": "execute_result" 375 | } 376 | ], 377 | "source": [ 378 | "call_llm(\"Write tweet about the AI revolution.\")" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": null, 384 | "metadata": {}, 385 | "outputs": [], 386 | "source": [] 387 | } 388 | ], 389 | "metadata": { 390 | "colab": { 391 | "provenance": [] 392 | }, 393 | "kernelspec": { 394 | "display_name": "Python 3.10.6 ('rainbow')", 395 | "language": "python", 396 | "name": "python3" 397 | }, 398 | "language_info": { 399 | "codemirror_mode": { 400 | "name": "ipython", 401 | "version": 3 402 | }, 403 | "file_extension": ".py", 404 | "mimetype": "text/x-python", 405 | "name": "python", 406 | "nbconvert_exporter": "python", 407 | "pygments_lexer": "ipython3", 408 | "version": "3.10.6" 409 | }, 410 | "vscode": { 411 | "interpreter": { 412 | "hash": "3ccef4e08d87aa1eeb90f63e0f071292ccb2e9c42e70f74ab2bf6f5493ca7bbc" 413 | } 414 | } 415 | }, 416 | "nbformat": 4, 417 | "nbformat_minor": 0 418 | } 419 | -------------------------------------------------------------------------------- /examples/create_prompt_plugins.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "xXnd1cCu1TkJ" 7 | }, 8 | "source": [ 9 | "# Create Prompt with Plugins Descriptions\n", 10 | "\n", 11 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/create_prompt_plugins.ipynb)\n", 12 | "\n", 13 | "The goal of this example is to load plugins specifications\n", 14 | "and create a prefix prompt describing the API to the LLM" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": { 20 | "id": "OZWQil8RrAKV" 21 | }, 22 | "source": [ 23 | "# Install" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": { 30 | "colab": { 31 | "base_uri": "https://localhost:8080/" 32 | }, 33 | "id": "7RUcr3tLEK7M", 34 | "outputId": "595c3751-91c9-4bb6-8326-9fbcca19fcff" 35 | }, 36 | "outputs": [ 37 | { 38 | "name": "stdout", 39 | "output_type": "stream", 40 | "text": [ 41 | "Note: you may need to restart the kernel to use updated packages.\n" 42 | ] 43 | } 44 | ], 45 | "source": [ 46 | "pip install plugnplai -q" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": { 52 | "id": "7fYY9i4Z3sBO" 53 | }, 54 | "source": [ 55 | "# Get the plugins from the directory" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": { 61 | "id": "nlC-9wyp2vtF" 62 | }, 63 | "source": [ 64 | "We want to install three plugins at maximum to fit the description on the context length\n", 65 | "\n", 66 | "Lets find one plugin for each category:\n", 67 | "1. travel\n", 68 | "2. shopping \n", 69 | "3. weather\n", 70 | "\n", 71 | "We can use PlugnPlai categories (see [API reference](https://plugnplai.github.io/))" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 2, 77 | "metadata": { 78 | "colab": { 79 | "base_uri": "https://localhost:8080/" 80 | }, 81 | "id": "MmqqHyg5Eee0", 82 | "outputId": "2e002204-ddfb-4bb4-8a29-bf0a1bed2a25" 83 | }, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "Travel plugins: ['https://gogaffl.com', 'https://trip.com', 'https://api.yelp.com', 'https://gps-telecom.com']\n", 90 | "Shopping plugins: ['https://pricerunner.com', 'https://server.shop.app', 'https://klarna.com']\n", 91 | "Weather plugins: ['https://api.tomorrow.io']\n" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "import plugnplai as pl\n", 97 | "\n", 98 | "# Get working plugins - only tested plugins (in progress)\n", 99 | "urlsTravel = pl.get_plugins(category='travel')\n", 100 | "print(f'Travel plugins: {urlsTravel}')\n", 101 | "\n", 102 | "urlsShopping = pl.get_plugins(category='shopping')\n", 103 | "print(f'Shopping plugins: {urlsShopping}')\n", 104 | "\n", 105 | "urlsWeather = pl.get_plugins(category='weather')\n", 106 | "print(f'Weather plugins: {urlsWeather}')" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 3, 112 | "metadata": { 113 | "colab": { 114 | "base_uri": "https://localhost:8080/" 115 | }, 116 | "id": "81MK5G3aJ6pA", 117 | "outputId": "4d4e72fa-ab73-45e3-d1f4-b478d0b5aa1e" 118 | }, 119 | "outputs": [ 120 | { 121 | "name": "stdout", 122 | "output_type": "stream", 123 | "text": [ 124 | "Our chosen Plugins: ['https://trip.com', 'https://klarna.com', 'https://api.tomorrow.io']\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "# Lets pick one of each list and add to our url list\n", 130 | "\n", 131 | "urls = []\n", 132 | "\n", 133 | "# Trip (list index 1)\n", 134 | "urls.append([plugin for plugin in urlsTravel if 'trip' in plugin][0])\n", 135 | "\n", 136 | "# Klarna (list index 2)\n", 137 | "urls.append([plugin for plugin in urlsShopping if 'klarna' in plugin][0])\n", 138 | "\n", 139 | "# Speak (list index 0)\n", 140 | "urls.append([plugin for plugin in urlsWeather if 'tomorrow' in plugin][0])\n", 141 | "\n", 142 | "print(f'Our chosen Plugins: {urls}')" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "# Load and activate the plugins" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 4, 155 | "metadata": { 156 | "id": "JMHi1UTpy8LV" 157 | }, 158 | "outputs": [], 159 | "source": [ 160 | "from plugnplai import Plugins\n", 161 | "\n", 162 | "plugins = Plugins.install_and_activate(urls)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Print the default prompt for the active plugins" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 5, 175 | "metadata": { 176 | "colab": { 177 | "base_uri": "https://localhost:8080/" 178 | }, 179 | "id": "g770AOGZ0vwH", 180 | "outputId": "e83aeca5-d65f-4367-a653-2a36523045a5" 181 | }, 182 | "outputs": [ 183 | { 184 | "name": "stdout", 185 | "output_type": "stream", 186 | "text": [ 187 | "\n", 188 | "# SYSTEM MESSAGE\n", 189 | "You are a large language model trained to assist humans.\n", 190 | "Knowledge Cutoff: 2021-09\n", 191 | "Current date: 2023-05-12\n", 192 | "Below is a list of available APIs that you can utilize to fulfill user requests. \n", 193 | "When using an API, please follow the specified format to make the API call. \n", 194 | "If possible, avoid asking follow-up questions and aim to complete the task with the information provided by the user.\n", 195 | "\n", 196 | "To make an API call, use the following format:\n", 197 | "\n", 198 | "[API]namespace.operationId[/API]\n", 199 | "[PARAMS]{ \n", 200 | " \"parameter_name\": \"parameter_value\",\n", 201 | " ...\n", 202 | "}[/PARAMS]\n", 203 | "\n", 204 | "For example, to call an API operation with the operation ID \"productsUsingGET\" in the \"KlarnaProducts\" namespace, \n", 205 | "and provide the required parameters \"q\" and \"size\", the format would be as follows:\n", 206 | "\n", 207 | "[API]KlarnaProducts.productsUsingGET[/API]\n", 208 | "[PARAMS]{\n", 209 | " \"q\": \"t-shirt\", \n", 210 | " \"size\": 3\n", 211 | "}[/PARAMS]\n", 212 | "\n", 213 | "Please ensure that you use the correct namespace and operation ID, and provide the necessary parameters for each API call. \n", 214 | "After requesting the API, refrain from writing anything else and wait for the API response, which will be delivered in a new message.\n", 215 | "\n", 216 | "## Plugins description\n", 217 | "\n", 218 | "### Plugin 1\n", 219 | "// Plugin for users to effortlessly get customized travel product recommendation and itinerary planning including hotels and flights using chatGPT.\n", 220 | "namespace Trip {\n", 221 | "\n", 222 | "operationId search_flight_ticket = (_: {'originCityCode'*: 'str', 'destinationCityCode'*: 'str', 'departureDate'*: 'str', 'returnDate'*: 'str', 'locale'*: 'str', 'oneWayOrRoundTrip'*: 'str'}) => any\n", 223 | "\n", 224 | "operationId search_hotel = (_: {'cityName'*: 'str', 'topHotel'*: 'int', 'locale'*: 'str', 'checkIn'*: 'any', 'checkOut'*: 'any'}) => any}\n", 225 | "\n", 226 | "### Plugin 2\n", 227 | "// Assistant uses the Klarna plugin to get relevant product suggestions for any shopping or product discovery purpose. Assistant will reply with the following 3 paragraphs 1) Search Results 2) Product Comparison of the Search Results 3) Followup Questions. The first paragraph contains a list of the products with their attributes listed clearly and concisely as bullet points under the product, together with a link to the product and an explanation. Links will always be returned and should be shown to the user. The second paragraph compares the results returned in a summary sentence starting with \"In summary\". Assistant comparisons consider only the most important features of the products that will help them fit the users request, and each product mention is brief, short and concise. In the third paragraph assistant always asks helpful follow-up questions and end with a question mark. When assistant is asking a follow-up question, it uses it's product expertise to provide information pertaining to the subject of the user's request that may guide them in their search for the right product.\n", 228 | "namespace KlarnaProducts {\n", 229 | "\n", 230 | "operationId productsUsingGET = (_: {'q'*: 'str', 'size': 'int', 'min_price': 'int', 'max_price': 'int'}) => any}\n", 231 | "\n", 232 | "### Plugin 3\n", 233 | "// Plugin that answers questions about the weather to help users predict, plan, and adapt their day to day to the weather forecast via contextualized chat-based insights.\n", 234 | "namespace weather {\n", 235 | "\n", 236 | "operationId handleWeatherQuestion = (_: {'question'*: 'str'}) => any}\n", 237 | "\n", 238 | "\n", 239 | "# USER MESSAGE\n", 240 | "\n" 241 | ] 242 | } 243 | ], 244 | "source": [ 245 | "print(plugins.prompt)" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "## Lets look at the length of the prompt\n", 253 | "\n", 254 | "Get the number of tokens of the prompt by just calling 'plugins.tokens' " 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 6, 260 | "metadata": { 261 | "colab": { 262 | "base_uri": "https://localhost:8080/" 263 | }, 264 | "id": "jnFld3xszdrx", 265 | "outputId": "fd6efdf5-6aca-45dc-bdac-f95c5a9c8d96" 266 | }, 267 | "outputs": [ 268 | { 269 | "name": "stdout", 270 | "output_type": "stream", 271 | "text": [ 272 | "731\n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "print(plugins.tokens)" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "metadata": {}, 283 | "source": [ 284 | "## Customize the prompt template\n", 285 | "\n", 286 | "You can customize the template by just passing 'template' as a variable\n", 287 | "\n", 288 | "Lets adapt this awesome and funny template from this [LangChain example](https://python.langchain.com/en/latest/use_cases/agents/custom_agent_with_plugin_retrieval.html)\n", 289 | "\n", 290 | "The template must have a {{plugins}} variable, to be filled with the descriptions. " 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": 7, 296 | "metadata": {}, 297 | "outputs": [], 298 | "source": [ 299 | "custom_template = '''\n", 300 | "Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following APIs:\n", 301 | "\n", 302 | "{{plugins}}\n", 303 | "To call any api write the name, operation Id and parameters in the following format:\n", 304 | "[API]namespace.operationId[/API]\n", 305 | "[PARAMS]{ \"parameter_name\": \"parameter_value\", ...}[/PARAMS]\n", 306 | "\n", 307 | "Like in this example to shop a t-shirt: \n", 308 | "[API]KlarnaProducts.productsUsingGET[/API]\n", 309 | "[PARAMS]{\"q\": \"t-shirt\", \"size\": 3}[/PARAMS]\n", 310 | "\n", 311 | "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s\n", 312 | "\n", 313 | "Question:\n", 314 | "'''" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 8, 320 | "metadata": {}, 321 | "outputs": [ 322 | { 323 | "name": "stdout", 324 | "output_type": "stream", 325 | "text": [ 326 | "\n", 327 | "Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following APIs:\n", 328 | "\n", 329 | "### Plugin 1\n", 330 | "// Plugin for users to effortlessly get customized travel product recommendation and itinerary planning including hotels and flights using chatGPT.\n", 331 | "namespace Trip {\n", 332 | "\n", 333 | "operationId search_flight_ticket = (_: {'originCityCode'*: 'str', 'destinationCityCode'*: 'str', 'departureDate'*: 'str', 'returnDate'*: 'str', 'locale'*: 'str', 'oneWayOrRoundTrip'*: 'str'}) => any\n", 334 | "\n", 335 | "operationId search_hotel = (_: {'cityName'*: 'str', 'topHotel'*: 'int', 'locale'*: 'str', 'checkIn'*: 'any', 'checkOut'*: 'any'}) => any}\n", 336 | "\n", 337 | "### Plugin 2\n", 338 | "// Assistant uses the Klarna plugin to get relevant product suggestions for any shopping or product discovery purpose. Assistant will reply with the following 3 paragraphs 1) Search Results 2) Product Comparison of the Search Results 3) Followup Questions. The first paragraph contains a list of the products with their attributes listed clearly and concisely as bullet points under the product, together with a link to the product and an explanation. Links will always be returned and should be shown to the user. The second paragraph compares the results returned in a summary sentence starting with \"In summary\". Assistant comparisons consider only the most important features of the products that will help them fit the users request, and each product mention is brief, short and concise. In the third paragraph assistant always asks helpful follow-up questions and end with a question mark. When assistant is asking a follow-up question, it uses it's product expertise to provide information pertaining to the subject of the user's request that may guide them in their search for the right product.\n", 339 | "namespace KlarnaProducts {\n", 340 | "\n", 341 | "operationId productsUsingGET = (_: {'q'*: 'str', 'size': 'int', 'min_price': 'int', 'max_price': 'int'}) => any}\n", 342 | "\n", 343 | "### Plugin 3\n", 344 | "// Plugin that answers questions about the weather to help users predict, plan, and adapt their day to day to the weather forecast via contextualized chat-based insights.\n", 345 | "namespace weather {\n", 346 | "\n", 347 | "operationId handleWeatherQuestion = (_: {'question'*: 'str'}) => any}\n", 348 | "\n", 349 | "\n", 350 | "To call any api write the name, operation Id and parameters in the following format:\n", 351 | "[API]namespace.operationId[/API]\n", 352 | "[PARAMS]{ \"parameter_name\": \"parameter_value\", ...}[/PARAMS]\n", 353 | "\n", 354 | "Like in this example to shop a t-shirt: \n", 355 | "[API]KlarnaProducts.productsUsingGET[/API]\n", 356 | "[PARAMS]{\"q\": \"t-shirt\", \"size\": 3}[/PARAMS]\n", 357 | "\n", 358 | "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s\n", 359 | "\n", 360 | "Question:\n", 361 | "\n", 362 | "Number of tokens: 592\n" 363 | ] 364 | } 365 | ], 366 | "source": [ 367 | "plugins = Plugins.install_and_activate(urls, template = custom_template)\n", 368 | "print(plugins.prompt)\n", 369 | "print(f'Number of tokens: {plugins.tokens}')" 370 | ] 371 | } 372 | ], 373 | "metadata": { 374 | "colab": { 375 | "provenance": [] 376 | }, 377 | "kernelspec": { 378 | "display_name": "Python 3.10.6 ('rainbow')", 379 | "language": "python", 380 | "name": "python3" 381 | }, 382 | "language_info": { 383 | "codemirror_mode": { 384 | "name": "ipython", 385 | "version": 3 386 | }, 387 | "file_extension": ".py", 388 | "mimetype": "text/x-python", 389 | "name": "python", 390 | "nbconvert_exporter": "python", 391 | "pygments_lexer": "ipython3", 392 | "version": "3.10.6" 393 | }, 394 | "vscode": { 395 | "interpreter": { 396 | "hash": "3ccef4e08d87aa1eeb90f63e0f071292ccb2e9c42e70f74ab2bf6f5493ca7bbc" 397 | } 398 | } 399 | }, 400 | "nbformat": 4, 401 | "nbformat_minor": 0 402 | } 403 | -------------------------------------------------------------------------------- /examples/langchain_chatgpt_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Appying plugins to your LangChain OpenAI GPT model (without agents)\n", 9 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/langchain_chatgpt_example.ipynb)\n", 10 | "\n", 11 | "This notebook displays how to use Plug and Plai to retrieve and implement plugins to a LangChain LLM model, without needing to convert the plugins to tools and attach them to an agent.\n", 12 | "## Preform necessary imports, and install plugnplai." 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "pip install plugnplai -q" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 62, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "import plugnplai\n", 31 | "from langchain.chat_models import ChatOpenAI\n", 32 | "from langchain.schema import HumanMessage, SystemMessage\n", 33 | "from IPython.display import display, Markdown" 34 | ] 35 | }, 36 | { 37 | "attachments": {}, 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "## Set up LLM" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 30, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "# Change model to \"gpt-3.5-turbo\" or \"gpt-3.5\" if you do not have access to GPT-4\n", 51 | "chat = ChatOpenAI(temperature=0, model=\"gpt-4\", openai_api_key=\"YOUR_API_KEY\")" 52 | ] 53 | }, 54 | { 55 | "attachments": {}, 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Retrieve and store plugins\n", 60 | "Retrieve all plugins, filter by category or framework, or enter specific urls. For this example, we will be using Klarna.com and Trip.com as our plugins." 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 58, 66 | "metadata": {}, 67 | "outputs": [ 68 | { 69 | "name": "stdout", 70 | "output_type": "stream", 71 | "text": [ 72 | "Our chosen Plugins: ['https://klarna.com', 'https://trip.com']\n" 73 | ] 74 | } 75 | ], 76 | "source": [ 77 | "# Get all plugins from plugnplai.com\n", 78 | "urls = plugnplai.get_plugins()\n", 79 | "\n", 80 | "# Get ChatGPT plugins - only ChatGPT verified plugins\n", 81 | "urls = plugnplai.get_plugins(filter = 'ChatGPT')\n", 82 | "\n", 83 | "# Get plugins verified for specific frameworks\n", 84 | "urls = plugnplai.get_plugins(verified_for = 'langchain')\n", 85 | "\n", 86 | "# Get plugins from direct url\n", 87 | "urls = ['https://klarna.com', 'https://trip.com']\n", 88 | "\n", 89 | "print(f'Our chosen Plugins: {urls}')" 90 | ] 91 | }, 92 | { 93 | "attachments": {}, 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "## Install and activate plugins" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 64, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "from plugnplai import Plugins\n", 107 | "\n", 108 | "plugins = Plugins.install_and_activate(urls)" 109 | ] 110 | }, 111 | { 112 | "attachments": {}, 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "## Apply plugins to LLM\n", 117 | "Use `@plugins.apply_plugins` decorator to easily apply plugins to your LLM function\n", 118 | "\n", 119 | "The LLM function MUST get a string as the user input as first variable and output the string with the response" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 60, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "@plugins.apply_plugins\n", 129 | "def call_llm(user_message):\n", 130 | " messages = [\n", 131 | " SystemMessage(content=\"\"),\n", 132 | " HumanMessage(content=user_message)\n", 133 | " ]\n", 134 | "\n", 135 | " res = chat(messages)\n", 136 | "\n", 137 | " llm_first_response = res.content\n", 138 | "\n", 139 | " return llm_first_response\n" 140 | ] 141 | }, 142 | { 143 | "attachments": {}, 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "## Test LLM\n", 148 | "\n", 149 | "Let's test both plugins now." 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 33, 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stdout", 159 | "output_type": "stream", 160 | "text": [ 161 | "Using KlarnaProducts\n" 162 | ] 163 | }, 164 | { 165 | "data": { 166 | "text/markdown": [ 167 | "Here are some t-shirts available on Klarna:\n", 168 | "\n", 169 | "1. [Patagonia Women's P-6 Logo Responsibili-Tee - T-shirt](https://www.klarna.com/us/shopping/pl/cl10001/3201782768/Clothing/Patagonia-Women-s-P-6-Logo-Responsibili-Tee-T-shirt/?utm_source=openai&ref-site=openai_plugin) - $23.84\n", 170 | " - Material: Polyester, Recycled Fabric, Cotton\n", 171 | " - Target Group: Woman\n", 172 | " - Color: Gray, Pink, White, Blue\n", 173 | "\n", 174 | "2. [Moschino T-shirt - White](https://www.klarna.com/us/shopping/pl/cl10001/3203506327/Clothing/Moschino-T-shirt-White/?utm_source=openai&ref-site=openai_plugin) - $121.00\n", 175 | " - Material: Cotton\n", 176 | " - Target Group: Man\n", 177 | " - Color: White\n", 178 | "\n", 179 | "3. [Polo Ralph Lauren Slim Fit Cotton T-shirt 3-pack](https://www.klarna.com/us/shopping/pl/cl10001/3201838628/Clothing/Polo-Ralph-Lauren-Slim-Fit-Cotton-T-shirt-3-pack/?utm_source=openai&ref-site=openai_plugin) - $28.90\n", 180 | " - Material: Cotton\n", 181 | " - Target Group: Man\n", 182 | " - Color: Gray, White, Blue, Multicolor, Black\n", 183 | "\n", 184 | "Please note that prices and availability are subject to change." 185 | ], 186 | "text/plain": [ 187 | "" 188 | ] 189 | }, 190 | "metadata": {}, 191 | "output_type": "display_data" 192 | } 193 | ], 194 | "source": [ 195 | "# Message that triggers the use of plugin 1\n", 196 | "response = call_llm(\"What are some t shirts available on Klarna?\")\n", 197 | "# Display in markdown format\n", 198 | "display(Markdown(response))" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 61, 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "name": "stdout", 208 | "output_type": "stream", 209 | "text": [ 210 | "Using Trip\n" 211 | ] 212 | }, 213 | { 214 | "data": { 215 | "text/markdown": [ 216 | "I found 3 top hotels in Paris for your stay from June 3 to June 8, 2023:\n", 217 | "\n", 218 | "1. [Pullman Paris Centre - Bercy](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2107175&checkin=2023-06-03&checkout=2023-06-08&curr=USD&allianceid=3842389&sid=22086800)\n", 219 | " - Address: 1 Rue de Libourne\n", 220 | " - Price: $270 per night\n", 221 | " - Rating: 4.5/5.0 (42 reviews)\n", 222 | " - 4-star hotel\n", 223 | " - Features: Swimming pool, fitness center\n", 224 | " - Opened in 2000, renovated in 2014\n", 225 | "\n", 226 | "2. [Shangri-La Paris](https://us.trip.com/hotels/detail/?cityId=192&hotelId=730333&checkin=2023-06-03&checkout=2023-06-08&curr=USD&allianceid=3842389&sid=22086800)\n", 227 | " - Address: 10 Av. d'Iéna\n", 228 | " - Price: $2117 per night\n", 229 | " - Rating: 4.6/5.0 (6 reviews)\n", 230 | " - 5-star hotel\n", 231 | " - Features: Sauna, swimming pool\n", 232 | " - Opened and renovated in 2010\n", 233 | "\n", 234 | "3. [Pullman Paris Tour Eiffel](https://us.trip.com/hotels/detail/?cityId=192&hotelId=2081163&checkin=2023-06-03&checkout=2023-06-08&curr=USD&allianceid=3842389&sid=22086800)\n", 235 | " - Address: 18 Avenue De Suffren, 22 Rue Jean Rey Entrée Au\n", 236 | " - Price: $1056 per night\n", 237 | " - Rating: 4.2/5.0 (113 reviews)\n", 238 | " - 4-star hotel\n", 239 | " - Features: Fitness center, multi-function hall\n", 240 | " - Opened in 1964, renovated in 2019\n", 241 | "\n", 242 | "Please note that prices and availability are subject to change. Click on the hotel links to get more information and book your stay." 243 | ], 244 | "text/plain": [ 245 | "" 246 | ] 247 | }, 248 | "metadata": {}, 249 | "output_type": "display_data" 250 | } 251 | ], 252 | "source": [ 253 | "# Message that triggers the use of plugin 2\n", 254 | "response = call_llm(\"Book a hotel in Paris for June 3-8?\")\n", 255 | "display(Markdown(response))" 256 | ] 257 | } 258 | ], 259 | "metadata": { 260 | "kernelspec": { 261 | "display_name": "Python 3", 262 | "language": "python", 263 | "name": "python3" 264 | }, 265 | "language_info": { 266 | "codemirror_mode": { 267 | "name": "ipython", 268 | "version": 3 269 | }, 270 | "file_extension": ".py", 271 | "mimetype": "text/x-python", 272 | "name": "python", 273 | "nbconvert_exporter": "python", 274 | "pygments_lexer": "ipython3", 275 | "version": "3.11.3" 276 | }, 277 | "orig_nbformat": 4 278 | }, 279 | "nbformat": 4, 280 | "nbformat_minor": 2 281 | } 282 | -------------------------------------------------------------------------------- /examples/oauth_example/oauth_server.py: -------------------------------------------------------------------------------- 1 | # This file is example for the handling the login with oauth authentication. 2 | # it has two endpoints 3 | # 1. /login : it is used for redirecting the user to a aunthentication_url(you can get this deatils from the mantifest file) of particular plugin. 4 | # 2. /callback : it is called automatically on success or failure response of the /login endpoint. 5 | # requirements. 6 | # pip install requests_oauthlib 7 | # run this file first. 8 | 9 | 10 | import json 11 | from urllib.parse import urljoin 12 | 13 | from requests_oauthlib import OAuth2Session 14 | from flask import Flask, request, session, redirect 15 | import ssl 16 | 17 | redirect_uri = 'https://localhost:5000/callback' 18 | app = Flask(__name__) 19 | app.secret_key = "qazwsx" 20 | # Update the SSL context with the certificate and key file paths 21 | 22 | context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 23 | # add the certificate and key from the pyopenssl or generate it. 24 | context.load_cert_chain(certfile='certificate.crt', keyfile='private.key') 25 | client_id = "client_id" 26 | client_secret = "client_secret" 27 | client_url = "client_url" 28 | token_url = "authorization_token_url" 29 | 30 | @app.route('/login', methods=["GET"]) 31 | def login(): 32 | try: 33 | global oauth 34 | oauth = OAuth2Session(client_id, redirect_uri=redirect_uri) 35 | authorization_url, state = oauth.authorization_url(client_url) 36 | session['oauth_state'] = state 37 | return redirect(authorization_url) 38 | except Exception as e: 39 | print(e) 40 | 41 | 42 | @app.route('/callback') 43 | def callback(): 44 | if 'error' in request.args: 45 | return 'Error: ' + request.args['error'] 46 | 47 | token = oauth.fetch_token( 48 | token_url, 49 | client_secret=client_secret, 50 | code=request.args.get("code", "") 51 | ) 52 | 53 | return token 54 | 55 | 56 | if __name__ == '__main__': 57 | app.run(debug=True, ssl_context=context) 58 | -------------------------------------------------------------------------------- /examples/oauth_example/run_plugin_with_auth.py: -------------------------------------------------------------------------------- 1 | # run server.py first 2 | # this file is the example for the retry mechanism with asana plugin. 3 | # enter the access_token when your plugin is using the oauth authentication. 4 | 5 | import guidance 6 | 7 | guidance.llm = guidance.llms.OpenAI("gpt-4", token="openai_api_key") 8 | import plugnplai 9 | 10 | 11 | def send_single_user_request(system_prompt, user_message): 12 | send_single_user_request = guidance( 13 | ''' 14 | {{#system~}} 15 | {{system_prompt}} 16 | {{~/system}} 17 | 18 | {{#user~}} 19 | {{user_message}} 20 | {{~/user}} 21 | 22 | {{#assistant~}} 23 | {{gen 'assistant_response' temperature=0 max_tokens=300}} 24 | {{~/assistant}}''' 25 | ) 26 | return send_single_user_request(system_prompt=system_prompt, user_message=user_message) 27 | 28 | 29 | def api_return_message(user_message, llm_first_response, api_response): 30 | create_api_return_message = guidance( 31 | ''' 32 | {{#user~}} 33 | {{user_message}} 34 | {{~/user}} 35 | 36 | {{#system~}} 37 | Assistant is a large language model with access to plugins. 38 | 39 | Assistant called a plugin in response to previous user message. 40 | # API REQUEST SUMMARY 41 | {{llm_first_response}} 42 | 43 | # API RESPONSE 44 | {{api_response}} 45 | {{~/system}} 46 | 47 | {{#assistant~}} 48 | {{gen 'assistant_response' temperature=0 max_tokens=300}} 49 | {{~/assistant}}''' 50 | ) 51 | return create_api_return_message( 52 | user_message=user_message, llm_first_response=llm_first_response, api_response=api_response 53 | ) 54 | 55 | 56 | if __name__ == '__main__': 57 | 58 | user_inputs_query = "Create a task in Asana with the title 'Prepare presentation' and due date '2023-05-25' in workspace id 1" 59 | # Use the correct URL 60 | url_of_plugin_to_test = "https://app.asana.com" 61 | 62 | api_key = input("input your api key: ") 63 | 64 | print('\n----\n1 -> Entered Process\n') 65 | 66 | urls = [url_of_plugin_to_test] 67 | # urls.append(url_of_plugin_to_test) 68 | plugins = plugnplai.Plugins.install_and_activate(urls) 69 | 70 | print('\n----\n2 -> Plugin Prompt\n') 71 | 72 | print(plugins.prompt) 73 | 74 | print('\n----\n3 -> User Query\n') 75 | user_first_message = user_inputs_query 76 | print(user_first_message) 77 | 78 | assistant_first_response = send_single_user_request(system_prompt=plugins.prompt, user_message=user_first_message) 79 | 80 | llm_first_response = assistant_first_response['assistant_response'] 81 | 82 | print('\n----\n4 -> Assistant First Response\n') 83 | print(llm_first_response) 84 | 85 | print('\n----\n5 -> API Call Dict\n') 86 | call_dict = plugnplai.parse_llm_response(llm_first_response) 87 | print(call_dict) 88 | 89 | 90 | # pass the access_token as api_key incase of the oauth technique. 91 | api_response = plugins.call_api( 92 | plugin_name=call_dict['plugin_name'], operation_id=call_dict['operation_id'], parameters=call_dict['parameters'], 93 | url=url_of_plugin_to_test, 94 | api_key=api_key 95 | ) 96 | 97 | # ---------------------------------RETRY MECHANISM-----------------------------------------------------# 98 | # retry mechanism when access_token is expired. 99 | if "token has expired" in response.text: 100 | 101 | # ------------------------------REPLACE WITH ORIGINAL VALUES---------------------------------------# 102 | client_id = "client_id" 103 | client_secret = "client_secret" 104 | req_url = "authorization_token_url" 105 | refresh_token = "refresh_token" 106 | 107 | # make a request to a sever ( to a authorization_url ) with a grant type as a refresh_token. 108 | try: 109 | res = requests.post(req_url, params={"client_id":client_id, "client_secret":client_secret, "refresh_token":refresh_token, "grant_type": "refresh_token"}) 110 | except Exception as e: 111 | print(f"something went wrong {e}") 112 | return "Something went wrong please relogin" 113 | token_obj = res.json() 114 | print(token_obj) 115 | api_key = token_obj["access_token"] 116 | api_response = plugins.call_api( 117 | plugin_name=call_dict['plugin_name'], operation_id=call_dict['operation_id'], parameters=call_dict['parameters'], 118 | url=url_of_plugin_to_test, 119 | api_key=api_key 120 | ) 121 | 122 | 123 | print('\n----\n6 -> API Response\n') 124 | print(api_response) 125 | -------------------------------------------------------------------------------- /examples/retrieve_plugins_api.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Retrieving Plugins using Plug and Plai API\n", 9 | "\n", 10 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/retrieve_plugins_api.ipynb)\n", 11 | "\n", 12 | "This example shows how to retrieve compatible plugins using Plug and Plai within LangChain abstractions.\n", 13 | "\n", 14 | "### Install Plug and Plai and necessary LangChain tools" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [ 22 | { 23 | "name": "stdout", 24 | "output_type": "stream", 25 | "text": [ 26 | "\n", 27 | "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.1.2\u001b[0m\n", 28 | "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", 29 | "Note: you may need to restart the kernel to use updated packages.\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "pip install plugnplai -q" 35 | ] 36 | }, 37 | { 38 | "attachments": {}, 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "### Store all plugins in a dictionary\n", 43 | "1. Use the 'get_plugins' function to get all plugins from plugnplai.com\n", 44 | "2. Load the specifications and store in a dictionary of key: \"name_for_model\", value: PluginObject (from plugin.PluginObject - it contains all information to describe and call the plugin later)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "Http Error: 403 Client Error: Forbidden for url: https://ramp.com/.well-known/ai-plugin.json\n", 57 | "Error Connecting: HTTPSConnectionPool(host='theansible.ai', port=443): Max retries exceeded with url: /.well-known/ai-plugin.json (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -5] No address associated with hostname'))\n", 58 | "Http Error: 401 Client Error: for url: https://staging.valispace.com/rest/?format=openapi\n", 59 | "Http Error: 500 Server Error: Internal Server Error for url: https://api.kyuda.io/v1/sdk/user/openapi.json\n", 60 | "Error Connecting: HTTPSConnectionPool(host='docketalarm.com', port=443): Max retries exceeded with url: /.well-known/ai-plugin.json (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 101] Network is unreachable'))\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "import plugnplai as pl\n", 66 | "\n", 67 | "# Get all plugins from plugnplai.com\n", 68 | "urls = pl.get_plugins()\n", 69 | "\n", 70 | "dict_plugins = {}\n", 71 | "\n", 72 | "# Lets load 5 plugins as an example \n", 73 | "# (loading all plugins may take a while, we are loading only 50, but feel free to try loading more)\n", 74 | "max_line_length = 100\n", 75 | "for url in urls[:50]: # 50 plugins - remove [:50] to load all plugins\n", 76 | " try:\n", 77 | " manifest, specification = pl.spec_from_url(url)\n", 78 | " plugin = pl.PluginObject(url, specification, manifest)\n", 79 | " dict_plugins[plugin.name_for_model] = plugin\n", 80 | " # print(f\"Plugin {plugin.name_for_model} successfully loaded.\", end=\"\", flush=True)\n", 81 | " except Exception as e:\n", 82 | " # print(f\"Error loading plugin {url}: {e}\", end=\"\", flush=True)\n", 83 | " pass" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 11, 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "name": "stdout", 93 | "output_type": "stream", 94 | "text": [ 95 | "Number of available plugins: 42\n" 96 | ] 97 | } 98 | ], 99 | "source": [ 100 | "# print number of plugins in dict_plugins\n", 101 | "print(f\"Number of available plugins: {len(dict_plugins)}\")" 102 | ] 103 | }, 104 | { 105 | "attachments": {}, 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "### Retrieve the best plugin for the task\n", 110 | "Use pl.retrieve to call PlugnPlai retrieve api based on the user message - https://www.plugnplai.com/_functions/retrieve?text={user_message_here}" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 12, 116 | "metadata": {}, 117 | "outputs": [ 118 | { 119 | "data": { 120 | "text/plain": [ 121 | "dict_keys(['speak', 'WolframAlpha', 'Wolfram', 'KlarnaProducts', 'Zapier', 'Shop', 'Milo', 'guru', 'biztoc', 'calculator', 'datasette_datasette_io_3c330f', 'url_reader', 'owd', 'quickchart', 'screenshot', 'portfoliopilot', 'triple_whale_benchmarks', 'recipe_retrieval', 'Semgrep', 'woxo', 'textbelt', 'mrkter', 'kittycad', 'SceneXplain', 'Transvribe', 'web_search', 'messagebird', 'AppyPieAIAppBuilder', 'Asana', 'dalle_plugin', 'MixerBox_OnePlayer_music', 'luma_events', 'safari', 'seoanalysis', 'doctors', 'KalendarAI', 'code_repo_interaction', 'onelook_thesaurus', 'Ambition', 'CensysGPT', 'Slack', 'yt_persona_generator'])" 122 | ] 123 | }, 124 | "execution_count": 12, 125 | "metadata": {}, 126 | "output_type": "execute_result" 127 | } 128 | ], 129 | "source": [ 130 | "dict_plugins.keys()" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 13, 136 | "metadata": {}, 137 | "outputs": [ 138 | { 139 | "name": "stdout", 140 | "output_type": "stream", 141 | "text": [ 142 | "Top plugins: ['KlarnaProducts']\n" 143 | ] 144 | } 145 | ], 146 | "source": [ 147 | "def top_plugins(user_message):\n", 148 | "\n", 149 | " top_plugins_names = pl.retrieve(text = user_message, available_plugins = dict_plugins.keys())\n", 150 | "\n", 151 | " # Convert the top_plugins (list of strings) in a list of PluginObject\n", 152 | " top_plugins = [dict_plugins[plugin] for plugin in top_plugins_names]\n", 153 | "\n", 154 | " plugins = pl.Plugins(top_plugins)\n", 155 | "\n", 156 | " # activate the plugins\n", 157 | " for name in top_plugins_names:\n", 158 | " plugins.activate(name)\n", 159 | "\n", 160 | " return top_plugins_names, plugins\n", 161 | "\n", 162 | "names, plugins = top_plugins(\"What t shirts are available in klarna?\")\n", 163 | "print(f\"Top plugins: {names}\")" 164 | ] 165 | }, 166 | { 167 | "attachments": {}, 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "# Load the top plugin into the prompt using Plugins class " 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 14, 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "name": "stdout", 181 | "output_type": "stream", 182 | "text": [ 183 | "\n", 184 | "# SYSTEM MESSAGE\n", 185 | "You are a large language model trained to assist humans.\n", 186 | "Knowledge Cutoff: 2021-09\n", 187 | "Current date: 2023-06-24\n", 188 | "Below is a list of available APIs that you can utilize to fulfill user requests. \n", 189 | "When using an API, please follow the specified format to make the API call. \n", 190 | "Don't ask follow-up questions and aim to complete the task with the information provided by the user.\n", 191 | "\n", 192 | "To make an API call, use the following format (using JSON double quotes for the API call parameters):\n", 193 | "\n", 194 | "namespace.operationId({\"parameter_name\": \"parameter_value\", ...})\n", 195 | "\n", 196 | "For example, to call an API operation with the operation ID \"productsUsingGET\" in the \"KlarnaProducts\" namespace, \n", 197 | "and provide the required parameters \"q\" and \"size\", the format would be as follows:\n", 198 | "\n", 199 | "KlarnaProducts.productsUsingGET({\"q\": \"t-shirt\", \"size\": 3})\n", 200 | "\n", 201 | "Please ensure that you use the correct namespace and operation ID, and provide the necessary parameters for each API call. \n", 202 | "After requesting the API, refrain from writing anything else and wait for the API response, which will be delivered in a new message.\n", 203 | "\n", 204 | "## Plugins description ('*' are required parameters):\n", 205 | "\n", 206 | "### Plugin 1\n", 207 | "// Assistant uses the Klarna plugin to get relevant product suggestions for any shopping or product discovery purpose. Assistant will reply with the following 3 paragraphs 1) Search Results 2) Product Comparison of the Search Results 3) Followup Questions. The first paragraph contains a list of the products with their attributes listed clearly and concisely as bullet points under the product, together with a link to the product and an explanation. Links will always be returned and should be shown to the user. The second paragraph compares the results returned in a summary sentence starting with \"In summary\". Assistant comparisons consider only the most important features of the products that will help them fit the users request, and each product mention is brief, short and concise. In the third paragraph assistant always asks helpful follow-up questions and end with a question mark. When assistant is asking a follow-up question, it uses it's product expertise to provide information pertaining to the subject of the user's request that may guide them in their search for the right product.\n", 208 | "namespace KlarnaProducts {\n", 209 | "\n", 210 | "operationId productsUsingGET = (_: {'countryCode'*: 'str', 'q'*: 'str', 'size': 'int', 'min_price': 'int', 'max_price': 'int'}) => any}\n", 211 | "\n", 212 | "\n", 213 | "# USER MESSAGE\n", 214 | "\n" 215 | ] 216 | } 217 | ], 218 | "source": [ 219 | "print(plugins.prompt)" 220 | ] 221 | }, 222 | { 223 | "attachments": {}, 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "# Now lets just follow the example in plugins_step_by_step.ipynb" 228 | ] 229 | }, 230 | { 231 | "attachments": {}, 232 | "cell_type": "markdown", 233 | "metadata": {}, 234 | "source": [ 235 | "### Call the LLM using LangChain" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 42, 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [ 244 | "# You will need to first define your API key\n", 245 | "import os\n", 246 | "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_API_KEY\"" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 16, 252 | "metadata": {}, 253 | "outputs": [], 254 | "source": [ 255 | "from langchain.chat_models import ChatOpenAI\n", 256 | "from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate\n", 257 | "from langchain.schema import AIMessage, HumanMessage, SystemMessage\n", 258 | "from IPython.display import display, Markdown" 259 | ] 260 | }, 261 | { 262 | "attachments": {}, 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "#### Uncomment or modify the message to test different plugins" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 30, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "# Test Klarna Plugin\n", 276 | "HUMAN_MESSAGE = \"What t shirts are available in klarna?\"\n", 277 | "\n", 278 | "# Test Trip Plugin\n", 279 | "# HUMAN_MESSAGE = \"I need a hotel in Paris between Dec.3-8\"\n", 280 | "\n", 281 | "# Test Speak Plugin\n", 282 | "# HUMAN_MESSAGE = \"How to say I love you in Portuguese?\"" 283 | ] 284 | }, 285 | { 286 | "attachments": {}, 287 | "cell_type": "markdown", 288 | "metadata": {}, 289 | "source": [ 290 | "### Retrieve plugins" 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": 37, 296 | "metadata": {}, 297 | "outputs": [], 298 | "source": [ 299 | "names, plugins = top_plugins(HUMAN_MESSAGE)" 300 | ] 301 | }, 302 | { 303 | "attachments": {}, 304 | "cell_type": "markdown", 305 | "metadata": {}, 306 | "source": [ 307 | "#### Call LLM" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 38, 313 | "metadata": {}, 314 | "outputs": [ 315 | { 316 | "name": "stdout", 317 | "output_type": "stream", 318 | "text": [ 319 | "KlarnaProducts.productsUsingGET({\"countryCode\": \"us\", \"q\": \"t-shirt\", \"size\": 5})\n" 320 | ] 321 | } 322 | ], 323 | "source": [ 324 | "chat = ChatOpenAI(temperature=0, model=\"gpt-4-0613\")\n", 325 | "\n", 326 | "messages = [\n", 327 | " SystemMessage(content=plugins.prompt),\n", 328 | " HumanMessage(content=HUMAN_MESSAGE)\n", 329 | "]\n", 330 | "\n", 331 | "res = chat(messages)\n", 332 | "\n", 333 | "llm_first_response = res.content\n", 334 | "\n", 335 | "print(llm_first_response)" 336 | ] 337 | }, 338 | { 339 | "attachments": {}, 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "## Parse the LLM response" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": 39, 349 | "metadata": {}, 350 | "outputs": [ 351 | { 352 | "name": "stdout", 353 | "output_type": "stream", 354 | "text": [ 355 | "{'plugin_name': 'KlarnaProducts', 'operation_id': 'productsUsingGET', 'parameters': {'countryCode': 'us', 'q': 't-shirt', 'size': 5}}\n" 356 | ] 357 | } 358 | ], 359 | "source": [ 360 | "# import the parser function\n", 361 | "from plugnplai import parse_llm_response\n", 362 | "\n", 363 | "# Parse the LLM response importing '\n", 364 | "call_dict = parse_llm_response(llm_first_response)\n", 365 | "print(call_dict)" 366 | ] 367 | }, 368 | { 369 | "attachments": {}, 370 | "cell_type": "markdown", 371 | "metadata": {}, 372 | "source": [ 373 | "## Call Plugin" 374 | ] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "execution_count": 40, 379 | "metadata": {}, 380 | "outputs": [ 381 | { 382 | "name": "stdout", 383 | "output_type": "stream", 384 | "text": [ 385 | "200\n" 386 | ] 387 | }, 388 | { 389 | "data": { 390 | "text/plain": [ 391 | "{'products': [{'name': 'Polo Ralph Lauren Slim Fit Cotton T-shirt 3-pack',\n", 392 | " 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3201838628/Clothing/Polo-Ralph-Lauren-Slim-Fit-Cotton-T-shirt-3-pack/?utm_source=openai&ref-site=openai_plugin',\n", 393 | " 'price': '$27.65',\n", 394 | " 'attributes': ['Material:Cotton',\n", 395 | " 'Target Group:Man',\n", 396 | " 'Color:Gray,White,Blue,Multicolor,Black',\n", 397 | " 'Size:S,XL,XS,L,M,XXL']},\n", 398 | " {'name': 'Palm Angels Bear T-shirt - White',\n", 399 | " 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3200094728/Clothing/Palm-Angels-Bear-T-shirt-White/?utm_source=openai&ref-site=openai_plugin',\n", 400 | " 'price': '$218.00',\n", 401 | " 'attributes': ['Size (US):12,14,8,10',\n", 402 | " 'Material:Cotton',\n", 403 | " 'Target Group:Man',\n", 404 | " 'Color:White',\n", 405 | " 'Size:S,XL,3XL,XS,L,M,XXL']},\n", 406 | " {'name': 'adidas Adicolor Classics Trefoil T-shirt - Black/White',\n", 407 | " 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3201095924/Clothing/adidas-Adicolor-Classics-Trefoil-T-shirt-Black-White/?utm_source=openai&ref-site=openai_plugin',\n", 408 | " 'price': '$11.22',\n", 409 | " 'attributes': ['Material:Cotton',\n", 410 | " 'Target Group:Man',\n", 411 | " 'Color:Black',\n", 412 | " 'Size:S,XL,XS,L,M,XXL']},\n", 413 | " {'name': 'Psycho Bunny Mens Copa Gradient Logo Graphic Tee',\n", 414 | " 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3203663222/Clothing/Psycho-Bunny-Mens-Copa-Gradient-Logo-Graphic-Tee/?utm_source=openai&ref-site=openai_plugin',\n", 415 | " 'price': '$49.00',\n", 416 | " 'attributes': ['Material:Cotton',\n", 417 | " 'Target Group:Man',\n", 418 | " 'Color:White,Blue,Black,Orange',\n", 419 | " 'Size:XXS,S,XL,3XL,XS,L,M,XXL']},\n", 420 | " {'name': \"Armani Exchange Men's Script Logo T-shirt\",\n", 421 | " 'url': 'https://www.klarna.com/us/shopping/pl/cl10001/3202187360/Clothing/Armani-Exchange-Men-s-Script-Logo-T-shirt/?utm_source=openai&ref-site=openai_plugin',\n", 422 | " 'price': '$36.95',\n", 423 | " 'attributes': ['Material:Cotton',\n", 424 | " 'Target Group:Man',\n", 425 | " 'Color:Blue,Black',\n", 426 | " 'Size:S,XL,XS,L,M,XXL']}]}" 427 | ] 428 | }, 429 | "execution_count": 40, 430 | "metadata": {}, 431 | "output_type": "execute_result" 432 | } 433 | ], 434 | "source": [ 435 | "r = plugins.call_api(plugin_name = call_dict['plugin_name'],\n", 436 | " operation_id = call_dict['operation_id'],\n", 437 | " parameters = call_dict['parameters']\n", 438 | " )\n", 439 | "\n", 440 | "print(r.status_code)\n", 441 | "api_response = r.json()\n", 442 | "r.json()" 443 | ] 444 | }, 445 | { 446 | "attachments": {}, 447 | "cell_type": "markdown", 448 | "metadata": {}, 449 | "source": [ 450 | "## LLM responds using the API data" 451 | ] 452 | }, 453 | { 454 | "cell_type": "code", 455 | "execution_count": 41, 456 | "metadata": {}, 457 | "outputs": [ 458 | { 459 | "data": { 460 | "text/markdown": [ 461 | "Here are some t-shirts available on Klarna:\n", 462 | "\n", 463 | "1. [Polo Ralph Lauren Slim Fit Cotton T-shirt 3-pack](https://www.klarna.com/us/shopping/pl/cl10001/3201838628/Clothing/Polo-Ralph-Lauren-Slim-Fit-Cotton-T-shirt-3-pack/?utm_source=openai&ref-site=openai_plugin) - $27.65\n", 464 | " - Material: Cotton\n", 465 | " - Target Group: Man\n", 466 | " - Color: Gray, White, Blue, Multicolor, Black\n", 467 | " - Size: S, XL, XS, L, M, XXL\n", 468 | "\n", 469 | "2. [Palm Angels Bear T-shirt - White](https://www.klarna.com/us/shopping/pl/cl10001/3200094728/Clothing/Palm-Angels-Bear-T-shirt-White/?utm_source=openai&ref-site=openai_plugin) - $218.00\n", 470 | " - Size (US): 12, 14, 8, 10\n", 471 | " - Material: Cotton\n", 472 | " - Target Group: Man\n", 473 | " - Color: White\n", 474 | " - Size: S, XL, 3XL, XS, L, M, XXL\n", 475 | "\n", 476 | "3. [adidas Adicolor Classics Trefoil T-shirt - Black/White](https://www.klarna.com/us/shopping/pl/cl10001/3201095924/Clothing/adidas-Adicolor-Classics-Trefoil-T-shirt-Black-White/?utm_source=openai&ref-site=openai_plugin) - $11.22\n", 477 | " - Material: Cotton\n", 478 | " - Target Group: Man\n", 479 | " - Color: Black\n", 480 | " - Size: S, XL, XS, L, M, XXL\n", 481 | "\n", 482 | "4. [Psycho Bunny Mens Copa Gradient Logo Graphic Tee](https://www.klarna.com/us/shopping/pl/cl10001/3203663222/Clothing/Psycho-Bunny-Mens-Copa-Gradient-Logo-Graphic-Tee/?utm_source=openai&ref-site=openai_plugin) - $49.00\n", 483 | " - Material: Cotton\n", 484 | " - Target Group: Man\n", 485 | " - Color: White, Blue, Black, Orange\n", 486 | " - Size: XXS, S, XL, 3XL, XS, L, M, XXL\n", 487 | "\n", 488 | "5. [Armani Exchange Men's Script Logo T-shirt](https://www.klarna.com/us/shopping/pl/cl10001/3202187360/Clothing/Armani-Exchange-Men-s-Script-Logo-T-shirt/?utm_source=openai&ref-site=openai_plugin) - $36.95\n", 489 | " - Material: Cotton\n", 490 | " - Target Group: Man\n", 491 | " - Color: Blue, Black\n", 492 | " - Size: S, XL, XS, L, M, XXL\n", 493 | "\n", 494 | "Please note that prices and availability are subject to change." 495 | ], 496 | "text/plain": [ 497 | "" 498 | ] 499 | }, 500 | "metadata": {}, 501 | "output_type": "display_data" 502 | } 503 | ], 504 | "source": [ 505 | "from plugnplai import Plugins\n", 506 | "\n", 507 | "api_return_prompt = f\"\"\"\n", 508 | "Assistant is a large language model with access to plugins.\n", 509 | "\n", 510 | "Assistant called a plugin in response to this human message:\n", 511 | "# HUMAN MESSAGE\n", 512 | "{HUMAN_MESSAGE}\n", 513 | "\n", 514 | "# API REQUEST SUMMARY\n", 515 | "{llm_first_response}\n", 516 | "\n", 517 | "# API RESPONSE\n", 518 | "{api_response}\n", 519 | "\"\"\"\n", 520 | "\n", 521 | "# # Install the plugins ewith the original template\n", 522 | "# plugins = Plugins.install_and_activate(urls)\n", 523 | "\n", 524 | "chat = ChatOpenAI(temperature=0, model=\"gpt-4-0613\")\n", 525 | "# chat = ChatOpenAI(temperature=0, model=\"gpt-3.5-turbo\")\n", 526 | "\n", 527 | "messages = [\n", 528 | " SystemMessage(content=api_return_prompt),\n", 529 | " HumanMessage(content=\"HUMAN_MESSAGE\")\n", 530 | "]\n", 531 | "\n", 532 | "res = chat(messages)\n", 533 | "\n", 534 | "display(Markdown(res.content))" 535 | ] 536 | } 537 | ], 538 | "metadata": { 539 | "kernelspec": { 540 | "display_name": "Python 3", 541 | "language": "python", 542 | "name": "python3" 543 | }, 544 | "language_info": { 545 | "codemirror_mode": { 546 | "name": "ipython", 547 | "version": 3 548 | }, 549 | "file_extension": ".py", 550 | "mimetype": "text/x-python", 551 | "name": "python", 552 | "nbconvert_exporter": "python", 553 | "pygments_lexer": "ipython3", 554 | "version": "3.9.13" 555 | }, 556 | "orig_nbformat": 4 557 | }, 558 | "nbformat": 4, 559 | "nbformat_minor": 2 560 | } 561 | -------------------------------------------------------------------------------- /examples/retrieve_plugins_for_langchain_agent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# Retrieving Plugins for LangChain Agent Using Plug and Plai\n", 9 | "\n", 10 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edreisMD/plugnplai/blob/main/examples/retrieve_plugins_for_langchain_agent.ipynb)\n", 11 | "\n", 12 | "This example shows how to retrieve compatible plugins using Plug and Plai within LangChain abstractions.\n", 13 | "\n", 14 | "### 1. Install Plug and Plai and necessary LangChain tools" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "pip install plugnplai -q" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 3, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "from langchain.chat_models import ChatOpenAI\n", 33 | "from langchain.agents import load_tools, initialize_agent\n", 34 | "from langchain.agents import AgentType\n", 35 | "from langchain.tools import AIPluginTool" 36 | ] 37 | }, 38 | { 39 | "attachments": {}, 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "### 2. Retrieve compatible plugins\n", 44 | "Use the 'get_plugins' function to retrieve compatible plugins for a given agent. You can filter your search by specifying the LLM or framework you will be working with." 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 4, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "https://api.speak.com\n", 57 | "https://wolframcloud.com\n", 58 | "https://wolframalpha.com\n", 59 | "https://klarna.com\n", 60 | "https://nla.zapier.com\n", 61 | "https://server.shop.app\n", 62 | "https://joinmilo.com\n", 63 | "https://portfoliopilot.com\n", 64 | "https://api.tasty.co\n", 65 | "https://remoteambition.com\n", 66 | "https://openai.creaticode.com\n", 67 | "https://yabblezone.net\n" 68 | ] 69 | } 70 | ], 71 | "source": [ 72 | "import plugnplai\n", 73 | "\n", 74 | "# Get all plugins from plugnplai.com\n", 75 | "urls = plugnplai.get_plugins()\n", 76 | "\n", 77 | "# Or get ChatGPT plugins - only ChatGPT verified plugins\n", 78 | "urls = plugnplai.get_plugins(filter = 'ChatGPT')\n", 79 | "for url in urls:\n", 80 | " print(url)\n", 81 | "\n", 82 | "# Or get plugins that are verified for working with LangChain\n", 83 | "urls = plugnplai.get_plugins(verified_for = 'langchain')" 84 | ] 85 | }, 86 | { 87 | "attachments": {}, 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "### 3. Convert to LangChain tool\n", 92 | "You will need to add \"/.well-known/ai-plugin.json\" to the end of the plugin URL to convert it to a LangChain tool." 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 8, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "tool = AIPluginTool.from_plugin_url(\"https://www.klarna.com/.well-known/ai-plugin.json\")\n", 102 | "\n", 103 | "tools = [tool]" 104 | ] 105 | }, 106 | { 107 | "attachments": {}, 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "### 4. Initialize LangChain agent\n", 112 | "Replace `\"YOUR_API_KEY\"` with your OpenAI API key and `\"gpt-4\"` with \"gpt-3.5\" or \"gpt-3.5-turbo\" if you do not have access to GPT-4." 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "llm = ChatOpenAI(\n", 122 | " temperature=0,\n", 123 | " model=\"gpt-4\",\n", 124 | " openai_api_key=\"YOUR_API_KEY\",\n", 125 | " )\n", 126 | "\n", 127 | "agent_chain = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)\n", 128 | "agent_chain.run(\"what t shirts are available in klarna?\")" 129 | ] 130 | }, 131 | { 132 | "attachments": {}, 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "**> Entering new AgentExecutor chain...**\n", 137 | "I need to check the Klarna Shopping API to see if it has information on available t shirts.\n", 138 | "**Action: KlarnaProducts**\n", 139 | "Action Input: None\n", 140 | "\n", 141 | "Observation: Usage Guide: Use the Klarna plugin to get relevant product suggestions for any shopping or researching purpose. The query to be sent should not include stopwords like articles, prepositions and determinants. The api works best when searching for words that are related to products, like their name, brand, model or category. Links will always be returned and should be shown to the user.\n", 142 | "\n", 143 | "OpenAPI Spec: {'openapi': '3.0.1', 'info': {'version': 'v0', 'title': 'Open AI Klarna product Api'}, 'servers': [{'url': 'https://www.klarna.com/us/shopping'}], 'tags': [{'name': 'open-ai-product-endpoint', 'description': 'Open AI Product Endpoint. Query for products.'}], 'paths': {'/public/openai/v0/products': {'get': {'tags': ['open-ai-product-endpoint'], 'summary': 'API for fetching Klarna product information', 'operationId': 'productsUsingGET', 'parameters': [{'name': 'q', 'in': 'query', 'description': 'query, must be between 2 and 100 characters', 'required': True, 'schema': {'type': 'string'}}, {'name': 'size', 'in': 'query', 'description': 'number of products returned', 'required': False, 'schema': {'type': 'integer'}}, {'name': 'budget', 'in': 'query', 'description': 'maximum price of the matching product in local currency, filters results', 'required': False, 'schema': {'type': 'integer'}}], 'responses': {'200': {'description': 'Products found', 'content': {'application/json': {'schema': {'$ref': '#/components/schemas/ProductResponse'}}}}, '503': {'description': 'one or more services are unavailable'}}, 'deprecated': False}}}, 'components': {'schemas': {'Product': {'type': 'object', 'properties': {'attributes': {'type': 'array', 'items': {'type': 'string'}}, 'name': {'type': 'string'}, 'price': {'type': 'string'}, 'url': {'type': 'string'}}, 'title': 'Product'}, 'ProductResponse': {'type': 'object', 'properties': {'products': {'type': 'array', 'items': {'$ref': '#/components/schemas/Product'}}}, 'title': 'ProductResponse'}}}}\n", 144 | "**Thought:**I need to use the Klarna Shopping API to search for t shirts.\n", 145 | "Action: requests_get\n", 146 | "Action Input: https://www.klarna.com/us/shopping/public/openai/v0/products?q=t%20shirts\n", 147 | "\n", 148 | "Observation: {\"products\":[{\"name\":\"Lacoste Men's Pack of Plain T-Shirts\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3202043025/Clothing/Lacoste-Men-s-Pack-of-Plain-T-Shirts/?utm_source=openai\",\"price\":\"$26.60\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:White,Black\"]},{\"name\":\"Hanes Men's Ultimate 6pk. Crewneck T-Shirts\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3201808270/Clothing/Hanes-Men-s-Ultimate-6pk.-Crewneck-T-Shirts/?utm_source=openai\",\"price\":\"$13.82\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:White\"]},{\"name\":\"Nike Boy's Jordan Stretch T-shirts\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl359/3201863202/Children-s-Clothing/Nike-Boy-s-Jordan-Stretch-T-shirts/?utm_source=openai\",\"price\":\"$14.99\",\"attributes\":[\"Material:Cotton\",\"Color:White,Green\",\"Model:Boy\",\"Size (Small-Large):S,XL,L,M\"]},{\"name\":\"Polo Classic Fit Cotton V-Neck T-Shirts 3-Pack\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3203028500/Clothing/Polo-Classic-Fit-Cotton-V-Neck-T-Shirts-3-Pack/?utm_source=openai\",\"price\":\"$29.95\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:White,Blue,Black\"]},{\"name\":\"adidas Comfort T-shirts Men's 3-pack\",\"url\":\"https://www.klarna.com/us/shopping/pl/cl10001/3202640533/Clothing/adidas-Comfort-T-shirts-Men-s-3-pack/?utm_source=openai\",\"price\":\"$14.99\",\"attributes\":[\"Material:Cotton\",\"Target Group:Man\",\"Color:White,Black\",\"Neckline:Round\"]}]}\n", 149 | "**Thought:**The available t shirts in Klarna are Lacoste Men's Pack of Plain T-Shirts, Hanes Men's Ultimate 6pk. Crewneck T-Shirts, Nike Boy's Jordan Stretch T-shirts, Polo Classic Fit Cotton V-Neck T-Shirts 3-Pack, and adidas Comfort T-shirts Men's 3-pack.\n", 150 | "Final Answer: The available t shirts in Klarna are Lacoste Men's Pack of Plain T-Shirts, Hanes Men's Ultimate 6pk. Crewneck T-Shirts, Nike Boy's Jordan Stretch T-shirts, Polo Classic Fit Cotton V-Neck T-Shirts 3-Pack, and adidas Comfort T-shirts Men's 3-pack.\n", 151 | "\n", 152 | "**> Finished chain.**" 153 | ] 154 | }, 155 | { 156 | "attachments": {}, 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "\"The available t shirts in Klarna are Lacoste Men's Pack of Plain T-Shirts, Hanes Men's Ultimate 6pk. Crewneck T-Shirts, Nike Boy's Jordan Stretch T-shirts, Polo Classic Fit Cotton V-Neck T-Shirts 3-Pack, and adidas Comfort T-shirts Men's 3-pack.\"" 161 | ] 162 | } 163 | ], 164 | "metadata": { 165 | "kernelspec": { 166 | "display_name": "Python 3", 167 | "language": "python", 168 | "name": "python3" 169 | }, 170 | "language_info": { 171 | "codemirror_mode": { 172 | "name": "ipython", 173 | "version": 3 174 | }, 175 | "file_extension": ".py", 176 | "mimetype": "text/x-python", 177 | "name": "python", 178 | "nbconvert_exporter": "python", 179 | "pygments_lexer": "ipython3", 180 | "version": "3.11.3" 181 | }, 182 | "orig_nbformat": 4 183 | }, 184 | "nbformat": 4, 185 | "nbformat_minor": 2 186 | } 187 | -------------------------------------------------------------------------------- /plugnplai/__init__.py: -------------------------------------------------------------------------------- 1 | from plugnplai.utils import ( 2 | get_openapi_spec, 3 | get_openapi_url, 4 | get_plugin_manifest, 5 | get_plugins, 6 | spec_from_url, 7 | get_category_names, 8 | parse_llm_response 9 | ) 10 | from plugnplai.embeddings import PluginRetriever 11 | from plugnplai.plugins import PluginObject, Plugins, build_request_body, count_tokens 12 | from plugnplai.api.retrieve import retrieve 13 | 14 | from importlib import metadata 15 | # use the method from LangChain (Chase, H. (2022). LangChain [Computer software]. https://github.com/hwchase17/langchain) to get the version of the package 16 | try: 17 | __version__ = metadata.version(__package__) 18 | except metadata.PackageNotFoundError: 19 | # Case where package metadata is not available. 20 | __version__ = "" 21 | del metadata # optional, avoids polluting the results of dir(__package__) 22 | 23 | __all__ = [ 24 | "PluginObject", 25 | "Plugins", 26 | "PluginRetriever", 27 | "get_plugins", 28 | "get_plugin_manifest", 29 | "get_openapi_url", 30 | "get_openapi_spec", 31 | "get_category_names", 32 | "spec_from_url", 33 | "parse_llm_response", 34 | "build_request_body", 35 | "count_tokens", 36 | "retrieve" 37 | ] -------------------------------------------------------------------------------- /plugnplai/api/retrieve.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | def make_request_get(url: str): 4 | """Helper function to make a GET request. 5 | 6 | Args: 7 | url (str): URL to make the GET request to. 8 | 9 | Returns: 10 | Response: HTTP response object. 11 | """ 12 | response = requests.get(url) 13 | return response 14 | 15 | def retrieve(text: str, available_plugins: list): 16 | """Retrieve top plugins based on the provided text query. 17 | 18 | Args: 19 | text (str): Text query to find the top plugins. 20 | available_plugins (list): List of available plugin names. 21 | 22 | Returns: 23 | list/dict/str: Returns a list of top plugins if successful, dictionary of error info if unsuccessful, or error message if exception is caught. 24 | """ 25 | base_url = "https://www.plugnplai.com/_functions" 26 | # Construct the endpoint URL based on the text query 27 | url = f'{base_url.strip("/")}/retrieve?text={text}' 28 | # Make the HTTP GET request 29 | try: 30 | response = make_request_get(url) 31 | # Check if the response status code is successful (200 OK) 32 | if response.status_code == 200: 33 | # Parse the JSON response 34 | plugins = response.json() 35 | # Filter plugins to only include ones that are in the list of available plugins 36 | filtered_plugins = [plugin for plugin in plugins if plugin in available_plugins] 37 | return filtered_plugins 38 | elif response.status_code in [400, 500]: 39 | # Handle unsuccessful responses 40 | return response.json() # Assuming the API returns error info in JSON format 41 | else: 42 | # Handle other potential status codes 43 | return f"An error occurred: {response.status_code} {response.reason}" 44 | except requests.exceptions.RequestException as e: 45 | return str(e) 46 | -------------------------------------------------------------------------------- /plugnplai/api_call.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The aim is to build utilitarian functions here to call the different types of API auth 3 | ''' 4 | 5 | # def make_request_get(url: str, timeout=5): 6 | # """Make an HTTP GET request. 7 | 8 | # Args: 9 | # url (str): URL to make request to. 10 | # timeout (int, optional): Timeout in seconds. Defaults to 5. 11 | 12 | # Returns: 13 | # requests.Response: Response from request. 14 | # """ 15 | # try: 16 | # response = requests.get(url, timeout=timeout) 17 | # response.raise_for_status() # Raises stored HTTPError, if one occurred. 18 | # except requests.exceptions.HTTPError as errh: 19 | # print ("Http Error:",errh) 20 | # except requests.exceptions.ConnectionError as errc: 21 | # print ("Error Connecting:",errc) 22 | # except requests.exceptions.Timeout as errt: 23 | # print ("Timeout Error:",errt) 24 | # except requests.exceptions.RequestException as err: 25 | # print ("Something went wrong",err) 26 | # return None 27 | # return response 28 | 29 | 30 | 31 | # def call_operation(self, operation_id: str, parameters: Dict[str, Any]) -> Optional[requests.Response]: 32 | # """Call an operation in the plugin. 33 | 34 | # Parameters 35 | # ---------- 36 | # operation_id : str 37 | # The ID of the operation to call. 38 | # parameters : dict 39 | # The parameters to pass to the operation. 40 | 41 | # Returns 42 | # ------- 43 | # requests.Response or None 44 | # The response from the API call, or None if unsuccessful. 45 | # """ 46 | # # Get the operation details from the operation_details_dict attribute 47 | # operation_details = self.operation_details_dict.get(operation_id) 48 | # if not operation_details: 49 | # print(f'Operation {operation_id} not found') 50 | # return None 51 | 52 | # # Separate parameters by type 53 | # path_parameters = {} 54 | # query_parameters = {} 55 | # header_parameters = {} 56 | # cookie_parameters = {} 57 | # body = None 58 | 59 | # for parameter in operation_details['parameters']: 60 | # if parameter['in'] == 'path': 61 | # path_parameters[parameter['name']] = parameters.get(parameter['name']) 62 | # elif parameter['in'] == 'query': 63 | # query_parameters[parameter['name']] = parameters.get(parameter['name']) 64 | # elif parameter['in'] == 'header': 65 | # header_parameters[parameter['name']] = parameters.get(parameter['name']) 66 | # elif parameter['in'] == 'cookie': 67 | # cookie_parameters[parameter['name']] = parameters.get(parameter['name']) 68 | 69 | # if operation_details['requestBody']: 70 | # body_schema = operation_details['requestBody']['content']['application/json']['schema'] 71 | # body = build_request_body(body_schema, parameters) 72 | 73 | # # Replace path parameters in the URL 74 | # url = operation_details['url'] 75 | # for name, value in path_parameters.items(): 76 | # url = url.replace('{' + name + '}', str(value)) 77 | 78 | # # Make the API call 79 | # method = operation_details['method'] 80 | # if method.lower() == 'get': 81 | # response = requests.get(url, params=query_parameters, headers=header_parameters, cookies=cookie_parameters) 82 | # elif method.lower() == 'post': 83 | # headers = {'Content-Type': 'application/json'} 84 | # response = requests.post(url, params=query_parameters, headers=headers, cookies=cookie_parameters, json=body) 85 | 86 | # return response -------------------------------------------------------------------------------- /plugnplai/embeddings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Embeddings for LangChain. 3 | 4 | This module provides a PluginRetriever class that retrieves plugin 5 | information based on queries using embeddings and vector stores. 6 | 7 | Classes 8 | ------- 9 | PluginRetriever 10 | 11 | """ 12 | from langchain.embeddings import OpenAIEmbeddings 13 | from langchain.schema import Document 14 | from langchain.vectorstores import FAISS 15 | from plugnplai.utils import get_plugin_manifest, get_plugins 16 | 17 | class PluginRetriever: 18 | """PluginRetriever retrieves plugin information based on queries using embeddings and vector stores. 19 | 20 | Parameters 21 | ---------- 22 | manifests : list 23 | List of manifest objects. 24 | returnList : list, optional 25 | List of objects to be returned. Can be a list of URLs or a list of 26 | objects like LangChain AIPluging object. Defaults to None. 27 | 28 | Methods 29 | ------- 30 | __init__(manifests, returnList=None) 31 | from_urls(urls) 32 | from_directory(provider='plugnplai') 33 | retrieve_names(query) 34 | retrieve_urls(query) 35 | """ 36 | 37 | def __init__(self, manifests, returnList=None): 38 | """Initialize the PluginRetriever. 39 | 40 | Parameters 41 | ---------- 42 | manifests : list 43 | List of manifest objects. 44 | returnList : list, optional 45 | List of objects to be returned. Can be a list of URLs or a list of 46 | objects like LangChain AIPluging object. Defaults to None. 47 | """ 48 | self.returnList = returnList 49 | if self.returnList: 50 | # add urls to manifests 51 | for i in range(len(manifests)): 52 | manifests[i]["plugin_object"] = self.returnList[i] 53 | 54 | self.docs = [ 55 | Document( 56 | page_content=manifest["description_for_model"], 57 | metadata={ 58 | "plugin_name": manifest["name_for_model"], 59 | "plugin_object": manifest.get("plugin_object", None), 60 | }, 61 | ) 62 | for manifest in manifests 63 | ] 64 | 65 | # Initialize embeddings 66 | self.embeddings = OpenAIEmbeddings() 67 | 68 | # Create vector store from documents 69 | self.vector_store = FAISS.from_documents(self.docs, self.embeddings) 70 | 71 | # Create a retriever 72 | self.retriever = self.vector_store.as_retriever() 73 | 74 | @classmethod 75 | def from_urls(cls, urls): 76 | """Create a PluginRetriever object from a list of URLs. 77 | 78 | Parameters 79 | ---------- 80 | urls : list 81 | List of URLs. 82 | 83 | Returns 84 | ------- 85 | PluginRetriever 86 | Initialized PluginRetriever object. 87 | """ 88 | manifests = [get_plugin_manifest(url) for url in urls] 89 | return cls(manifests, urls) 90 | 91 | @classmethod 92 | def from_directory(cls, provider='plugnplai'): 93 | """Create a PluginRetriever object from a directory. 94 | 95 | Parameters 96 | ---------- 97 | provider : str, optional 98 | Provider name. Defaults to 'plugnplai'. 99 | 100 | Returns 101 | ------- 102 | PluginRetriever 103 | Initialized PluginRetriever object. 104 | """ 105 | 106 | urls = get_plugins(filter = 'working', provider = provider) 107 | manifests = [get_plugin_manifest(url) for url in urls] 108 | return cls(manifests) 109 | 110 | def retrieve_names(self, query): 111 | """Retrieve plugin names based on a query. 112 | 113 | Parameters 114 | ---------- 115 | query : 116 | Query string. 117 | 118 | Returns 119 | ------- 120 | list 121 | List of plugin names. 122 | """ 123 | # Get relevant documents based on query 124 | docs = self.retriever.get_relevant_documents(query) 125 | 126 | # Get toolkits based on relevant documents 127 | plugin_names = [d.metadata["plugin_name"] for d in docs] 128 | 129 | return plugin_names 130 | 131 | def retrieve_urls(self, query): 132 | """Retrieve plugin URLs based on a query. 133 | 134 | Parameters 135 | ---------- 136 | query : 137 | Query string. 138 | 139 | Returns 140 | ------- 141 | list 142 | List of plugin URLs. 143 | """ 144 | if not self.urls: 145 | raise Exception("No urls provided in constructor.") 146 | 147 | # Get relevant documents based on query 148 | docs = self.retriever.get_relevant_documents(query) 149 | 150 | # Get toolkits based on relevant documents 151 | plugin_urls = [d.metadata["plugin_object"] for d in docs] 152 | 153 | return plugin_urls 154 | -------------------------------------------------------------------------------- /plugnplai/prompt_templates.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | today_date = datetime.today().date() 3 | 4 | # Template to be filled with the plugins description, 5 | # this template muste have a {{plugins}} variable 6 | template_gpt4 = ''' 7 | # SYSTEM MESSAGE 8 | You are a large language model trained to assist humans. 9 | Knowledge Cutoff: 2021-09 10 | Current date: DATE_TODAY 11 | Below is a list of available APIs that you can utilize to fulfill user requests. 12 | When using an API, please follow the specified format to make the API call. 13 | Don't ask follow-up questions and aim to complete the task with the information provided by the user. 14 | 15 | To make an API call, use the following format (using JSON double quotes for the API call parameters): 16 | 17 | namespace.operationId({"parameter_name": "parameter_value", ...}) 18 | 19 | For example, to call an API operation with the operation ID "productsUsingGET" in the "KlarnaProducts" namespace, 20 | and provide the required parameters "q" and "size", the format would be as follows: 21 | 22 | KlarnaProducts.productsUsingGET({"q": "t-shirt", "size": 3}) 23 | 24 | Please ensure that you use the correct namespace and operation ID, and provide the necessary parameters for each API call. 25 | After requesting the API, refrain from writing anything else and wait for the API response, which will be delivered in a new message. 26 | 27 | ## Plugins description ('*' are required parameters): 28 | 29 | {{plugins}} 30 | # USER MESSAGE 31 | '''.replace('DATE_TODAY', str(today_date)) -------------------------------------------------------------------------------- /plugnplai/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import ast 3 | import os 4 | 5 | import jsonref 6 | import requests 7 | import yaml 8 | import re 9 | 10 | def make_request_get(url: str, timeout=5): 11 | """Make an HTTP GET request. 12 | 13 | Args: 14 | url (str): URL to make request to. 15 | timeout (int, optional): Timeout in seconds. Defaults to 5. 16 | 17 | Returns: 18 | requests.Response: Response from request. 19 | """ 20 | try: 21 | response = requests.get(url, timeout=timeout) 22 | response.raise_for_status() # Raises stored HTTPError, if one occurred. 23 | except requests.exceptions.HTTPError as errh: 24 | print ("Http Error:",errh) 25 | except requests.exceptions.ConnectionError as errc: 26 | print ("Error Connecting:",errc) 27 | except requests.exceptions.Timeout as errt: 28 | print ("Timeout Error:",errt) 29 | except requests.exceptions.RequestException as err: 30 | print ("Something went wrong",err) 31 | return None 32 | return response 33 | 34 | def get_plugins(filter: str = None, verified_for = None, category: str = None, provider: str = "plugnplai"): 35 | """Get list of plugin URLs from a provider. 36 | 37 | Args: 38 | filter (str, optional): Filter to apply. Options are "working" or "ChatGPT". Defaults to None. 39 | verified_for (str, optional): Filter to plugins verified for a framework. Options are "langchain" or "plugnplai". Defaults to None. 40 | category (str, optional): Category to filter for. Defaults to None. 41 | provider (str, optional): Provider to get plugins from. Options are "plugnplai" or "pluginso". Defaults to "plugnplai". 42 | 43 | Returns: 44 | list: List of plugin URLs. 45 | """ 46 | if provider == "plugnplai": 47 | base_url = "https://www.plugnplai.com/_functions" 48 | # Construct the endpoint URL based on the filter and category arguments 49 | if filter in ["working", "ChatGPT"]: 50 | url = f'{base_url.strip("/")}/getUrls/{filter}' 51 | elif verified_for in ["langchain", "plugnplai"]: 52 | url = f'{base_url.strip("/")}/getUrls/{verified_for}' 53 | elif category is not None: 54 | url = f'{base_url.strip("/")}/getCategoryUrls/{category}' 55 | else: 56 | url = f'{base_url.strip("/")}/getUrls' 57 | # Make the HTTP GET request 58 | response = make_request_get(url) 59 | # Check if the response status code is successful (200 OK) 60 | if response.status_code == 200: 61 | # Parse the JSON response and return the result 62 | return response.json() 63 | else: 64 | # Handle unsuccessful responses 65 | return f"An error occurred: {response.status_code} {response.reason}" 66 | elif provider == "pluginso": 67 | url = "https://plugin.so/api/plugins/list" 68 | response = make_request_get(url) 69 | if response.status_code == 200: 70 | # Parse the JSON response and return the result 71 | return [f"https://{entry['domain']}" for entry in response.json()] 72 | else: 73 | # Handle unsuccessful responses 74 | return f"An error occurred: {response.status_code} {response.reason}" 75 | 76 | def get_category_names(provider: str = "plugnplai"): 77 | """Get list of category names from a provider. 78 | 79 | Args: 80 | provider (str, optional): Provider to get category names from. Options are "plugnplai" or "pluginso". Defaults to "plugnplai". 81 | 82 | Returns: 83 | list: List of category names. 84 | """ 85 | if provider == "plugnplai": 86 | base_url = "https://www.plugnplai.com/_functions" 87 | url = f'{base_url.strip("/")}/categoryNames' 88 | # Make the HTTP GET request 89 | response = make_request_get(url) 90 | # Check if the response status code is successful (200 OK) 91 | if response.status_code == 200: 92 | # Parse the JSON response and return the result 93 | return response.json() 94 | else: 95 | # Handle unsuccessful responses 96 | return f"An error occurred: {response.status_code} {response.reason}" 97 | else: 98 | return "Provider not supported for this operation." 99 | 100 | # given a plugin url, get the ai-plugin.json manifest, in "/.well-known/ai-plugin.json" 101 | def get_plugin_manifest(url: str): 102 | """Get plugin manifest from URL. 103 | 104 | Args: 105 | url (str): Plugin URL. 106 | 107 | Returns: 108 | dict: Plugin manifest. 109 | """ 110 | urlJson = os.path.join(url, ".well-known/ai-plugin.json") 111 | response = make_request_get(urlJson) 112 | return response.json() 113 | 114 | def _is_partial_url(url, openapi_url): 115 | """Check if OpenAPI URL is partial. 116 | 117 | Args: 118 | url (str): Base URL. 119 | openapi_url (str): OpenAPI URL. 120 | 121 | Returns: 122 | str: Full OpenAPI URL. 123 | """ 124 | if openapi_url.startswith("/"): 125 | # remove slash in the end of url if present 126 | url = url.strip("/") 127 | openapi_url = url + openapi_url 128 | elif "localhost" in openapi_url: 129 | openapi_url = "/"+openapi_url.split("/")[-1] 130 | return _is_partial_url(url, openapi_url) 131 | return openapi_url 132 | 133 | def get_openapi_url(url, manifest): 134 | """Get full OpenAPI URL from plugin URL and manifest. 135 | 136 | Args: 137 | url (str): Plugin URL. 138 | manifest (dict): Plugin manifest. 139 | 140 | Returns: 141 | str: Full OpenAPI URL. 142 | """ 143 | openapi_url = manifest["api"]["url"] 144 | return _is_partial_url(url, openapi_url) 145 | 146 | # This code uses the following source: https://github.com/hwchase17/langchain/blob/master/langchain/tools/plugin.py 147 | def marshal_spec(txt: str) -> dict: 148 | """Convert YAML or JSON serialized spec to dict. 149 | 150 | Args: 151 | txt (str): YAML or JSON serialized spec. 152 | 153 | Returns: 154 | dict: Spec as a dict. 155 | """ 156 | try: 157 | return json.loads(txt) 158 | except json.JSONDecodeError: 159 | return yaml.safe_load(txt) 160 | 161 | 162 | def get_openapi_spec(openapi_url): 163 | """Get OpenAPI spec from URL. 164 | 165 | Args: 166 | openapi_url (str): OpenAPI URL. 167 | 168 | Returns: 169 | dict: OpenAPI spec. 170 | """ 171 | openapi_spec_str = make_request_get(openapi_url, timeout=20).text 172 | openapi_spec = marshal_spec(openapi_spec_str) 173 | # Use jsonref to resolve references 174 | resolved_openapi_spec = jsonref.JsonRef.replace_refs(openapi_spec) 175 | return resolved_openapi_spec 176 | 177 | 178 | def spec_from_url(url): 179 | """Get plugin manifest and OpenAPI spec from URL. 180 | 181 | Args: 182 | url (str): Plugin URL. 183 | 184 | Returns: 185 | dict: Plugin manifest. 186 | dict: OpenAPI spec. 187 | """ 188 | manifest = get_plugin_manifest(url) 189 | openapi_url = get_openapi_url(url, manifest) 190 | openapi_spec = get_openapi_spec(openapi_url) 191 | return manifest, openapi_spec 192 | 193 | 194 | def extract_parameters(openapi_spec, path, method): 195 | """Extract parameters from OpenAPI spec for a path and method. 196 | 197 | Args: 198 | openapi_spec (dict): OpenAPI spec. 199 | path (str): Path. 200 | method (str): Method. 201 | 202 | Returns: 203 | dict: Parameters. 204 | """ 205 | parameters = {} 206 | 207 | # Extract path parameters and query parameters 208 | if "parameters" in openapi_spec["paths"][path][method]: 209 | for param in openapi_spec["paths"][path][method]["parameters"]: 210 | param_name = param["name"] 211 | param_type = param["in"] # e.g., 'path', 'query', 'header' 212 | parameters[param_name] = {"type": param_type, "schema": param["schema"]} 213 | 214 | # Extract request body properties 215 | if "requestBody" in openapi_spec["paths"][path][method]: 216 | content = openapi_spec["paths"][path][method]["requestBody"]["content"] 217 | if "application/json" in content: 218 | json_schema = content["application/json"]["schema"] 219 | if "properties" in json_schema: 220 | for prop_name, prop_schema in json_schema["properties"].items(): 221 | parameters[prop_name] = {"type": "body", "schema": prop_schema} 222 | 223 | return parameters 224 | 225 | 226 | def extract_all_parameters(openapi_spec): 227 | """Extract all parameters from OpenAPI spec. 228 | 229 | Args: 230 | openapi_spec (dict): OpenAPI spec. 231 | 232 | Returns: 233 | dict: All parameters. 234 | """ 235 | all_parameters = {} 236 | 237 | # Mapping of long type names to short names 238 | type_shorteners = { 239 | "string": "str", 240 | "integer": "int", 241 | "boolean": "bool", 242 | "number": "num", 243 | "array": "arr", 244 | "object": "obj", 245 | } 246 | 247 | # Iterate over all paths in the specification 248 | for path, path_item in openapi_spec["paths"].items(): 249 | # Iterate over all methods (e.g., 'get', 'post', 'put') in the path item 250 | for method, operation in path_item.items(): 251 | # Skip non-method keys such as 'parameters' that can be present in the path item 252 | if method not in [ 253 | "get", 254 | "post", 255 | "put", 256 | "delete", 257 | "patch", 258 | "options", 259 | "head", 260 | "trace", 261 | ]: 262 | continue 263 | 264 | # Extract the operation ID 265 | operation_id = operation.get("operationId", f"{method}_{path}") 266 | 267 | # Extract the summary, or use an empty string if it doesn't exist 268 | summary = operation.get("summary", "") 269 | 270 | # Extract parameters for the current operation 271 | parameters = extract_parameters(openapi_spec, path, method) 272 | 273 | # Shorten the types in the parameters dictionary 274 | for param_info in parameters.values(): 275 | param_type = param_info["schema"].get("type") 276 | if param_type in type_shorteners: 277 | param_info["schema"]["type"] = type_shorteners[param_type] 278 | 279 | # Add the extracted information to the dictionary with the operation ID as the key 280 | all_parameters[operation_id] = { 281 | "summary": summary, 282 | "path": path, 283 | "method": method, 284 | "parameters": parameters, 285 | } 286 | 287 | return all_parameters 288 | 289 | def parse_llm_response(response: str) -> dict: 290 | """Parse LLM response to extract API call information. 291 | 292 | Args: 293 | response (str): LLM response. 294 | 295 | Returns: 296 | dict: API call information. 297 | """ 298 | pattern = r'\s*(.*?)\s*\((.*?)\)\s*' 299 | match = re.search(pattern, response, re.DOTALL) 300 | 301 | if not match: 302 | return {} 303 | 304 | api = match.group(1) 305 | params_str = match.group(2) 306 | 307 | try: 308 | # Try parsing as JSON first 309 | params = json.loads(params_str) 310 | except json.JSONDecodeError: 311 | try: 312 | # If that fails, try parsing as a Python literal expression 313 | params = ast.literal_eval(params_str) 314 | except (ValueError, SyntaxError): 315 | params = {} 316 | 317 | return { 318 | 'plugin_name': api.split('.')[0], 319 | 'operation_id': api.split('.')[1], 320 | 'parameters': params 321 | } 322 | -------------------------------------------------------------------------------- /plugnplai/verify_plugins/__init__.py: -------------------------------------------------------------------------------- 1 | from plugnplai.verify_plugins.verify_plugnplai import test_one_plugin_plugnplai, test_plugins_plugnplai 2 | from plugnplai.verify_plugins.verify_langchain import test_one_plugin_langchain 3 | 4 | __all__ = [ 5 | "test_one_plugin_plugnplai", 6 | "test_plugins_plugnplai", 7 | "test_one_plugin_langchain", 8 | "test_plugins_langchain", 9 | ] -------------------------------------------------------------------------------- /plugnplai/verify_plugins/verify_for_plugnplai.csv: -------------------------------------------------------------------------------- 1 | ,user_msg,api_call,example 2 | https://api.speak.com,How do I say 'Where is the nearest pharmacy?' in Italian?,"speak.translate({'phrase_to_translate': 'Where is the nearest pharmacy?', 'learning_language': 'Italian', 'native_language': 'English', 'additional_context': '', 'full_query': 'How do I say \""Where is the nearest pharmacy?\"" in Italian?'})", 3 | https://wolframcloud.com,What is the integral of x * cos^3(x)?,WolframAlpha.getShortAnswer({'i': 'integral of x * cos^3(x)'}), 4 | https://wolframalpha.com,What is the distance between Earth and Mars?,Wolfram.getWolframAlphaResults({'input': 'distance between Earth and Mars'}), 5 | https://klarna.com,Find me 3 laptops under $1000,"KlarnaProducts.productsUsingGET({'q': 'laptop', 'size': 3, 'max_price': 1000})", 6 | https://nla.zapier.com,Preview a Zap that sends an email when a new lead is added to my CRM,"Zapier.preview_a_zap({""description_of_zap"": ""sends an email when a new lead is added to my CRM""})", 7 | https://server.shop.app,I'm looking for a pair of running shoes under $150.,"Shop.search({'query': 'running shoes', 'price_max': 150})", 8 | https://joinmilo.com,What's a magic moment I can create with my family today?,Milo.askMilo({'query': 'magic moment for family today'}), 9 | https://api.getguru.com,Find information about employee benefits in our company wiki,"guru.get_querySearch({'searchTerms': 'employee benefits', 'maxResults': 5})", 10 | https://biztoc.com,Find me the latest news about Tesla.,biztoc.getNews({'query': 'Tesla'}), 11 | https://chat-calculator-plugin.supportmirage.repl.co,Calculate the square root of 81,calculator.sqrt({'a': 81}), 12 | https://datasette.io,What is the total number of rows in the 'employees' table?,"datasette_datasette_io_3c330f.query({'sql': 'SELECT COUNT(*) FROM employees', '_shape': 'array'})", 13 | https://greenyroad.com,Please parse the content of these two URLs: https://example.com/article1 and https://example.com/article2,"url_reader.parseURLs({'urls': 'https://example.com/article1,https://example.com/article2'})", 14 | https://domainsg.pt,Check the availability of the domain 'examplestore.com' and compare prices for the '.com' TLD across registrars.,"owd.domainCheckUsingPOST({'domains': ['examplestore.com'], 'registrar': 'all'})owd.domainCompareUsingGET({'tld': 'com'})", 15 | https://quickchart.io,,,"{""user_msg"": ""Generate a QR code for the website https://www.example.com"", ""api_call"": ""quickchart.qrCode({""text"": ""https://www.example.com"", ""size"": 200})""}" 16 | https://urlbox.io,Take a screenshot of the website https://example.com in PNG format with a width of 1024 and a height of 768.,"screenshot.renderSync({'format': 'png', 'url': 'https://example.com', 'width': 1024, 'height': 768})", 17 | https://portfoliopilot.com,What are the top 3 ETFs with a focus on technology and a low expense ratio?,"portfoliopilot.getTopETFs({'philosophy': 'technology', 'is_diversified': false, 'max_expense_ratio': 0.1})", 18 | https://api.triplewhale.com,"What are the average benchmarks for Facebook ads in the electronics category with a high ad spend and high average order value between January 1, 2021, and December 31, 2021?","triple_whale_benchmarks.bi-avg-get({'dailyagg': False, 'platforms': 'Facebook', 'category': 'Electronics', 'aov_segment': 'High', 'spend_segment': 'High', 'start': '2021-01-01', 'end': '2021-12-31'})", 19 | https://api.tasty.co,Find me a delicious pasta recipe.,recipe_retrieval.food_query({'queries': ['delicious pasta']}), 20 | https://semgrep.dev,Please list the recent issues for deployment_slug 'example-deployment' and project_name 'example-project',"Semgrep.semgrep_app.saas.handlers.issue.openapi_list_recent_issues({'deployment_slug': 'example-deployment', 'project_name': 'example-project'})", 21 | https://woxo.tech,Create a video about the history of computers in English.,"woxo.createVideo({'topic': 'history of computers', 'languageCode': 'en'})", 22 | https://textbelt.com,,,"{""user_msg"": ""Send an SMS to 123-456-7890 with the message 'Hello, this is a test message from the LLM.'"", ""api_call"": ""textbelt.send({""phone"": ""123-456-7890"", ""message"": ""Hello, this is a test message from the LLM.""})""}" 23 | https://scenex.jina.ai,Please explain the content of this image: https://example.com/image.jpg,"SceneXplain.explainImage({'image': 'https://example.com/image.jpg', 'languages': ['en'], 'features': ['objects', 'colors', 'text']})", 24 | https://transvribe.com,What does the speaker say about the benefits of meditation at 5 minutes into this video? https://www.youtube.com/watch?v=example123,"Transvribe.ask({'url': 'https://www.youtube.com/watch?v=example123', 's': '5:00'})", 25 | https://websearch.plugsugar.com,Search for the best pizza places in New York City,web_search.searchGoogle({'query': 'best pizza places in New York City'}), 26 | https://sparkpost.com,"Send a message to channelId ABC123 and workspaceId DEF456 with the text 'Hello, World!'","messagebird.createChannelMessage({'channelId': 'ABC123', 'workspaceId': 'DEF456', 'body': {'type': 'text', 'content': 'Hello, World!'}})", 27 | https://appypie.com,Generate a text message using the AppyPieAIAppBuilder plugin.,"AppyPieAIAppBuilder.getText({'text': 'Hello, this is a test message.'})", 28 | https://app.asana.com,Create a task in Asana with the title 'Prepare presentation' and due date '2023-05-25',"Asana.createTask({'data': {'name': 'Prepare presentation', 'due_on': '2023-05-25'}})", 29 | https://mixerbox.com,Find me a playlist for working out,"MixerBox_OnePlayer_music.getPlaylistByType({'locale': 'en_US', 'type': 'workout'})", 30 | https://lu.ma,Get a list of upcoming events on Lu.ma with a limit of 5 events.,"luma_events.homeGetEvents({'pagination_cursor': '', 'pagination_limit': 5, 'period': 'upcoming'})", 31 | https://kalendar.ai,Show me my sales stats,KalendarAI.Stats({}), 32 | https://gh-plugin.teammait.com,Fetch the contents of the README.md file from the repository 'octocat/Hello-World',"github_repo_interaction.callOctokitMethod({""octokitMethod"": ""repos.getContent"", ""args"": {""owner"": ""octocat"", ""repo"": ""Hello-World"", ""path"": ""README.md""}})", 33 | https://datamuse.com,Find a word that means 'happy' and starts with the letter 'j',"onelook_thesaurus.getWords({'max': 100, 'ml': 'happy', 'sp': 'j*'})", 34 | https://remoteambition.com,"I'm looking for software engineering jobs near San Francisco, California.","Ambition.search({'query': 'software engineer', 'latitude': 37.7749, 'longitude': -122.4194, 'num_results': 5})", 35 | https://slack.com,Search for messages containing the word 'deadline' in Slack.,Slack.ai_alpha_search_messages({'query': 'deadline'}), 36 | https://stingray-app-9pild.ondigitalocean.app,Generate a persona based on the comments of the video with ID 'abc123xyz',yt_persona_generator.getVideoComments({'video_id': 'abc123xyz'}), 37 | https://yabblezone.net,I want to create a survey about electric cars and their impact on the environment.,"yabble.createSurveyAndFetchQuestions({'survey_topic': 'electric cars', 'additional_information': 'impact on the environment'})", 38 | https://polygon.io,What is the stock price of Apple Inc. (AAPL)?,polygon.SnapshotSummary({'ticker.any_of': 'AAPL'}), 39 | https://gpt.collov.com,I want to redesign my living room in a modern style. Here's the image URL: https://example.com/room_image.jpg,"Collov_Ai_Design.Collov Ai Design({'roomType': 'living room', 'style': 'modern', 'uploadUrl': 'https://example.com/room_image.jpg'})", 40 | https://api.yelp.com,Find me some Italian restaurants in New York City.,"yelp_business_search.v3_business_search({'term': 'Italian', 'location': 'New York City'})", 41 | https://coupert.com,Find me some coupon codes for Amazon.com,Coupert.storeUsingPost({'domain': 'amazon.com'}), 42 | https://oneword.domains,Check the availability of the domain 'examplestore.com' and compare prices for the '.com' TLD across registrars.,"owd.domainCheckUsingPOST({'domains': ['examplestore.com'], 'registrar': 'all'})owd.domainCompareUsingGET({'tld': 'com'})", 43 | https://telnyx.com,"Send a text message to +1234567890 from +0987654321 with the content 'Hello, this is a test message.'","TelnyxAPI._send_message_v2_openai_requests_v2_messages_post({'from': '+0987654321', 'to': '+1234567890', 'text': 'Hello, this is a test message.'})", 44 | https://trip.com,"Find me a round trip flight from New York City (JFK) to Los Angeles (LAX) departing on June 10th, 2023 and returning on June 20th, 2023.","Trip.search_flight_ticket({'originCityCode': 'JFK', 'destinationCityCode': 'LAX', 'departureDate': '2023-06-10', 'returnDate': '2023-06-20', 'locale': 'en', 'oneWayOrRoundTrip': 'RoundTrip'})", 45 | https://plugin.so,Find information about the domain example.com,"Plugin.so.query.search.getDomain({'domain': 'example.com', 'refresh': 'false'})", 46 | https://public-api.wordpress.com,Create a new blog post on my website with the title 'My Summer Vacation' and content 'I had an amazing time during my summer vacation. The beach was beautiful and the weather was perfect.',"a8c_wpcom.createPost({'site_id': 12345, 'title': 'My Summer Vacation', 'content': 'I had an amazing time during my summer vacation. The beach was beautiful and the weather was perfect.'})", 47 | https://baltimorehomecleaning.com,I would like to book a home cleaning service for May 25th at 2 PM.,"BHCHSC.bookHomeCleaningService({""date"": ""2023-05-25"", ""time"": ""14:00""})", 48 | https://twtdata.com,What are the stats for the Twitter account with the username 'elonmusk'?,twtData.com.getStats({'username': 'elonmusk'}), 49 | https://docsbot.ai,Find the top 3 documents related to machine learning in my library.,"DocsBot.Semantic_Search_teams__team_id__bots__bot_id__search_post({'team_id': '12345', 'bot_id': '67890', 'query': 'machine learning', 'top_k': 3})", 50 | https://savvytrader.com,Get the current stock price for Apple and Tesla.,"savvy_trader_ai.getQuotes({'stocks': ['AAPL', 'TSLA']})", 51 | https://pandia.pro,Obtenez les dernières actualités sur l'intelligence artificielle en français.,pandia.getLatestPosts({}), 52 | https://plugin.autoinfra.ai,Please run the 'top' command and show me the output.,AutoInfra1.runCommand({'command': 'top -b -n 1'}), 53 | https://infobot.ai,Create a wiki page about the ongoing Mars exploration mission.,infobot.createTopic({'topic_content': 'Mars exploration mission'}), 54 | https://gochitchat.ai,Can you please summarize the content of this link: https://example.com/article,linkReader.getContent({'url': 'https://example.com/article'}), 55 | https://polarr.co,I want a filter that can give my photo a vintage look.,polarr.get_gpt_plugin_search_ml_adjustment_assets_gpt_plugin_search_get({'prompt': 'vintage filter'}), 56 | https://dev.to,Find me some articles about machine learning on DEV Community.,"dev.getArticles({'q': 'machine learning', 'page': 1, 'per_page': 5, 'top': 'false'})", 57 | https://preview.techspecs.io,What are the specifications of the iPhone 13?,tech_specs.getTechSpecs({'query': 'iPhone 13'}), 58 | https://smyth.seo.app,"Please rewrite the content of this URL for better SEO using the keyphrase 'organic gardening tips', language 'en', country 'US', and location 'California': https://example.com/organic-gardening","SEO.rewriteForSEOFromURL({'keyphrase': 'organic gardening tips', 'lang': 'en', 'country': 'US', 'location': 'California', 'url': 'https://example.com/organic-gardening', '_context': 'Rewrite the content for better SEO'})", 59 | https://chat.noteable.io,Create a new notebook called 'My Example Notebook' in project '12345678-1234-5678-1234-567812345678',"noteable.create_notebook({'project_id': '12345678-1234-5678-1234-567812345678', 'notebook_name': 'My Example Notebook'})", 60 | https://decisionjournalapp.com,I want to create a decision about whether to buy a new car or not.,"decision_journal.create_decision({""data"": {""title"": ""Buy a new car"", ""description"": ""Deciding whether to buy a new car or not"", ""status"": ""open"", ""due_date"": ""2023-06-01""}})", 61 | https://app.reportdash.com,,,"{""user_msg"": ""Generate a report for my digital marketing channels from January 1, 2023, to May 20, 2023."", ""api_call"": ""ReportDash.generateReport({""start_date"": ""2023-01-01"", ""end_date"": ""2023-05-20""})""}" 62 | https://gogaffl.com,Find me some local guides in Paris,trips.getLocals({'location': 'Paris'}), 63 | https://haulingbuddies.com,"Find animal transporters near 123 Main Street, Springfield","haulingbuddies.searchTransportersByAddress({'address': '123 Main Street, Springfield'})", 64 | https://api.tomorrow.io,What's the weather like in New York today?,weather.handleWeatherQuestion({'question': 'What's the weather like in New York today?'}), 65 | https://wahi.com,"Find real estate listings in Toronto with 3 bedrooms, 2 bathrooms, and a maximum price of $800,000.","wahi.searchListings({'minBeds': 3, 'minBaths': 2, 'maxPrice': 800000, 'searchString': 'Toronto'})", 66 | https://pybullet.org,Add a new todo item: Buy groceries,"todo.addTodo({'username': 'example_user', 'todo': 'Buy groceries'})", 67 | https://audd.io,Can you identify the song in this video? Here's the URL: https://www.example.com/video and my API token is 1234567890abcdef.,"audd_song_id.identifyMusic({'api_token': '1234567890abcdef', 'url': 'https://www.example.com/video'})", 68 | https://avian.io,Retrieve my Google Analytics data for the last 30 days.,"avian.v1_google_analytics_four_data_retrieve({""start_date"": ""2023-04-21"", ""end_date"": ""2023-05-21""})", 69 | https://inkitt.com,Find me some stories about time travel,Inkitt.searchTitles({'q': 'time travel'}), 70 | https://lexi-shopping-assistant-chatgpt-plugin.iamnazzty.repl.co,I'm looking for a good book on machine learning. Can you recommend one?,"product_recommendation.getProductRecommendation({'userMessage': 'I\'m looking for a good book on machine learning. Can you recommend one?', 'searchPhrase': 'machine learning book', 'embeddingId': '12345', 'language': 'en', 'country': 'US'})", 71 | https://keyplays.malcsilberman.repl.co,Find the standings for the English Premier League,"keyplays_football.fetchStandings({'league_search': 'English Premier League', 'timezone': 'UTC'})", 72 | https://ndricks.com,Tell me about the latest news for the Los Angeles Lakers.,ndricks_sports_api.getTeamInfo({'team': 'Los Angeles Lakers'}), 73 | https://opentrivia.drengskapur.workers.dev,Give me 5 trivia questions with medium difficulty.,"opentrivia.getTriviaQuestions({'amount': 5, 'difficulty': 'medium'})", 74 | https://stage.glowing.ai,"I want to schedule daily motivational quotes to be sent to my phone at 8 am from May 22nd to June 1st. My name is John, my phone number is +1234567890, and my time zone is UTC-4.","Glowing.createSchedule({'phone_number': '+1234567890', 'name': 'John', 'local_time': '08:00', 'start_date': '2023-05-22', 'end_date': '2023-06-01', 'time_zone': 'UTC-4', 'messages': ['Daily motivational quote']})", 75 | https://plugin.speechki.org,"Convert this text to speech using speaker 1: 'Hello, how are you today?'","speechki_tts_plugin.tts({'speaker_id': 1, 'text': 'Hello, how are you today?'})", 76 | https://searchweb.keymate.ai,What are the latest advancements in electric vehicle technology?,internetSearch.searchGet({'q': 'latest advancements in electric vehicle technology'}), 77 | https://chatgpt.vipmanor.com,"Find me properties for sale in Los Angeles, CA and New York, NY with a minimum of 3 bedrooms and a maximum price of $1,000,000.","Manorlead.searchListings({'city_state_list': ['Los Angeles, CA', 'New York, NY'], 'lease_or_sale': 'sale', 'min_bed': 3, 'max_price': 1000000})", 78 | https://word-sneak.jeevnayak.repl.co,Let's play a game of Word Sneak!,word_sneak.get_secret_words({}), 79 | https://chatwithpdf.sdan.io,Load this PDF and tell me the main points: https://example.com/sample.pdf,chatwithpdf.loadPdf({'pdf_url': 'https://example.com/sample.pdf'}), 80 | https://comicfinder.fly.dev,Find me a comic related to programming.,comic_finder.findcomic({'request': 'programming'}), 81 | https://api.getchange.io,Find nonprofits focused on environmental conservation in California.,"nonprofits.getNonprofits({'category': 'environmental conservation', 'state': 'California', 'limit': 3, 'page': 1})", 82 | https://jettel.de,Can you provide the full transcript of this YouTube video? The video ID is 12345abcde.,video_insights.GetFullTranscriptOperationYoutube({'video-id': '12345abcde'}), 83 | https://mbplayer.com,Find me a playlist for working out,"MixerBox_OnePlayer_music.getPlaylistByType({'locale': 'en_US', 'type': 'workout'})", 84 | https://metar.pluginai.ai,Get METAR weather data for JFK airport.,metar.getMetar({'icao': 'KJFK'}), 85 | https://xyz-prompt-perfect.uc.r.appspot.com,perfect How can I make my writing better?,"rephrase.rephrasePrompt({'conversation_id': '12345', 'text': 'How can I make my writing better?'})", 86 | https://chatgpt-plugin.2u.com,Find me a course on machine learning.,edX.searchCourses({'query': 'machine learning'}), 87 | https://scholar-ai.net,Find me a paper about the effects of caffeine on cognitive function.,"scholarai.searchAbstracts({'subject': 'caffeine', 'phrase': ['cognitive function']})", 88 | https://chatgpt-plugin.prod.golden.dev,What is the current population of New York City?,"golden_data_plugin.semantic_disambiguate_api_semantic_disambiguate_post({'name': 'New York City', 'context': 'population', 'constraints': [], 'n_results': 1, 'allow_null_is_a': False})", 89 | https://bardeen.ai,Use the QueryMagicBox operation to find the weather in New York City.,Bardeen.QueryMagicBox({'query': 'weather in New York City'}), 90 | https://progressier.com,Create a PWA for my Webflow website.,progressier.newPwa({'platform': 'Webflow'}), 91 | https://labs.cactiml.com,What is the recommended treatment for type 2 diabetes?,MedQA.plugin({'query': 'recommended treatment for type 2 diabetes'}), 92 | https://screenshotone.com,Take a screenshot of the website https://example.com and save it as a PNG image.,"screenshots.takeScreenshot({'url': 'https://example.com', 'html': '', 'markdown': '', 'cache': False, 'response_type': 'image', 'format': 'png', 'dark_mode': False, 'viewport_width': 1280, 'viewport_height': 800, 'device_scale_factor': 1, 'block_cookie_banners': False, 'block_chats': False, 'block_ads': False, 'delay': 0})", 93 | https://portfolioslab.com,Please provide me with the historical performance of the stock with the symbol AAPL.,PortfoliosLab.getSymbol({'symbol': 'AAPL'}), 94 | https://retriever.openindex.ai,What are the main points of the Sarbanes-Oxley Act?,"retrieval.query_query_post({'queries': ['main points of the Sarbanes-Oxley Act'], 'namespace': 'financial_documents'})", 95 | https://buywisely.com.au,I'm looking for a laptop with good performance and battery life.,buywisely.getProductsFromOpenAI({'keyword': 'laptop'}), 96 | https://agones.gr,Find the latest match result between Manchester United and Chelsea,"Agones.getAgonesResults({'team1': 'Manchester United', 'team2': 'Chelsea', 'date_type': 'latest'})", 97 | https://aitoolhunt.com,Find AI tools for video editing,aitoolhunt.searchQuery({'search': 'video editing'}), 98 | https://chatspot.ai,Tell me about the company with the domain example.com,chatspot.chat({'message': 'Tell me about the company with the domain example.com'}), 99 | https://amazingtalker.co.kr,I want to find a Spanish teacher who can also speak English and is located in Spain.,"find_teachers.findTeachers({'teach_subject': 'Spanish', 'auxiliary_language': 'English', 'teacher_location': 'Spain'})", 100 | https://travelmyth.com,"Find me a romantic and beachfront hotel in Miami for 2 adults from June 10th, 2023 to June 15th, 2023.","travelmyth.fetchHotels({'destination': 'Miami', 'checkinDay': 10, 'checkinMonth': 6, 'checkinYear': 2023, 'checkoutDay': 15, 'checkoutMonth': 6, 'checkoutYear': 2023, 'adults': 2, 'children': 0, 'rooms': 1, 'categories': 'romantic,beachfront'})", 101 | https://tym.world,"Please get me a list of products with a parent product ID of 5, skipping the first 3 products, limiting the results to 10, ordered by name in ascending order, and with the culture set to 'en-US'.","tym.getProducts({'parent': 5, 'skip': 3, 'limit': 10, 'orderBy': 'name', 'order': 'asc', 'culture': 'en-US'})", 102 | https://alphavantage.co,use alpha_vantage to get the stock price of Microsoft,alpha_vantage.getStockPrice({'symbol': 'MSFT'}), 103 | -------------------------------------------------------------------------------- /plugnplai/verify_plugins/verify_langchain.py: -------------------------------------------------------------------------------- 1 | def test_one_plugin_langchain(): 2 | 3 | return True 4 | 5 | def test_plugins_langchain(): 6 | 7 | return True -------------------------------------------------------------------------------- /plugnplai/verify_plugins/verify_plugnplai.py: -------------------------------------------------------------------------------- 1 | from ..utils import get_plugin_manifest, get_openapi_url, get_openapi_spec 2 | from ..plugins import PluginObject, Plugins, count_tokens 3 | import json 4 | 5 | def test_one_plugin_plugnplai(url): 6 | # test get manifest (from {url}/.well-known/ai-plugin.json) 7 | try: 8 | manifest = get_plugin_manifest(url) 9 | except Exception as e: 10 | raise Exception("Error getting manifest from {url}: {e}".format(url=url, e=e)) 11 | 12 | # test get openapi url (from manifest) 13 | try: 14 | openapi_url = get_openapi_url(url, manifest) 15 | except Exception as e: 16 | raise Exception("Error getting openapi url from manifest: {e}".format(e=e)) 17 | 18 | # test get openapi spec (from {openapi_url}) 19 | try: 20 | openapi_spec = get_openapi_spec(openapi_url) 21 | except Exception as e: 22 | raise Exception("Error getting openapi spec from {openapi_url}: {e}".format(openapi_url=openapi_url, e=e)) 23 | 24 | # test PluginObject 25 | try: 26 | plugin = PluginObject(url, openapi_spec, manifest) 27 | except Exception as e: 28 | raise Exception("Error creating PluginObject: {e}".format(e=e)) 29 | 30 | # Test install Plugins 31 | try: 32 | plugins = Plugins([url]) 33 | except Exception as e: 34 | raise Exception("Error installing Plugins: {e}".format(e=e)) 35 | 36 | # Get the names of installed plugins 37 | try: 38 | names = plugins.list_installed() 39 | except Exception as e: 40 | raise Exception("Error retrieving list of active plugins: {e}".format(e=e)) 41 | 42 | # Activate the plugin 43 | try: 44 | plugins.activate(names[0]) 45 | error_activating = False 46 | except Exception as e: 47 | error_activating = True 48 | pass 49 | 50 | if error_activating: 51 | # test activate manually 52 | try: 53 | plugin_name = list(plugins.installed_plugins.keys())[0] 54 | plugin = plugins.installed_plugins.get(plugin_name) 55 | plugins.active_plugins[plugin_name] = plugin 56 | except Exception as e: 57 | raise Exception("Error activating plugin: {e}".format(e=e)) 58 | 59 | # test fill_prompt() 60 | try: 61 | prompt = plugins.fill_prompt(plugins.template) 62 | except Exception as e: 63 | raise Exception("Error filling prompt: {e}".format(e=e)) 64 | 65 | # test conunt tokens 66 | try: 67 | count = count_tokens(prompt) 68 | except Exception as e: 69 | raise Exception("Error counting tokens: {e}".format(e=e)) 70 | 71 | # test build_functions() 72 | try: 73 | functions = plugins.build_functions() 74 | except Exception as e: 75 | raise Exception("Error building functions: {e}".format(e=e)) 76 | 77 | # test loading functions with json library 78 | try: 79 | dict_functions = json.loads(functions) 80 | except Exception as e: 81 | raise Exception("Error loading functions with json library: {e}".format(e=e)) 82 | 83 | return True 84 | 85 | def test_plugins_plugnplai(urls): 86 | list_pass = [] 87 | list_fail = [] 88 | list_errors = [] 89 | for url in urls: 90 | try: 91 | r = test_one_plugin_plugnplai(url) 92 | if r: 93 | list_pass.append(url) 94 | except Exception as e: 95 | list_fail.append(url) 96 | list_errors.append(e) 97 | pass 98 | return list_pass, list_fail, list_errors -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "plugnplai" 3 | version = "0.0.24" 4 | description = "Connect plugins to AI" 5 | authors = [] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | requests = "^2.28.2" 12 | pyyaml = "^6.0" 13 | pydantic = "^1.10.7" 14 | jsonref = "^1.1.0" 15 | langchain = "^0.0.160" 16 | tiktoken = "^0.3.3" 17 | faiss-cpu = "^1.7.4" 18 | openai = "^0.27.6" 19 | 20 | [tool.poetry.group.dev.dependencies] 21 | black = "^23.3.0" 22 | ipykernel = "^6.23.2" 23 | 24 | [build-system] 25 | requires = ["poetry-core"] 26 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edreisMD/plugnplai/53b426865587848b088f1610e91f6ea90e9e7f87/tests/__init__.py --------------------------------------------------------------------------------