Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added reddit tool and reddit agent example #354

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/composio/tools/local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from composio.tools.local.sqltool import SqlTool
from composio.tools.local.webtool import WebTool
from composio.tools.local.zep import ZepTool
from composio.tools.local.reddit import Reddit


TOOLS_PATH = Path(__file__).parent
Expand All @@ -43,4 +44,5 @@
SqlTool,
WebTool,
ZepTool,
Reddit,
]
1 change: 1 addition & 0 deletions python/composio/tools/local/reddit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .tool import Reddit
2 changes: 2 additions & 0 deletions python/composio/tools/local/reddit/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .filter import Filter
from .comment import Comment
84 changes: 84 additions & 0 deletions python/composio/tools/local/reddit/actions/comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from pydantic import BaseModel, Field
from composio.tools.local.base import Action


class CommentToolRequest(BaseModel):
post_id: str = Field(..., description="The post to comment on")
type: str = Field(default="submission", description="The type of post to comment on like submission or comment")
message: str = Field(..., description="The comment to make")


class CommentToolResponse(BaseModel):
success: bool = Field(..., description="Whether the comment was successful")


class Comment(Action[CommentToolRequest, CommentToolResponse]):
"""
Comment on a post
"""

_display_name = "Comment on Post"
_request_schema = CommentToolRequest
_response_schema = CommentToolResponse
# _tags = ["Web"]
_tool_name = "reddit"

def execute(self, request_data: CommentToolRequest, authorisation_data: dict) -> dict | CommentToolResponse:
try:
# pylint: disable=import-outside-toplevel
import praw
import os

# pylint: enable=import-outside-toplevel

except ImportError as e:
raise ImportError("Failed to import praw:", e) from e

# Load the environment variables
client_id = os.getenv("CLIENT_ID")
if client_id is None:
self.logger.error("CLIENT_ID environment variable not set")
raise ValueError("CLIENT_ID environment variable not set")

client_secret = os.getenv("CLIENT_SECRET")
if client_secret is None:
self.logger.error("CLIENT_SECRET environment variable not set")
raise ValueError("CLIENT_SECRET environment variable not set")

user_agent = os.getenv("USER_AGENT")
if user_agent is None:
self.logger.error("USER_AGENT environment variable not set")
raise ValueError("USER_AGENT environment variable not set")

username = os.getenv("USERNAME")
if username is None:
self.logger.error("USERNAME environment variable not set")
raise ValueError("USERNAME environment variable not set")

password = os.getenv("PASSWORD")
if password is None:
self.logger.error("PASSWORD environment variable not set")
raise ValueError("PASSWORD environment variable not set")

# Initialise the Reddit instance
reddit = praw.Reddit(
client_id=client_id,
client_secret=client_secret,
user_agent=user_agent,
username=username,
password=password
)

# Get the post
if request_data.type == "submission":
post = reddit.submission(request_data.post_id)

elif request_data.type == "comment":
post = reddit.comment(request_data.post_id)
else:
self.logger.error("Invalid post type")
raise ValueError("Invalid post type")

post.reply(request_data.message) # Comment on the post

return CommentToolResponse(success=True)
85 changes: 85 additions & 0 deletions python/composio/tools/local/reddit/actions/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import typing as t
from pydantic import BaseModel, Field
from composio.tools.local.base import Action


class FilterToolRequest(BaseModel):
subreddit: str = Field(..., description="The subreddit to filter")
query: t.List[str] = Field(..., description="The query to filter by")
sort: str = Field(default="new", description="The sorting method can be one of: new, hot, top, relevance, or comments")
limit: int = Field(default=10, description="The number of posts to return")
time_filter: str = Field(default="all", description="The time filter can be one of: all, day, hour, month, week, or year")


class FilterToolResponse(BaseModel):
posts: t.List[t.Dict[str, str]] = Field(..., description="The filtered posts")


class Filter(Action[FilterToolRequest, FilterToolResponse]):
"""
Filter the posts in a subreddit by a query
"""

_display_name = "Filter Posts"
_request_schema = FilterToolRequest
_response_schema = FilterToolResponse
# _tags = ["Web"]
_tool_name = "reddit"

def execute(self, request_data: FilterToolRequest, authorisation_data: dict) -> dict | FilterToolResponse:
try:
# pylint: disable=import-outside-toplevel
import praw
import os
import datetime

# pylint: enable=import-outside-toplevel

except ImportError as e:
raise ImportError("Failed to import praw:", e) from e

# Search parameters
search_params = {}

if request_data.query:
search_params["query"] = "".join(request_data.query)
if request_data.sort:
search_params["sort"] = request_data.sort
if request_data.limit:
search_params["limit"] = request_data.limit
if request_data.time_filter:
search_params["time_filter"] = request_data.time_filter

# Load the environment variables
client_id = os.getenv("CLIENT_ID")
if client_id is None:
self.logger.error("CLIENT_ID environment variable not set")
raise ValueError("CLIENT_ID environment variable not set")

client_secret = os.getenv("CLIENT_SECRET")
if client_secret is None:
self.logger.error("CLIENT_SECRET environment variable not set")
raise ValueError("CLIENT_SECRET environment variable not set")

