Skip to content

Commit

Permalink
Add AWS Tools
Browse files Browse the repository at this point in the history
  • Loading branch information
collindutter committed Nov 19, 2024
1 parent fd3d8be commit fa4a01d
Show file tree
Hide file tree
Showing 28 changed files with 1,294 additions and 179 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.aider*
147 changes: 18 additions & 129 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,143 +1,32 @@
# Griptape Extension Template
# Griptape AWS Extension

A Github template repository for creating Griptape extensions.
## Overview
This extension provides several [Tools](https://docs.griptape.ai/stable/griptape-tools/) for [AWS](https://aws.amazon.com/).

## Getting Started
```python
import boto3

Via github web page:
from griptape.structures import Agent
from griptape.tools import AwsS3Tool

Click on `Use this template`
# Initialize the AWS S3 client
aws_s3_client = AwsS3Tool(session=boto3.Session(), off_prompt=True)

![](https://docs.github.com/assets/cb-36544/images/help/repository/use-this-template-button.png)
# Create an agent with the AWS S3 client tool
agent = Agent(tools=[aws_s3_client])


Via `gh`:

```
$ gh repo create griptape-extension-name -p griptape/griptape-extension-template
```

## What is a Griptape Extension?

Griptape Extensions can add new functionality to the [Griptape framework](https://github.com/griptape-ai/griptape), such as new Tools, Drivers, Tasks, or Structures.
With extensions, you can integrate custom APIs, tools, and services into the Griptape ecosystem.

This repository provides a recommended structure for organizing your extension code, as well as helpful tools for testing and development.

## Extension Structure

The template repository is structured as follows:

```bash
tree -I __init__.py -I __pycache__

├── griptape
│ └── extension_name # Name whatever you want
│ └── tools
│ └── reverse_string
│ └── tool.py
...more directories for other interfaces (drivers, tasks, structures, etc)...
└── tests
└── unit
└── tools
└── test_reverse_string_tool.py
├── examples
└── tools
└── example_agent.py # Example usage of the extension
├── LICENSE # Choose the appropriate license
├── Makefile # Contains useful commands for development
├── pyproject.toml # Contains the project's metadata
├── README.md # Describes the extension and how to use it
```

## Development

### Poetry

This project uses [Poetry](https://python-poetry.org/) for dependency management.
It is recommended to configure Poetry to use [in-project](https://python-poetry.org/docs/configuration/#virtualenvsin-project) virtual environments:

```bash
poetry config virtualenvs.in-project true
```

This will create a `.venv` directory in the project root, where the virtual environment will be stored.
This ensures that the virtual environment is always in the same location, regardless of where the project is cloned.

### Useful Commands

#### Installing Dependencies

```bash
make install
# Task to list all the AWS S3 buckets
agent.run("List all my S3 buckets.")
```

#### Running Tests
## Installation

Poetry:
```bash
make test
poetry add https://github.com/griptape-ai/griptape-aws.git
```

#### Running Checks (linting, formatting, etc)

```bash
make check
```

#### Running Formatter

```bash
make format
```

#### Running Example

This template includes an [example](https://github.com/griptape-ai/tool-template/blob/main/examples/tools/example_agent.py) demonstrating how to use the extension. It shows how to import the `ReverseStringTool`, provide it to an Agent, and run it.

1. Set the required environment variables. The example needs the `OPENAI_API_KEY` environment variable to be set.
2. Run the example:

```bash
poetry run python examples/tools/example_agent.py
```

If successful, you should see:
```
[11/18/24 14:55:14] INFO ToolkitTask 6bb7fa5581d147b2a39e801631c98005
Input: Use the ReverseStringTool to reverse 'Griptape'
[11/18/24 14:55:15] INFO Subtask c3036471831144529b8d5300c6849203
Actions: [
{
"tag": "call_VE4tGBFL7iB7VDbkKaIFIkwY",
"name": "ReverseStringTool",
"path": "reverse_string",
"input": {
"values": {
"input": "Griptape"
}
}
}
]
INFO Subtask c3036471831144529b8d5300c6849203
Response: epatpirG
[11/18/24 14:55:16] INFO ToolkitTask 6bb7fa5581d147b2a39e801631c98005
Output: The reversed string of "Griptape" is "epatpirG".
```

## Installing in Other Projects

Extensions are designed to be shared. Extensions are made to easily install into existing Python projects.

The easiest way to include your extension into an existing project is to install directly from the repository, like so:
```bash
poetry add git+https://github.com/{your-org}/{your-extension-name}.git
```

To install a local copy of the extension for development, run:
Pip:
```bash
poetry add -e /path/to/your/extension
pip install git+https://github.com/griptape-ai/griptape-aws.git
```

Any changes made to the extension will be automatically reflected in the project without needing to reinstall it.

Advanced customers may seek to publish their extensions to PyPi. Those instructions are beyond the scope of this README.
13 changes: 13 additions & 0 deletions examples/tools/aws_iam_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import boto3

from griptape.structures import Agent
from griptape.tools import AwsIamTool

# Initialize the AWS IAM client
aws_iam_client = AwsIamTool(session=boto3.Session())

# Create an agent with the AWS IAM client tool
agent = Agent(tools=[aws_iam_client])

# Run the agent with a high-level task
agent.run("List all my IAM users")
13 changes: 13 additions & 0 deletions examples/tools/aws_s3_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import boto3

from griptape.structures import Agent
from griptape.tools import AwsS3Tool

# Initialize the AWS S3 client
aws_s3_client = AwsS3Tool(session=boto3.Session(), off_prompt=True)

# Create an agent with the AWS S3 client tool
agent = Agent(tools=[aws_s3_client])

# Task to list all the AWS S3 buckets
agent.run("List all my S3 buckets.")
7 changes: 0 additions & 7 deletions examples/tools/example_agent.py

This file was deleted.

File renamed without changes.
7 changes: 7 additions & 0 deletions griptape/aws/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .base_aws_tool import BaseAwsTool
from .aws_cli.tool import AwsCliTool
from .aws_iam.tool import AwsIamTool
from .aws_pricing.tool import AwsPricingTool
from .aws_s3.tool import AwsS3Tool

__all__ = ["BaseAwsTool", "AwsCliTool", "AwsIamTool", "AwsPricingTool", "AwsS3Tool"]
File renamed without changes.
5 changes: 5 additions & 0 deletions griptape/aws/tools/aws_cli/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "v1"
name: AWS CLI
description: Tool for accessing AWS CLI v2 commands.
contact_email: [email protected]
legal_info_url: https://www.griptape.ai/legal
1 change: 1 addition & 0 deletions griptape/aws/tools/aws_cli/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
git+https://github.com/aws/[email protected]#egg=awscli
49 changes: 49 additions & 0 deletions griptape/aws/tools/aws_cli/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from griptape.artifacts import BaseArtifact, TextArtifact, ErrorArtifact
from schema import Schema, Literal
from griptape.utils.decorators import activity
from griptape.utils import minify_json, CommandRunner
from griptape.aws.tools import BaseAwsTool
from attr import define, field


@define
class AwsCliTool(BaseAwsTool):
aws_cli_policy: str = field(
default="""{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}""",
kw_only=True,
)

@property
def schema_template_args(self) -> dict:
return {"policy": minify_json(self.aws_cli_policy)}

@activity(
config={
"description": "Can be used to execute AWS CLI v2 commands limited by this policy: {{ _self.schema_template_args['policy'] }}",
"schema": Schema(
{
Literal(
"command", description="AWS CLI v2 command starting with 'aws'"
): str
}
),
}
)
def execute(self, params: dict) -> BaseArtifact:
command = params["values"]["command"]
result = CommandRunner().run(f"AWS_PAGER='' {command} --output json")

if isinstance(result, ErrorArtifact):
return result
else:
value = result.value

if value == "":
final_result = "[]"
else:
try:
final_result = minify_json(value)
except Exception:
final_result = value

return TextArtifact(final_result)
Empty file.
101 changes: 101 additions & 0 deletions griptape/aws/tools/aws_iam/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from attrs import define, field
from schema import Literal, Schema

from griptape.artifacts import ErrorArtifact, ListArtifact, TextArtifact
from griptape.aws.tools import BaseAwsTool
from griptape.utils.decorators import activity, lazy_property

if TYPE_CHECKING:
from mypy_boto3_iam import IAMClient


@define
class AwsIamTool(BaseAwsTool):
_client: IAMClient = field(
default=None, kw_only=True, alias="client", metadata={"serializable": False}
)

@lazy_property()
def client(self) -> IAMClient:
return self.session.client("iam")

@activity(
config={
"description": "Can be use to get a policy for an AWS IAM user.",
"schema": Schema(
{
Literal(
"user_name", description="Username of the AWS IAM user."
): str,
Literal(
"policy_name",
description="PolicyName of the AWS IAM Policy embedded in the specified IAM user.",
): str,
},
),
},
)
def get_user_policy(self, params: dict) -> TextArtifact | ErrorArtifact:
try:
policy = self.client.get_user_policy(
UserName=params["values"]["user_name"],
PolicyName=params["values"]["policy_name"],
)
return TextArtifact(policy["PolicyDocument"])
except Exception as e:
return ErrorArtifact(f"error returning policy document: {e}")

@activity(config={"description": "Can be used to list AWS MFA Devices"})
def list_mfa_devices(self) -> ListArtifact | ErrorArtifact:
try:
devices = self.client.list_mfa_devices()
return ListArtifact([TextArtifact(str(d)) for d in devices["MFADevices"]])
except Exception as e:
return ErrorArtifact(f"error listing mfa devices: {e}")

@activity(
config={
"description": "Can be used to list policies for a given IAM user.",
"schema": Schema(
{
Literal(
"user_name",
description="Username of the AWS IAM user for which to list policies.",
): str
},
),
},
)
def list_user_policies(self, params: dict) -> ListArtifact | ErrorArtifact:
try:
policies = self.client.list_user_policies(
UserName=params["values"]["user_name"]
)
policy_names = policies["PolicyNames"]

attached_policies = self.client.list_attached_user_policies(
UserName=params["values"]["user_name"]
)
attached_policy_names = [
p["PolicyName"]
for p in attached_policies["AttachedPolicies"]
if "PolicyName" in p
]

return ListArtifact(
[TextArtifact(str(p)) for p in policy_names + attached_policy_names]
)
except Exception as e:
return ErrorArtifact(f"error listing iam user policies: {e}")

@activity(config={"description": "Can be used to list AWS IAM users."})
def list_users(self) -> ListArtifact | ErrorArtifact:
try:
users = self.client.list_users()
return ListArtifact([TextArtifact(str(u)) for u in users["Users"]])
except Exception as e:
return ErrorArtifact(f"error listing s3 users: {e}")
Empty file.
5 changes: 5 additions & 0 deletions griptape/aws/tools/aws_pricing/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "v1"
name: AWS Pricing Tool
description: Tool for getting current pricing information from AWS.
contact_email: [email protected]
legal_info_url: https://www.griptape.ai/legal
1 change: 1 addition & 0 deletions griptape/aws/tools/aws_pricing/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
boto3
Loading

0 comments on commit fa4a01d

Please sign in to comment.