Skip to content

Commit

Permalink
Add support for structure config yaml file (#35)
Browse files Browse the repository at this point in the history
* Add support for structure config yaml file

* Clean up README

* Update structure config model

* Structure validation on create
  • Loading branch information
zachgiordano authored Jul 31, 2024
1 parent 7bb64ec commit ae0286d
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 97 deletions.
147 changes: 84 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,118 +6,139 @@
[![Griptape Discord](https://dcbadge.vercel.app/api/server/gnWRz88eym?compact=true&style=flat)](https://discord.gg/gnWRz88eym)

The Griptape CLI is a command-line interface for interacting with features of [Griptape Cloud](https://www.griptape.ai/cloud).
Today, it provides an emulator for Griptape Cloud Managed Structures, which allows you to run and test your Managed Structures locally.
Today, it provides an emulator for Griptape Cloud Managed Structures, which allows you to run and test your Managed Structures locally.

## Prerequisites

- A GitHub account.
- Python 3.11.
- [Poetry](https://python-poetry.org) for running the example client program.
- Linux operating system functionality. For Windows users, Griptape recommends the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/linux/install) for this functionality.

## Installation
## Installation

1. Install griptape-cli
```bash
pipx install griptape-cli
```

```bash
pipx install griptape-cli
```

You can also install a pre-release version from GitHub.
```bash
git clone https://github.com/griptape-ai/griptape-cli.git
```

```bash
pipx install ./griptape-cli --force
```
```bash
git clone https://github.com/griptape-ai/griptape-cli.git
```

```bash
pipx install ./griptape-cli --force
```

2. Verify installation
```bash
gt --help
```

```bash
gt --help
```

## Skatepark Emulator

You can use the CLI to spin up Skatepark, a local emulator for Griptape Cloud Managed Structures. It exposes an API that is identical to the one you would interact with when running your Managed Structure in Griptape Cloud.
Use Skatepark to develop, test, and validate that your program will operate as expected when deployed as a Griptape Cloud Managed Structure. Skatepark gives you confidence that when you bring your Structure into Griptape Cloud as a Managed Structure, it will continue to operate as expected, at scale.

1. Start by creating a new repository in your own Github account from the [Managed Structure Template](https://github.com/griptape-ai/managed-structure-template).
1. Make sure you're logged in to GitHub.
2. Go to the [Managed Structure Template repo](https://github.com/griptape-ai/managed-structure-template).
3. Select the *Use this template* drop-down.
4. Choose *Create a new repository*.
5. Provide a name for the new repository and (optionally) a description.
6. Press the *Create repository* button.
7. You now have a repository in your own GitHub account that is a copy of the Managed Structure Template for you to begin working with.
1. Make sure you're logged in to GitHub.
2. Go to the [Managed Structure Template repo](https://github.com/griptape-ai/managed-structure-template).
3. Select the *Use this template* drop-down.
4. Choose *Create a new repository*.
5. Provide a name for the new repository and (optionally) a description.
6. Press the *Create repository* button.
7. You now have a repository in your own GitHub account that is a copy of the Managed Structure Template for you to begin working with.
2. Clone your newly-created repository to a directory in your local development environment so that you can begin authoring your own Griptape Cloud Managed Structure.
3. Start Skatepark.
```bash
gt skatepark start
```

```bash
gt skatepark start
```

4. Open a new terminal window. Navigate to the directory with the locally-cloned repository.
5. Register a Structure with Skatepark.
```bash
gt skatepark register --main-file structure.py
```
This will result in the ID of the registered Structure. It is important to make note of this ID as it will be used to distinguish which Structure you want to run. You can register any number of Structures.

The example client program uses the environment variable `GT_STRUCTURE_ID` to determine which Structure to run.
Set this environment variable to the Structure ID you registered in the previous step.
```bash
export GT_STRUCTURE_ID={STRUCTURE_ID}
```
```bash
gt skatepark register --structure-config-file structure_config.yaml
```

Or you can register and set the environment variable in one step.
```bash
export GT_STRUCTURE_ID=$(gt skatepark register --main-file structure.py --tldr)
```
This will result in the ID of the registered Structure. It is important to make note of this ID as it will be used to distinguish which Structure you want to run. You can register any number of Structures.

The example client program uses the environment variable `GT_STRUCTURE_ID` to determine which Structure to run.
Set this environment variable to the Structure ID you registered in the previous step.

```bash
export GT_STRUCTURE_ID={STRUCTURE_ID}
```

Or you can register and set the environment variable in one step.

```bash
export GT_STRUCTURE_ID=$(gt skatepark register --structure-config-file structure_config.yaml --tldr)
```

> [!IMPORTANT]
> Structures registered with the Skatepark are not persisted across restarts. You will need to re-register the Structure each time you restart Skatepark.
> [!IMPORTANT]
> Structures registered with the Skatepark are not persisted across restarts. You will need to re-register the Structure each time you restart Skatepark.
6. Confirm that the Structure is registered.
```bash
gt skatepark list
```
You should see a list of registered Structures and the directories they point to, confirming that your Structure was properly registered
7. You can load environment variables into your Structure by creating an `.env` file in the directory of the Structure you registered.
1. Create a file named `.env` in the same directory that your Structure code is in.
2. Open the `.env` file in a text editor.
3. Copy the contents of `.env.structure.example` into the `.env` file. This specifies all of the values necessary and explains how they are used.
3. The template expects an `OPENAI_API_KEY` environment variable by default to function. Add OPENAI_API_KEY=_your OpenAI API Key here_ to the `.env` file and save it.
4. As you expand on the template, you may add any other environment variables your Structure depends on to this file.
8. Rebuild the structure to load in the new environment variable.
Note that this is only required for changes to `.env` or `requirements.txt`. Code changes do not require a rebuild.
```bash
gt skatepark build
```

```bash
gt skatepark list
```

You should see a list of registered Structures and the directories they point to, confirming that your Structure was properly registered

7. You can load environment variables into your Structure by creating an `.env` file in the directory of the Structure you registered.
1. Create a file named `.env` in the same directory that your Structure code is in.
2. Open the `.env` file in a text editor.
3. Copy the contents of `.env.structure.example` into the `.env` file. This specifies all of the values necessary and explains how they are used.
4. The template expects an `OPENAI_API_KEY` environment variable by default to function. Add OPENAI*API_KEY=\_your OpenAI API Key here* to the `.env` file and save it.
5. As you expand on the template, you may add any other environment variables your Structure depends on to this file.
8. Rebuild the structure to load in the new environment variable.
Note that this is only required for changes to `.env` or `requirements.txt`. Code changes do not require a rebuild.

```bash
gt skatepark build
```

9. Now that your Structure is registered and built, we want to be able to call it from within another program. The managed structure template offers an example client that can invoke your Structure. You have to configure the client with details on where to find the Structure in order for it to be called.
1. Create a file named `.env` in the `example-client` directory.
2. Open the `.env` file in a text editor
3. Copy the contents of `.env.client.example` (located within the `example-client/` directory) into the `.env` file. This specifies all of the values necessary and explains how they are used.
4. The client needs to know where the Structure is hosted, as specified in the `GT_CLOUD_BASE_URL` environment variable. When testing against the Skatepark emulator, we will use the default value address that Skatepark is running on. Set `GT_CLOUD_BASE_URL=http://127.0.0.1:5000`.
5. The client needs to know which Structure it is attempting to invoke, as specified in the `GT_STRUCTURE_ID` environment variable. This is the same value you got when you registered the Structure with Skatepark in step 5. You can quickly get this value by typing `gt skatepark list` to see the list of all registered Structures. Copy this value into GT_STRUCTURE_ID=_your Skatepark Structure ID here_.
6. Save the `.env` file.
1. Create a file named `.env` in the `example-client` directory.
2. Open the `.env` file in a text editor
3. Copy the contents of `.env.client.example` (located within the `example-client/` directory) into the `.env` file. This specifies all of the values necessary and explains how they are used.
4. The client needs to know where the Structure is hosted, as specified in the `GT_CLOUD_BASE_URL` environment variable. When testing against the Skatepark emulator, we will use the default value address that Skatepark is running on. Set `GT_CLOUD_BASE_URL=http://127.0.0.1:5000`.
5. The client needs to know which Structure it is attempting to invoke, as specified in the `GT_STRUCTURE_ID` environment variable. This is the same value you got when you registered the Structure with Skatepark in step 5. You can quickly get this value by typing `gt skatepark list` to see the list of all registered Structures. Copy this value into GT*STRUCTURE_ID=\_your Skatepark Structure ID here*.
6. Save the `.env` file.
10. The example client is configured, now you can use it to call Skatepark's API for running the Structure.

Navigate to the `example-client` directory.

```bash
cd example-client
```

Install the dependencies required by the example client program.

```bash
poetry install --no-root
```

Run the example client program.

```bash
poetry run python client.py
```

You should see the result of the Structure answering `What is 123 * 34, 23 / 12.3, and 9 ^ 4`, indicating a successful run.
You should see the result of the Structure answering `What is 123 * 34, 23 / 12.3, and 9 ^ 4`, indicating a successful run.

> [!IMPORTANT]
> The client program is an _example_ for how to interact with the Managed Structure's API. It is useful for testing your Managed Structure locally, but ultimately you will want to integrate your Managed Structure with your own application.
> The client program is an _example_ for how to interact with the Managed Structure's API. It is useful for testing your Managed Structure locally, but ultimately you will want to integrate your Managed Structure with your own application.
## Simulating Structure Run Delay
# Simulating Structure Run Delay
By default, Skatepark adds a 2 second delay before transitioniong Structure Runs from the `QUEUED` state to the `RUNNING` state.
If you want to change this delay in order to test the behavior of your Structure when it is in the `QUEUED` state, you can do so by setting the `GT_SKATEPARK_QUEUE_DELAY` environment variable.
For example, a value of `GT_SKATEPARK_QUEUE_DELAY=5` will cause Skatepark to wait 5 seconds before transitioning the Structure Run from `QUEUED` to `RUNNING`. Setting a value of `GT_SKATEPARK_QUEUE_DELAY=0` will cause Skatepark to transition the Structure Run immediately.
Expand Down
113 changes: 102 additions & 11 deletions griptapecli/commands/skatepark.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import functools
import os

import click
import functools
import uvicorn
import requests
import uvicorn

from griptapecli.core.models import Structure, StructureRun


def server_options(func):
Expand Down Expand Up @@ -39,10 +42,10 @@ def structure_options(func):
required=False,
)
@click.option(
"--main-file",
"--structure-config-file",
type=str,
help="Main file for the Structure",
default="structure.py",
help="Config file for the Structure",
default="structure_config.yaml",
required=False,
)
@functools.wraps(func)
Expand Down Expand Up @@ -82,26 +85,26 @@ def register(
host: str,
port: int,
directory: str,
main_file: str,
structure_config_file: str,
tldr: bool,
) -> None:
"""Registers a Structure with Skatepark."""
url = f"http://{host}:{port}/api/structures"
directory = os.path.abspath(directory)
if tldr is False:
click.echo(f"Registering Structure from {directory}/{main_file}")
click.echo(f"Registering Structure from {directory}/{structure_config_file}")
response = requests.post(
url,
json={
"directory": directory,
"main_file": main_file,
"structure_config_file": structure_config_file,
},
)

try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
click.echo(f"HTTP Error: {e}")
click.echo(f"HTTP Error: {e.response.json().get('detail')}")
return

structure_id = response.json()["structure_id"]
Expand Down Expand Up @@ -145,6 +148,94 @@ def build(
click.echo(f"Structure built: {structure_id}")


@skatepark.command(name="run")
@server_options
@click.option(
"--structure-id",
"-s",
type=str,
help="Id of the Structure to run",
required=True,
prompt=True,
default=lambda: os.environ.get("GT_STRUCTURE_ID", None),
show_default="GT_STRUCTURE_ID environment variable",
)
@click.option(
"--arg",
"-a",
type=str,
help="Argument to pass to the Structure Run",
required=False,
prompt=True,
multiple=True,
)
@click.option(
"--env",
"-e",
type=dict,
help="Environment Variables to pass to the Structure Run",
required=False,
prompt=False,
default=lambda: {},
)
def run(host: str, port: int, structure_id: str, arg: list[str], env: dict) -> None:
"""Runs the Structure."""
click.echo(f"Running Structure: {structure_id}")
url = f"http://{host}:{port}/api/structures/{structure_id}/runs"

response = requests.post(
url,
json={
"args": list(arg),
"env": env,
},
)
try:
response.raise_for_status()
run_id = response.json()["structure_run_id"]
run = _get_structure_run(host, port, run_id)
while run.status not in [
StructureRun.Status.SUCCEEDED,
StructureRun.Status.FAILED,
StructureRun.Status.CANCELLED,
]:
run = _get_structure_run(host, port, run.structure_run_id)

if run.status == StructureRun.Status.SUCCEEDED:
click.echo(f"Structure run succeeded: {run_id}, output: {run.output}")
elif run.status == StructureRun.Status.FAILED:
click.echo(f"Structure run failed: {run_id}, logs: {run.logs}")
except requests.exceptions.HTTPError as e:
click.echo(f"HTTP Error: {e}")
return


def _get_structure_run(
host: str,
port: int,
structure_run_id: str,
) -> StructureRun:
url = f"http://{host}:{port}/api/structure-runs/{structure_run_id}"
response = requests.get(url)
response.raise_for_status()

structure_run = response.json()
return StructureRun(**structure_run)


def _get_structure(
host: str,
port: int,
structure_id: str,
) -> Structure:
url = f"http://{host}:{port}/api/structures/{structure_id}"
response = requests.get(url)
response.raise_for_status()

structure = response.json()
return Structure(**structure)


@skatepark.command(name="list")
@server_options
def list_structures(
Expand All @@ -166,9 +257,9 @@ def list_structures(
for structure in structures:
structure_id = structure["structure_id"]
directory = structure["directory"]
main_file = structure["main_file"]
structure_config_file = structure["structure_config_file"]
env = structure["env"]
click.echo(f"{structure_id} -> {directory}/{main_file} ({env})")
click.echo(f"{structure_id} -> {directory}/{structure_config_file} ({env})")
else:
click.echo("No structures registered")

Expand Down
Loading

0 comments on commit ae0286d

Please sign in to comment.