user_agent = os.getenv("USER_AGENT")
if user_agent is None:
self.logger.error("USER_AGENT environment variable not set")
raise ValueError("USER_AGENT environment variable not set")

# Create the Reddit instance
reddit = praw.Reddit(client_id=client_id, client_secret=client_secret, user_agent=user_agent)

posts: t.List[t.Dict[str, str]] = []

for submission in reddit.subreddit(request_data.subreddit).search(**search_params):
post = {
"id": submission.id,
"title": submission.title,
"author": submission.author.name,
"url": submission.url,
"created": datetime.datetime.fromtimestamp(submission.created_utc).strftime('%Y-%m-%d %H:%M:%S'),
}

posts.append(post)

return FilterToolResponse(posts=posts)
18 changes: 18 additions & 0 deletions python/composio/tools/local/reddit/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import typing as t
from composio.tools.local.base import Action, Tool
from .actions.filter import Filter
from .actions.comment import Comment


class Reddit(Tool):
"""
Reddit tool
"""

def actions(self) -> list[t.Type[Action]]:
return [Filter, Comment]

def triggers(self) -> list:
return [] # If applicable, define triggers here


12 changes: 12 additions & 0 deletions python/examples/reddit_agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Google API key (you can get it from https://console.developers.google.com/apis/credentials after creating a new project)
GOOGLE_API_KEY=

# Client ID and Client Secret can be obtained by creating a new app at https://www.reddit.com/prefs/apps
# User Agent will be in the format: platform:appname:version (by /u/username) (example: python:reddit-tool:v1.0 (by /u/thatsmeadarsh))
CLIENT_ID=
CLIENT_SECRET=
USER_AGENT=

# For comment action of Reddit tool, you need to provide your Reddit username and password
USERNAME=
PASSWORD=
32 changes: 32 additions & 0 deletions python/examples/reddit_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Reddit Agent
> Fork and Clone this repository if needed!


## Introduction
This is an example which uses Composio to automate the filter and comment action of Reddit.


## Steps to Run
**Navigate to the Project Directory:**
Change to the directory where the `setup.sh`, `main.py`, `requirements.txt`, `.env.example` and `README.md` files are located. For example:
```shell
cd path/to/project/directory
```

### 1. Run the Setup File
Make the setup.sh Script Executable (if necessary):
On Linux or macOS, you might need to make the setup.sh script executable:
```shell
chmod +x setup.sh
```
Execute the setup.sh script to set up the environment, install dependencies, login to composio:
```shell
./setup.sh
```
Now, Fill in the `.env` file with your secrets.
### 2. Run the python script
```shell
python cookbook/examples/commit_agent/main.py
```
Your notion page should automatically be populated with the data.

56 changes: 56 additions & 0 deletions python/examples/reddit_agent/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from dotenv import load_dotenv
from composio_crewai import ComposioToolSet
from composio import App
from crewai import Agent, Crew, Process, Task
from langchain_google_genai import ChatGoogleGenerativeAI


# Load the environment variables
load_dotenv()

# Initialize the language model
llm = ChatGoogleGenerativeAI(model="gemini-pro", temperature=0.1)

# Define tools for the agents
# We are using Reddit tool from composio.
composio_toolset = ComposioToolSet()
tools = composio_toolset.get_tools(apps=[App.REDDIT])


# Create and Execute Agent.
def run_crew():
reddit_agent = Agent(
role="Reddit Agent",
goal="""You take action on Reddit using Reddit APIs""",
backstory="""You are an AI agent responsible for taking actions on Reddit on users' behalf.
You need to take action on Reddit using Reddit APIs. Use correct tools to run APIs from the given tool-set.""",
verbose=True,
tools=tools,
llm=llm,
)

filter_task = Task(
description="Fetch newest 1 post from python on fastapi",
agent=reddit_agent,
expected_output="1 post from python on fastapi",
)

comment_task = Task(
description="Comment on the post fetched from python on fastapi",
agent=reddit_agent,
expected_output="Success",
)

my_crew = Crew(
agents=[reddit_agent],
tasks=[filter_task, comment_task],
process=Process.sequential,
full_output=True,
verbose=True,
)

my_crew.kickoff()


if __name__ == "__main__":
run_crew()
4 changes: 4 additions & 0 deletions python/examples/reddit_agent/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
crewai
composio-crewai
python-dotenv
langchain-google-genai
31 changes: 31 additions & 0 deletions python/examples/reddit_agent/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash

# Create a virtual environment
echo "Creating virtual environment..."
python3 -m venv ~/.venvs/reddit_agent

# Activate the virtual environment
echo "Activating virtual environment..."
source ~/.venvs/reddit_agent/bin/activate

# Install libraries from requirements.txt
echo "Installing libraries from requirements.txt..."
pip install -r requirements.txt

# Login to your account
echo "Login to your Composio acount"
composio login

# Copy env backup to .env file
if [ -f ".env.example" ]; then
echo "Copying .env.example to .env..."
cp .env.example .env
else
echo "No .env.example file found. Creating a new .env file..."
touch .env
fi

# Prompt user to fill the .env file
echo "Please fill in the .env file with the necessary environment variables."

echo "Setup completed successfully!"
Loading