Skip to content

Commit

Permalink
Copy over google tools
Browse files Browse the repository at this point in the history
  • Loading branch information
collindutter committed Nov 19, 2024
1 parent 9a51a3c commit aacf0ed
Show file tree
Hide file tree
Showing 31 changed files with 1,397 additions and 176 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*
160 changes: 32 additions & 128 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,143 +1,47 @@
# Griptape Extension Template
# Griptape Google Extension

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

## Getting Started
```python
import os

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

Click on `Use this template`
gmail_tool = GoogleGmailTool(
service_account_credentials={
"type": os.environ["GOOGLE_ACCOUNT_TYPE"],
"project_id": os.environ["GOOGLE_PROJECT_ID"],
"private_key_id": os.environ["GOOGLE_PRIVATE_KEY_ID"],
"private_key": os.environ["GOOGLE_PRIVATE_KEY"],
"client_email": os.environ["GOOGLE_CLIENT_EMAIL"],
"client_id": os.environ["GOOGLE_CLIENT_ID"],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ["GOOGLE_CERT_URL"],
},
owner_email=os.environ["GOOGLE_OWNER_EMAIL"],
)

![](https://docs.github.com/assets/cb-36544/images/help/repository/use-this-template-button.png)
agent = Agent(tools=[gmail_tool])


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
agent.run(
"Create a draft email in Gmail to [email protected] with the subject 'Test Draft', the body "
"'This is a test draft email.'",
)
```

## 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
## Installation

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

#### Running Tests

