diff --git a/README.md b/README.md index d3dd69e..1a4a263 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,47 @@ This API is similar to the Pytorch API, making it simple to adapt to your usecas ![Analogy with Torch](assets/analogy.png) + +### Updates: + +**29th Sept 2024**: + +We are introducing a new engine based on [litellm](https://github.com/BerriAI/litellm). This should allow +you to use any model you like, as long as it is supported by litellm. This means that now +**Bedrock, Together, Gemini and even more** are all supported by TextGrad! + +In addition to this, with the new engines it should be easy to enable and disable caching. + +We are in the process of testing these new engines and deprecating the old engines. If you have any issues, please let us know! + +The new litellm engines can be loaded with the following code: + +An example of loading a litellm engine: +```python +engine = get_engine("experimental:gpt-4o", cache=False) + +# this also works with + +set_backward_engine("experimental:gpt-4o", cache=False) +``` + +Be sure to set the relevant environment variables for the new engines! + +An example of forward pass: +```python + +import httpx +from textgrad.engine_experimental.litellm import LiteLLMEngine + +LiteLLMEngine("gpt-4o", cache=True).generate(content="hello, what's 3+4", system_prompt="you are an assistant") + +image_url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg" +image_data = httpx.get(image_url).content +``` + +In the examples folder you will find two new notebooks that show how to use the new engines. + + ## QuickStart If you know PyTorch, you know 80% of TextGrad. Let's walk through the key components with a simple example. Say we want to use GPT-4o to solve a simple @@ -96,7 +137,42 @@ answer > :white_check_mark: **answer: It will still take 1 hour to dry 30 shirts under the sun,** > **assuming they are all laid out properly to receive equal sunlight.** +### Updates: + +**29th Sept 2024**: + +We are introducing a new engine based on [litellm](https://github.com/BerriAI/litellm). This should allow +you to use any model you like, as long as it is supported by litellm. This means that now +**Bedrock, Together, Gemini and even more** are all supported by TextGrad! + +In addition to this, with the new engines it should be easy to enable and disable caching. + +We are in the process of testing these new engines and deprecating the old engines. If you have any issues, please let us know! +The new litellm engines can be loaded with the following code: + +An example of loading a litellm engine: +```python +engine = get_engine("experimental:gpt-4o", cache=False) + +# this also works with + +set_backward_engine("experimental:gpt-4o", cache=False) +``` + +An example of forward pass: +```python + +import httpx +from textgrad.engine_experimental.litellm import LiteLLMEngine + +LiteLLMEngine("gpt-4o", cache=True).generate(content="hello, what's 3+4", system_prompt="you are an assistant") + +image_url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg" +image_data = httpx.get(image_url).content +``` + +In the examples folder you will find two new notebooks that show how to use the new engines. We have many more examples around how TextGrad can optimize all kinds of variables -- code, solutions to problems, molecules, prompts, and all that! @@ -119,6 +195,8 @@ you need an OpenAI/Anthropic key to run the LLMs). + + ### Installation You can install TextGrad using any of the following methods. diff --git a/examples/notebooks/experimental_engines/Tutorial-ExperimentalEngines.ipynb b/examples/notebooks/experimental_engines/Tutorial-ExperimentalEngines.ipynb new file mode 100644 index 0000000..7c1e79b --- /dev/null +++ b/examples/notebooks/experimental_engines/Tutorial-ExperimentalEngines.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2661032c-2d9b-43f5-b074-28c119b57a14", + "metadata": {}, + "outputs": [], + "source": [ + "import textgrad\n", + "import os\n", + "from textgrad.engine_experimental.openai import OpenAIEngine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e74c489-c47e-413c-adae-cd1201f6f94f", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"OPENAI_API_KEY\"] = \"SOMETHING\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50c02746-dd33-4cb0-896e-4771e4b76ed7", + "metadata": {}, + "outputs": [], + "source": [ + "OpenAIEngine(\"gpt-4o-mini\", cache=True).generate(content=\"hello, what's 3+4\", system_prompt=\"you are an assistant\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0ac186e-5c34-4115-aeda-bbd301be2667", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b09f7e36-ae4f-4746-8548-c2c189827435", + "metadata": {}, + "outputs": [], + "source": [ + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ccce563-3d11-4d20-9c72-05d43cce4f6c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3053630a-dbf3-4d3e-b553-5c8ea73e2ccd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58db7bb4-7f0f-4517-bba2-60a51b85908b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ee586ac-473e-4807-b66d-8524d08dc236", + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "from textgrad.engine_experimental.litellm import LiteLLMEngine\n", + "\n", + "LiteLLMEngine(\"gpt-4o\", cache=True).generate(content=\"hello, what's 3+4\", system_prompt=\"you are an assistant\")\n", + "\n", + "image_url = \"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg\"\n", + "image_data = httpx.get(image_url).content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5b1f1d5-8971-4dee-9958-c708ba807921", + "metadata": {}, + "outputs": [], + "source": [ + "LiteLLMEngine(\"gpt-4o\", cache=True).generate(content=[image_data, \"what is this my boy\"], system_prompt=\"you are an assistant\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43d9b703-4488-4222-a7fb-773293c13514", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/notebooks/experimental_engines/Tutorial-PrimitivesExperimentalEngines.ipynb b/examples/notebooks/experimental_engines/Tutorial-PrimitivesExperimentalEngines.ipynb new file mode 100644 index 0000000..307a479 --- /dev/null +++ b/examples/notebooks/experimental_engines/Tutorial-PrimitivesExperimentalEngines.ipynb @@ -0,0 +1,426 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d66880e8-23d9-4f00-b120-7f289f682511", + "metadata": {}, + "source": [ + "# TextGrad Tutorials: Primitives\n", + "\n", + "![TextGrad](https://github.com/vinid/data/blob/master/logo_full.png?raw=true)\n", + "\n", + "An autograd engine -- for textual gradients!\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zou-group/TextGrad/blob/main/examples/notebooks/Prompt-Optimization.ipynb)\n", + "[![GitHub license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/)\n", + "[![Arxiv](https://img.shields.io/badge/arXiv-2406.07496-B31B1B.svg)](https://arxiv.org/abs/2406.07496)\n", + "[![Documentation Status](https://readthedocs.org/projects/textgrad/badge/?version=latest)](https://textgrad.readthedocs.io/en/latest/?badge=latest)\n", + "[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/textgrad)](https://pypi.org/project/textgrad/)\n", + "[![PyPI](https://img.shields.io/pypi/v/textgrad)](https://pypi.org/project/textgrad/)\n", + "\n", + "**Objectives for this tutorial:**\n", + "\n", + "* Introduce you to the primitives in TextGrad\n", + "\n", + "**Requirements:**\n", + "\n", + "* You need to have an OpenAI API key to run this tutorial. This should be set as an environment variable as OPENAI_API_KEY.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8149068-d7ef-4702-b5d1-ed0d7f1271fd", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install textgrad " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36a9c615-17a0-455c-8f9c-f0d25fb8824b", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:43:10.594204491Z", + "start_time": "2024-06-11T15:43:10.589328053Z" + } + }, + "outputs": [], + "source": [ + "# you might need to restart the notebook after installing textgrad\n", + "\n", + "from textgrad.engine import get_engine\n", + "from textgrad import Variable\n", + "from textgrad.optimizer import TextualGradientDescent\n", + "from textgrad.loss import TextLoss\n", + "from dotenv import load_dotenv\n", + "load_dotenv()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143e3cca-aa39-489d-b2e2-f3e19e4dad7e", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"OPENAI_API_KEY\"] = \"SOMETHING\"" + ] + }, + { + "cell_type": "markdown", + "id": "8887fbed36c7daf2", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introduction: Variable\n", + "\n", + "Variables in TextGrad are the metaphorical equivalent of tensors in PyTorch. They are the primary data structure that you will interact with when using TextGrad. \n", + "\n", + "Variables keep track of gradients and manage the data.\n", + "\n", + "Variables require two arguments (and there is an optional third one):\n", + "\n", + "1. `data`: The data that the variable will hold\n", + "2. `role_description`: A description of the role of the variable in the computation graph\n", + "3. `requires_grad`: (optional) A boolean flag that indicates whether the variable requires gradients" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c65fb4456d84c8fc", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:43:17.669096228Z", + "start_time": "2024-06-11T15:43:17.665325560Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "x = Variable(\"A sntence with a typo\", role_description=\"The input sentence\", requires_grad=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65857dd50408ebd7", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:43:18.184004948Z", + "start_time": "2024-06-11T15:43:18.178187640Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "x.gradients" + ] + }, + { + "cell_type": "markdown", + "id": "63f6a6921a1cce6a", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introduction: Engine\n", + "\n", + "When we talk about the engine in TextGrad, we are referring to an LLM. The engine is an abstraction we use to interact with the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "281644022ac1c65d", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:44:32.606319032Z", + "start_time": "2024-06-11T15:44:32.561460448Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "engine = get_engine(\"experimental:gpt-4o\", cache=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d61c58c-2cc1-4482-8962-e430632cf5f8", + "metadata": {}, + "outputs": [], + "source": [ + "engine.generate(content=\"hello, what's 3+4\", system_prompt=\"you are an assistant\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e41d873-a3b4-4022-b180-bb4bc13b32f7", + "metadata": {}, + "outputs": [], + "source": [ + "import litellm\n", + "litellm.set_verbose=True" + ] + }, + { + "cell_type": "markdown", + "id": "33c7d6eaa115cd6a", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "This object behaves like you would expect an LLM to behave: You can sample generation from the engine using the `generate` method. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37502bf67ef23c53", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T17:29:41.108552705Z", + "start_time": "2024-06-11T17:29:40.294256814Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "engine.generate(\"Hello how are you?\")" + ] + }, + { + "cell_type": "markdown", + "id": "b627edc07c0d3737", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introduction: Loss\n", + "\n", + "Again, Loss in TextGrad is the metaphorical equivalent of loss in PyTorch. We use Losses in different form in TextGrad but for now we will focus on a simple TextLoss. TextLoss is going to evaluate the loss wrt a string." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "252e0a0152b81f14", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:44:32.894722136Z", + "start_time": "2024-06-11T15:44:32.890708561Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "system_prompt = Variable(\"Evaluate the correctness of this sentence\", role_description=\"The system prompt\")\n", + "loss = TextLoss(system_prompt, engine=engine)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8865cb0d-bab5-4695-9cee-5fc939a8decc", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "6f05ec2bf907b3ba", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introduction: Optimizer\n", + "\n", + "Keeping on the analogy with PyTorch, the optimizer in TextGrad is the object that will update the parameters of the model. In this case, the parameters are the variables that have `requires_grad` set to `True`.\n", + "\n", + "**NOTE** This is a text optimizer! It will do all operations with text! " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78f93f80b9e3ad36", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:44:33.741130951Z", + "start_time": "2024-06-11T15:44:33.734977769Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "optimizer = TextualGradientDescent(parameters=[x], engine=engine)\n" + ] + }, + { + "cell_type": "markdown", + "id": "d26883eb74ce0d01", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Putting it all together\n", + "\n", + "We can now put all the pieces together. We have a variable, an engine, a loss, and an optimizer. We can now run a single optimization step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9817e0ae0179376d", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:44:41.730132530Z", + "start_time": "2024-06-11T15:44:34.997777872Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "l = loss(x)\n", + "l.backward(engine)\n", + "optimizer.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77e3fab0efdd579e", + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-11T15:44:41.738985151Z", + "start_time": "2024-06-11T15:44:41.731989729Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "x.value" + ] + }, + { + "cell_type": "markdown", + "id": "6a8aab93b80fb82c", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "While here it is not going to be useful, we can also do multiple optimization steps in a loop! Do not forget to reset the gradients after each step!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6bb8d0dcc2539d1", + "metadata": { + "ExecuteTime": { + "start_time": "2024-06-11T15:44:30.989940227Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "optimizer.zero_grad()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3a84aad4cd58737", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/textgrad/config.py b/textgrad/config.py index 1b01388..0684488 100644 --- a/textgrad/config.py +++ b/textgrad/config.py @@ -42,10 +42,10 @@ def get_engine(self): """ return self.engine -def set_backward_engine(engine: Union[EngineLM, str], override: bool = False): +def set_backward_engine(engine: Union[EngineLM, str], override: bool = False, **kwargs): singleton_backward_engine = SingletonBackwardEngine() if isinstance(engine, str): - engine = get_engine(engine) + engine = get_engine(engine, **kwargs) singleton_backward_engine.set_engine(engine, override=override) diff --git a/textgrad/engine/__init__.py b/textgrad/engine/__init__.py index 3e0ee24..ea7c6c1 100644 --- a/textgrad/engine/__init__.py +++ b/textgrad/engine/__init__.py @@ -1,4 +1,5 @@ from .base import EngineLM, CachedEngine +from textgrad.engine_experimental.litellm import LiteLLMEngine __ENGINE_NAME_SHORTCUTS__ = { "opus": "claude-3-opus-20240229", @@ -34,6 +35,10 @@ def get_engine(engine_name: str, **kwargs) -> EngineLM: if "seed" in kwargs and "gpt-4" not in engine_name and "gpt-3.5" not in engine_name and "gpt-35" not in engine_name: raise ValueError(f"Seed is currently supported only for OpenAI engines, not {engine_name}") + # check if engine_name starts with "experimental:" + if engine_name.startswith("experimental:"): + engine_name = engine_name.split("experimental:")[1] + return LiteLLMEngine(model_string=engine_name, **kwargs) if engine_name.startswith("azure"): from .openai import AzureChatOpenAI # remove engine_name "azure-" prefix diff --git a/textgrad/engine_experimental/base.py b/textgrad/engine_experimental/base.py index 3203a85..79c9da1 100644 --- a/textgrad/engine_experimental/base.py +++ b/textgrad/engine_experimental/base.py @@ -64,7 +64,7 @@ def _generate_from_multiple_input(self, prompt, system_prompt=None, **kwargs) -> def _generate_from_single_prompt(self, prompt, system_prompt=None, **kwargs) -> str: pass - def generate(self, content, system_prompt=Union[str | List[Union[str, bytes]]], **kwargs): + def generate(self, content, system_prompt: Union[str | List[Union[str, bytes]]] = None, **kwargs): sys_prompt_arg = system_prompt if system_prompt else self.system_prompt if isinstance(content, str):