Skip to content

Commit

Permalink
feat(email): Add email sender tool and update image generator tool (#…
Browse files Browse the repository at this point in the history
…2579)

This pull request adds a new email sender tool and updates the image
generator tool.
  • Loading branch information
StanGirard committed May 10, 2024
1 parent 105a2b8 commit 01c6e7b
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 11 deletions.
6 changes: 3 additions & 3 deletions backend/modules/assistant/ito/ito.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

from fastapi import UploadFile
from logger import get_logger
from modules.user.service.user_usage import UserUsage
from modules.assistant.dto.inputs import InputAssistant
from modules.assistant.ito.utils.pdf_generator import PDFGenerator, PDFModel
from modules.chat.controller.chat.utils import update_user_usage
from modules.contact_support.controller.settings import ContactsSettings
from modules.upload.controller.upload_routes import upload_file
from modules.user.entity.user_identity import UserIdentity
from modules.user.service.user_usage import UserUsage
from packages.emails.send_email import send_email
from pydantic import BaseModel
from unidecode import unidecode
Expand Down Expand Up @@ -111,8 +111,8 @@ async def send_output_by_email(
</div>
"""
params = {
"from": mail_from,
"to": mail_to,
"sender": mail_from,
"to": [mail_to],
"subject": "Quivr Ingestion Processed",
"reply_to": "no-reply@quivr.app",
"html": body,
Expand Down
24 changes: 18 additions & 6 deletions backend/modules/brain/integrations/GPT4/Brain.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
import operator
from typing import Annotated, AsyncIterable, List, Sequence, TypedDict
from typing import Annotated, AsyncIterable, List, Optional, Sequence, TypedDict
from uuid import UUID

from langchain.tools import BaseTool
Expand All @@ -15,7 +15,12 @@
from modules.chat.dto.chats import ChatQuestion
from modules.chat.dto.outputs import GetChatHistoryOutput
from modules.chat.service.chat_service import ChatService
from modules.tools import ImageGeneratorTool, URLReaderTool, WebSearchTool
from modules.tools import (
EmailSenderTool,
ImageGeneratorTool,
URLReaderTool,
WebSearchTool,
)


class AgentState(TypedDict):
Expand All @@ -37,8 +42,8 @@ class GPT4Brain(KnowledgeBrainQA):
KnowledgeBrainQA (_type_): A brain that store the knowledge internaly
"""

tools: List[BaseTool] = [WebSearchTool(), ImageGeneratorTool(), URLReaderTool()]
tool_executor: ToolExecutor = ToolExecutor(tools)
tools: Optional[List[BaseTool]] = None
tool_executor: Optional[ToolExecutor] = None
model_function: ChatOpenAI = None

def __init__(
Expand All @@ -48,6 +53,13 @@ def __init__(
super().__init__(
**kwargs,
)
self.tools = [
WebSearchTool(),
ImageGeneratorTool(),
URLReaderTool(),
EmailSenderTool(user_email=self.user_email),
]
self.tool_executor = ToolExecutor(tools=self.tools)

def calculate_pricing(self):
return 3
Expand Down Expand Up @@ -164,7 +176,7 @@ async def generate_stream(
transformed_history, streamed_chat_history = (
self.initialize_streamed_chat_history(chat_id, question)
)
filtered_history = self.filter_history(transformed_history, 20, 2000)
filtered_history = self.filter_history(transformed_history, 40, 2000)
response_tokens = []
config = {"metadata": {"conversation_id": str(chat_id)}}

Expand Down Expand Up @@ -232,7 +244,7 @@ def generate_answer(
transformed_history, _ = self.initialize_streamed_chat_history(
chat_id, question
)
filtered_history = self.filter_history(transformed_history, 20, 2000)
filtered_history = self.filter_history(transformed_history, 40, 2000)
config = {"metadata": {"conversation_id": str(chat_id)}}

prompt = ChatPromptTemplate.from_messages(
Expand Down
2 changes: 1 addition & 1 deletion backend/modules/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .image_generator import ImageGeneratorTool
from .web_search import WebSearchTool
from .url_reader import URLReaderTool

from .email_sender import EmailSenderTool
81 changes: 81 additions & 0 deletions backend/modules/tools/email_sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Extract and combine content recursively
from typing import Dict, Optional, Type

from langchain.callbacks.manager import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
from langchain.pydantic_v1 import BaseModel as BaseModelV1
from langchain.pydantic_v1 import Field as FieldV1
from langchain_community.document_loaders import PlaywrightURLLoader
from langchain_core.tools import BaseTool
from logger import get_logger
from models import BrainSettings
from modules.contact_support.controller.settings import ContactsSettings
from packages.emails.send_email import send_email
from pydantic import BaseModel

logger = get_logger(__name__)


class EmailInput(BaseModelV1):
text: str = FieldV1(
...,
title="text",
description="text to send in HTML email format. Use pretty formating, use bold, italic, next line, etc...",
)


class EmailSenderTool(BaseTool):
user_email: str
name = "email-sender"
description = "useful for when you need to send an email."
args_schema: Type[BaseModel] = EmailInput
brain_settings: BrainSettings = BrainSettings()
contact_settings: ContactsSettings = ContactsSettings()

def _run(
self, text: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> Dict:

html_body = f"""
<div style="text-align: center;">
<img src="https://quivr-cms.s3.eu-west-3.amazonaws.com/logo_quivr_white_7e3c72620f.png" alt="Quivr Logo" style="width: 100px; height: 100px; border-radius: 50%; margin: 0 auto; display: block;">
<br />
</div>
"""
html_body += f"""
{text}
"""
logger.debug(f"Email body: {html_body}")
logger.debug(f"Email to: {self.user_email}")
logger.debug(f"Email from: {self.contact_settings.resend_contact_sales_from}")
try:
r = send_email(
{
"sender": self.contact_settings.resend_contact_sales_from,
"to": self.user_email,
"reply_to": "no-reply@quivr.app",
"subject": "Email from your assistant",
"html": html_body,
}
)
logger.info("Resend response", r)
except Exception as e:
logger.error(f"Error sending email: {e}")
return {"content": "Error sending email because of error: " + str(e)}

return {"content": "Email sent"}

async def _arun(
self, url: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> Dict:
"""Run the tool asynchronously."""
loader = PlaywrightURLLoader(urls=[url], remove_selectors=["header", "footer"])
data = loader.load()

extracted_content = ""
for page in data:
extracted_content += page.page_content

return {"content": extracted_content}
3 changes: 2 additions & 1 deletion backend/modules/tools/image_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ async def _arun(
n=1,
)
image_url = response.data[0].url
revised_prompt = response.data[0].revised_prompt
# Make the url a markdown image
return f"![Generated Image]({image_url})"
return f"{revised_prompt} \n ![Generated Image]({image_url}) "
5 changes: 5 additions & 0 deletions backend/modules/tools/web_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,8 @@ def _parse_response(self, response: Dict) -> str:

def _format_result(self, title: str, description: str, url: str) -> str:
return f"**{title}**\n{description}\n{url}"


if __name__ == "__main__":
tool = WebSearchTool()
print(tool.run("python"))

0 comments on commit 01c6e7b

Please sign in to comment.