Pip:
```bash
make test
pip install git+https://github.com/griptape-ai/griptape-google.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:
```bash
poetry add -e /path/to/your/extension
```

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.
7 changes: 0 additions & 7 deletions examples/tools/example_agent.py

This file was deleted.

29 changes: 29 additions & 0 deletions examples/tools/google_calendar_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from griptape.structures import Agent
from griptape.google.tools import GoogleCalendarTool

# Create the GoogleCalendarTool tool
google_calendarendar_tool = GoogleCalendarTool(
service_account_credentials={
"type": os.environ["GOOGLE_ACCOUNT_TYPE"],
"project_id": os.environ["GOOGLE_PROJECT_ID"],
"private_key_id": os.environ["GOOGLE_PRIVATE_KEY_ID"],
"private_key": os.environ["GOOGLE_PRIVATE_KEY"],
"client_email": os.environ["GOOGLE_CLIENT_EMAIL"],
"client_id": os.environ["GOOGLE_CLIENT_ID"],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ["GOOGLE_CERT_URL"],
},
owner_email=os.environ["GOOGLE_OWNER_EMAIL"],
)

# Set up an agent using the GoogleCalendarTool tool
agent = Agent(tools=[google_calendarendar_tool])

# Task: Get upcoming events from a Google calendar
agent.run(
"Get me the details of the next upcoming event from my primary calendar.",
)
29 changes: 29 additions & 0 deletions examples/tools/google_docs_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from griptape.structures import Agent
from griptape.google.tools import GoogleDocsTool

# Create the GoogleDocsTool tool
google_docs_tool = GoogleDocsTool(
service_account_credentials={
"type": os.environ["GOOGLE_ACCOUNT_TYPE"],
"project_id": os.environ["GOOGLE_PROJECT_ID"],
"private_key_id": os.environ["GOOGLE_PRIVATE_KEY_ID"],
"private_key": os.environ["GOOGLE_PRIVATE_KEY"],
"client_email": os.environ["GOOGLE_CLIENT_EMAIL"],
"client_id": os.environ["GOOGLE_CLIENT_ID"],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ["GOOGLE_CERT_URL"],
},
owner_email=os.environ["GOOGLE_OWNER_EMAIL"],
)

# Set up an agent using the GoogleDocsTool tool
agent = Agent(tools=[google_docs_tool])

# Task: Create a new Google Doc and save content to it
agent.run(
"Create doc with name 'test_creation' in test folder with content 'Hey, Tony.",
)
29 changes: 29 additions & 0 deletions examples/tools/google_drive_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from griptape.structures import Agent
from griptape.google.tools import GoogleDriveTool

# Create the GoogleDriveTool tool
google_drive_tool = GoogleDriveTool(
service_account_credentials={
"type": os.environ["GOOGLE_ACCOUNT_TYPE"],
"project_id": os.environ["GOOGLE_PROJECT_ID"],
"private_key_id": os.environ["GOOGLE_PRIVATE_KEY_ID"],
"private_key": os.environ["GOOGLE_PRIVATE_KEY"],
"client_email": os.environ["GOOGLE_CLIENT_EMAIL"],
"client_id": os.environ["GOOGLE_CLIENT_ID"],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ["GOOGLE_CERT_URL"],
},
owner_email=os.environ["GOOGLE_OWNER_EMAIL"],
)

# Set up an agent using the GoogleDriveTool tool
agent = Agent(tools=[google_drive_tool])

# Task: Save content to my Google Drive (default directory is root)
agent.run(
"Save the content 'Hi this is Tony' in a filed named 'hello.txt' to my Drive.",
)
30 changes: 30 additions & 0 deletions examples/tools/google_gmail_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os

from griptape.structures import Agent
from griptape.google.tools import GoogleGmailTool

# Create the GoogleGmailTool tool
gmail_tool = GoogleGmailTool(
service_account_credentials={
"type": os.environ["GOOGLE_ACCOUNT_TYPE"],
"project_id": os.environ["GOOGLE_PROJECT_ID"],
"private_key_id": os.environ["GOOGLE_PRIVATE_KEY_ID"],
"private_key": os.environ["GOOGLE_PRIVATE_KEY"],
"client_email": os.environ["GOOGLE_CLIENT_EMAIL"],
"client_id": os.environ["GOOGLE_CLIENT_ID"],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": os.environ["GOOGLE_CERT_URL"],
},
owner_email=os.environ["GOOGLE_OWNER_EMAIL"],
)

# Set up an agent using the GoogleGmailTool tool
agent = Agent(tools=[gmail_tool])

# Task: Create a draft email in Gmail
agent.run(
"Create a draft email in Gmail to [email protected] with the subject 'Test Draft', the body "
"'This is a test draft email.'",
)
File renamed without changes.
13 changes: 13 additions & 0 deletions griptape/google/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .base_google_tool import BaseGoogleTool
from .google_calendar.tool import GoogleCalendarTool
from .google_docs.tool import GoogleDocsTool
from .google_drive.tool import GoogleDriveTool
from .google_gmail.tool import GoogleGmailTool

__all__ = [
"BaseGoogleTool",
"GoogleCalendarTool",
"GoogleDocsTool",
"GoogleDriveTool",
"GoogleGmailTool",
]
66 changes: 66 additions & 0 deletions griptape/google/tools/base_google_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from __future__ import annotations

from abc import ABC
from typing import Any

from attrs import define, field

from griptape.tools import BaseTool


@define
class BaseGoogleTool(BaseTool, ABC):
DRIVE_FILE_SCOPES = ["https://www.googleapis.com/auth/drive.file"]

DRIVE_AUTH_SCOPES = ["https://www.googleapis.com/auth/drive"]

owner_email: str = field(kw_only=True)
service_account_credentials: dict = field(kw_only=True)

def _build_client(self, scopes: list[str], service_name: str, version: str) -> Any:
from google.oauth2 import service_account
from googleapiclient.discovery import build

credentials = service_account.Credentials.from_service_account_info(
self.service_account_credentials,
scopes=scopes,
)

return build(
serviceName=service_name,
version=version,
credentials=credentials.with_subject(self.owner_email),
)

def _convert_path_to_file_id(self, service: Any, path: str) -> str | None:
parts = path.split("/")
current_id = "root"

for idx, part in enumerate(parts):
if idx == len(parts) - 1:
query = f"name='{part}' and '{current_id}' in parents"
else:
query = f"name='{part}' and '{current_id}' in parents and mimeType='application/vnd.google-apps.folder'"

response = service.files().list(q=query).execute()
files = response.get("files", [])

if not files:
if idx != len(parts) - 1:
folder_metadata = {
"name": part,
"mimeType": "application/vnd.google-apps.folder",
"parents": [current_id],
}
folder = (
service.files()
.create(body=folder_metadata, fields="id")
.execute()
)
current_id = folder.get("id")
else:
current_id = None
else:
current_id = files[0]["id"]

return current_id
File renamed without changes.
1 change: 1 addition & 0 deletions griptape/google/tools/google_calendar/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
google-api-python-client
Loading

0 comments on commit aacf0ed

Please sign in to comment.