Skip to content

Commit

Permalink
Enable Sam to send emails
Browse files Browse the repository at this point in the history
  • Loading branch information
codingjoe committed Feb 19, 2024
1 parent cbab86e commit a27f870
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 2 deletions.
28 changes: 27 additions & 1 deletion sam/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def process_run(event: {str, Any}, say: Say, voice_prompt: bool = False):
run = client.beta.threads.runs.create(
thread_id=thread_id,
assistant_id=config.OPENAI_ASSISTANT_ID,
tools=[utils.func_to_tool(utils.send_email)],
)
say.client.reactions_add(
channel=channel_id,
Expand All @@ -131,8 +132,33 @@ def process_run(event: {str, Any}, say: Say, voice_prompt: bool = False):
)
logger.info(f"User={user_id} started Run={run.id} for Thread={thread_id}")
for i in range(14): # ~ 5 minutes
if run.status not in ["queued", "in_progress"]:
if run.status not in ["queued", "in_progress", "requires_action"]:
break
if (
run.required_action
and run.required_action.submit_tool_outputs
and run.required_action.submit_tool_outputs.tool_calls
):
tool_outputs = []

for tool_call in run.required_action.submit_tool_outputs.tool_calls:
logger.info(
f"Tool Call={tool_call.id} Function={tool_call.function.name}"
)
logger.debug(f"Tool Call={tool_call.id} Arguments={tool_call.function.arguments}")
kwargs = json.loads(tool_call.function.arguments)
tool_outputs.append(
{
"tool_call_id": tool_call.id, # noqa
"output": utils.send_email(**kwargs),
}
)

client.beta.threads.runs.submit_tool_outputs(
run.id, # noqa
thread_id=thread_id,
tool_outputs=tool_outputs,
)
time.sleep(min(2**i, 30)) # exponential backoff capped at 30 seconds
run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
if run.status == "failed":
Expand Down
78 changes: 77 additions & 1 deletion sam/utils.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,63 @@
import enum
import functools
import inspect
import os
import re
import smtplib
import urllib.parse
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

import openai
import redis
import yaml

from . import config

__all__ = ["get_thread_id", "storage"]
__all__ = ["get_thread_id", "storage", "func_to_tool", "send_email"]

storage: redis.Redis = redis.from_url(config.REDIS_URL)

type_map = {
str: "string",
int: "integer",
float: "number",
list: "array",
dict: "object",
enum.StrEnum: "string",
enum.IntEnum: "integer",
}


def func_to_tool(fn: callable) -> dict:
signature: inspect.Signature = inspect.signature(fn)
description, args = fn.__doc__.split("Args:")
doc_data = yaml.safe_load(args.split("Returns:")[0])
return {
"type": "function",
"function": {
"name": fn.__name__,
"description": "\n".join(
filter(None, (line.strip() for line in description.splitlines()))
),
"parameters": {
"type": "object",
"properties": {
param.name: {
"type": type_map[param.annotation],
"description": doc_data[param.name],
}
for param in signature.parameters.values()
},
"required": [
param.name
for param in signature.parameters.values()
if param.default is inspect.Parameter.empty
],
},
},
}


@functools.lru_cache
def get_thread_id(slack_id) -> str:
Expand All @@ -31,3 +79,31 @@ def get_thread_id(slack_id) -> str:
storage.set(slack_id, thread_id)

return thread_id


def send_email(to: str, subject: str, body: str):
"""
Write and send email.
Args:
to: The recipient of the email, e.g. [email protected].
subject: The subject of the email.
body: The body of the email.
"""
email_url = os.getenv("EMAIL_URL")
from_email = os.getenv("FROM_EMAIL")
email_white_list = os.getenv("EMAIL_WHITE_LIST")
if email_white_list and not re.match(email_white_list, to):
return "Email not sent. The recipient is not in the whitelist."
urllib.parse.uses_netloc.append("smtps")
url = urllib.parse.urlparse(email_url)
with smtplib.SMTP_SSL(url.hostname, url.port) as server:
server.login(url.username, url.password)
msg = MIMEMultipart()
msg["From"] = url.username
msg["To"] = to
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
server.sendmail(from_email, to, msg.as_string())

return "Email sent successfully!"

0 comments on commit a27f870

Please sign in to comment.