Skip to content

Commit

Permalink
Gmail tools improvements and updates
Browse files Browse the repository at this point in the history
  • Loading branch information
willemcdejongh committed Feb 12, 2025
1 parent 551aadb commit 00ff054
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 10 deletions.
2 changes: 1 addition & 1 deletion cookbook/models/openai/tool_use_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
show_tool_calls=True,
markdown=True,
)
agent.print_response("Whats happening in France?", stream=True)
agent.print_response("Whats happening in France today 11 Feb 2025?", stream=True)
33 changes: 28 additions & 5 deletions cookbook/tools/gmail_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from agno.agent import Agent
from agno.models.google import Gemini
from agno.tools.gmail import GmailTools

from datetime import datetime, timedelta
agent = Agent(
name="Gmail Agent",
model=Gemini(id="gemini-2.0-flash-exp"),
Expand All @@ -21,8 +21,31 @@
debug_mode=True,
)

agent.print_response(
"summarize my last 5 emails with dates and key details, regarding ai agents",
markdown=True,
stream=True,
# agent.print_response(
# "summarize my last 5 emails with dates and key details, regarding ai agents",
# markdown=True,
# stream=True,
# )

tool = GmailTools(
credentials_path="storage/credentials.json",
)



# print(tool.get_emails_by_context(context="Security", count=5))

thread_id = "194fa616c1f1aa92"
message_id = "CAPe88YDTC4sgdVuiVXR6y7xx8T4tRDi90JacwM9=e7WeSSGkig@mail.gmail.com"
resp = tool.send_email_reply(
thread_id=thread_id,
message_id=message_id,
to="[email protected]",
subject="Re: Security",
body="Hello, I am a security agent. I am here to help you with your security needs.",
cc="",
)


# resp = tool.get_latest_emails(count=1)
print(resp)
90 changes: 86 additions & 4 deletions libs/agno/agno/tools/gmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ def __init__(
get_starred_emails: bool = True,
get_emails_by_context: bool = True,
get_emails_by_date: bool = True,
get_emails_by_thread: bool = True,
create_draft_email: bool = True,
send_email: bool = True,
send_email_reply: bool = True,
search_emails: bool = True,
creds: Optional[Credentials] = None,
credentials_path: Optional[str] = None,
Expand All @@ -118,9 +120,11 @@ def __init__(
get_starred_emails (bool): Enable getting starred emails. Defaults to True.
get_emails_by_context (bool): Enable getting emails by context. Defaults to True.
get_emails_by_date (bool): Enable getting emails by date. Defaults to True.
get_emails_by_thread (bool): Enable getting emails by thread. Defaults to True.
create_draft_email (bool): Enable creating draft emails. Defaults to True.
send_email (bool): Enable sending emails. Defaults to True.
search_emails (bool): Enable searching emails. Defaults to True.
send_email_reply (bool): Enable sending email replies. Defaults to True.
creds (Optional[Credentials]): Pre-existing credentials. Defaults to None.
credentials_path (Optional[str]): Path to credentials file. Defaults to None.
token_path (Optional[str]): Path to token file. Defaults to None.
Expand All @@ -146,6 +150,7 @@ def __init__(
get_starred_emails,
get_emails_by_context,
get_emails_by_date,
get_emails_by_thread,
search_emails,
]

Expand All @@ -167,10 +172,14 @@ def __init__(
self.register(self.get_emails_by_context)
if get_emails_by_date:
self.register(self.get_emails_by_date)
if get_emails_by_thread:
self.register(self.get_emails_by_thread)
if create_draft_email:
self.register(self.create_draft_email)
if send_email:
self.register(self.send_email)
if send_email_reply:
self.register(self.send_email_reply)
if search_emails:
self.register(self.search_emails)

Expand Down Expand Up @@ -287,6 +296,27 @@ def get_unread_emails(self, count: int) -> str:
except Exception as error:
return f"Unexpected error retrieving unread emails: {type(error).__name__}: {error}"

@authenticate
def get_emails_by_thread(self, thread_id: str) -> str:
"""
Retrieve all emails from a specific thread.
Args:
thread_id (str): The ID of the email thread.
Returns:
str: Formatted string containing email thread details.
"""
try:
thread = self.service.users().threads().get(userId="me", id=thread_id).execute() # type: ignore
messages = thread.get("messages", [])
emails = self._get_message_details(messages)
return self._format_emails(emails)
except HttpError as error:
return f"Error retrieving emails from thread {thread_id}: {error}"
except Exception as error:
return f"Unexpected error retrieving emails from thread {thread_id}: {type(error).__name__}: {error}"

@authenticate
def get_starred_emails(self, count: int) -> str:
"""
Expand Down Expand Up @@ -397,6 +427,33 @@ def send_email(self, to: str, subject: str, body: str, cc: Optional[str] = None)
message = self.service.users().messages().send(userId="me", body=message).execute() # type: ignore
return str(message)

@authenticate
def send_email_reply(self, thread_id: str, message_id: str, to: str, subject: str, body: str, cc: Optional[str] = None) -> str:
"""
Respond to an existing email thread.
Args:
thread_id (str): The ID of the email thread to reply to.
message_id (str): The ID of the email being replied to.
to (str): Comma-separated recipient email addresses.
subject (str): Email subject (prefixed with "Re:" if not already).
body (str): Email body content.
cc (Optional[str]): Comma-separated CC email addresses (optional).
Returns:
str: Stringified dictionary containing sent email details including id.
"""
self._validate_email_params(to, subject, body)

# Ensure subject starts with "Re:" for consistency
if not subject.lower().startswith("re:"):
subject = f"Re: {subject}"

body = body.replace("\n", "<br>")
message = self._create_message(to.split(","), subject, body, cc.split(",") if cc else None, thread_id, message_id)
message = self.service.users().messages().send(userId="me", body=message).execute() # type: ignore
return str(message)

@authenticate
def search_emails(self, query: str, count: int) -> str:
"""
Expand Down Expand Up @@ -435,21 +492,46 @@ def _validate_email_params(self, to: str, subject: str, body: str) -> None:
if body is None:
raise ValueError("Email body cannot be None")

def _create_message(self, to: List[str], subject: str, body: str, cc: Optional[List[str]] = None) -> dict:
"""Create email message"""
# def _create_message(self, to: List[str], subject: str, body: str, cc: Optional[List[str]] = None) -> dict:
# """Create email message"""
# body = body.replace("\\n", "\n")
# message = MIMEText(body, "html")
# message["to"] = ", ".join(to)
# message["from"] = "me"
# message["subject"] = subject
# if cc:
# message["cc"] = ", ".join(cc)
# return {"raw": base64.urlsafe_b64encode(message.as_bytes()).decode()}

def _create_message(self, to: List[str], subject: str, body: str, cc: Optional[List[str]] = None, thread_id: Optional[str] = None, message_id: Optional[str] = None) -> dict:

body = body.replace("\\n", "\n")
message = MIMEText(body, "html")
message["to"] = ", ".join(to)
message["from"] = "me"
message["subject"] = subject

if cc:
message["cc"] = ", ".join(cc)
return {"raw": base64.urlsafe_b64encode(message.as_bytes()).decode()}
message["Cc"] = ", ".join(cc)

# Add reply headers if this is a response
if thread_id and message_id:
message["In-Reply-To"] = message_id
message["References"] = message_id

raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
email_data = {"raw": raw_message}

# if thread_id:
email_data["threadId"] = thread_id

return email_data

def _get_message_details(self, messages: List[dict]) -> List[dict]:
"""Get details for list of messages"""
details = []
for msg in messages:
print(msg)
msg_data = self.service.users().messages().get(userId="me", id=msg["id"], format="full").execute() # type: ignore
details.append(
{
Expand Down

0 comments on commit 00ff054

Please sign in to comment.